From 2c10adba858b409fc5a7f445863fbda6589afc04 Mon Sep 17 00:00:00 2001 From: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Date: Thu, 19 Mar 2026 13:09:03 +1300 Subject: [PATCH 1/4] Bump version to 2026.3.0 --- Doxyfile | 2 +- esphome/const.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Doxyfile b/Doxyfile index 53eae489669..d8a030536e9 100644 --- a/Doxyfile +++ b/Doxyfile @@ -48,7 +48,7 @@ PROJECT_NAME = ESPHome # could be handy for archiving the generated documentation or if some version # control system is used. -PROJECT_NUMBER = 2026.3.0b5 +PROJECT_NUMBER = 2026.3.0 # Using the PROJECT_BRIEF tag one can provide an optional one line description # for a project that appears at the top of each page and should give viewer a diff --git a/esphome/const.py b/esphome/const.py index 579235ff69a..756ad694647 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -4,7 +4,7 @@ from enum import Enum from esphome.enum import StrEnum -__version__ = "2026.3.0b5" +__version__ = "2026.3.0" ALLOWED_NAME_CHARS = "abcdefghijklmnopqrstuvwxyz0123456789-_" VALID_SUBSTITUTIONS_CHARACTERS = ( From 8fe36cde23f7974683eab4090a1639890ef1b41d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 18 Mar 2026 18:41:05 -1000 Subject: [PATCH 2/4] [core] Replace std::function with lightweight Callback in CallbackManager (#14853) --- .../alarm_control_panel.cpp | 16 ---- .../alarm_control_panel/alarm_control_panel.h | 31 ++++---- esphome/components/button/button.cpp | 1 - esphome/components/button/button.h | 4 +- esphome/components/canbus/canbus.h | 5 +- esphome/components/climate/climate.cpp | 8 -- esphome/components/climate/climate.h | 8 +- esphome/components/cover/cover.cpp | 1 - esphome/components/cover/cover.h | 2 +- esphome/components/datetime/datetime_base.h | 4 +- esphome/components/dfplayer/dfplayer.h | 4 +- .../components/display_menu_base/menu_item.h | 10 +-- .../esp32_improv/esp32_improv_component.h | 4 +- esphome/components/event/event.cpp | 4 - esphome/components/event/event.h | 4 +- esphome/components/ezo/ezo.h | 20 ++--- .../components/factory_reset/factory_reset.h | 4 +- esphome/components/fan/fan.cpp | 1 - esphome/components/fan/fan.h | 4 +- .../fingerprint_grow/fingerprint_grow.h | 32 ++++---- .../graphical_display_menu.h | 2 +- esphome/components/haier/haier_base.cpp | 4 - esphome/components/haier/haier_base.h | 4 +- esphome/components/haier/hon_climate.cpp | 8 -- esphome/components/haier/hon_climate.h | 8 +- esphome/components/hlk_fm22x/hlk_fm22x.h | 25 +++---- .../components/key_collector/key_collector.h | 12 +-- .../components/key_provider/key_provider.cpp | 4 - .../components/key_provider/key_provider.h | 2 +- esphome/components/ld2450/ld2450.cpp | 4 - esphome/components/ld2450/ld2450.h | 2 +- esphome/components/lock/lock.cpp | 2 - esphome/components/lock/lock.h | 4 +- esphome/components/ltr501/ltr501.h | 8 +- esphome/components/ltr_als_ps/ltr_als_ps.h | 8 +- esphome/components/lvgl/lvgl_esphome.h | 4 +- .../components/media_player/media_player.cpp | 4 - .../components/media_player/media_player.h | 4 +- esphome/components/microphone/microphone.cpp | 19 ----- esphome/components/microphone/microphone.h | 11 ++- .../microphone/microphone_source.cpp | 18 ----- .../components/microphone/microphone_source.h | 17 ++++- .../modbus_controller/modbus_controller.cpp | 12 --- .../modbus_controller/modbus_controller.h | 12 ++- esphome/components/nextion/nextion.cpp | 24 ------ esphome/components/nextion/nextion.h | 22 ++++-- esphome/components/number/number.cpp | 4 - esphome/components/number/number.h | 4 +- .../components/online_image/online_image.cpp | 8 -- .../components/online_image/online_image.h | 8 +- esphome/components/opentherm/hub.h | 8 +- esphome/components/pid/pid_climate.h | 4 +- esphome/components/pn532/pn532.h | 4 +- esphome/components/pn7150/pn7150.h | 8 +- esphome/components/pn7160/pn7160.h | 8 +- esphome/components/rf_bridge/rf_bridge.h | 8 +- .../rotary_encoder/rotary_encoder.h | 10 +-- esphome/components/rtttl/rtttl.h | 4 +- esphome/components/safe_mode/safe_mode.h | 4 +- esphome/components/sdl/sdl_esphome.h | 4 +- esphome/components/select/select.cpp | 4 - esphome/components/select/select.h | 4 +- esphome/components/sensor/sensor.cpp | 5 -- esphome/components/sensor/sensor.h | 6 +- esphome/components/sim800l/sim800l.h | 20 ++--- esphome/components/sml/sml.cpp | 4 - esphome/components/sml/sml.h | 2 +- esphome/components/speaker/speaker.h | 4 +- esphome/components/switch/switch.cpp | 3 - esphome/components/switch/switch.h | 4 +- esphome/components/text/text.cpp | 4 - esphome/components/text/text.h | 4 +- .../components/text_sensor/text_sensor.cpp | 7 -- esphome/components/text_sensor/text_sensor.h | 6 +- esphome/components/time/real_time_clock.h | 6 +- esphome/components/tuya/tuya.h | 4 +- esphome/components/uart/uart_component.h | 4 +- esphome/components/udp/udp_component.h | 4 +- esphome/components/update/update_entity.h | 4 +- esphome/components/valve/valve.cpp | 1 - esphome/components/valve/valve.h | 2 +- esphome/components/zephyr/cdc_acm.h | 4 +- esphome/components/zigbee/zigbee_zephyr.h | 2 +- esphome/core/entity_base.h | 24 +++--- esphome/core/helpers.h | 75 ++++++++++++++++--- 85 files changed, 319 insertions(+), 385 deletions(-) delete mode 100644 esphome/components/microphone/microphone.cpp diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.cpp b/esphome/components/alarm_control_panel/alarm_control_panel.cpp index ab0a780cefb..fb61776532f 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.cpp +++ b/esphome/components/alarm_control_panel/alarm_control_panel.cpp @@ -51,22 +51,6 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) { } } -void AlarmControlPanel::add_on_state_callback(std::function &&callback) { - this->state_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_cleared_callback(std::function &&callback) { - this->cleared_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_chime_callback(std::function &&callback) { - this->chime_callback_.add(std::move(callback)); -} - -void AlarmControlPanel::add_on_ready_callback(std::function &&callback) { - this->ready_callback_.add(std::move(callback)); -} - void AlarmControlPanel::arm_with_code_(AlarmControlPanelCall &(AlarmControlPanelCall::*arm_method)(), const char *code) { auto call = this->make_call(); diff --git a/esphome/components/alarm_control_panel/alarm_control_panel.h b/esphome/components/alarm_control_panel/alarm_control_panel.h index e8dc197e26f..cf99d359e75 100644 --- a/esphome/components/alarm_control_panel/alarm_control_panel.h +++ b/esphome/components/alarm_control_panel/alarm_control_panel.h @@ -37,25 +37,24 @@ class AlarmControlPanel : public EntityBase { * * @param callback The callback function */ - void add_on_state_callback(std::function &&callback); + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); + } - /** Add a callback for when the state of the alarm_control_panel clears from triggered - * - * @param callback The callback function - */ - void add_on_cleared_callback(std::function &&callback); + /** Add a callback for when the state of the alarm_control_panel clears from triggered. */ + template void add_on_cleared_callback(F &&callback) { + this->cleared_callback_.add(std::forward(callback)); + } - /** Add a callback for when a chime zone goes from closed to open - * - * @param callback The callback function - */ - void add_on_chime_callback(std::function &&callback); + /** Add a callback for when a chime zone goes from closed to open. */ + template void add_on_chime_callback(F &&callback) { + this->chime_callback_.add(std::forward(callback)); + } - /** Add a callback for when a ready state changes - * - * @param callback The callback function - */ - void add_on_ready_callback(std::function &&callback); + /** Add a callback for when a ready state changes. */ + template void add_on_ready_callback(F &&callback) { + this->ready_callback_.add(std::forward(callback)); + } /** A numeric representation of the supported features as per HomeAssistant * diff --git a/esphome/components/button/button.cpp b/esphome/components/button/button.cpp index 8c06cfe59b8..b1c491805e0 100644 --- a/esphome/components/button/button.cpp +++ b/esphome/components/button/button.cpp @@ -20,6 +20,5 @@ void Button::press() { this->press_action(); this->press_callback_.call(); } -void Button::add_on_press_callback(std::function &&callback) { this->press_callback_.add(std::move(callback)); } } // namespace esphome::button diff --git a/esphome/components/button/button.h b/esphome/components/button/button.h index 0f7576a419f..96e9107532b 100644 --- a/esphome/components/button/button.h +++ b/esphome/components/button/button.h @@ -34,7 +34,9 @@ class Button : public EntityBase { * * @param callback The void() callback. */ - void add_on_press_callback(std::function &&callback); + template void add_on_press_callback(F &&callback) { + this->press_callback_.add(std::forward(callback)); + } protected: /** You should implement this virtual method if you want to create your own button. diff --git a/esphome/components/canbus/canbus.h b/esphome/components/canbus/canbus.h index f7b84111bd5..420125e1d34 100644 --- a/esphome/components/canbus/canbus.h +++ b/esphome/components/canbus/canbus.h @@ -91,10 +91,7 @@ class Canbus : public Component { * - rtr If this is a remote transmission request * - data The message data */ - void add_callback( - std::function &data)> callback) { - this->callback_manager_.add(std::move(callback)); - } + template void add_callback(F &&callback) { this->callback_manager_.add(std::forward(callback)); } protected: template friend class CanbusSendAction; diff --git a/esphome/components/climate/climate.cpp b/esphome/components/climate/climate.cpp index 43d25effa33..3f44b986dc0 100644 --- a/esphome/components/climate/climate.cpp +++ b/esphome/components/climate/climate.cpp @@ -356,14 +356,6 @@ ClimateCall &ClimateCall::set_swing_mode(optional swing_mode) return *this; } -void Climate::add_on_state_callback(std::function &&callback) { - this->state_callback_.add(std::move(callback)); -} - -void Climate::add_on_control_callback(std::function &&callback) { - this->control_callback_.add(std::move(callback)); -} - // Random 32bit value; If this changes existing restore preferences are invalidated static const uint32_t RESTORE_STATE_VERSION = 0x848EA6ADUL; diff --git a/esphome/components/climate/climate.h b/esphome/components/climate/climate.h index aa9ca91bc2c..e2cb743c0a6 100644 --- a/esphome/components/climate/climate.h +++ b/esphome/components/climate/climate.h @@ -192,7 +192,9 @@ class Climate : public EntityBase { * * @param callback The callback to call. */ - void add_on_state_callback(std::function &&callback); + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); + } /** * Add a callback for the climate device configuration; each time the configuration parameters of a climate device @@ -200,7 +202,9 @@ class Climate : public EntityBase { * * @param callback The callback to call. */ - void add_on_control_callback(std::function &&callback); + template void add_on_control_callback(F &&callback) { + this->control_callback_.add(std::forward(callback)); + } /** Make a climate device control call, this is used to control the climate device, see the ClimateCall description * for more info. diff --git a/esphome/components/cover/cover.cpp b/esphome/components/cover/cover.cpp index 0589aa23796..bb5965d861b 100644 --- a/esphome/components/cover/cover.cpp +++ b/esphome/components/cover/cover.cpp @@ -139,7 +139,6 @@ bool CoverCall::get_stop() const { return this->stop_; } CoverCall Cover::make_call() { return {this}; } -void Cover::add_on_state_callback(std::function &&f) { this->state_callback_.add(std::move(f)); } void Cover::publish_state(bool save) { this->position = clamp(this->position, 0.0f, 1.0f); this->tilt = clamp(this->tilt, 0.0f, 1.0f); diff --git a/esphome/components/cover/cover.h b/esphome/components/cover/cover.h index 8cf9aa092aa..9a75e684871 100644 --- a/esphome/components/cover/cover.h +++ b/esphome/components/cover/cover.h @@ -125,7 +125,7 @@ class Cover : public EntityBase { /// Construct a new cover call used to control the cover. CoverCall make_call(); - void add_on_state_callback(std::function &&f); + template void add_on_state_callback(F &&f) { this->state_callback_.add(std::forward(f)); } /** Publish the current state of the cover. * diff --git a/esphome/components/datetime/datetime_base.h b/esphome/components/datetime/datetime_base.h index 1b0b3d54639..98f23aa7137 100644 --- a/esphome/components/datetime/datetime_base.h +++ b/esphome/components/datetime/datetime_base.h @@ -14,7 +14,9 @@ class DateTimeBase : public EntityBase { public: virtual ESPTime state_as_esptime() const = 0; - void add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); + } #ifdef USE_TIME void set_rtc(time::RealTimeClock *rtc) { this->rtc_ = rtc; } diff --git a/esphome/components/dfplayer/dfplayer.h b/esphome/components/dfplayer/dfplayer.h index 03d2230ca68..2c4ee03470d 100644 --- a/esphome/components/dfplayer/dfplayer.h +++ b/esphome/components/dfplayer/dfplayer.h @@ -51,8 +51,8 @@ class DFPlayer : public uart::UARTDevice, public Component { bool is_playing() { return is_playing_; } void dump_config() override; - void add_on_finished_playback_callback(std::function callback) { - this->on_finished_playback_callback_.add(std::move(callback)); + template void add_on_finished_playback_callback(F &&callback) { + this->on_finished_playback_callback_.add(std::forward(callback)); } protected: diff --git a/esphome/components/display_menu_base/menu_item.h b/esphome/components/display_menu_base/menu_item.h index 36de1460310..57d7350b9e7 100644 --- a/esphome/components/display_menu_base/menu_item.h +++ b/esphome/components/display_menu_base/menu_item.h @@ -44,9 +44,9 @@ class MenuItem { MenuItemMenu *get_parent() { return this->parent_; } MenuItemType get_type() const { return this->item_type_; } template void set_text(V val) { this->text_ = val; } - void add_on_enter_callback(std::function &&cb) { this->on_enter_callbacks_.add(std::move(cb)); } - void add_on_leave_callback(std::function &&cb) { this->on_leave_callbacks_.add(std::move(cb)); } - void add_on_value_callback(std::function &&cb) { this->on_value_callbacks_.add(std::move(cb)); } + template void add_on_enter_callback(F &&cb) { this->on_enter_callbacks_.add(std::forward(cb)); } + template void add_on_leave_callback(F &&cb) { this->on_leave_callbacks_.add(std::forward(cb)); } + template void add_on_value_callback(F &&cb) { this->on_value_callbacks_.add(std::forward(cb)); } std::string get_text() const { return const_cast(this)->text_.value(this); } virtual bool get_immediate_edit() const { return false; } @@ -170,8 +170,8 @@ class MenuItemCommand : public MenuItem { class MenuItemCustom : public MenuItemEditable { public: explicit MenuItemCustom() : MenuItemEditable(MENU_ITEM_CUSTOM) {} - void add_on_next_callback(std::function &&cb) { this->on_next_callbacks_.add(std::move(cb)); } - void add_on_prev_callback(std::function &&cb) { this->on_prev_callbacks_.add(std::move(cb)); } + template void add_on_next_callback(F &&cb) { this->on_next_callbacks_.add(std::forward(cb)); } + template void add_on_prev_callback(F &&cb) { this->on_prev_callbacks_.add(std::forward(cb)); } bool has_value() const override { return this->value_getter_.has_value(); } std::string get_value_text() const override; diff --git a/esphome/components/esp32_improv/esp32_improv_component.h b/esphome/components/esp32_improv/esp32_improv_component.h index 8f4cfd79581..41799f23251 100644 --- a/esphome/components/esp32_improv/esp32_improv_component.h +++ b/esphome/components/esp32_improv/esp32_improv_component.h @@ -48,8 +48,8 @@ class ESP32ImprovComponent : public Component, public improv_base::ImprovBase { bool should_start() const { return this->should_start_; } #ifdef USE_ESP32_IMPROV_STATE_CALLBACK - void add_on_state_callback(std::function &&callback) { - this->state_callback_.add(std::move(callback)); + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); } #endif #ifdef USE_BINARY_SENSOR diff --git a/esphome/components/event/event.cpp b/esphome/components/event/event.cpp index 667d4218f3c..ec63fd9c3eb 100644 --- a/esphome/components/event/event.cpp +++ b/esphome/components/event/event.cpp @@ -45,9 +45,5 @@ void Event::set_event_types(const std::vector &event_types) { this->last_event_type_ = nullptr; // Reset when types change } -void Event::add_on_event_callback(std::function &&callback) { - this->event_callback_.add(std::move(callback)); -} - } // namespace event } // namespace esphome diff --git a/esphome/components/event/event.h b/esphome/components/event/event.h index 5b6a94b47c0..ebbee0bfe23 100644 --- a/esphome/components/event/event.h +++ b/esphome/components/event/event.h @@ -66,7 +66,9 @@ class Event : public EntityBase { /// Check if an event has been triggered. bool has_event() const { return this->last_event_type_ != nullptr; } - void add_on_event_callback(std::function &&callback); + template void add_on_event_callback(F &&callback) { + this->event_callback_.add(std::forward(callback)); + } protected: LazyCallbackManager event_callback_; diff --git a/esphome/components/ezo/ezo.h b/esphome/components/ezo/ezo.h index f1a2802cbd7..d80869fbd9a 100644 --- a/esphome/components/ezo/ezo.h +++ b/esphome/components/ezo/ezo.h @@ -44,8 +44,8 @@ class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2 // Device Information void get_device_information(); - void add_device_infomation_callback(std::function &&callback) { - this->device_infomation_callback_.add(std::move(callback)); + template void add_device_infomation_callback(F &&callback) { + this->device_infomation_callback_.add(std::forward(callback)); } // Sleep @@ -56,15 +56,13 @@ class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2 // Slope void get_slope(); - void add_slope_callback(std::function &&callback) { - this->slope_callback_.add(std::move(callback)); - } + template void add_slope_callback(F &&callback) { this->slope_callback_.add(std::forward(callback)); } // T void get_t(); void set_t(float value); void set_tempcomp_value(float temp); // For backwards compatibility - void add_t_callback(std::function &&callback) { this->t_callback_.add(std::move(callback)); } + template void add_t_callback(F &&callback) { this->t_callback_.add(std::forward(callback)); } // Calibration void get_calibration(); @@ -73,20 +71,18 @@ class EZOSensor : public sensor::Sensor, public PollingComponent, public i2c::I2 void set_calibration_point_high(float value); void set_calibration_generic(float value); void clear_calibration(); - void add_calibration_callback(std::function &&callback) { - this->calibration_callback_.add(std::move(callback)); + template void add_calibration_callback(F &&callback) { + this->calibration_callback_.add(std::forward(callback)); } // LED void get_led_state(); void set_led_state(bool on); - void add_led_state_callback(std::function &&callback) { this->led_callback_.add(std::move(callback)); } + template void add_led_state_callback(F &&callback) { this->led_callback_.add(std::forward(callback)); } // Custom void send_custom(const std::string &to_send); - void add_custom_callback(std::function &&callback) { - this->custom_callback_.add(std::move(callback)); - } + template void add_custom_callback(F &&callback) { this->custom_callback_.add(std::forward(callback)); } protected: std::deque> commands_; diff --git a/esphome/components/factory_reset/factory_reset.h b/esphome/components/factory_reset/factory_reset.h index 990bb2edb66..34f89d73b60 100644 --- a/esphome/components/factory_reset/factory_reset.h +++ b/esphome/components/factory_reset/factory_reset.h @@ -17,8 +17,8 @@ class FactoryResetComponent : public Component { void dump_config() override; void setup() override; - void add_increment_callback(std::function &&callback) { - this->increment_callback_.add(std::move(callback)); + template void add_increment_callback(F &&callback) { + this->increment_callback_.add(std::forward(callback)); } protected: diff --git a/esphome/components/fan/fan.cpp b/esphome/components/fan/fan.cpp index c1e0a3dc2e1..97336e17b58 100644 --- a/esphome/components/fan/fan.cpp +++ b/esphome/components/fan/fan.cpp @@ -193,7 +193,6 @@ void Fan::apply_preset_mode_(const FanCall &call) { } } -void Fan::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } void Fan::publish_state() { auto traits = this->get_traits(); diff --git a/esphome/components/fan/fan.h b/esphome/components/fan/fan.h index 2caf3a712a2..e7b3681e32e 100644 --- a/esphome/components/fan/fan.h +++ b/esphome/components/fan/fan.h @@ -122,7 +122,9 @@ class Fan : public EntityBase { FanCall make_call(); /// Register a callback that will be called each time the state changes. - void add_on_state_callback(std::function &&callback); + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); + } void publish_state(); diff --git a/esphome/components/fingerprint_grow/fingerprint_grow.h b/esphome/components/fingerprint_grow/fingerprint_grow.h index db9d5ce564f..63839534f64 100644 --- a/esphome/components/fingerprint_grow/fingerprint_grow.h +++ b/esphome/components/fingerprint_grow/fingerprint_grow.h @@ -127,30 +127,30 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic void set_enrolling_binary_sensor(binary_sensor::BinarySensor *enrolling_binary_sensor) { this->enrolling_binary_sensor_ = enrolling_binary_sensor; } - void add_on_finger_scan_start_callback(std::function callback) { - this->finger_scan_start_callback_.add(std::move(callback)); + template void add_on_finger_scan_start_callback(F &&callback) { + this->finger_scan_start_callback_.add(std::forward(callback)); } - void add_on_finger_scan_matched_callback(std::function callback) { - this->finger_scan_matched_callback_.add(std::move(callback)); + template void add_on_finger_scan_matched_callback(F &&callback) { + this->finger_scan_matched_callback_.add(std::forward(callback)); } - void add_on_finger_scan_unmatched_callback(std::function callback) { - this->finger_scan_unmatched_callback_.add(std::move(callback)); + template void add_on_finger_scan_unmatched_callback(F &&callback) { + this->finger_scan_unmatched_callback_.add(std::forward(callback)); } - void add_on_finger_scan_misplaced_callback(std::function callback) { - this->finger_scan_misplaced_callback_.add(std::move(callback)); + template void add_on_finger_scan_misplaced_callback(F &&callback) { + this->finger_scan_misplaced_callback_.add(std::forward(callback)); } - void add_on_finger_scan_invalid_callback(std::function callback) { - this->finger_scan_invalid_callback_.add(std::move(callback)); + template void add_on_finger_scan_invalid_callback(F &&callback) { + this->finger_scan_invalid_callback_.add(std::forward(callback)); } - void add_on_enrollment_scan_callback(std::function callback) { - this->enrollment_scan_callback_.add(std::move(callback)); + template void add_on_enrollment_scan_callback(F &&callback) { + this->enrollment_scan_callback_.add(std::forward(callback)); } - void add_on_enrollment_done_callback(std::function callback) { - this->enrollment_done_callback_.add(std::move(callback)); + template void add_on_enrollment_done_callback(F &&callback) { + this->enrollment_done_callback_.add(std::forward(callback)); } - void add_on_enrollment_failed_callback(std::function callback) { - this->enrollment_failed_callback_.add(std::move(callback)); + template void add_on_enrollment_failed_callback(F &&callback) { + this->enrollment_failed_callback_.add(std::forward(callback)); } void enroll_fingerprint(uint16_t finger_id, uint8_t num_buffers); diff --git a/esphome/components/graphical_display_menu/graphical_display_menu.h b/esphome/components/graphical_display_menu/graphical_display_menu.h index 96f2bd79fd5..007889557dd 100644 --- a/esphome/components/graphical_display_menu/graphical_display_menu.h +++ b/esphome/components/graphical_display_menu/graphical_display_menu.h @@ -44,7 +44,7 @@ class GraphicalDisplayMenu : public display_menu_base::DisplayMenuComponent { void set_foreground_color(Color foreground_color); void set_background_color(Color background_color); - void add_on_redraw_callback(std::function &&cb) { this->on_redraw_callbacks_.add(std::move(cb)); } + template void add_on_redraw_callback(F &&cb) { this->on_redraw_callbacks_.add(std::forward(cb)); } void draw(display::Display *display, const display::Rect *bounds); diff --git a/esphome/components/haier/haier_base.cpp b/esphome/components/haier/haier_base.cpp index 1882aa439ef..35eaf36d329 100644 --- a/esphome/components/haier/haier_base.cpp +++ b/esphome/components/haier/haier_base.cpp @@ -197,10 +197,6 @@ void HaierClimateBase::send_custom_command(const haier_protocol::HaierMessage &m this->action_request_ = PendingAction({ActionRequest::SEND_CUSTOM_COMMAND, message}); } -void HaierClimateBase::add_status_message_callback(std::function &&callback) { - this->status_message_callback_.add(std::move(callback)); -} - haier_protocol::HandlerError HaierClimateBase::answer_preprocess_( haier_protocol::FrameType request_message_type, haier_protocol::FrameType expected_request_message_type, haier_protocol::FrameType answer_message_type, haier_protocol::FrameType expected_answer_message_type, diff --git a/esphome/components/haier/haier_base.h b/esphome/components/haier/haier_base.h index e24217bfd9d..87aa1d65eff 100644 --- a/esphome/components/haier/haier_base.h +++ b/esphome/components/haier/haier_base.h @@ -74,7 +74,9 @@ class HaierClimateBase : public esphome::Component, void set_answer_timeout(uint32_t timeout); void set_send_wifi(bool send_wifi); void send_custom_command(const haier_protocol::HaierMessage &message); - void add_status_message_callback(std::function &&callback); + template void add_status_message_callback(F &&callback) { + this->status_message_callback_.add(std::forward(callback)); + } protected: enum class ProtocolPhases { diff --git a/esphome/components/haier/hon_climate.cpp b/esphome/components/haier/hon_climate.cpp index b8889ef2bdc..b7888f7976e 100644 --- a/esphome/components/haier/hon_climate.cpp +++ b/esphome/components/haier/hon_climate.cpp @@ -114,14 +114,6 @@ void HonClimate::start_steri_cleaning() { } } -void HonClimate::add_alarm_start_callback(std::function &&callback) { - this->alarm_start_callback_.add(std::move(callback)); -} - -void HonClimate::add_alarm_end_callback(std::function &&callback) { - this->alarm_end_callback_.add(std::move(callback)); -} - haier_protocol::HandlerError HonClimate::get_device_version_answer_handler_(haier_protocol::FrameType request_type, haier_protocol::FrameType message_type, const uint8_t *data, size_t data_size) { diff --git a/esphome/components/haier/hon_climate.h b/esphome/components/haier/hon_climate.h index 9bddac3f923..7c48a3748b8 100644 --- a/esphome/components/haier/hon_climate.h +++ b/esphome/components/haier/hon_climate.h @@ -124,8 +124,12 @@ class HonClimate : public HaierClimateBase { void set_extra_sensors_packet_bytes_size(size_t size) { this->extra_sensors_packet_bytes_ = size; }; void set_status_message_header_size(size_t size) { this->status_message_header_size_ = size; }; void set_control_method(HonControlMethod method) { this->control_method_ = method; }; - void add_alarm_start_callback(std::function &&callback); - void add_alarm_end_callback(std::function &&callback); + template void add_alarm_start_callback(F &&callback) { + this->alarm_start_callback_.add(std::forward(callback)); + } + template void add_alarm_end_callback(F &&callback) { + this->alarm_end_callback_.add(std::forward(callback)); + } float get_active_alarm_count() const { return this->active_alarm_count_; } protected: diff --git a/esphome/components/hlk_fm22x/hlk_fm22x.h b/esphome/components/hlk_fm22x/hlk_fm22x.h index 0ea4636281c..d897d51881b 100644 --- a/esphome/components/hlk_fm22x/hlk_fm22x.h +++ b/esphome/components/hlk_fm22x/hlk_fm22x.h @@ -91,24 +91,23 @@ class HlkFm22xComponent : public PollingComponent, public uart::UARTDevice { void set_version_text_sensor(text_sensor::TextSensor *version_text_sensor) { this->version_text_sensor_ = version_text_sensor; } - void add_on_face_scan_matched_callback(std::function callback) { - this->face_scan_matched_callback_.add(std::move(callback)); + template void add_on_face_scan_matched_callback(F &&callback) { + this->face_scan_matched_callback_.add(std::forward(callback)); } - void add_on_face_scan_unmatched_callback(std::function callback) { - this->face_scan_unmatched_callback_.add(std::move(callback)); + template void add_on_face_scan_unmatched_callback(F &&callback) { + this->face_scan_unmatched_callback_.add(std::forward(callback)); } - void add_on_face_scan_invalid_callback(std::function callback) { - this->face_scan_invalid_callback_.add(std::move(callback)); + template void add_on_face_scan_invalid_callback(F &&callback) { + this->face_scan_invalid_callback_.add(std::forward(callback)); } - void add_on_face_info_callback( - std::function callback) { - this->face_info_callback_.add(std::move(callback)); + template void add_on_face_info_callback(F &&callback) { + this->face_info_callback_.add(std::forward(callback)); } - void add_on_enrollment_done_callback(std::function callback) { - this->enrollment_done_callback_.add(std::move(callback)); + template void add_on_enrollment_done_callback(F &&callback) { + this->enrollment_done_callback_.add(std::forward(callback)); } - void add_on_enrollment_failed_callback(std::function callback) { - this->enrollment_failed_callback_.add(std::move(callback)); + template void add_on_enrollment_failed_callback(F &&callback) { + this->enrollment_failed_callback_.add(std::forward(callback)); } void enroll_face(const std::string &name, HlkFm22xFaceDirection direction); diff --git a/esphome/components/key_collector/key_collector.h b/esphome/components/key_collector/key_collector.h index 8e30c333df7..014e2034bd3 100644 --- a/esphome/components/key_collector/key_collector.h +++ b/esphome/components/key_collector/key_collector.h @@ -21,14 +21,14 @@ class KeyCollector : public Component { void set_back_keys(std::string back_keys) { this->back_keys_ = std::move(back_keys); }; void set_clear_keys(std::string clear_keys) { this->clear_keys_ = std::move(clear_keys); }; void set_allowed_keys(std::string allowed_keys) { this->allowed_keys_ = std::move(allowed_keys); }; - void add_on_progress_callback(std::function &&callback) { - this->progress_callbacks_.add(std::move(callback)); + template void add_on_progress_callback(F &&callback) { + this->progress_callbacks_.add(std::forward(callback)); } - void add_on_result_callback(std::function &&callback) { - this->result_callbacks_.add(std::move(callback)); + template void add_on_result_callback(F &&callback) { + this->result_callbacks_.add(std::forward(callback)); } - void add_on_timeout_callback(std::function &&callback) { - this->timeout_callbacks_.add(std::move(callback)); + template void add_on_timeout_callback(F &&callback) { + this->timeout_callbacks_.add(std::forward(callback)); } void set_timeout(int timeout) { this->timeout_ = timeout; }; void set_enabled(bool enabled); diff --git a/esphome/components/key_provider/key_provider.cpp b/esphome/components/key_provider/key_provider.cpp index 5a0e24b13fa..64b0729d4d1 100644 --- a/esphome/components/key_provider/key_provider.cpp +++ b/esphome/components/key_provider/key_provider.cpp @@ -3,10 +3,6 @@ namespace esphome { namespace key_provider { -void KeyProvider::add_on_key_callback(std::function &&callback) { - this->key_callback_.add(std::move(callback)); -} - void KeyProvider::send_key_(uint8_t key) { this->key_callback_.call(key); } } // namespace key_provider diff --git a/esphome/components/key_provider/key_provider.h b/esphome/components/key_provider/key_provider.h index 272d3eecadc..97403427512 100644 --- a/esphome/components/key_provider/key_provider.h +++ b/esphome/components/key_provider/key_provider.h @@ -9,7 +9,7 @@ namespace key_provider { /// interface for components that provide keypresses class KeyProvider { public: - void add_on_key_callback(std::function &&callback); + template void add_on_key_callback(F &&callback) { this->key_callback_.add(std::forward(callback)); } protected: void send_key_(uint8_t key); diff --git a/esphome/components/ld2450/ld2450.cpp b/esphome/components/ld2450/ld2450.cpp index f9701cbdf66..0a1147c9240 100644 --- a/esphome/components/ld2450/ld2450.cpp +++ b/esphome/components/ld2450/ld2450.cpp @@ -407,10 +407,6 @@ void LD2450Component::restart_and_read_all_info() { this->set_timeout(1500, [this]() { this->read_all_info(); }); } -void LD2450Component::add_on_data_callback(std::function &&callback) { - this->data_callback_.add(std::move(callback)); -} - // Send command with values to LD2450 void LD2450Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) { ESP_LOGV(TAG, "Sending COMMAND %02X", command); diff --git a/esphome/components/ld2450/ld2450.h b/esphome/components/ld2450/ld2450.h index 9409dfc21df..e774dd9c756 100644 --- a/esphome/components/ld2450/ld2450.h +++ b/esphome/components/ld2450/ld2450.h @@ -145,7 +145,7 @@ class LD2450Component : public Component, public uart::UARTDevice { int32_t zone3_y1, int32_t zone3_x2, int32_t zone3_y2); /// Add a callback that will be called after each successfully processed periodic data frame. - void add_on_data_callback(std::function &&callback); + template void add_on_data_callback(F &&callback) { this->data_callback_.add(std::forward(callback)); } protected: void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len); diff --git a/esphome/components/lock/lock.cpp b/esphome/components/lock/lock.cpp index 939c84720bf..4aa636e998c 100644 --- a/esphome/components/lock/lock.cpp +++ b/esphome/components/lock/lock.cpp @@ -48,8 +48,6 @@ void Lock::publish_state(LockState state) { #endif } -void Lock::add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } - void LockCall::perform() { ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str()); this->validate_(); diff --git a/esphome/components/lock/lock.h b/esphome/components/lock/lock.h index bebd296eacb..707431d5433 100644 --- a/esphome/components/lock/lock.h +++ b/esphome/components/lock/lock.h @@ -150,7 +150,9 @@ class Lock : public EntityBase { * * @param callback The void(bool) callback. */ - void add_on_state_callback(std::function &&callback); + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); + } protected: friend LockCall; diff --git a/esphome/components/ltr501/ltr501.h b/esphome/components/ltr501/ltr501.h index d9a53c9bd46..2bd838a0fe2 100644 --- a/esphome/components/ltr501/ltr501.h +++ b/esphome/components/ltr501/ltr501.h @@ -160,12 +160,12 @@ class LTRAlsPs501Component : public PollingComponent, public i2c::I2CDevice { CallbackManager on_ps_high_trigger_callback_; CallbackManager on_ps_low_trigger_callback_; - void add_on_ps_high_trigger_callback_(std::function callback) { - this->on_ps_high_trigger_callback_.add(std::move(callback)); + template void add_on_ps_high_trigger_callback_(F &&callback) { + this->on_ps_high_trigger_callback_.add(std::forward(callback)); } - void add_on_ps_low_trigger_callback_(std::function callback) { - this->on_ps_low_trigger_callback_.add(std::move(callback)); + template void add_on_ps_low_trigger_callback_(F &&callback) { + this->on_ps_low_trigger_callback_.add(std::forward(callback)); } }; diff --git a/esphome/components/ltr_als_ps/ltr_als_ps.h b/esphome/components/ltr_als_ps/ltr_als_ps.h index 3ab2cea074f..2e24a14283d 100644 --- a/esphome/components/ltr_als_ps/ltr_als_ps.h +++ b/esphome/components/ltr_als_ps/ltr_als_ps.h @@ -160,12 +160,12 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice { CallbackManager on_ps_high_trigger_callback_; CallbackManager on_ps_low_trigger_callback_; - void add_on_ps_high_trigger_callback_(std::function callback) { - this->on_ps_high_trigger_callback_.add(std::move(callback)); + template void add_on_ps_high_trigger_callback_(F &&callback) { + this->on_ps_high_trigger_callback_.add(std::forward(callback)); } - void add_on_ps_low_trigger_callback_(std::function callback) { - this->on_ps_low_trigger_callback_.add(std::move(callback)); + template void add_on_ps_low_trigger_callback_(F &&callback) { + this->on_ps_low_trigger_callback_.add(std::forward(callback)); } }; diff --git a/esphome/components/lvgl/lvgl_esphome.h b/esphome/components/lvgl/lvgl_esphome.h index 9c82f3646bc..aa8dd2fba52 100644 --- a/esphome/components/lvgl/lvgl_esphome.h +++ b/esphome/components/lvgl/lvgl_esphome.h @@ -158,9 +158,7 @@ class LvglComponent : public PollingComponent { void setup() override; void update() override; void loop() override; - void add_on_idle_callback(std::function &&callback) { - this->idle_callbacks_.add(std::move(callback)); - } + template void add_on_idle_callback(F &&callback) { this->idle_callbacks_.add(std::forward(callback)); } static void monitor_cb(lv_disp_drv_t *disp_drv, uint32_t time, uint32_t px); static void render_start_cb(lv_disp_drv_t *disp_drv); diff --git a/esphome/components/media_player/media_player.cpp b/esphome/components/media_player/media_player.cpp index a53d598b0fe..70086089ff2 100644 --- a/esphome/components/media_player/media_player.cpp +++ b/esphome/components/media_player/media_player.cpp @@ -198,10 +198,6 @@ MediaPlayerCall &MediaPlayerCall::set_announcement(bool announce) { return *this; } -void MediaPlayer::add_on_state_callback(std::function &&callback) { - this->state_callback_.add(std::move(callback)); -} - void MediaPlayer::publish_state() { this->state_callback_.call(); #if defined(USE_MEDIA_PLAYER) && defined(USE_CONTROLLER_REGISTRY) diff --git a/esphome/components/media_player/media_player.h b/esphome/components/media_player/media_player.h index 35097477181..26eca469e7f 100644 --- a/esphome/components/media_player/media_player.h +++ b/esphome/components/media_player/media_player.h @@ -155,7 +155,9 @@ class MediaPlayer : public EntityBase { void publish_state(); - void add_on_state_callback(std::function &&callback); + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); + } virtual bool is_muted() const { return false; } diff --git a/esphome/components/microphone/microphone.cpp b/esphome/components/microphone/microphone.cpp deleted file mode 100644 index 0fbb393fd20..00000000000 --- a/esphome/components/microphone/microphone.cpp +++ /dev/null @@ -1,19 +0,0 @@ -#include "microphone.h" - -namespace esphome { -namespace microphone { - -void Microphone::add_data_callback(std::function &)> &&data_callback) { - std::function &)> mute_handled_callback = - [this, data_callback](const std::vector &data) { - if (this->mute_state_) { - data_callback(std::vector(data.size(), 0)); - } else { - data_callback(data); - }; - }; - this->data_callbacks_.add(std::move(mute_handled_callback)); -} - -} // namespace microphone -} // namespace esphome diff --git a/esphome/components/microphone/microphone.h b/esphome/components/microphone/microphone.h index fcf98224581..50ce1a72818 100644 --- a/esphome/components/microphone/microphone.h +++ b/esphome/components/microphone/microphone.h @@ -4,7 +4,6 @@ #include #include -#include #include #include "esphome/core/helpers.h" @@ -22,7 +21,15 @@ class Microphone { public: virtual void start() = 0; virtual void stop() = 0; - void add_data_callback(std::function &)> &&data_callback); + template void add_data_callback(F &&data_callback) { + this->data_callbacks_.add([this, data_callback](const std::vector &data) { + if (this->mute_state_) { + data_callback(std::vector(data.size(), 0)); + } else { + data_callback(data); + } + }); + } bool is_running() const { return this->state_ == STATE_RUNNING; } bool is_stopped() const { return this->state_ == STATE_STOPPED; } diff --git a/esphome/components/microphone/microphone_source.cpp b/esphome/components/microphone/microphone_source.cpp index 00efcf22a14..fb4ebc4a042 100644 --- a/esphome/components/microphone/microphone_source.cpp +++ b/esphome/components/microphone/microphone_source.cpp @@ -6,24 +6,6 @@ namespace microphone { static const int32_t Q25_MAX_VALUE = (1 << 25) - 1; static const int32_t Q25_MIN_VALUE = ~Q25_MAX_VALUE; -void MicrophoneSource::add_data_callback(std::function &)> &&data_callback) { - std::function &)> filtered_callback = - [this, data_callback](const std::vector &data) { - if (this->enabled_ || this->passive_) { - if (this->processed_samples_.use_count() == 0) { - // Create vector if its unused - this->processed_samples_ = std::make_shared>(); - } - - // Take temporary ownership of samples vector to avoid deallaction before the callback finishes - std::shared_ptr> output_samples = this->processed_samples_; - this->process_audio_(data, *output_samples); - data_callback(*output_samples); - } - }; - this->mic_->add_data_callback(std::move(filtered_callback)); -} - audio::AudioStreamInfo MicrophoneSource::get_audio_stream_info() { return audio::AudioStreamInfo(this->bits_per_sample_, this->channels_.count(), this->mic_->get_audio_stream_info().get_sample_rate()); diff --git a/esphome/components/microphone/microphone_source.h b/esphome/components/microphone/microphone_source.h index 1e81a284b67..5c8053e502e 100644 --- a/esphome/components/microphone/microphone_source.h +++ b/esphome/components/microphone/microphone_source.h @@ -7,7 +7,6 @@ #include #include #include -#include #include namespace esphome { @@ -47,7 +46,21 @@ class MicrophoneSource { /// @param channel 0-indexed channel number to enable void add_channel(uint8_t channel) { this->channels_.set(channel); } - void add_data_callback(std::function &)> &&data_callback); + template void add_data_callback(F &&data_callback) { + this->mic_->add_data_callback([this, data_callback](const std::vector &data) { + if (this->enabled_ || this->passive_) { + if (this->processed_samples_.use_count() == 0) { + // Create vector if its unused + this->processed_samples_ = std::make_shared>(); + } + + // Take temporary ownership of samples vector to avoid deallocation before the callback finishes + std::shared_ptr> output_samples = this->processed_samples_; + this->process_audio_(data, *output_samples); + data_callback(*output_samples); + } + }); + } void set_gain_factor(int32_t gain_factor) { this->gain_factor_ = clamp(gain_factor, 1, MAX_GAIN_FACTOR); } int32_t get_gain_factor() { return this->gain_factor_; } diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index f77f51a20df..ea6ba9d0859 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -837,17 +837,5 @@ int64_t payload_to_number(const std::vector &data, SensorValueType sens return value; } -void ModbusController::add_on_command_sent_callback(std::function &&callback) { - this->command_sent_callback_.add(std::move(callback)); -} - -void ModbusController::add_on_online_callback(std::function &&callback) { - this->online_callback_.add(std::move(callback)); -} - -void ModbusController::add_on_offline_callback(std::function &&callback) { - this->offline_callback_.add(std::move(callback)); -} - } // namespace modbus_controller } // namespace esphome diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index bd3d4d705ec..78c3b959654 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -508,11 +508,17 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { /// get if the module is offline, didn't respond the last command bool get_module_offline() { return module_offline_; } /// Set callback for commands - void add_on_command_sent_callback(std::function &&callback); + template void add_on_command_sent_callback(F &&callback) { + this->command_sent_callback_.add(std::forward(callback)); + } /// Set callback for online changes - void add_on_online_callback(std::function &&callback); + template void add_on_online_callback(F &&callback) { + this->online_callback_.add(std::forward(callback)); + } /// Set callback for offline changes - void add_on_offline_callback(std::function &&callback); + template void add_on_offline_callback(F &&callback) { + this->offline_callback_.add(std::forward(callback)); + } /// called by esphome generated code to set the max_cmd_retries. void set_max_cmd_retries(uint8_t max_cmd_retries) { this->max_cmd_retries_ = max_cmd_retries; } /// get how many times a command will be (re)sent if no response is received diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index 01ceb3d765e..85da6af48aa 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -214,30 +214,6 @@ void Nextion::update() { } } -void Nextion::add_sleep_state_callback(std::function &&callback) { - this->sleep_callback_.add(std::move(callback)); -} - -void Nextion::add_wake_state_callback(std::function &&callback) { - this->wake_callback_.add(std::move(callback)); -} - -void Nextion::add_setup_state_callback(std::function &&callback) { - this->setup_callback_.add(std::move(callback)); -} - -void Nextion::add_new_page_callback(std::function &&callback) { - this->page_callback_.add(std::move(callback)); -} - -void Nextion::add_touch_event_callback(std::function &&callback) { - this->touch_callback_.add(std::move(callback)); -} - -void Nextion::add_buffer_overflow_event_callback(std::function &&callback) { - this->buffer_overflow_callback_.add(std::move(callback)); -} - void Nextion::update_all_components() { if ((!this->is_setup() && !this->connection_state_.ignore_is_setup_) || this->is_sleeping()) return; diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 7999e3c4e3e..2842e57ce83 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -1138,37 +1138,47 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * @param callback The void() callback. */ - void add_sleep_state_callback(std::function &&callback); + template void add_sleep_state_callback(F &&callback) { + this->sleep_callback_.add(std::forward(callback)); + } /** Add a callback to be notified of wake state changes. * * @param callback The void() callback. */ - void add_wake_state_callback(std::function &&callback); + template void add_wake_state_callback(F &&callback) { + this->wake_callback_.add(std::forward(callback)); + } /** Add a callback to be notified when the nextion completes its initialize setup. * * @param callback The void() callback. */ - void add_setup_state_callback(std::function &&callback); + template void add_setup_state_callback(F &&callback) { + this->setup_callback_.add(std::forward(callback)); + } /** Add a callback to be notified when the nextion changes pages. * * @param callback The void(std::string) callback. */ - void add_new_page_callback(std::function &&callback); + template void add_new_page_callback(F &&callback) { this->page_callback_.add(std::forward(callback)); } /** Add a callback to be notified when Nextion has a touch event. * * @param callback The void() callback. */ - void add_touch_event_callback(std::function &&callback); + template void add_touch_event_callback(F &&callback) { + this->touch_callback_.add(std::forward(callback)); + } /** Add a callback to be notified when the nextion reports a buffer overflow. * * @param callback The void() callback. */ - void add_buffer_overflow_event_callback(std::function &&callback); + template void add_buffer_overflow_event_callback(F &&callback) { + this->buffer_overflow_callback_.add(std::forward(callback)); + } void update_all_components(); diff --git a/esphome/components/number/number.cpp b/esphome/components/number/number.cpp index c0653c3b304..fb5d6e9f28c 100644 --- a/esphome/components/number/number.cpp +++ b/esphome/components/number/number.cpp @@ -29,8 +29,4 @@ void Number::publish_state(float state) { #endif } -void Number::add_on_state_callback(std::function &&callback) { - this->state_callback_.add(std::move(callback)); -} - } // namespace esphome::number diff --git a/esphome/components/number/number.h b/esphome/components/number/number.h index 0425714702f..579d488cf06 100644 --- a/esphome/components/number/number.h +++ b/esphome/components/number/number.h @@ -34,7 +34,9 @@ class Number : public EntityBase { NumberCall make_call() { return NumberCall(this); } - void add_on_state_callback(std::function &&callback); + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); + } NumberTraits traits; diff --git a/esphome/components/online_image/online_image.cpp b/esphome/components/online_image/online_image.cpp index 22bf6a3056f..24926aa4dcd 100644 --- a/esphome/components/online_image/online_image.cpp +++ b/esphome/components/online_image/online_image.cpp @@ -228,14 +228,6 @@ void OnlineImage::end_connection_() { this->disable_loop(); } -void OnlineImage::add_on_finished_callback(std::function &&callback) { - this->download_finished_callback_.add(std::move(callback)); -} - -void OnlineImage::add_on_error_callback(std::function &&callback) { - this->download_error_callback_.add(std::move(callback)); -} - void OnlineImage::release() { // Clear cache headers this->etag_ = ""; diff --git a/esphome/components/online_image/online_image.h b/esphome/components/online_image/online_image.h index 12c25645260..3a348cbb07a 100644 --- a/esphome/components/online_image/online_image.h +++ b/esphome/components/online_image/online_image.h @@ -65,8 +65,12 @@ class OnlineImage : public PollingComponent, */ void release(); - void add_on_finished_callback(std::function &&callback); - void add_on_error_callback(std::function &&callback); + template void add_on_finished_callback(F &&callback) { + this->download_finished_callback_.add(std::forward(callback)); + } + template void add_on_error_callback(F &&callback) { + this->download_error_callback_.add(std::forward(callback)); + } protected: bool validate_url_(const std::string &url); diff --git a/esphome/components/opentherm/hub.h b/esphome/components/opentherm/hub.h index ee0cfd104d1..960e23d6dd3 100644 --- a/esphome/components/opentherm/hub.h +++ b/esphome/components/opentherm/hub.h @@ -160,11 +160,11 @@ class OpenthermHub : public Component { void set_dhw_block(bool value) { this->dhw_block = value; } void set_sync_mode(bool sync_mode) { this->sync_mode_ = sync_mode; } - void add_on_before_send_callback(std::function &&callback) { - this->before_send_callback_.add(std::move(callback)); + template void add_on_before_send_callback(F &&callback) { + this->before_send_callback_.add(std::forward(callback)); } - void add_on_before_process_response_callback(std::function &&callback) { - this->before_process_response_callback_.add(std::move(callback)); + template void add_on_before_process_response_callback(F &&callback) { + this->before_process_response_callback_.add(std::forward(callback)); } float get_setup_priority() const override { return setup_priority::HARDWARE; } diff --git a/esphome/components/pid/pid_climate.h b/esphome/components/pid/pid_climate.h index 3708c29ff1b..479a0e48ee4 100644 --- a/esphome/components/pid/pid_climate.h +++ b/esphome/components/pid/pid_climate.h @@ -72,8 +72,8 @@ class PIDClimate : public climate::Climate, public Component { // float get_deadband() const { return controller_.deadband; } // float get_proportional_deadband_multiplier() const { return controller_.proportional_deadband_multiplier; } - void add_on_pid_computed_callback(std::function &&callback) { - pid_computed_callback_.add(std::move(callback)); + template void add_on_pid_computed_callback(F &&callback) { + this->pid_computed_callback_.add(std::forward(callback)); } void set_default_target_temperature(float default_target_temperature) { default_target_temperature_ = default_target_temperature; diff --git a/esphome/components/pn532/pn532.h b/esphome/components/pn532/pn532.h index e57ecd81046..1f6a6b3bc3f 100644 --- a/esphome/components/pn532/pn532.h +++ b/esphome/components/pn532/pn532.h @@ -43,8 +43,8 @@ class PN532 : public PollingComponent { void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); } void register_ontagremoved_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); } - void add_on_finished_write_callback(std::function callback) { - this->on_finished_write_callback_.add(std::move(callback)); + template void add_on_finished_write_callback(F &&callback) { + this->on_finished_write_callback_.add(std::forward(callback)); } bool is_writing() { return this->next_task_ != READ; }; diff --git a/esphome/components/pn7150/pn7150.h b/esphome/components/pn7150/pn7150.h index c5dd2838325..a468d80943e 100644 --- a/esphome/components/pn7150/pn7150.h +++ b/esphome/components/pn7150/pn7150.h @@ -167,12 +167,12 @@ class PN7150 : public nfc::Nfcc, public Component { void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); } void register_ontagremoved_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); } - void add_on_emulated_tag_scan_callback(std::function callback) { - this->on_emulated_tag_scan_callback_.add(std::move(callback)); + template void add_on_emulated_tag_scan_callback(F &&callback) { + this->on_emulated_tag_scan_callback_.add(std::forward(callback)); } - void add_on_finished_write_callback(std::function callback) { - this->on_finished_write_callback_.add(std::move(callback)); + template void add_on_finished_write_callback(F &&callback) { + this->on_finished_write_callback_.add(std::forward(callback)); } bool is_writing() { return this->next_task_ != EP_READ; }; diff --git a/esphome/components/pn7160/pn7160.h b/esphome/components/pn7160/pn7160.h index 77ab49399c5..44f7eb07965 100644 --- a/esphome/components/pn7160/pn7160.h +++ b/esphome/components/pn7160/pn7160.h @@ -184,12 +184,12 @@ class PN7160 : public nfc::Nfcc, public Component { void register_ontag_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontag_.push_back(trig); } void register_ontagremoved_trigger(nfc::NfcOnTagTrigger *trig) { this->triggers_ontagremoved_.push_back(trig); } - void add_on_emulated_tag_scan_callback(std::function callback) { - this->on_emulated_tag_scan_callback_.add(std::move(callback)); + template void add_on_emulated_tag_scan_callback(F &&callback) { + this->on_emulated_tag_scan_callback_.add(std::forward(callback)); } - void add_on_finished_write_callback(std::function callback) { - this->on_finished_write_callback_.add(std::move(callback)); + template void add_on_finished_write_callback(F &&callback) { + this->on_finished_write_callback_.add(std::forward(callback)); } bool is_writing() { return this->next_task_ != EP_READ; }; diff --git a/esphome/components/rf_bridge/rf_bridge.h b/esphome/components/rf_bridge/rf_bridge.h index c93b636c38c..e5780c9ebe5 100644 --- a/esphome/components/rf_bridge/rf_bridge.h +++ b/esphome/components/rf_bridge/rf_bridge.h @@ -49,11 +49,11 @@ class RFBridgeComponent : public uart::UARTDevice, public Component { public: void loop() override; void dump_config() override; - void add_on_code_received_callback(std::function callback) { - this->data_callback_.add(std::move(callback)); + template void add_on_code_received_callback(F &&callback) { + this->data_callback_.add(std::forward(callback)); } - void add_on_advanced_code_received_callback(std::function callback) { - this->advanced_data_callback_.add(std::move(callback)); + template void add_on_advanced_code_received_callback(F &&callback) { + this->advanced_data_callback_.add(std::forward(callback)); } void send_code(RFBridgeData data); void send_advanced_code(const RFBridgeAdvancedData &data); diff --git a/esphome/components/rotary_encoder/rotary_encoder.h b/esphome/components/rotary_encoder/rotary_encoder.h index 865554cd4d5..4b776fe55e9 100644 --- a/esphome/components/rotary_encoder/rotary_encoder.h +++ b/esphome/components/rotary_encoder/rotary_encoder.h @@ -82,15 +82,15 @@ class RotaryEncoderSensor : public sensor::Sensor, public Component { void dump_config() override; void loop() override; - void add_on_clockwise_callback(std::function callback) { - this->on_clockwise_callback_.add(std::move(callback)); + template void add_on_clockwise_callback(F &&callback) { + this->on_clockwise_callback_.add(std::forward(callback)); } - void add_on_anticlockwise_callback(std::function callback) { - this->on_anticlockwise_callback_.add(std::move(callback)); + template void add_on_anticlockwise_callback(F &&callback) { + this->on_anticlockwise_callback_.add(std::forward(callback)); } - void register_listener(std::function listener) { this->listeners_.add(std::move(listener)); } + template void register_listener(F &&listener) { this->listeners_.add(std::forward(listener)); } protected: InternalGPIOPin *pin_a_; diff --git a/esphome/components/rtttl/rtttl.h b/esphome/components/rtttl/rtttl.h index e37cccae9ef..bff43d2edd8 100644 --- a/esphome/components/rtttl/rtttl.h +++ b/esphome/components/rtttl/rtttl.h @@ -45,8 +45,8 @@ class Rtttl : public Component { bool is_playing() { return this->state_ != State::STOPPED; } - void add_on_finished_playback_callback(std::function callback) { - this->on_finished_playback_callback_.add(std::move(callback)); + template void add_on_finished_playback_callback(F &&callback) { + this->on_finished_playback_callback_.add(std::forward(callback)); } protected: diff --git a/esphome/components/safe_mode/safe_mode.h b/esphome/components/safe_mode/safe_mode.h index 1b28ea28f20..2733054962e 100644 --- a/esphome/components/safe_mode/safe_mode.h +++ b/esphome/components/safe_mode/safe_mode.h @@ -34,8 +34,8 @@ class SafeModeComponent final : public Component { void mark_successful(); #ifdef USE_SAFE_MODE_CALLBACK - void add_on_safe_mode_callback(std::function &&callback) { - this->safe_mode_callback_.add(std::move(callback)); + template void add_on_safe_mode_callback(F &&callback) { + this->safe_mode_callback_.add(std::forward(callback)); } #endif diff --git a/esphome/components/sdl/sdl_esphome.h b/esphome/components/sdl/sdl_esphome.h index bf5fde14282..c025e8ff6e9 100644 --- a/esphome/components/sdl/sdl_esphome.h +++ b/esphome/components/sdl/sdl_esphome.h @@ -37,11 +37,11 @@ class Sdl : public display::Display { int get_height() override { return this->height_; } float get_setup_priority() const override { return setup_priority::HARDWARE; } void dump_config() override { LOG_DISPLAY("", "SDL", this); } - void add_key_listener(int32_t keycode, std::function &&callback) { + template void add_key_listener(int32_t keycode, F &&callback) { if (!this->key_callbacks_.count(keycode)) { this->key_callbacks_[keycode] = CallbackManager(); } - this->key_callbacks_[keycode].add(std::move(callback)); + this->key_callbacks_[keycode].add(std::forward(callback)); } int mouse_x{}; diff --git a/esphome/components/select/select.cpp b/esphome/components/select/select.cpp index 91e27b30dee..df90c657e2b 100644 --- a/esphome/components/select/select.cpp +++ b/esphome/components/select/select.cpp @@ -42,10 +42,6 @@ StringRef Select::current_option() const { return this->has_state() ? StringRef(this->option_at(this->active_index_)) : StringRef(); } -void Select::add_on_state_callback(std::function &&callback) { - this->state_callback_.add(std::move(callback)); -} - bool Select::has_option(const std::string &option) const { return this->index_of(option.c_str()).has_value(); } bool Select::has_option(const char *option) const { return this->index_of(option).has_value(); } diff --git a/esphome/components/select/select.h b/esphome/components/select/select.h index c91acd1e19e..465283d92a2 100644 --- a/esphome/components/select/select.h +++ b/esphome/components/select/select.h @@ -76,7 +76,9 @@ class Select : public EntityBase { /// Return the option value at the provided index offset (as const char* from flash). const char *option_at(size_t index) const; - void add_on_state_callback(std::function &&callback); + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); + } protected: friend class SelectCall; diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index a7af6403efc..b4e59dfeb57 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -79,11 +79,6 @@ void Sensor::publish_state(float state) { #endif } -void Sensor::add_on_state_callback(std::function &&callback) { this->callback_.add(std::move(callback)); } -void Sensor::add_on_raw_state_callback(std::function &&callback) { - this->raw_callback_.add(std::move(callback)); -} - #ifdef USE_SENSOR_FILTER void Sensor::add_filter(Filter *filter) { // inefficient, but only happens once on every sensor setup and nobody's going to have massive amounts of diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 197896f6f68..b3bd9620364 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -111,9 +111,11 @@ class Sensor : public EntityBase { // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) /// Add a callback that will be called every time a filtered value arrives. - void add_on_state_callback(std::function &&callback); + template void add_on_state_callback(F &&callback) { this->callback_.add(std::forward(callback)); } /// Add a callback that will be called every time the sensor sends a raw value. - void add_on_raw_state_callback(std::function &&callback); + template void add_on_raw_state_callback(F &&callback) { + this->raw_callback_.add(std::forward(callback)); + } /** This member variable stores the last state that has passed through all filters. * diff --git a/esphome/components/sim800l/sim800l.h b/esphome/components/sim800l/sim800l.h index e9e2f66d789..d79279ea720 100644 --- a/esphome/components/sim800l/sim800l.h +++ b/esphome/components/sim800l/sim800l.h @@ -61,20 +61,20 @@ class Sim800LComponent : public uart::UARTDevice, public PollingComponent { #ifdef USE_SENSOR void set_rssi_sensor(sensor::Sensor *rssi_sensor) { rssi_sensor_ = rssi_sensor; } #endif - void add_on_sms_received_callback(std::function callback) { - this->sms_received_callback_.add(std::move(callback)); + template void add_on_sms_received_callback(F &&callback) { + this->sms_received_callback_.add(std::forward(callback)); } - void add_on_incoming_call_callback(std::function callback) { - this->incoming_call_callback_.add(std::move(callback)); + template void add_on_incoming_call_callback(F &&callback) { + this->incoming_call_callback_.add(std::forward(callback)); } - void add_on_call_connected_callback(std::function callback) { - this->call_connected_callback_.add(std::move(callback)); + template void add_on_call_connected_callback(F &&callback) { + this->call_connected_callback_.add(std::forward(callback)); } - void add_on_call_disconnected_callback(std::function callback) { - this->call_disconnected_callback_.add(std::move(callback)); + template void add_on_call_disconnected_callback(F &&callback) { + this->call_disconnected_callback_.add(std::forward(callback)); } - void add_on_ussd_received_callback(std::function callback) { - this->ussd_received_callback_.add(std::move(callback)); + template void add_on_ussd_received_callback(F &&callback) { + this->ussd_received_callback_.add(std::forward(callback)); } void send_sms(const std::string &recipient, const std::string &message); void send_ussd(const std::string &ussd_code); diff --git a/esphome/components/sml/sml.cpp b/esphome/components/sml/sml.cpp index c1ffdc0e689..c8d5fcc2694 100644 --- a/esphome/components/sml/sml.cpp +++ b/esphome/components/sml/sml.cpp @@ -61,10 +61,6 @@ void Sml::loop() { } } -void Sml::add_on_data_callback(std::function, bool)> &&callback) { - this->data_callbacks_.add(std::move(callback)); -} - void Sml::process_sml_file_(const BytesView &sml_data) { SmlFile sml_file(sml_data); std::vector obis_info = sml_file.get_obis_info(); diff --git a/esphome/components/sml/sml.h b/esphome/components/sml/sml.h index 15ca43944cb..29a2f48bbec 100644 --- a/esphome/components/sml/sml.h +++ b/esphome/components/sml/sml.h @@ -24,7 +24,7 @@ class Sml : public Component, public uart::UARTDevice { void loop() override; void dump_config() override; std::vector sml_listeners_{}; - void add_on_data_callback(std::function, bool)> &&callback); + template void add_on_data_callback(F &&callback) { this->data_callbacks_.add(std::forward(callback)); } protected: void process_sml_file_(const BytesView &sml_data); diff --git a/esphome/components/speaker/speaker.h b/esphome/components/speaker/speaker.h index 373d2e3a746..5b89d00c69f 100644 --- a/esphome/components/speaker/speaker.h +++ b/esphome/components/speaker/speaker.h @@ -106,8 +106,8 @@ class Speaker { /// Parameters: /// - Frames played /// - System time in microseconds when the frames were written to the DAC - void add_audio_output_callback(std::function &&callback) { - this->audio_output_callback_.add(std::move(callback)); + template void add_audio_output_callback(F &&callback) { + this->audio_output_callback_.add(std::forward(callback)); } protected: diff --git a/esphome/components/switch/switch.cpp b/esphome/components/switch/switch.cpp index 9e9af213680..df762addbb1 100644 --- a/esphome/components/switch/switch.cpp +++ b/esphome/components/switch/switch.cpp @@ -69,9 +69,6 @@ void Switch::publish_state(bool state) { } bool Switch::assumed_state() { return false; } -void Switch::add_on_state_callback(std::function &&callback) { - this->state_callback_.add(std::move(callback)); -} void Switch::set_inverted(bool inverted) { this->inverted_ = inverted; } bool Switch::is_inverted() const { return this->inverted_; } diff --git a/esphome/components/switch/switch.h b/esphome/components/switch/switch.h index c4f8525793a..b7761cba0a1 100644 --- a/esphome/components/switch/switch.h +++ b/esphome/components/switch/switch.h @@ -93,7 +93,9 @@ class Switch : public EntityBase { * * @param callback The void(bool) callback. */ - void add_on_state_callback(std::function &&callback); + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); + } /** Returns the initial state of the switch, as persisted previously, or empty if never persisted. diff --git a/esphome/components/text/text.cpp b/esphome/components/text/text.cpp index d8ab6b1b92c..12abc5d9390 100644 --- a/esphome/components/text/text.cpp +++ b/esphome/components/text/text.cpp @@ -29,8 +29,4 @@ void Text::publish_state(const char *state, size_t len) { #endif } -void Text::add_on_state_callback(std::function &&callback) { - this->state_callback_.add(std::move(callback)); -} - } // namespace esphome::text diff --git a/esphome/components/text/text.h b/esphome/components/text/text.h index 7d255e56880..eb6a68f9980 100644 --- a/esphome/components/text/text.h +++ b/esphome/components/text/text.h @@ -30,7 +30,9 @@ class Text : public EntityBase { /// Instantiate a TextCall object to modify this text component's state. TextCall make_call() { return TextCall(this); } - void add_on_state_callback(std::function &&callback); + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); + } protected: friend class TextCall; diff --git a/esphome/components/text_sensor/text_sensor.cpp b/esphome/components/text_sensor/text_sensor.cpp index 91561c5f420..aa49a85d265 100644 --- a/esphome/components/text_sensor/text_sensor.cpp +++ b/esphome/components/text_sensor/text_sensor.cpp @@ -83,13 +83,6 @@ void TextSensor::clear_filters() { } #endif // USE_TEXT_SENSOR_FILTER -void TextSensor::add_on_state_callback(std::function callback) { - this->callback_.add(std::move(callback)); -} -void TextSensor::add_on_raw_state_callback(std::function callback) { - this->raw_callback_.add(std::move(callback)); -} - const std::string &TextSensor::get_state() const { return this->state; } const std::string &TextSensor::get_raw_state() const { #ifdef USE_TEXT_SENSOR_FILTER diff --git a/esphome/components/text_sensor/text_sensor.h b/esphome/components/text_sensor/text_sensor.h index d26cfade966..8941790e7cc 100644 --- a/esphome/components/text_sensor/text_sensor.h +++ b/esphome/components/text_sensor/text_sensor.h @@ -62,9 +62,11 @@ class TextSensor : public EntityBase { void clear_filters(); #endif - void add_on_state_callback(std::function callback); + template void add_on_state_callback(F &&callback) { this->callback_.add(std::forward(callback)); } /// Add a callback that will be called every time the sensor sends a raw value. - void add_on_raw_state_callback(std::function callback); + template void add_on_raw_state_callback(F &&callback) { + this->raw_callback_.add(std::forward(callback)); + } // ========== INTERNAL METHODS ========== // (In most use cases you won't need these) diff --git a/esphome/components/time/real_time_clock.h b/esphome/components/time/real_time_clock.h index f9de5f5614c..06ee2ea5af4 100644 --- a/esphome/components/time/real_time_clock.h +++ b/esphome/components/time/real_time_clock.h @@ -55,9 +55,9 @@ class RealTimeClock : public PollingComponent { /// Get the current time as the UTC epoch since January 1st 1970. time_t timestamp_now() { return ::time(nullptr); } - void add_on_time_sync_callback(std::function &&callback) { - this->time_sync_callback_.add(std::move(callback)); - }; + template void add_on_time_sync_callback(F &&callback) { + this->time_sync_callback_.add(std::forward(callback)); + } void dump_config() override; diff --git a/esphome/components/tuya/tuya.h b/esphome/components/tuya/tuya.h index 76431ddfe45..7e6b50f0847 100644 --- a/esphome/components/tuya/tuya.h +++ b/esphome/components/tuya/tuya.h @@ -112,8 +112,8 @@ class Tuya : public Component, public uart::UARTDevice { void add_ignore_mcu_update_on_datapoints(uint8_t ignore_mcu_update_on_datapoints) { this->ignore_mcu_update_on_datapoints_.push_back(ignore_mcu_update_on_datapoints); } - void add_on_initialized_callback(std::function callback) { - this->initialized_callback_.add(std::move(callback)); + template void add_on_initialized_callback(F &&callback) { + this->initialized_callback_.add(std::forward(callback)); } protected: diff --git a/esphome/components/uart/uart_component.h b/esphome/components/uart/uart_component.h index 853de719fef..ee2b006039c 100644 --- a/esphome/components/uart/uart_component.h +++ b/esphome/components/uart/uart_component.h @@ -190,9 +190,7 @@ class UARTComponent { #endif // USE_ESP8266 || USE_ESP32 #ifdef USE_UART_DEBUGGER - void add_debug_callback(std::function &&callback) { - this->debug_callback_.add(std::move(callback)); - } + template void add_debug_callback(F &&callback) { this->debug_callback_.add(std::forward(callback)); } #endif protected: diff --git a/esphome/components/udp/udp_component.h b/esphome/components/udp/udp_component.h index 7fd63080655..fb0edf2ebd7 100644 --- a/esphome/components/udp/udp_component.h +++ b/esphome/components/udp/udp_component.h @@ -28,9 +28,7 @@ class UDPComponent : public Component { void set_broadcast_port(uint16_t port) { this->broadcast_port_ = port; } void set_should_broadcast() { this->should_broadcast_ = true; } void set_should_listen() { this->should_listen_ = true; } - void add_listener(std::function)> &&listener) { - this->packet_listeners_.add(std::move(listener)); - } + template void add_listener(F &&listener) { this->packet_listeners_.add(std::forward(listener)); } void setup() override; void loop() override; void dump_config() override; diff --git a/esphome/components/update/update_entity.h b/esphome/components/update/update_entity.h index 82eaacaf76d..f7d0032f217 100644 --- a/esphome/components/update/update_entity.h +++ b/esphome/components/update/update_entity.h @@ -40,7 +40,9 @@ class UpdateEntity : public EntityBase { const UpdateInfo &update_info = update_info_; const UpdateState &state = state_; - void add_on_state_callback(std::function &&callback) { this->state_callback_.add(std::move(callback)); } + template void add_on_state_callback(F &&callback) { + this->state_callback_.add(std::forward(callback)); + } Trigger *get_update_available_trigger() { if (!update_available_trigger_) { update_available_trigger_ = std::make_unique>(); diff --git a/esphome/components/valve/valve.cpp b/esphome/components/valve/valve.cpp index 493ffd8da26..636da1f3c34 100644 --- a/esphome/components/valve/valve.cpp +++ b/esphome/components/valve/valve.cpp @@ -125,7 +125,6 @@ bool ValveCall::get_stop() const { return this->stop_; } ValveCall Valve::make_call() { return {this}; } -void Valve::add_on_state_callback(std::function &&f) { this->state_callback_.add(std::move(f)); } void Valve::publish_state(bool save) { this->position = clamp(this->position, 0.0f, 1.0f); diff --git a/esphome/components/valve/valve.h b/esphome/components/valve/valve.h index aab819a7788..b4141f5ff59 100644 --- a/esphome/components/valve/valve.h +++ b/esphome/components/valve/valve.h @@ -117,7 +117,7 @@ class Valve : public EntityBase { /// Construct a new valve call used to control the valve. ValveCall make_call(); - void add_on_state_callback(std::function &&f); + template void add_on_state_callback(F &&f) { this->state_callback_.add(std::forward(f)); } /** Publish the current state of the valve. * diff --git a/esphome/components/zephyr/cdc_acm.h b/esphome/components/zephyr/cdc_acm.h index 2e9da85a111..4dc14397d83 100644 --- a/esphome/components/zephyr/cdc_acm.h +++ b/esphome/components/zephyr/cdc_acm.h @@ -11,9 +11,7 @@ class CdcAcm : public Component { public: CdcAcm(); void setup() override; - void add_on_rate_callback(std::function &&callback) { - this->rate_callbacks_.add(std::move(callback)); - } + template void add_on_rate_callback(F &&callback) { this->rate_callbacks_.add(std::forward(callback)); } protected: static void cdc_dte_rate_callback_(const device *device, uint32_t rate); diff --git a/esphome/components/zigbee/zigbee_zephyr.h b/esphome/components/zigbee/zigbee_zephyr.h index dcc2b40a166..3fa5818ec57 100644 --- a/esphome/components/zigbee/zigbee_zephyr.h +++ b/esphome/components/zigbee/zigbee_zephyr.h @@ -74,7 +74,7 @@ class ZigbeeComponent : public Component { // endpoints are enumerated from 1 this->callbacks_[endpoint - 1] = std::move(cb); } - void add_join_callback(std::function &&cb) { this->join_cb_.add(std::move(cb)); } + template void add_join_callback(F &&cb) { this->join_cb_.add(std::forward(cb)); } void zboss_signal_handler_esphome(zb_bufid_t bufid); void factory_reset(); Trigger<> *get_join_trigger() { return &this->join_trigger_; }; diff --git a/esphome/core/entity_base.h b/esphome/core/entity_base.h index 4c6e5f6596b..8c1f1a213e5 100644 --- a/esphome/core/entity_base.h +++ b/esphome/core/entity_base.h @@ -307,15 +307,11 @@ template class StatefulEntityBase : public EntityBase { virtual T get_state_default(T default_value) const { return this->state_.value_or(default_value); } void invalidate_state() { this->set_new_state({}); } - void add_full_state_callback(std::function previous, optional current)> &&callback) { - if (this->full_state_callbacks_ == nullptr) - this->full_state_callbacks_ = new CallbackManager previous, optional current)>(); // NOLINT - this->full_state_callbacks_->add(std::move(callback)); + template void add_full_state_callback(F &&callback) { + this->full_state_callbacks_.add(std::forward(callback)); } - void add_on_state_callback(std::function &&callback) { - if (this->state_callbacks_ == nullptr) - this->state_callbacks_ = new CallbackManager(); // NOLINT - this->state_callbacks_->add(std::move(callback)); + template void add_on_state_callback(F &&callback) { + this->state_callbacks_.add(std::forward(callback)); } void set_trigger_on_initial_state(bool trigger_on_initial_state) { @@ -333,21 +329,19 @@ template class StatefulEntityBase : public EntityBase { virtual bool set_new_state(const optional &new_state) { if (this->state_ != new_state) { // call the full state callbacks with the previous and new state - if (this->full_state_callbacks_ != nullptr) - this->full_state_callbacks_->call(this->state_, new_state); + this->full_state_callbacks_.call(this->state_, new_state); // trigger legacy callbacks only if the new state is valid and either the trigger on initial state is enabled or // the previous state was valid auto had_state = this->has_state(); this->state_ = new_state; - if (this->state_callbacks_ != nullptr && new_state.has_value() && (this->trigger_on_initial_state_ || had_state)) - this->state_callbacks_->call(new_state.value()); + if (new_state.has_value() && (this->trigger_on_initial_state_ || had_state)) + this->state_callbacks_.call(new_state.value()); return true; } return false; } bool trigger_on_initial_state_{true}; - // callbacks with full state and previous state - CallbackManager previous, optional current)> *full_state_callbacks_{}; - CallbackManager *state_callbacks_{}; + LazyCallbackManager previous, optional current)> full_state_callbacks_; + LazyCallbackManager state_callbacks_; }; } // namespace esphome diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index d220626bcf3..a703b5a5f37 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -1729,6 +1729,51 @@ constexpr float fahrenheit_to_celsius(float value) { return (value - 32.0f) / 1. /// @name Utilities /// @{ +/// Lightweight type-erased callback (8 bytes on 32-bit) that avoids std::function overhead. +/// No null check, no exceptions, no heap allocation for small trivially-copyable callables. +/// +/// With C++20 if constexpr, automatically detects [this] lambdas (sizeof <= sizeof(void*), +/// trivially copyable) and stores them inline. Larger callables are heap-allocated. +template struct Callback; + +template struct Callback { + // The inline storage path stores callable bytes in ctx_ via memcpy. + // sizeof equality with uintptr_t ensures void* can round-trip arbitrary bit patterns, + // which combined with flat address spaces on all ESPHome targets means no trap representations. + static_assert(sizeof(void *) == sizeof(std::uintptr_t), "void* must be the same size as uintptr_t"); + + void (*fn_)(void *, Ts...){nullptr}; + void *ctx_{nullptr}; + + /// Invoke the callback. Only valid on Callbacks created via create(), never on default-constructed instances. + void call(Ts... args) const { this->fn_(this->ctx_, args...); } + + /// Create from any callable. Small trivially-copyable callables (like [this] lambdas) + /// are stored inline in the ctx pointer without heap allocation. + template static Callback create(F &&callable) { + using DecayF = std::decay_t; + if constexpr (sizeof(DecayF) <= sizeof(void *) && std::is_trivially_copyable_v) { + // Small trivial callable (e.g. [this]() { this->method(); }) - store inline in ctx. + // Safe under C++20 (P0593R6): byte copy into aligned storage implicitly + // creates objects of implicit-lifetime types (trivially copyable qualifies). + Callback cb; // fn and ctx are zero-initialized by default + __builtin_memcpy(&cb.ctx_, &callable, sizeof(DecayF)); + cb.fn_ = [](void *c, Ts... args) { + alignas(DecayF) char buf[sizeof(DecayF)]; + __builtin_memcpy(buf, &c, sizeof(DecayF)); + (*std::launder(reinterpret_cast(buf)))(args...); + }; + return cb; + } else { + // Large or non-trivial callable - heap allocate. + // Intentionally never freed: callbacks in ESPHome are registered during setup() + // and live for device lifetime. Same lifetime as the previous std::function approach. + auto *stored = new DecayF(std::forward(callable)); + return {[](void *c, Ts... args) { (*static_cast(c))(args...); }, static_cast(stored)}; + } + } +}; + template class CallbackManager; /** Helper class to allow having multiple subscribers to a callback. @@ -1737,13 +1782,14 @@ template class CallbackManager; */ template class CallbackManager { public: - /// Add a callback to the list. - void add(std::function &&callback) { this->callbacks_.push_back(std::move(callback)); } + /// Add any callable. Small trivially-copyable callables (like [this] lambdas) + /// are stored inline without heap allocation or std::function. + template void add(F &&callback) { this->add_(Callback::create(std::forward(callback))); } - /// Call all callbacks in this manager. + /// Call all callbacks in this manager. No null check on invoke. void call(Ts... args) { for (auto &cb : this->callbacks_) - cb(args...); + cb.call(args...); } size_t size() const { return this->callbacks_.size(); } @@ -1751,7 +1797,10 @@ template class CallbackManager { void operator()(Ts... args) { call(args...); } protected: - std::vector> callbacks_; + template friend class LazyCallbackManager; + /// Non-template core to avoid code duplication per lambda type. + void add_(Callback cb) { this->callbacks_.push_back(cb); } + std::vector> callbacks_; }; template class LazyCallbackManager; @@ -1784,13 +1833,8 @@ template class LazyCallbackManager { LazyCallbackManager(LazyCallbackManager &&) = delete; LazyCallbackManager &operator=(LazyCallbackManager &&) = delete; - /// Add a callback to the list. Allocates the underlying CallbackManager on first use. - void add(std::function &&callback) { - if (!this->callbacks_) { - this->callbacks_ = new CallbackManager(); - } - this->callbacks_->add(std::move(callback)); - } + /// Add any callable. Allocates the underlying CallbackManager on first use. + template void add(F &&callback) { this->add_(Callback::create(std::forward(callback))); } /// Call all callbacks in this manager. No-op if no callbacks registered. void call(Ts... args) { @@ -1809,6 +1853,13 @@ template class LazyCallbackManager { void operator()(Ts... args) { this->call(args...); } protected: + /// Non-template core to avoid code duplication per lambda type. + void add_(Callback cb) { + if (!this->callbacks_) { + this->callbacks_ = new CallbackManager(); + } + this->callbacks_->add_(cb); + } CallbackManager *callbacks_{nullptr}; }; From 2271ac64701b5524997d2c1c7335b585a97cc939 Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 18 Mar 2026 18:41:45 -1000 Subject: [PATCH 3/4] [api] Extract overflow buffer from frame helper into APIOverflowBuffer (#14871) --- esphome/components/api/__init__.py | 5 +- esphome/components/api/api_frame_helper.cpp | 161 ++++-------------- esphome/components/api/api_frame_helper.h | 43 ++--- .../components/api/api_frame_helper_noise.cpp | 6 +- .../api/api_frame_helper_plaintext.cpp | 6 +- .../components/api/api_overflow_buffer.cpp | 73 ++++++++ esphome/components/api/api_overflow_buffer.h | 76 +++++++++ 7 files changed, 214 insertions(+), 156 deletions(-) create mode 100644 esphome/components/api/api_overflow_buffer.cpp create mode 100644 esphome/components/api/api_overflow_buffer.h diff --git a/esphome/components/api/__init__.py b/esphome/components/api/__init__.py index 9772e6afca7..4c3cf81927d 100644 --- a/esphome/components/api/__init__.py +++ b/esphome/components/api/__init__.py @@ -301,11 +301,12 @@ CONFIG_SCHEMA = cv.All( # Maximum queued send buffers per connection before dropping connection # Each buffer uses ~8-12 bytes overhead plus actual message size # Platform defaults based on available RAM and typical message rates: + # CONF_MAX_SEND_QUEUE defaults are power of 2 for efficient modulo cv.SplitDefault( CONF_MAX_SEND_QUEUE, - esp8266=5, # Limited RAM, need to fail fast + esp8266=4, # Limited RAM, need to fail fast esp32=8, # More RAM, can buffer more - rp2040=5, # Limited RAM + rp2040=8, # Moderate RAM bk72xx=8, # Moderate RAM nrf52=8, # Moderate RAM rtl87xx=8, # Moderate RAM diff --git a/esphome/components/api/api_frame_helper.cpp b/esphome/components/api/api_frame_helper.cpp index fbee2940226..6d3bd51b586 100644 --- a/esphome/components/api/api_frame_helper.cpp +++ b/esphome/components/api/api_frame_helper.cpp @@ -100,150 +100,61 @@ const LogString *api_error_to_logstr(APIError err) { return LOG_STR("UNKNOWN"); } -// Default implementation for loop - handles sending buffered data -APIError APIFrameHelper::loop() { - if (this->tx_buf_count_ > 0) { - APIError err = try_send_tx_buf_(); - if (err != APIError::OK && err != APIError::WOULD_BLOCK) { - return err; +APIError APIFrameHelper::drain_overflow_and_handle_errors_() { + if (this->overflow_buf_.try_drain(this->socket_.get()) == -1) { + int err = errno; + if (this->check_socket_write_err_(err) != APIError::WOULD_BLOCK) { + HELPER_LOG("Socket write failed with errno %d", err); + return APIError::SOCKET_WRITE_FAILED; } } - return APIError::OK; // Convert WOULD_BLOCK to OK to avoid connection termination + return APIError::OK; } -// Common socket write error handling -APIError APIFrameHelper::handle_socket_write_error_() { - const int err = errno; - if (err == EWOULDBLOCK || err == EAGAIN) { - return APIError::WOULD_BLOCK; - } - HELPER_LOG("Socket write failed with errno %d", err); - this->state_ = State::FAILED; - return APIError::SOCKET_WRITE_FAILED; -} - -// Helper method to buffer data from IOVs -void APIFrameHelper::buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, - uint16_t offset) { - // Check if queue is full - if (this->tx_buf_count_ >= API_MAX_SEND_QUEUE) { - HELPER_LOG("Send queue full (%u buffers), dropping connection", this->tx_buf_count_); - this->state_ = State::FAILED; - return; - } - - uint16_t buffer_size = total_write_len - offset; - auto &buffer = this->tx_buf_[this->tx_buf_tail_]; - buffer = std::make_unique(SendBuffer{ - .data = std::make_unique(buffer_size), - .size = buffer_size, - .offset = 0, - }); - - uint16_t to_skip = offset; - uint16_t write_pos = 0; - - for (int i = 0; i < iovcnt; i++) { - if (to_skip >= iov[i].iov_len) { - // Skip this entire segment - to_skip -= static_cast(iov[i].iov_len); - } else { - // Include this segment (partially or fully) - const uint8_t *src = reinterpret_cast(iov[i].iov_base) + to_skip; - uint16_t len = static_cast(iov[i].iov_len) - to_skip; - std::memcpy(buffer->data.get() + write_pos, src, len); - write_pos += len; - to_skip = 0; - } - } - - // Update circular buffer tracking - this->tx_buf_tail_ = (this->tx_buf_tail_ + 1) % API_MAX_SEND_QUEUE; - this->tx_buf_count_++; -} - -// This method writes data to socket or buffers it +// Write data to socket, overflow to backlog buffer if LWIP TCP send buffer is full. +// Returns OK if all data was sent or successfully queued. +// Returns SOCKET_WRITE_FAILED on hard error (sets state to FAILED). APIError APIFrameHelper::write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len) { - // Returns APIError::OK if successful (or would block, but data has been buffered) - // Returns APIError::SOCKET_WRITE_FAILED if socket write failed, and sets state to FAILED - - if (iovcnt == 0) - return APIError::OK; // Nothing to do, success - #ifdef HELPER_LOG_PACKETS for (int i = 0; i < iovcnt; i++) { LOG_PACKET_SENDING(reinterpret_cast(iov[i].iov_base), iov[i].iov_len); } #endif - // Try to send any existing buffered data first if there is any - if (this->tx_buf_count_ > 0) { - APIError send_result = try_send_tx_buf_(); - // If real error occurred (not just WOULD_BLOCK), return it - if (send_result != APIError::OK && send_result != APIError::WOULD_BLOCK) { - return send_result; - } + uint16_t skip = 0; - // If there is still data in the buffer, we can't send, buffer - // the new data and return - if (this->tx_buf_count_ > 0) { - this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0); - return APIError::OK; // Success, data buffered - } + // Drain any existing backlog first + if (!this->overflow_buf_.empty()) [[unlikely]] { + APIError err = this->drain_overflow_and_handle_errors_(); + if (err != APIError::OK) + return err; } - // Try to send directly if no buffered data - // Optimize for single iovec case (common for plaintext API) - ssize_t sent = - (iovcnt == 1) ? this->socket_->write(iov[0].iov_base, iov[0].iov_len) : this->socket_->writev(iov, iovcnt); + // If backlog is clear, try direct send + if (this->overflow_buf_.empty()) [[likely]] { + ssize_t sent = + (iovcnt == 1) ? this->socket_->write(iov[0].iov_base, iov[0].iov_len) : this->socket_->writev(iov, iovcnt); - if (sent == -1) { - APIError err = this->handle_socket_write_error_(); - if (err == APIError::WOULD_BLOCK) { - // Socket would block, buffer the data - this->buffer_data_from_iov_(iov, iovcnt, total_write_len, 0); - return APIError::OK; // Success, data buffered - } - return err; // Socket write failed - } else if (static_cast(sent) < total_write_len) { - // Partially sent, buffer the remaining data - this->buffer_data_from_iov_(iov, iovcnt, total_write_len, static_cast(sent)); - } - - return APIError::OK; // Success, all data sent or buffered -} - -// Common implementation for trying to send buffered data -// IMPORTANT: Caller MUST ensure tx_buf_count_ > 0 before calling this method -APIError APIFrameHelper::try_send_tx_buf_() { - // Try to send from tx_buf - we assume it's not empty as it's the caller's responsibility to check - while (this->tx_buf_count_ > 0) { - // Get the first buffer in the queue - SendBuffer *front_buffer = this->tx_buf_[this->tx_buf_head_].get(); - - // Try to send the remaining data in this buffer - ssize_t sent = this->socket_->write(front_buffer->current_data(), front_buffer->remaining()); - - if (sent == -1) { - return this->handle_socket_write_error_(); - } else if (sent == 0) { - // Nothing sent but not an error - return APIError::WOULD_BLOCK; - } else if (static_cast(sent) < front_buffer->remaining()) { - // Partially sent, update offset - // Cast to ensure no overflow issues with uint16_t - front_buffer->offset += static_cast(sent); - return APIError::WOULD_BLOCK; // Stop processing more buffers if we couldn't send a complete buffer + if (sent == -1) [[unlikely]] { + int err = errno; + if (this->check_socket_write_err_(err) != APIError::WOULD_BLOCK) { + HELPER_LOG("Socket write failed with errno %d", err); + return APIError::SOCKET_WRITE_FAILED; + } + } else if (static_cast(sent) >= total_write_len) [[likely]] { + return APIError::OK; } else { - // Buffer completely sent, remove it from the queue - this->tx_buf_[this->tx_buf_head_].reset(); - this->tx_buf_head_ = (this->tx_buf_head_ + 1) % API_MAX_SEND_QUEUE; - this->tx_buf_count_--; - // Continue loop to try sending the next buffer + skip = static_cast(sent); } } - return APIError::OK; // All buffers sent successfully + // Queue unsent data into overflow buffer + if (!this->overflow_buf_.enqueue_iov(iov, iovcnt, total_write_len, skip)) { + HELPER_LOG("Overflow buffer full, dropping connection"); + this->state_ = State::FAILED; + return APIError::SOCKET_WRITE_FAILED; + } + return APIError::OK; } const char *APIFrameHelper::get_peername_to(std::span buf) const { diff --git a/esphome/components/api/api_frame_helper.h b/esphome/components/api/api_frame_helper.h index e78c71507c8..72ccf8aa562 100644 --- a/esphome/components/api/api_frame_helper.h +++ b/esphome/components/api/api_frame_helper.h @@ -9,6 +9,7 @@ #include "esphome/core/defines.h" #ifdef USE_API #include "esphome/components/api/api_buffer.h" +#include "esphome/components/api/api_overflow_buffer.h" #include "esphome/components/socket/socket.h" #include "esphome/core/application.h" #include "esphome/core/log.h" @@ -104,9 +105,9 @@ class APIFrameHelper { } virtual ~APIFrameHelper() = default; virtual APIError init() = 0; - virtual APIError loop(); + virtual APIError loop() = 0; virtual APIError read_packet(ReadPacketBuffer *buffer) = 0; - bool can_write_without_blocking() { return this->state_ == State::DATA && this->tx_buf_count_ == 0; } + bool can_write_without_blocking() { return this->state_ == State::DATA && this->overflow_buf_.empty(); } int getpeername(struct sockaddr *addr, socklen_t *addrlen) { return socket_->getpeername(addr, addrlen); } APIError close() { if (state_ == State::CLOSED) @@ -189,28 +190,23 @@ class APIFrameHelper { } protected: - // Buffer containing data to be sent - struct SendBuffer { - std::unique_ptr data; - uint16_t size{0}; // Total size of the buffer - uint16_t offset{0}; // Current offset within the buffer - - // Using uint16_t reduces memory usage since ESPHome API messages are limited to UINT16_MAX (65535) bytes - uint16_t remaining() const { return size - offset; } - const uint8_t *current_data() const { return data.get() + offset; } - }; + // Drain backlogged overflow data to the socket and handle errors. + // Called when overflow_buf_.empty() is false. Out-of-line to keep the + // fast path (empty check) inline at call sites. + // Returns OK for transient errors (WOULD_BLOCK), SOCKET_WRITE_FAILED for hard errors. + APIError drain_overflow_and_handle_errors_(); // Common implementation for writing raw data to socket APIError write_raw_(const struct iovec *iov, int iovcnt, uint16_t total_write_len); - // Try to send data from the tx buffer - APIError try_send_tx_buf_(); - - // Helper method to buffer data from IOVs - void buffer_data_from_iov_(const struct iovec *iov, int iovcnt, uint16_t total_write_len, uint16_t offset); - - // Common socket write error handling - APIError handle_socket_write_error_(); + // Check if a socket write errno is a hard error (not WOULD_BLOCK/EAGAIN). + // Returns WOULD_BLOCK for transient errors, SOCKET_WRITE_FAILED for hard errors. + APIError check_socket_write_err_(int err) { + if (err == EWOULDBLOCK || err == EAGAIN) + return APIError::WOULD_BLOCK; + this->state_ = State::FAILED; + return APIError::SOCKET_WRITE_FAILED; + } // Socket ownership (4 bytes on 32-bit, 8 bytes on 64-bit) std::unique_ptr socket_; @@ -245,8 +241,8 @@ class APIFrameHelper { return APIError::WOULD_BLOCK; } - // Containers (size varies, but typically 12+ bytes on 32-bit) - std::array, API_MAX_SEND_QUEUE> tx_buf_; + // Backlog for unsent data when TCP send buffer is full (rarely used in production) + APIOverflowBuffer overflow_buf_; APIBuffer rx_buf_; // Client name buffer - stores name from Hello message or initial peername @@ -257,9 +253,6 @@ class APIFrameHelper { State state_{State::INITIALIZE}; uint8_t frame_header_padding_{0}; uint8_t frame_footer_size_{0}; - uint8_t tx_buf_head_{0}; - uint8_t tx_buf_tail_{0}; - uint8_t tx_buf_count_{0}; // Nagle batching counter for log messages. 0 means NODELAY is enabled (immediate send). // Values 1..LOG_NAGLE_COUNT count log messages in the current Nagle batch. // After LOG_NAGLE_COUNT logs, we flush by re-enabling NODELAY and resetting to 0. diff --git a/esphome/components/api/api_frame_helper_noise.cpp b/esphome/components/api/api_frame_helper_noise.cpp index b635d84f168..78e87793fc8 100644 --- a/esphome/components/api/api_frame_helper_noise.cpp +++ b/esphome/components/api/api_frame_helper_noise.cpp @@ -153,8 +153,10 @@ APIError APINoiseFrameHelper::loop() { } } - // Use base class implementation for buffer sending - return APIFrameHelper::loop(); + if (!this->overflow_buf_.empty()) [[unlikely]] { + return this->drain_overflow_and_handle_errors_(); + } + return APIError::OK; } /** Read a packet into the rx_buf_. diff --git a/esphome/components/api/api_frame_helper_plaintext.cpp b/esphome/components/api/api_frame_helper_plaintext.cpp index e97b558fa39..9e669b31ee2 100644 --- a/esphome/components/api/api_frame_helper_plaintext.cpp +++ b/esphome/components/api/api_frame_helper_plaintext.cpp @@ -64,8 +64,10 @@ APIError APIPlaintextFrameHelper::loop() { if (state_ != State::DATA) { return APIError::BAD_STATE; } - // Use base class implementation for buffer sending - return APIFrameHelper::loop(); + if (!this->overflow_buf_.empty()) [[unlikely]] { + return this->drain_overflow_and_handle_errors_(); + } + return APIError::OK; } /** Read a packet into the rx_buf_. diff --git a/esphome/components/api/api_overflow_buffer.cpp b/esphome/components/api/api_overflow_buffer.cpp new file mode 100644 index 00000000000..e242d4553e4 --- /dev/null +++ b/esphome/components/api/api_overflow_buffer.cpp @@ -0,0 +1,73 @@ +#include "api_overflow_buffer.h" +#ifdef USE_API +#include + +namespace esphome::api { + +APIOverflowBuffer::~APIOverflowBuffer() { + for (auto *entry : this->queue_) { + if (entry != nullptr) + Entry::destroy(entry); + } +} + +ssize_t APIOverflowBuffer::try_drain(socket::Socket *socket) { + while (this->count_ > 0) { + Entry *front = this->queue_[this->head_]; + + ssize_t sent = socket->write(front->current_data(), front->remaining()); + + if (sent <= 0) { + // -1 = error (caller checks errno for EWOULDBLOCK vs hard error) + // 0 = nothing sent (treat as no progress) + return sent; + } + + if (static_cast(sent) < front->remaining()) { + // Partially sent, update offset and stop + front->offset += static_cast(sent); + return sent; + } + + // Entry fully sent — free it and advance + Entry::destroy(front); + this->queue_[this->head_] = nullptr; + this->head_ = (this->head_ + 1) % API_MAX_SEND_QUEUE; + this->count_--; + } + + return 0; // All drained +} + +bool APIOverflowBuffer::enqueue_iov(const struct iovec *iov, int iovcnt, uint16_t total_len, uint16_t skip) { + if (this->count_ >= API_MAX_SEND_QUEUE) + return false; + + uint16_t buffer_size = total_len - skip; + // NOLINTNEXTLINE(cppcoreguidelines-owning-memory) + auto *entry = new Entry{new uint8_t[buffer_size], buffer_size, 0}; + this->queue_[this->tail_] = entry; + + uint16_t to_skip = skip; + uint16_t write_pos = 0; + + for (int i = 0; i < iovcnt; i++) { + if (to_skip >= iov[i].iov_len) { + to_skip -= static_cast(iov[i].iov_len); + } else { + const uint8_t *src = reinterpret_cast(iov[i].iov_base) + to_skip; + uint16_t len = static_cast(iov[i].iov_len) - to_skip; + std::memcpy(entry->data + write_pos, src, len); + write_pos += len; + to_skip = 0; + } + } + + this->tail_ = (this->tail_ + 1) % API_MAX_SEND_QUEUE; + this->count_++; + return true; +} + +} // namespace esphome::api + +#endif // USE_API diff --git a/esphome/components/api/api_overflow_buffer.h b/esphome/components/api/api_overflow_buffer.h new file mode 100644 index 00000000000..19aae680f01 --- /dev/null +++ b/esphome/components/api/api_overflow_buffer.h @@ -0,0 +1,76 @@ +#pragma once +#include +#include +#include + +#include "esphome/core/defines.h" +#ifdef USE_API + +#include "esphome/components/socket/headers.h" +#include "esphome/components/socket/socket.h" +#include "esphome/core/helpers.h" + +namespace esphome::api { + +/// Circular queue of heap-allocated byte buffers used as a TCP send backlog. +/// +/// Under normal operation this buffer is **never used** — data goes straight +/// from the frame helper to the socket. It only fills when the LWIP TCP +/// send buffer is full (slow client, congested network, heavy logging). +/// The queue drains automatically on subsequent write/loop calls once the +/// socket becomes writable again. +/// +/// Capacity is compile-time-fixed via API_MAX_SEND_QUEUE (set from Python +/// config). If the queue fills completely the connection is marked failed. +class APIOverflowBuffer { + public: + /// A single heap-allocated send-backlog entry. + /// Lifetime is manually managed — see destroy(). + struct Entry { + uint8_t *data; + uint16_t size; // Total size of the buffer + uint16_t offset; // Current send offset within the buffer + + uint16_t remaining() const { return this->size - this->offset; } + const uint8_t *current_data() const { return this->data + this->offset; } + + /// Free this entry and its data buffer. + static ESPHOME_ALWAYS_INLINE void destroy(Entry *entry) { + delete[] entry->data; + delete entry; // NOLINT(cppcoreguidelines-owning-memory) + } + }; + + ~APIOverflowBuffer(); + + /// True when no backlogged data is waiting. + bool empty() const { return this->count_ == 0; } + + /// True when the queue has no room for another entry. + bool full() const { return this->count_ >= API_MAX_SEND_QUEUE; } + + /// Number of entries currently queued. + uint8_t count() const { return this->count_; } + + /// Try to drain queued data to the socket. + /// Returns bytes-written > 0 on success/partial, 0 if all drained or no progress, + /// -1 on error (caller must check errno to distinguish EWOULDBLOCK from hard errors). + /// Callers only need to act on -1; 0 and positive values both mean "no error". + /// Frees entries as they are fully sent. + ssize_t try_drain(socket::Socket *socket); + + /// Enqueue unsent IOV data into the backlog. + /// Copies iov data starting at byte offset `skip` into a new entry. + /// Returns false if the queue is full (caller should fail the connection). + bool enqueue_iov(const struct iovec *iov, int iovcnt, uint16_t total_len, uint16_t skip); + + protected: + std::array queue_{}; + uint8_t head_{0}; + uint8_t tail_{0}; + uint8_t count_{0}; +}; + +} // namespace esphome::api + +#endif // USE_API From a1aff7cadf88b14e39fcda559d3f2895b0a43e8d Mon Sep 17 00:00:00 2001 From: "J. Nick Koston" Date: Wed, 18 Mar 2026 18:42:05 -1000 Subject: [PATCH 4/4] [preferences] Devirtualize preference backend and manager classes (#14825) --- esphome/components/esp32/preference_backend.h | 27 ++ esphome/components/esp32/preferences.cpp | 306 +++++++++--------- esphome/components/esp32/preferences.h | 29 +- .../components/esp8266/preference_backend.h | 29 ++ esphome/components/esp8266/preferences.cpp | 248 +++++++------- esphome/components/esp8266/preferences.h | 24 +- esphome/components/host/preference_backend.h | 29 ++ esphome/components/host/preferences.cpp | 11 +- esphome/components/host/preferences.h | 39 +-- .../components/libretiny/preference_backend.h | 32 ++ esphome/components/libretiny/preferences.cpp | 269 +++++++-------- esphome/components/libretiny/preferences.h | 26 +- .../components/rp2040/preference_backend.h | 27 ++ esphome/components/rp2040/preferences.cpp | 189 +++++------ esphome/components/rp2040/preferences.h | 29 +- .../components/zephyr/preference_backend.h | 48 +++ esphome/components/zephyr/preferences.cpp | 233 ++++++------- esphome/components/zephyr/preferences.h | 31 +- esphome/core/preference_backend.h | 83 +++++ esphome/core/preferences.h | 75 ++--- 20 files changed, 1021 insertions(+), 763 deletions(-) create mode 100644 esphome/components/esp32/preference_backend.h create mode 100644 esphome/components/esp8266/preference_backend.h create mode 100644 esphome/components/host/preference_backend.h create mode 100644 esphome/components/libretiny/preference_backend.h create mode 100644 esphome/components/rp2040/preference_backend.h create mode 100644 esphome/components/zephyr/preference_backend.h create mode 100644 esphome/core/preference_backend.h diff --git a/esphome/components/esp32/preference_backend.h b/esphome/components/esp32/preference_backend.h new file mode 100644 index 00000000000..893bc35f0c0 --- /dev/null +++ b/esphome/components/esp32/preference_backend.h @@ -0,0 +1,27 @@ +#pragma once +#ifdef USE_ESP32 + +#include +#include + +namespace esphome::esp32 { + +class ESP32PreferenceBackend final { + public: + bool save(const uint8_t *data, size_t len); + bool load(uint8_t *data, size_t len); + + uint32_t key; + uint32_t nvs_handle; +}; + +class ESP32Preferences; +ESP32Preferences *get_preferences(); + +} // namespace esphome::esp32 + +namespace esphome { +using PreferenceBackend = esp32::ESP32PreferenceBackend; +} // namespace esphome + +#endif // USE_ESP32 diff --git a/esphome/components/esp32/preferences.cpp b/esphome/components/esp32/preferences.cpp index a3ef10b21f9..7260bf54e01 100644 --- a/esphome/components/esp32/preferences.cpp +++ b/esphome/components/esp32/preferences.cpp @@ -1,16 +1,14 @@ #ifdef USE_ESP32 +#include "preferences.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/preferences.h" #include #include #include -#include #include -namespace esphome { -namespace esp32 { +namespace esphome::esp32 { static const char *const TAG = "esp32.preferences"; @@ -24,185 +22,175 @@ struct NVSData { static std::vector s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -class ESP32PreferenceBackend : public ESPPreferenceBackend { - public: - uint32_t key; - uint32_t nvs_handle; - bool save(const uint8_t *data, size_t len) override { - // try find in pending saves and update that - for (auto &obj : s_pending_save) { - if (obj.key == this->key) { - obj.data.set(data, len); - return true; - } +bool ESP32PreferenceBackend::save(const uint8_t *data, size_t len) { + // try find in pending saves and update that + for (auto &obj : s_pending_save) { + if (obj.key == this->key) { + obj.data.set(data, len); + return true; } - NVSData save{}; - save.key = this->key; - save.data.set(data, len); - s_pending_save.push_back(std::move(save)); - ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len); - return true; } - bool load(uint8_t *data, size_t len) override { - // try find in pending saves and load from that - for (auto &obj : s_pending_save) { - if (obj.key == this->key) { - if (obj.data.size() != len) { - // size mismatch - return false; - } - memcpy(data, obj.data.data(), len); - return true; - } - } + NVSData save{}; + save.key = this->key; + save.data.set(data, len); + s_pending_save.push_back(std::move(save)); + ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len); + return true; +} +bool ESP32PreferenceBackend::load(uint8_t *data, size_t len) { + // try find in pending saves and load from that + for (auto &obj : s_pending_save) { + if (obj.key == this->key) { + if (obj.data.size() != len) { + // size mismatch + return false; + } + memcpy(data, obj.data.data(), len); + return true; + } + } + + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key); + size_t actual_len; + esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err)); + return false; + } + if (actual_len != len) { + ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len); + return false; + } + err = nvs_get_blob(this->nvs_handle, key_str, data, &len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err)); + return false; + } else { + ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key_str, len); + } + return true; +} + +void ESP32Preferences::open() { + nvs_flash_init(); + esp_err_t err = nvs_open("esphome", NVS_READWRITE, &this->nvs_handle); + if (err == 0) + return; + + ESP_LOGW(TAG, "nvs_open failed: %s - erasing NVS", esp_err_to_name(err)); + nvs_flash_deinit(); + nvs_flash_erase(); + nvs_flash_init(); + + err = nvs_open("esphome", NVS_READWRITE, &this->nvs_handle); + if (err != 0) { + this->nvs_handle = 0; + } +} + +ESPPreferenceObject ESP32Preferences::make_preference(size_t length, uint32_t type) { + auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + pref->nvs_handle = this->nvs_handle; + pref->key = type; + + return ESPPreferenceObject(pref); +} + +bool ESP32Preferences::sync() { + if (s_pending_save.empty()) + return true; + + ESP_LOGV(TAG, "Saving %zu items...", s_pending_save.size()); + int cached = 0, written = 0, failed = 0; + esp_err_t last_err = ESP_OK; + uint32_t last_key = 0; + + for (const auto &save : s_pending_save) { char key_str[KEY_BUFFER_SIZE]; - snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key); - size_t actual_len; - esp_err_t err = nvs_get_blob(this->nvs_handle, key_str, nullptr, &actual_len); - if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err)); - return false; - } - if (actual_len != len) { - ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len); - return false; - } - err = nvs_get_blob(this->nvs_handle, key_str, data, &len); - if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err)); - return false; - } else { - ESP_LOGVV(TAG, "nvs_get_blob: key: %s, len: %zu", key_str, len); - } - return true; - } -}; - -class ESP32Preferences : public ESPPreferences { - public: - uint32_t nvs_handle; - - void open() { - nvs_flash_init(); - esp_err_t err = nvs_open("esphome", NVS_READWRITE, &nvs_handle); - if (err == 0) - return; - - ESP_LOGW(TAG, "nvs_open failed: %s - erasing NVS", esp_err_to_name(err)); - nvs_flash_deinit(); - nvs_flash_erase(); - nvs_flash_init(); - - err = nvs_open("esphome", NVS_READWRITE, &nvs_handle); - if (err != 0) { - nvs_handle = 0; - } - } - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - return this->make_preference(length, type); - } - ESPPreferenceObject make_preference(size_t length, uint32_t type) override { - auto *pref = new ESP32PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->nvs_handle = this->nvs_handle; - pref->key = type; - - return ESPPreferenceObject(pref); - } - - bool sync() override { - if (s_pending_save.empty()) - return true; - - ESP_LOGV(TAG, "Saving %zu items...", s_pending_save.size()); - int cached = 0, written = 0, failed = 0; - esp_err_t last_err = ESP_OK; - uint32_t last_key = 0; - - for (const auto &save : s_pending_save) { - char key_str[KEY_BUFFER_SIZE]; - snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); - ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str); - if (this->is_changed_(this->nvs_handle, save, key_str)) { - esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.data(), save.data.size()); - ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size()); - if (err != 0) { - ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", key_str, save.data.size(), esp_err_to_name(err)); - failed++; - last_err = err; - last_key = save.key; - continue; - } - written++; - } else { - ESP_LOGV(TAG, "NVS data not changed skipping %" PRIu32 " len=%zu", save.key, save.data.size()); - cached++; + snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); + ESP_LOGVV(TAG, "Checking if NVS data %s has changed", key_str); + if (this->is_changed_(this->nvs_handle, save, key_str)) { + esp_err_t err = nvs_set_blob(this->nvs_handle, key_str, save.data.data(), save.data.size()); + ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size()); + if (err != 0) { + ESP_LOGV(TAG, "nvs_set_blob('%s', len=%zu) failed: %s", key_str, save.data.size(), esp_err_to_name(err)); + failed++; + last_err = err; + last_key = save.key; + continue; } + written++; + } else { + ESP_LOGV(TAG, "NVS data not changed skipping %" PRIu32 " len=%zu", save.key, save.data.size()); + cached++; } - s_pending_save.clear(); + } + s_pending_save.clear(); - ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written, - failed); - if (failed > 0) { - ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%" PRIu32, failed, esp_err_to_name(last_err), - last_key); - } - - // note: commit on esp-idf currently is a no-op, nvs_set_blob always writes - esp_err_t err = nvs_commit(this->nvs_handle); - if (err != 0) { - ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err)); - return false; - } - - return failed == 0; + ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written, + failed); + if (failed > 0) { + ESP_LOGE(TAG, "Writing %d items failed. Last error=%s for key=%" PRIu32, failed, esp_err_to_name(last_err), + last_key); } - protected: - bool is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str) { - size_t actual_len; - esp_err_t err = nvs_get_blob(nvs_handle, key_str, nullptr, &actual_len); - if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err)); - return true; - } - // Check size first before allocating memory - if (actual_len != to_save.data.size()) { - return true; - } - // Most preferences are small, use stack buffer with heap fallback for large ones - SmallBufferWithHeapFallback<256> stored_data(actual_len); - err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len); - if (err != 0) { - ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err)); - return true; - } - return memcmp(to_save.data.data(), stored_data.get(), to_save.data.size()) != 0; + // note: commit on esp-idf currently is a no-op, nvs_set_blob always writes + esp_err_t err = nvs_commit(this->nvs_handle); + if (err != 0) { + ESP_LOGV(TAG, "nvs_commit() failed: %s", esp_err_to_name(err)); + return false; } - bool reset() override { - ESP_LOGD(TAG, "Erasing storage"); - s_pending_save.clear(); + return failed == 0; +} - nvs_flash_deinit(); - nvs_flash_erase(); - // Make the handle invalid to prevent any saves until restart - nvs_handle = 0; +bool ESP32Preferences::is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str) { + size_t actual_len; + esp_err_t err = nvs_get_blob(nvs_handle, key_str, nullptr, &actual_len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s'): %s - the key might not be set yet", key_str, esp_err_to_name(err)); return true; } -}; + // Check size first before allocating memory + if (actual_len != to_save.data.size()) { + return true; + } + // Most preferences are small, use stack buffer with heap fallback for large ones + SmallBufferWithHeapFallback<256> stored_data(actual_len); + err = nvs_get_blob(nvs_handle, key_str, stored_data.get(), &actual_len); + if (err != 0) { + ESP_LOGV(TAG, "nvs_get_blob('%s') failed: %s", key_str, esp_err_to_name(err)); + return true; + } + return memcmp(to_save.data.data(), stored_data.get(), to_save.data.size()) != 0; +} + +bool ESP32Preferences::reset() { + ESP_LOGD(TAG, "Erasing storage"); + s_pending_save.clear(); + + nvs_flash_deinit(); + nvs_flash_erase(); + // Make the handle invalid to prevent any saves until restart + this->nvs_handle = 0; + return true; +} static ESP32Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +ESP32Preferences *get_preferences() { return &s_preferences; } + void setup_preferences() { s_preferences.open(); global_preferences = &s_preferences; } -} // namespace esp32 +} // namespace esphome::esp32 +namespace esphome { ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - } // namespace esphome #endif // USE_ESP32 diff --git a/esphome/components/esp32/preferences.h b/esphome/components/esp32/preferences.h index e44213e4cf9..0e187d87a99 100644 --- a/esphome/components/esp32/preferences.h +++ b/esphome/components/esp32/preferences.h @@ -1,12 +1,33 @@ #pragma once #ifdef USE_ESP32 -namespace esphome { -namespace esp32 { +#include "esphome/core/preference_backend.h" + +namespace esphome::esp32 { + +struct NVSData; + +class ESP32Preferences final : public PreferencesMixin { + public: + using PreferencesMixin::make_preference; + void open(); + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) { + return this->make_preference(length, type); + } + ESPPreferenceObject make_preference(size_t length, uint32_t type); + bool sync(); + bool reset(); + + uint32_t nvs_handle; + + protected: + bool is_changed_(uint32_t nvs_handle, const NVSData &to_save, const char *key_str); +}; void setup_preferences(); -} // namespace esp32 -} // namespace esphome +} // namespace esphome::esp32 + +DECLARE_PREFERENCE_ALIASES(esphome::esp32::ESP32Preferences) #endif // USE_ESP32 diff --git a/esphome/components/esp8266/preference_backend.h b/esphome/components/esp8266/preference_backend.h new file mode 100644 index 00000000000..f9da8ff1656 --- /dev/null +++ b/esphome/components/esp8266/preference_backend.h @@ -0,0 +1,29 @@ +#pragma once +#ifdef USE_ESP8266 + +#include +#include + +namespace esphome::esp8266 { + +class ESP8266PreferenceBackend final { + public: + bool save(const uint8_t *data, size_t len); + bool load(uint8_t *data, size_t len); + + uint32_t type = 0; + uint16_t offset = 0; + uint8_t length_words = 0; // Max 255 words (1020 bytes of data) + bool in_flash = false; +}; + +class ESP8266Preferences; +ESP8266Preferences *get_preferences(); + +} // namespace esphome::esp8266 + +namespace esphome { +using PreferenceBackend = esp8266::ESP8266PreferenceBackend; +} // namespace esphome + +#endif // USE_ESP8266 diff --git a/esphome/components/esp8266/preferences.cpp b/esphome/components/esp8266/preferences.cpp index e749b1f6332..0b31c53ff87 100644 --- a/esphome/components/esp8266/preferences.cpp +++ b/esphome/components/esp8266/preferences.cpp @@ -5,11 +5,9 @@ extern "C" { #include "spi_flash.h" } -#include "esphome/core/defines.h" +#include "preferences.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/preferences.h" -#include "preferences.h" #include @@ -137,155 +135,135 @@ static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) { static constexpr size_t PREF_MAX_BUFFER_WORDS = ESP8266_FLASH_STORAGE_SIZE > RTC_NORMAL_REGION_WORDS ? ESP8266_FLASH_STORAGE_SIZE : RTC_NORMAL_REGION_WORDS; -class ESP8266PreferenceBackend : public ESPPreferenceBackend { - public: - uint32_t type = 0; - uint16_t offset = 0; - uint8_t length_words = 0; // Max 255 words (1020 bytes of data) - bool in_flash = false; +bool ESP8266PreferenceBackend::save(const uint8_t *data, size_t len) { + if (bytes_to_words(len) != this->length_words) + return false; + const size_t buffer_size = static_cast(this->length_words) + 1; + if (buffer_size > PREF_MAX_BUFFER_WORDS) + return false; + uint32_t buffer[PREF_MAX_BUFFER_WORDS]; + memset(buffer, 0, buffer_size * sizeof(uint32_t)); + memcpy(buffer, data, len); + buffer[this->length_words] = calculate_crc(buffer, buffer + this->length_words, this->type); + return this->in_flash ? save_to_flash(this->offset, buffer, buffer_size) + : save_to_rtc(this->offset, buffer, buffer_size); +} - bool save(const uint8_t *data, size_t len) override { - if (bytes_to_words(len) != this->length_words) - return false; - const size_t buffer_size = static_cast(this->length_words) + 1; - if (buffer_size > PREF_MAX_BUFFER_WORDS) - return false; - uint32_t buffer[PREF_MAX_BUFFER_WORDS]; - memset(buffer, 0, buffer_size * sizeof(uint32_t)); - memcpy(buffer, data, len); - buffer[this->length_words] = calculate_crc(buffer, buffer + this->length_words, this->type); - return this->in_flash ? save_to_flash(this->offset, buffer, buffer_size) - : save_to_rtc(this->offset, buffer, buffer_size); +bool ESP8266PreferenceBackend::load(uint8_t *data, size_t len) { + if (bytes_to_words(len) != this->length_words) + return false; + const size_t buffer_size = static_cast(this->length_words) + 1; + if (buffer_size > PREF_MAX_BUFFER_WORDS) + return false; + uint32_t buffer[PREF_MAX_BUFFER_WORDS]; + bool ret = this->in_flash ? load_from_flash(this->offset, buffer, buffer_size) + : load_from_rtc(this->offset, buffer, buffer_size); + if (!ret) + return false; + if (buffer[this->length_words] != calculate_crc(buffer, buffer + this->length_words, this->type)) + return false; + memcpy(data, buffer, len); + return true; +} + +void ESP8266Preferences::setup() { + ESP_LOGVV(TAG, "Loading preferences from flash"); + + { + InterruptLock lock; + spi_flash_read(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); + } +} + +ESPPreferenceObject ESP8266Preferences::make_preference(size_t length, uint32_t type, bool in_flash) { + const uint32_t length_words = bytes_to_words(length); + if (length_words > MAX_PREFERENCE_WORDS) { + ESP_LOGE(TAG, "Preference too large: %u words", static_cast(length_words)); + return {}; } - bool load(uint8_t *data, size_t len) override { - if (bytes_to_words(len) != this->length_words) - return false; - const size_t buffer_size = static_cast(this->length_words) + 1; - if (buffer_size > PREF_MAX_BUFFER_WORDS) - return false; - uint32_t buffer[PREF_MAX_BUFFER_WORDS]; - bool ret = this->in_flash ? load_from_flash(this->offset, buffer, buffer_size) - : load_from_rtc(this->offset, buffer, buffer_size); - if (!ret) - return false; - if (buffer[this->length_words] != calculate_crc(buffer, buffer + this->length_words, this->type)) - return false; - memcpy(data, buffer, len); - return true; - } -}; + const uint32_t total_words = length_words + 1; // +1 for CRC + uint16_t offset; -class ESP8266Preferences : public ESPPreferences { - public: - uint32_t current_offset = 0; - uint32_t current_flash_offset = 0; // in words - - void setup() { - ESP_LOGVV(TAG, "Loading preferences from flash"); - - { - InterruptLock lock; - spi_flash_read(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); - } - } - - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - const uint32_t length_words = bytes_to_words(length); - if (length_words > MAX_PREFERENCE_WORDS) { - ESP_LOGE(TAG, "Preference too large: %u words", static_cast(length_words)); + if (in_flash) { + if (this->current_flash_offset + total_words > ESP8266_FLASH_STORAGE_SIZE) return {}; + offset = static_cast(this->current_flash_offset); + this->current_flash_offset += total_words; + } else { + uint32_t start = this->current_offset; + bool in_normal = start < RTC_NORMAL_REGION_WORDS; + // Normal: offset 0-95 maps to RTC offset 32-127 + // Eboot: offset 96-127 maps to RTC offset 0-31 + if (in_normal && start + total_words > RTC_NORMAL_REGION_WORDS) { + // start is in normal but end is not -> switch to Eboot + this->current_offset = start = RTC_NORMAL_REGION_WORDS; + in_normal = false; } - - const uint32_t total_words = length_words + 1; // +1 for CRC - uint16_t offset; - - if (in_flash) { - if (this->current_flash_offset + total_words > ESP8266_FLASH_STORAGE_SIZE) - return {}; - offset = static_cast(this->current_flash_offset); - this->current_flash_offset += total_words; - } else { - uint32_t start = this->current_offset; - bool in_normal = start < RTC_NORMAL_REGION_WORDS; - // Normal: offset 0-95 maps to RTC offset 32-127 - // Eboot: offset 96-127 maps to RTC offset 0-31 - if (in_normal && start + total_words > RTC_NORMAL_REGION_WORDS) { - // start is in normal but end is not -> switch to Eboot - this->current_offset = start = RTC_NORMAL_REGION_WORDS; - in_normal = false; - } - if (start + total_words > PREF_TOTAL_WORDS) - return {}; // Doesn't fit in RTC memory - // Convert preference offset to RTC memory offset - offset = static_cast(in_normal ? start + RTC_EBOOT_REGION_WORDS : start - RTC_NORMAL_REGION_WORDS); - this->current_offset = start + total_words; - } - - auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->offset = offset; - pref->type = type; - pref->length_words = static_cast(length_words); - pref->in_flash = in_flash; - return pref; + if (start + total_words > PREF_TOTAL_WORDS) + return {}; // Doesn't fit in RTC memory + // Convert preference offset to RTC memory offset + offset = static_cast(in_normal ? start + RTC_EBOOT_REGION_WORDS : start - RTC_NORMAL_REGION_WORDS); + this->current_offset = start + total_words; } - ESPPreferenceObject make_preference(size_t length, uint32_t type) override { -#ifdef USE_ESP8266_PREFERENCES_FLASH - return make_preference(length, type, true); -#else - return make_preference(length, type, false); -#endif - } + auto *pref = new ESP8266PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + pref->offset = offset; + pref->type = type; + pref->length_words = static_cast(length_words); + pref->in_flash = in_flash; + return ESPPreferenceObject(pref); +} - bool sync() override { - if (!s_flash_dirty) - return true; - if (s_prevent_write) - return false; - - ESP_LOGD(TAG, "Saving"); - SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK; - { - InterruptLock lock; - erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); - if (erase_res == SPI_FLASH_RESULT_OK) { - write_res = spi_flash_write(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); - } - } - if (erase_res != SPI_FLASH_RESULT_OK) { - ESP_LOGE(TAG, "Erasing failed"); - return false; - } - if (write_res != SPI_FLASH_RESULT_OK) { - ESP_LOGE(TAG, "Writing failed"); - return false; - } - - s_flash_dirty = false; +bool ESP8266Preferences::sync() { + if (!s_flash_dirty) return true; + if (s_prevent_write) + return false; + + ESP_LOGD(TAG, "Saving"); + SpiFlashOpResult erase_res, write_res = SPI_FLASH_RESULT_OK; + { + InterruptLock lock; + erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); + if (erase_res == SPI_FLASH_RESULT_OK) { + write_res = spi_flash_write(get_esp8266_flash_address(), s_flash_storage, ESP8266_FLASH_STORAGE_SIZE * 4); + } + } + if (erase_res != SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Erasing failed"); + return false; + } + if (write_res != SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Writing failed"); + return false; } - bool reset() override { - ESP_LOGD(TAG, "Erasing storage"); - SpiFlashOpResult erase_res; - { - InterruptLock lock; - erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); - } - if (erase_res != SPI_FLASH_RESULT_OK) { - ESP_LOGE(TAG, "Erasing failed"); - return false; - } + s_flash_dirty = false; + return true; +} - // Protect flash from writing till restart - s_prevent_write = true; - return true; +bool ESP8266Preferences::reset() { + ESP_LOGD(TAG, "Erasing storage"); + SpiFlashOpResult erase_res; + { + InterruptLock lock; + erase_res = spi_flash_erase_sector(get_esp8266_flash_sector()); } -}; + if (erase_res != SPI_FLASH_RESULT_OK) { + ESP_LOGE(TAG, "Erasing failed"); + return false; + } + + // Protect flash from writing till restart + s_prevent_write = true; + return true; +} static ESP8266Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +ESP8266Preferences *get_preferences() { return &s_preferences; } + void setup_preferences() { s_preferences.setup(); global_preferences = &s_preferences; diff --git a/esphome/components/esp8266/preferences.h b/esphome/components/esp8266/preferences.h index 16cf80a1295..43557d5ec5b 100644 --- a/esphome/components/esp8266/preferences.h +++ b/esphome/components/esp8266/preferences.h @@ -1,12 +1,34 @@ #pragma once - #ifdef USE_ESP8266 +#include "esphome/core/preference_backend.h" + namespace esphome::esp8266 { +class ESP8266Preferences final : public PreferencesMixin { + public: + using PreferencesMixin::make_preference; + void setup(); + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash); + ESPPreferenceObject make_preference(size_t length, uint32_t type) { +#ifdef USE_ESP8266_PREFERENCES_FLASH + return this->make_preference(length, type, true); +#else + return this->make_preference(length, type, false); +#endif + } + bool sync(); + bool reset(); + + uint32_t current_offset = 0; + uint32_t current_flash_offset = 0; // in words +}; + void setup_preferences(); void preferences_prevent_write(bool prevent); } // namespace esphome::esp8266 +DECLARE_PREFERENCE_ALIASES(esphome::esp8266::ESP8266Preferences) + #endif // USE_ESP8266 diff --git a/esphome/components/host/preference_backend.h b/esphome/components/host/preference_backend.h new file mode 100644 index 00000000000..68537cad28f --- /dev/null +++ b/esphome/components/host/preference_backend.h @@ -0,0 +1,29 @@ +#pragma once +#ifdef USE_HOST + +#include +#include + +namespace esphome::host { + +class HostPreferenceBackend final { + public: + explicit HostPreferenceBackend(uint32_t key) : key_(key) {} + + bool save(const uint8_t *data, size_t len); + bool load(uint8_t *data, size_t len); + + protected: + uint32_t key_{}; +}; + +class HostPreferences; +HostPreferences *get_preferences(); + +} // namespace esphome::host + +namespace esphome { +using PreferenceBackend = host::HostPreferenceBackend; +} // namespace esphome + +#endif // USE_HOST diff --git a/esphome/components/host/preferences.cpp b/esphome/components/host/preferences.cpp index 275c202e3ed..fce3d62dda0 100644 --- a/esphome/components/host/preferences.cpp +++ b/esphome/components/host/preferences.cpp @@ -6,8 +6,7 @@ #include "esphome/core/application.h" #include "esphome/core/log.h" -namespace esphome { -namespace host { +namespace esphome::host { namespace fs = std::filesystem; static const char *const TAG = "host.preferences"; @@ -77,6 +76,8 @@ ESPPreferenceObject HostPreferences::make_preference(size_t length, uint32_t typ static HostPreferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +HostPreferences *get_preferences() { return &s_preferences; } + void setup_preferences() { host_preferences = &s_preferences; global_preferences = &s_preferences; @@ -88,9 +89,11 @@ bool HostPreferenceBackend::save(const uint8_t *data, size_t len) { bool HostPreferenceBackend::load(uint8_t *data, size_t len) { return host_preferences->load(this->key_, data, len); } -HostPreferences *host_preferences; -} // namespace host +HostPreferences *host_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +} // namespace esphome::host + +namespace esphome { ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) } // namespace esphome diff --git a/esphome/components/host/preferences.h b/esphome/components/host/preferences.h index 6b2e7eb8f94..25858799ff1 100644 --- a/esphome/components/host/preferences.h +++ b/esphome/components/host/preferences.h @@ -1,33 +1,22 @@ #pragma once - #ifdef USE_HOST -#include "esphome/core/preferences.h" +#include "esphome/core/preference_backend.h" +#include #include +#include +#include -namespace esphome { -namespace host { +namespace esphome::host { -class HostPreferenceBackend : public ESPPreferenceBackend { +class HostPreferences final : public PreferencesMixin { public: - explicit HostPreferenceBackend(uint32_t key) { this->key_ = key; } + using PreferencesMixin::make_preference; + bool sync(); + bool reset(); - bool save(const uint8_t *data, size_t len) override; - bool load(uint8_t *data, size_t len) override; - - protected: - uint32_t key_{}; -}; - -class HostPreferences : public ESPPreferences { - public: - bool sync() override; - bool reset() override; - - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override; - ESPPreferenceObject make_preference(size_t length, uint32_t type) override { - return make_preference(length, type, false); - } + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash); + ESPPreferenceObject make_preference(size_t length, uint32_t type) { return make_preference(length, type, false); } bool save(uint32_t key, const uint8_t *data, size_t len) { if (len > 255) @@ -58,10 +47,12 @@ class HostPreferences : public ESPPreferences { std::string filename_{}; std::map> data{}; }; + void setup_preferences(); extern HostPreferences *host_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -} // namespace host -} // namespace esphome +} // namespace esphome::host + +DECLARE_PREFERENCE_ALIASES(esphome::host::HostPreferences) #endif // USE_HOST diff --git a/esphome/components/libretiny/preference_backend.h b/esphome/components/libretiny/preference_backend.h new file mode 100644 index 00000000000..66b6847bee6 --- /dev/null +++ b/esphome/components/libretiny/preference_backend.h @@ -0,0 +1,32 @@ +#pragma once +#ifdef USE_LIBRETINY + +#include +#include + +// Forward declare FlashDB types to avoid pulling in flashdb.h +struct fdb_kvdb; +struct fdb_blob; + +namespace esphome::libretiny { + +class LibreTinyPreferenceBackend final { + public: + bool save(const uint8_t *data, size_t len); + bool load(uint8_t *data, size_t len); + + uint32_t key; + struct fdb_kvdb *db; + struct fdb_blob *blob; +}; + +class LibreTinyPreferences; +LibreTinyPreferences *get_preferences(); + +} // namespace esphome::libretiny + +namespace esphome { +using PreferenceBackend = libretiny::LibreTinyPreferenceBackend; +} // namespace esphome + +#endif // USE_LIBRETINY diff --git a/esphome/components/libretiny/preferences.cpp b/esphome/components/libretiny/preferences.cpp index 1c101136e16..f22c12f1fbe 100644 --- a/esphome/components/libretiny/preferences.cpp +++ b/esphome/components/libretiny/preferences.cpp @@ -1,12 +1,11 @@ #ifdef USE_LIBRETINY +#include "preferences.h" #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/preferences.h" -#include #include #include -#include +#include namespace esphome::libretiny { @@ -22,163 +21,147 @@ struct NVSData { static std::vector s_pending_save; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) -class LibreTinyPreferenceBackend : public ESPPreferenceBackend { - public: - uint32_t key; - fdb_kvdb_t db; - fdb_blob_t blob; +bool LibreTinyPreferenceBackend::save(const uint8_t *data, size_t len) { + // try find in pending saves and update that + for (auto &obj : s_pending_save) { + if (obj.key == this->key) { + obj.data.set(data, len); + return true; + } + } + NVSData save{}; + save.key = this->key; + save.data.set(data, len); + s_pending_save.push_back(std::move(save)); + ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len); + return true; +} - bool save(const uint8_t *data, size_t len) override { - // try find in pending saves and update that - for (auto &obj : s_pending_save) { - if (obj.key == this->key) { - obj.data.set(data, len); - return true; +bool LibreTinyPreferenceBackend::load(uint8_t *data, size_t len) { + // try find in pending saves and load from that + for (auto &obj : s_pending_save) { + if (obj.key == this->key) { + if (obj.data.size() != len) { + // size mismatch + return false; } + memcpy(data, obj.data.data(), len); + return true; } - NVSData save{}; - save.key = this->key; - save.data.set(data, len); - s_pending_save.push_back(std::move(save)); - ESP_LOGVV(TAG, "s_pending_save: key: %" PRIu32 ", len: %zu", this->key, len); - return true; } - bool load(uint8_t *data, size_t len) override { - // try find in pending saves and load from that - for (auto &obj : s_pending_save) { - if (obj.key == this->key) { - if (obj.data.size() != len) { - // size mismatch - return false; - } - memcpy(data, obj.data.data(), len); - return true; - } - } + char key_str[KEY_BUFFER_SIZE]; + snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key); + fdb_blob_make(this->blob, data, len); + size_t actual_len = fdb_kv_get_blob(this->db, key_str, this->blob); + if (actual_len != len) { + ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len); + return false; + } else { + ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %zu", key_str, len); + } + return true; +} +void LibreTinyPreferences::open() { + // + fdb_err_t err = fdb_kvdb_init(&this->db, "esphome", "kvs", NULL, NULL); + if (err != FDB_NO_ERR) { + LT_E("fdb_kvdb_init(...) failed: %d", err); + } else { + LT_I("Preferences initialized"); + } +} + +ESPPreferenceObject LibreTinyPreferences::make_preference(size_t length, uint32_t type) { + auto *pref = new LibreTinyPreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + pref->db = &this->db; + pref->blob = &this->blob; + pref->key = type; + + return ESPPreferenceObject(pref); +} + +bool LibreTinyPreferences::sync() { + if (s_pending_save.empty()) + return true; + + ESP_LOGV(TAG, "Saving %zu items...", s_pending_save.size()); + int cached = 0, written = 0, failed = 0; + fdb_err_t last_err = FDB_NO_ERR; + uint32_t last_key = 0; + + for (const auto &save : s_pending_save) { char key_str[KEY_BUFFER_SIZE]; - snprintf(key_str, sizeof(key_str), "%" PRIu32, this->key); - fdb_blob_make(this->blob, data, len); - size_t actual_len = fdb_kv_get_blob(this->db, key_str, this->blob); - if (actual_len != len) { - ESP_LOGVV(TAG, "NVS length does not match (%zu!=%zu)", actual_len, len); - return false; - } else { - ESP_LOGVV(TAG, "fdb_kv_get_blob: key: %s, len: %zu", key_str, len); - } - return true; - } -}; - -class LibreTinyPreferences : public ESPPreferences { - public: - struct fdb_kvdb db; - struct fdb_blob blob; - - void open() { - // - fdb_err_t err = fdb_kvdb_init(&db, "esphome", "kvs", NULL, NULL); - if (err != FDB_NO_ERR) { - LT_E("fdb_kvdb_init(...) failed: %d", err); - } else { - LT_I("Preferences initialized"); - } - } - - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - return this->make_preference(length, type); - } - - ESPPreferenceObject make_preference(size_t length, uint32_t type) override { - auto *pref = new LibreTinyPreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->db = &this->db; - pref->blob = &this->blob; - pref->key = type; - - return ESPPreferenceObject(pref); - } - - bool sync() override { - if (s_pending_save.empty()) - return true; - - ESP_LOGV(TAG, "Saving %zu items...", s_pending_save.size()); - int cached = 0, written = 0, failed = 0; - fdb_err_t last_err = FDB_NO_ERR; - uint32_t last_key = 0; - - for (const auto &save : s_pending_save) { - char key_str[KEY_BUFFER_SIZE]; - snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); - ESP_LOGVV(TAG, "Checking if FDB data %s has changed", key_str); - if (this->is_changed_(&this->db, save, key_str)) { - ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size()); - fdb_blob_make(&this->blob, save.data.data(), save.data.size()); - fdb_err_t err = fdb_kv_set_blob(&this->db, key_str, &this->blob); - if (err != FDB_NO_ERR) { - ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%zu) failed: %d", key_str, save.data.size(), err); - failed++; - last_err = err; - last_key = save.key; - continue; - } - written++; - } else { - ESP_LOGD(TAG, "FDB data not changed; skipping %" PRIu32 " len=%zu", save.key, save.data.size()); - cached++; + snprintf(key_str, sizeof(key_str), "%" PRIu32, save.key); + ESP_LOGVV(TAG, "Checking if FDB data %s has changed", key_str); + if (this->is_changed_(&this->db, save, key_str)) { + ESP_LOGV(TAG, "sync: key: %s, len: %zu", key_str, save.data.size()); + fdb_blob_make(&this->blob, save.data.data(), save.data.size()); + fdb_err_t err = fdb_kv_set_blob(&this->db, key_str, &this->blob); + if (err != FDB_NO_ERR) { + ESP_LOGV(TAG, "fdb_kv_set_blob('%s', len=%zu) failed: %d", key_str, save.data.size(), err); + failed++; + last_err = err; + last_key = save.key; + continue; } + written++; + } else { + ESP_LOGD(TAG, "FDB data not changed; skipping %" PRIu32 " len=%zu", save.key, save.data.size()); + cached++; } - s_pending_save.clear(); + } + s_pending_save.clear(); - ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written, - failed); - if (failed > 0) { - ESP_LOGE(TAG, "Writing %d items failed. Last error=%d for key=%" PRIu32, failed, last_err, last_key); - } - - return failed == 0; + ESP_LOGD(TAG, "Writing %d items: %d cached, %d written, %d failed", cached + written + failed, cached, written, + failed); + if (failed > 0) { + ESP_LOGE(TAG, "Writing %d items failed. Last error=%d for key=%" PRIu32, failed, last_err, last_key); } - protected: - bool is_changed_(fdb_kvdb_t db, const NVSData &to_save, const char *key_str) { - struct fdb_kv kv; - fdb_kv_t kvp = fdb_kv_get_obj(db, key_str, &kv); - if (kvp == nullptr) { - ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", key_str); - return true; - } + return failed == 0; +} - // Check size first - if different, data has changed - if (kv.value_len != to_save.data.size()) { - return true; - } - - // Most preferences are small, use stack buffer with heap fallback for large ones - SmallBufferWithHeapFallback<256> stored_data(kv.value_len); - fdb_blob_make(&this->blob, stored_data.get(), kv.value_len); - size_t actual_len = fdb_kv_get_blob(db, key_str, &this->blob); - if (actual_len != kv.value_len) { - ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %u != %u", key_str, actual_len, kv.value_len); - return true; - } - - // Compare the actual data - return memcmp(to_save.data.data(), stored_data.get(), kv.value_len) != 0; - } - - bool reset() override { - ESP_LOGD(TAG, "Erasing storage"); - s_pending_save.clear(); - - fdb_kv_set_default(&db); - fdb_kvdb_deinit(&db); +bool LibreTinyPreferences::is_changed_(fdb_kvdb_t db, const NVSData &to_save, const char *key_str) { + struct fdb_kv kv; + fdb_kv_t kvp = fdb_kv_get_obj(db, key_str, &kv); + if (kvp == nullptr) { + ESP_LOGV(TAG, "fdb_kv_get_obj('%s'): nullptr - the key might not be set yet", key_str); return true; } -}; + + // Check size first - if different, data has changed + if (kv.value_len != to_save.data.size()) { + return true; + } + + // Most preferences are small, use stack buffer with heap fallback for large ones + SmallBufferWithHeapFallback<256> stored_data(kv.value_len); + fdb_blob_make(&this->blob, stored_data.get(), kv.value_len); + size_t actual_len = fdb_kv_get_blob(db, key_str, &this->blob); + if (actual_len != kv.value_len) { + ESP_LOGV(TAG, "fdb_kv_get_blob('%s') len mismatch: %zu != %zu", key_str, actual_len, (size_t) kv.value_len); + return true; + } + + // Compare the actual data + return memcmp(to_save.data.data(), stored_data.get(), kv.value_len) != 0; +} + +bool LibreTinyPreferences::reset() { + ESP_LOGD(TAG, "Erasing storage"); + s_pending_save.clear(); + + fdb_kv_set_default(&this->db); + fdb_kvdb_deinit(&this->db); + return true; +} static LibreTinyPreferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +LibreTinyPreferences *get_preferences() { return &s_preferences; } + void setup_preferences() { s_preferences.open(); global_preferences = &s_preferences; @@ -187,9 +170,7 @@ void setup_preferences() { } // namespace esphome::libretiny namespace esphome { - ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - } // namespace esphome #endif // USE_LIBRETINY diff --git a/esphome/components/libretiny/preferences.h b/esphome/components/libretiny/preferences.h index 68f377bd3ee..8365d590c22 100644 --- a/esphome/components/libretiny/preferences.h +++ b/esphome/components/libretiny/preferences.h @@ -1,11 +1,35 @@ #pragma once - #ifdef USE_LIBRETINY +#include "esphome/core/preference_backend.h" +#include + namespace esphome::libretiny { +struct NVSData; + +class LibreTinyPreferences final : public PreferencesMixin { + public: + using PreferencesMixin::make_preference; + void open(); + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) { + return this->make_preference(length, type); + } + ESPPreferenceObject make_preference(size_t length, uint32_t type); + bool sync(); + bool reset(); + + struct fdb_kvdb db; + struct fdb_blob blob; + + protected: + bool is_changed_(fdb_kvdb_t db, const NVSData &to_save, const char *key_str); +}; + void setup_preferences(); } // namespace esphome::libretiny +DECLARE_PREFERENCE_ALIASES(esphome::libretiny::LibreTinyPreferences) + #endif // USE_LIBRETINY diff --git a/esphome/components/rp2040/preference_backend.h b/esphome/components/rp2040/preference_backend.h new file mode 100644 index 00000000000..790ee8831de --- /dev/null +++ b/esphome/components/rp2040/preference_backend.h @@ -0,0 +1,27 @@ +#pragma once +#ifdef USE_RP2040 + +#include +#include + +namespace esphome::rp2040 { + +class RP2040PreferenceBackend final { + public: + bool save(const uint8_t *data, size_t len); + bool load(uint8_t *data, size_t len); + + size_t offset = 0; + uint32_t type = 0; +}; + +class RP2040Preferences; +RP2040Preferences *get_preferences(); + +} // namespace esphome::rp2040 + +namespace esphome { +using PreferenceBackend = rp2040::RP2040PreferenceBackend; +} // namespace esphome + +#endif // USE_RP2040 diff --git a/esphome/components/rp2040/preferences.cpp b/esphome/components/rp2040/preferences.cpp index fa72fd9a246..0a91136a9fd 100644 --- a/esphome/components/rp2040/preferences.cpp +++ b/esphome/components/rp2040/preferences.cpp @@ -11,10 +11,8 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -#include "esphome/core/preferences.h" -namespace esphome { -namespace rp2040 { +namespace esphome::rp2040 { static const char *const TAG = "rp2040.preferences"; @@ -39,129 +37,116 @@ template uint8_t calculate_crc(It first, It last, uint32_t type) { return crc; } -class RP2040PreferenceBackend : public ESPPreferenceBackend { - public: - size_t offset = 0; - uint32_t type = 0; +bool RP2040PreferenceBackend::save(const uint8_t *data, size_t len) { + const size_t buffer_size = len + 1; + if (buffer_size > PREF_MAX_BUFFER_SIZE) + return false; + uint8_t buffer[PREF_MAX_BUFFER_SIZE]; + memcpy(buffer, data, len); + buffer[len] = calculate_crc(buffer, buffer + len, this->type); - bool save(const uint8_t *data, size_t len) override { - const size_t buffer_size = len + 1; - if (buffer_size > PREF_MAX_BUFFER_SIZE) + for (size_t i = 0; i < buffer_size; i++) { + uint32_t j = this->offset + i; + if (j >= RP2040_FLASH_STORAGE_SIZE) return false; - uint8_t buffer[PREF_MAX_BUFFER_SIZE]; - memcpy(buffer, data, len); - buffer[len] = calculate_crc(buffer, buffer + len, this->type); - - for (size_t i = 0; i < buffer_size; i++) { - uint32_t j = this->offset + i; - if (j >= RP2040_FLASH_STORAGE_SIZE) - return false; - uint8_t v = buffer[i]; - uint8_t *ptr = &s_flash_storage[j]; - if (*ptr != v) - s_flash_dirty = true; - *ptr = v; - } - return true; + uint8_t v = buffer[i]; + uint8_t *ptr = &s_flash_storage[j]; + if (*ptr != v) + s_flash_dirty = true; + *ptr = v; } - bool load(uint8_t *data, size_t len) override { - const size_t buffer_size = len + 1; - if (buffer_size > PREF_MAX_BUFFER_SIZE) + return true; +} + +bool RP2040PreferenceBackend::load(uint8_t *data, size_t len) { + const size_t buffer_size = len + 1; + if (buffer_size > PREF_MAX_BUFFER_SIZE) + return false; + uint8_t buffer[PREF_MAX_BUFFER_SIZE]; + + for (size_t i = 0; i < buffer_size; i++) { + uint32_t j = this->offset + i; + if (j >= RP2040_FLASH_STORAGE_SIZE) return false; - uint8_t buffer[PREF_MAX_BUFFER_SIZE]; + buffer[i] = s_flash_storage[j]; + } - for (size_t i = 0; i < buffer_size; i++) { - uint32_t j = this->offset + i; - if (j >= RP2040_FLASH_STORAGE_SIZE) - return false; - buffer[i] = s_flash_storage[j]; - } + uint8_t crc = calculate_crc(buffer, buffer + len, this->type); + if (buffer[len] != crc) { + return false; + } - uint8_t crc = calculate_crc(buffer, buffer + len, this->type); - if (buffer[len] != crc) { - return false; - } + memcpy(data, buffer, len); + return true; +} - memcpy(data, buffer, len); +RP2040Preferences::RP2040Preferences() : eeprom_sector_(&_EEPROM_start) {} + +void RP2040Preferences::setup() { + ESP_LOGVV(TAG, "Loading preferences from flash"); + memcpy(s_flash_storage, this->eeprom_sector_, RP2040_FLASH_STORAGE_SIZE); +} + +ESPPreferenceObject RP2040Preferences::make_preference(size_t length, uint32_t type) { + uint32_t start = this->current_flash_offset; + uint32_t end = start + length + 1; + if (end > RP2040_FLASH_STORAGE_SIZE) { + return {}; + } + auto *pref = new RP2040PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) + pref->offset = start; + pref->type = type; + this->current_flash_offset = end; + return ESPPreferenceObject(pref); +} + +bool RP2040Preferences::sync() { + if (!s_flash_dirty) return true; - } -}; + if (s_prevent_write) + return false; -class RP2040Preferences : public ESPPreferences { - public: - uint32_t current_flash_offset = 0; + ESP_LOGD(TAG, "Saving"); - RP2040Preferences() : eeprom_sector_(&_EEPROM_start) {} - void setup() { - ESP_LOGVV(TAG, "Loading preferences from flash"); - memcpy(s_flash_storage, this->eeprom_sector_, RP2040_FLASH_STORAGE_SIZE); + { + InterruptLock lock; + ::rp2040.idleOtherCore(); + flash_range_erase((intptr_t) this->eeprom_sector_ - (intptr_t) XIP_BASE, 4096); + flash_range_program((intptr_t) this->eeprom_sector_ - (intptr_t) XIP_BASE, s_flash_storage, + RP2040_FLASH_STORAGE_SIZE); + ::rp2040.resumeOtherCore(); } - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - return make_preference(length, type); + s_flash_dirty = false; + return true; +} + +bool RP2040Preferences::reset() { + ESP_LOGD(TAG, "Erasing storage"); + { + InterruptLock lock; + ::rp2040.idleOtherCore(); + flash_range_erase((intptr_t) this->eeprom_sector_ - (intptr_t) XIP_BASE, 4096); + ::rp2040.resumeOtherCore(); } - - ESPPreferenceObject make_preference(size_t length, uint32_t type) override { - uint32_t start = this->current_flash_offset; - uint32_t end = start + length + 1; - if (end > RP2040_FLASH_STORAGE_SIZE) { - return {}; - } - auto *pref = new RP2040PreferenceBackend(); // NOLINT(cppcoreguidelines-owning-memory) - pref->offset = start; - pref->type = type; - current_flash_offset = end; - return {pref}; - } - - bool sync() override { - if (!s_flash_dirty) - return true; - if (s_prevent_write) - return false; - - ESP_LOGD(TAG, "Saving"); - - { - InterruptLock lock; - ::rp2040.idleOtherCore(); - flash_range_erase((intptr_t) eeprom_sector_ - (intptr_t) XIP_BASE, 4096); - flash_range_program((intptr_t) eeprom_sector_ - (intptr_t) XIP_BASE, s_flash_storage, RP2040_FLASH_STORAGE_SIZE); - ::rp2040.resumeOtherCore(); - } - - s_flash_dirty = false; - return true; - } - - bool reset() override { - ESP_LOGD(TAG, "Erasing storage"); - { - InterruptLock lock; - ::rp2040.idleOtherCore(); - flash_range_erase((intptr_t) eeprom_sector_ - (intptr_t) XIP_BASE, 4096); - ::rp2040.resumeOtherCore(); - } - s_prevent_write = true; - return true; - } - - protected: - uint8_t *eeprom_sector_; -}; + s_prevent_write = true; + return true; +} static RP2040Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +RP2040Preferences *get_preferences() { return &s_preferences; } + void setup_preferences() { s_preferences.setup(); global_preferences = &s_preferences; } void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; } -} // namespace rp2040 +} // namespace esphome::rp2040 +namespace esphome { ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - } // namespace esphome #endif // USE_RP2040 diff --git a/esphome/components/rp2040/preferences.h b/esphome/components/rp2040/preferences.h index b815c6d58a6..eb8c3e5f64f 100644 --- a/esphome/components/rp2040/preferences.h +++ b/esphome/components/rp2040/preferences.h @@ -1,14 +1,33 @@ #pragma once - #ifdef USE_RP2040 -namespace esphome { -namespace rp2040 { +#include "esphome/core/preference_backend.h" + +namespace esphome::rp2040 { + +class RP2040Preferences final : public PreferencesMixin { + public: + using PreferencesMixin::make_preference; + RP2040Preferences(); + void setup(); + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) { + return this->make_preference(length, type); + } + ESPPreferenceObject make_preference(size_t length, uint32_t type); + bool sync(); + bool reset(); + + uint32_t current_flash_offset = 0; + + protected: + uint8_t *eeprom_sector_; +}; void setup_preferences(); void preferences_prevent_write(bool prevent); -} // namespace rp2040 -} // namespace esphome +} // namespace esphome::rp2040 + +DECLARE_PREFERENCE_ALIASES(esphome::rp2040::RP2040Preferences) #endif // USE_RP2040 diff --git a/esphome/components/zephyr/preference_backend.h b/esphome/components/zephyr/preference_backend.h new file mode 100644 index 00000000000..07e5d8053c7 --- /dev/null +++ b/esphome/components/zephyr/preference_backend.h @@ -0,0 +1,48 @@ +#pragma once +#ifdef USE_ZEPHYR +#ifdef CONFIG_SETTINGS + +#include +#include +#include +#include +#include +#include + +namespace esphome::zephyr { + +static constexpr const char *ESPHOME_SETTINGS_KEY = "esphome"; + +// Buffer size for key: "esphome/" (8) + max hex uint32 (8) + null terminator (1) = 17; use 20 for safety margin +static constexpr size_t KEY_BUFFER_SIZE = 20; + +class ZephyrPreferenceBackend final { + public: + explicit ZephyrPreferenceBackend(uint32_t type) : type_(type) {} + ZephyrPreferenceBackend(uint32_t type, std::vector &&data) : data(std::move(data)), type_(type) {} + + bool save(const uint8_t *data, size_t len); + bool load(uint8_t *data, size_t len); + + uint32_t get_type() const { return this->type_; } + void format_key(char *buf, size_t size) const { + snprintf(buf, size, "%s/%" PRIx32, ESPHOME_SETTINGS_KEY, this->type_); + } + + std::vector data; + + protected: + uint32_t type_ = 0; +}; + +class ZephyrPreferences; +ZephyrPreferences *get_preferences(); + +} // namespace esphome::zephyr + +namespace esphome { +using PreferenceBackend = zephyr::ZephyrPreferenceBackend; +} // namespace esphome + +#endif // CONFIG_SETTINGS +#endif // USE_ZEPHYR diff --git a/esphome/components/zephyr/preferences.cpp b/esphome/components/zephyr/preferences.cpp index f02fa16326c..df69c0e6522 100644 --- a/esphome/components/zephyr/preferences.cpp +++ b/esphome/components/zephyr/preferences.cpp @@ -2,167 +2,138 @@ #ifdef CONFIG_SETTINGS #include -#include "esphome/core/preferences.h" +#include "preferences.h" #include "esphome/core/log.h" #include #include #include -namespace esphome { -namespace zephyr { +namespace esphome::zephyr { static const char *const TAG = "zephyr.preferences"; -#define ESPHOME_SETTINGS_KEY "esphome" +bool ZephyrPreferenceBackend::save(const uint8_t *data, size_t len) { + this->data.resize(len); + std::memcpy(this->data.data(), data, len); + ESP_LOGVV(TAG, "save key: %" PRIu32 ", len: %zu", this->type_, len); + return true; +} -// Buffer size for key: "esphome/" (8) + max hex uint32 (8) + null terminator (1) = 17; use 20 for safety margin -static constexpr size_t KEY_BUFFER_SIZE = 20; - -class ZephyrPreferenceBackend : public ESPPreferenceBackend { - public: - ZephyrPreferenceBackend(uint32_t type) { this->type_ = type; } - ZephyrPreferenceBackend(uint32_t type, std::vector &&data) : data(std::move(data)) { this->type_ = type; } - - bool save(const uint8_t *data, size_t len) override { - this->data.resize(len); - std::memcpy(this->data.data(), data, len); - ESP_LOGVV(TAG, "save key: %u, len: %d", this->type_, len); - return true; - } - - bool load(uint8_t *data, size_t len) override { - if (len != this->data.size()) { - char key_buf[KEY_BUFFER_SIZE]; - this->format_key(key_buf, sizeof(key_buf)); - ESP_LOGE(TAG, "size of setting key %s changed, from: %u, to: %u", key_buf, this->data.size(), len); - return false; - } - std::memcpy(data, this->data.data(), len); - ESP_LOGVV(TAG, "load key: %u, len: %d", this->type_, len); - return true; - } - - uint32_t get_type() const { return this->type_; } - void format_key(char *buf, size_t size) const { snprintf(buf, size, ESPHOME_SETTINGS_KEY "/%" PRIx32, this->type_); } - - std::vector data; - - protected: - uint32_t type_ = 0; -}; - -class ZephyrPreferences : public ESPPreferences { - public: - void open() { - int err = settings_subsys_init(); - if (err) { - ESP_LOGE(TAG, "Failed to initialize settings subsystem, err: %d", err); - return; - } - - static struct settings_handler settings_cb = { - .name = ESPHOME_SETTINGS_KEY, - .h_set = load_setting, - .h_export = export_settings, - }; - - err = settings_register(&settings_cb); - if (err) { - ESP_LOGE(TAG, "setting_register failed, err, %d", err); - return; - } - - err = settings_load_subtree(ESPHOME_SETTINGS_KEY); - if (err) { - ESP_LOGE(TAG, "Cannot load settings, err: %d", err); - return; - } - ESP_LOGD(TAG, "Loaded %u settings.", this->backends_.size()); - } - - ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) override { - return make_preference(length, type); - } - - ESPPreferenceObject make_preference(size_t length, uint32_t type) override { - for (auto *backend : this->backends_) { - if (backend->get_type() == type) { - return ESPPreferenceObject(backend); - } - } - printf("type %u size %u\n", type, this->backends_.size()); - auto *pref = new ZephyrPreferenceBackend(type); // NOLINT(cppcoreguidelines-owning-memory) +bool ZephyrPreferenceBackend::load(uint8_t *data, size_t len) { + if (len != this->data.size()) { char key_buf[KEY_BUFFER_SIZE]; - pref->format_key(key_buf, sizeof(key_buf)); - ESP_LOGD(TAG, "Add new setting %s.", key_buf); - this->backends_.push_back(pref); - return ESPPreferenceObject(pref); + this->format_key(key_buf, sizeof(key_buf)); + ESP_LOGE(TAG, "size of setting key %s changed, from: %zu, to: %zu", key_buf, this->data.size(), len); + return false; + } + std::memcpy(data, this->data.data(), len); + ESP_LOGVV(TAG, "load key: %" PRIu32 ", len: %zu", this->type_, len); + return true; +} + +void ZephyrPreferences::open() { + int err = settings_subsys_init(); + if (err) { + ESP_LOGE(TAG, "Failed to initialize settings subsystem, err: %d", err); + return; } - bool sync() override { - ESP_LOGD(TAG, "Save settings"); - int err = settings_save(); - if (err) { - ESP_LOGE(TAG, "Cannot save settings, err: %d", err); - return false; + static struct settings_handler settings_cb = { + .name = ESPHOME_SETTINGS_KEY, + .h_set = load_setting, + .h_export = export_settings, + }; + + err = settings_register(&settings_cb); + if (err) { + ESP_LOGE(TAG, "setting_register failed, err, %d", err); + return; + } + + err = settings_load_subtree(ESPHOME_SETTINGS_KEY); + if (err) { + ESP_LOGE(TAG, "Cannot load settings, err: %d", err); + return; + } + ESP_LOGD(TAG, "Loaded %zu settings.", this->backends_.size()); +} + +ESPPreferenceObject ZephyrPreferences::make_preference(size_t length, uint32_t type) { + for (auto *backend : this->backends_) { + if (backend->get_type() == type) { + return ESPPreferenceObject(backend); } - return true; } + auto *pref = new ZephyrPreferenceBackend(type); // NOLINT(cppcoreguidelines-owning-memory) + char key_buf[KEY_BUFFER_SIZE]; + pref->format_key(key_buf, sizeof(key_buf)); + ESP_LOGD(TAG, "Add new setting %s.", key_buf); + this->backends_.push_back(pref); + return ESPPreferenceObject(pref); +} - bool reset() override { - ESP_LOGD(TAG, "Reset settings"); - for (auto *backend : this->backends_) { - // save empty delete data - backend->data.clear(); - } - sync(); - return true; +bool ZephyrPreferences::sync() { + ESP_LOGD(TAG, "Save settings"); + int err = settings_save(); + if (err) { + ESP_LOGE(TAG, "Cannot save settings, err: %d", err); + return false; } + return true; +} - protected: - std::vector backends_; - - static int load_setting(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) { - auto type = parse_hex(name); - if (!type.has_value()) { - std::string full_name(ESPHOME_SETTINGS_KEY); - full_name += "/"; - full_name += name; - // Delete unusable keys. Otherwise it will stay in flash forever. - settings_delete(full_name.c_str()); - return 1; - } - std::vector data(len); - int err = read_cb(cb_arg, data.data(), len); - - ESP_LOGD(TAG, "load setting, name: %s(%u), len %u, err %u", name, *type, len, err); - auto *pref = new ZephyrPreferenceBackend(*type, std::move(data)); // NOLINT(cppcoreguidelines-owning-memory) - static_cast(global_preferences)->backends_.push_back(pref); - return 0; +bool ZephyrPreferences::reset() { + ESP_LOGD(TAG, "Reset settings"); + for (auto *backend : this->backends_) { + // save empty delete data + backend->data.clear(); } + this->sync(); + return true; +} - static int export_settings(int (*cb)(const char *name, const void *value, size_t val_len)) { - for (auto *backend : static_cast(global_preferences)->backends_) { - char name[KEY_BUFFER_SIZE]; - backend->format_key(name, sizeof(name)); - int err = cb(name, backend->data.data(), backend->data.size()); - ESP_LOGD(TAG, "save in flash, name %s, len %u, err %d", name, backend->data.size(), err); - } - return 0; +int ZephyrPreferences::load_setting(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg) { + auto type = parse_hex(name); + if (!type.has_value()) { + std::string full_name(ESPHOME_SETTINGS_KEY); + full_name += "/"; + full_name += name; + // Delete unusable keys. Otherwise it will stay in flash forever. + settings_delete(full_name.c_str()); + return 1; } -}; + std::vector data(len); + int err = read_cb(cb_arg, data.data(), len); + + ESP_LOGD(TAG, "load setting, name: %s(%" PRIu32 "), len %zu, err %d", name, *type, len, err); + auto *pref = new ZephyrPreferenceBackend(*type, std::move(data)); // NOLINT(cppcoreguidelines-owning-memory) + get_preferences()->backends_.push_back(pref); + return 0; +} + +int ZephyrPreferences::export_settings(int (*cb)(const char *name, const void *value, size_t val_len)) { + for (auto *backend : get_preferences()->backends_) { + char name[KEY_BUFFER_SIZE]; + backend->format_key(name, sizeof(name)); + int err = cb(name, backend->data.data(), backend->data.size()); + ESP_LOGD(TAG, "save in flash, name %s, len %zu, err %d", name, backend->data.size(), err); + } + return 0; +} static ZephyrPreferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +ZephyrPreferences *get_preferences() { return &s_preferences; } + void setup_preferences() { global_preferences = &s_preferences; s_preferences.open(); } -} // namespace zephyr +} // namespace esphome::zephyr +namespace esphome { ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - } // namespace esphome #endif diff --git a/esphome/components/zephyr/preferences.h b/esphome/components/zephyr/preferences.h index 4bee96d79e8..9e2555f9105 100644 --- a/esphome/components/zephyr/preferences.h +++ b/esphome/components/zephyr/preferences.h @@ -1,11 +1,36 @@ #pragma once - #ifdef USE_ZEPHYR +#ifdef CONFIG_SETTINGS + +#include "esphome/core/preference_backend.h" +#include +#include namespace esphome::zephyr { +class ZephyrPreferences final : public PreferencesMixin { + public: + using PreferencesMixin::make_preference; + void open(); + ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) { + return this->make_preference(length, type); + } + ESPPreferenceObject make_preference(size_t length, uint32_t type); + bool sync(); + bool reset(); + + protected: + std::vector backends_; + + static int load_setting(const char *name, size_t len, settings_read_cb read_cb, void *cb_arg); + static int export_settings(int (*cb)(const char *name, const void *value, size_t val_len)); +}; + void setup_preferences(); -} +} // namespace esphome::zephyr -#endif +DECLARE_PREFERENCE_ALIASES(esphome::zephyr::ZephyrPreferences) + +#endif // CONFIG_SETTINGS +#endif // USE_ZEPHYR diff --git a/esphome/core/preference_backend.h b/esphome/core/preference_backend.h new file mode 100644 index 00000000000..3766934da46 --- /dev/null +++ b/esphome/core/preference_backend.h @@ -0,0 +1,83 @@ +#pragma once + +#include + +#include "esphome/core/defines.h" +#include "esphome/core/helpers.h" + +// Include the concrete preference backend for the active platform. +// Each header defines its backend class, forward-declares its manager class, +// declares get_preferences(), and provides the PreferenceBackend alias. +#ifdef USE_ESP32 +#include "esphome/components/esp32/preference_backend.h" +#elif defined(USE_ESP8266) +#include "esphome/components/esp8266/preference_backend.h" +#elif defined(USE_RP2040) +#include "esphome/components/rp2040/preference_backend.h" +#elif defined(USE_LIBRETINY) +#include "esphome/components/libretiny/preference_backend.h" +#elif defined(USE_HOST) +#include "esphome/components/host/preference_backend.h" +#elif defined(USE_ZEPHYR) && defined(CONFIG_SETTINGS) +#include "esphome/components/zephyr/preference_backend.h" +#endif + +namespace esphome { + +#if !defined(USE_ESP32) && !defined(USE_ESP8266) && !defined(USE_RP2040) && !defined(USE_LIBRETINY) && \ + !defined(USE_HOST) && !(defined(USE_ZEPHYR) && defined(CONFIG_SETTINGS)) +// Stub for static analysis when no platform is defined. +struct PreferenceBackend { + bool save(const uint8_t *, size_t) { return false; } + bool load(uint8_t *, size_t) { return false; } +}; +#endif + +using ESPPreferenceBackend = PreferenceBackend; + +class ESPPreferenceObject { + public: + ESPPreferenceObject() = default; + explicit ESPPreferenceObject(PreferenceBackend *backend) : backend_(backend) {} + + template bool save(const T *src) { + if (this->backend_ == nullptr) + return false; + return this->backend_->save(reinterpret_cast(src), sizeof(T)); + } + + template bool load(T *dest) { + if (this->backend_ == nullptr) + return false; + return this->backend_->load(reinterpret_cast(dest), sizeof(T)); + } + + protected: + PreferenceBackend *backend_{nullptr}; +}; + +/// CRTP mixin providing type-safe template make_preference() helpers. +/// Platform preferences classes inherit this to avoid duplicating these templates. +template class PreferencesMixin { + public: + template::value, bool> = true> + ESPPreferenceObject make_preference(uint32_t type, bool in_flash) { + return static_cast(this)->make_preference(sizeof(T), type, in_flash); + } + + template::value, bool> = true> + ESPPreferenceObject make_preference(uint32_t type) { + return static_cast(this)->make_preference(sizeof(T), type); + } +}; + +// Macro for platform preferences.h headers to declare the standard aliases. +// Must be used at file scope (outside any namespace). +#define DECLARE_PREFERENCE_ALIASES(platform_class) \ + namespace esphome { \ + using Preferences = platform_class; \ + using ESPPreferences = Preferences; \ + extern ESPPreferences *global_preferences; /* NOLINT(cppcoreguidelines-avoid-non-const-global-variables) */ \ + } + +} // namespace esphome diff --git a/esphome/core/preferences.h b/esphome/core/preferences.h index 6d2dd967e9a..64a0a927e67 100644 --- a/esphome/core/preferences.h +++ b/esphome/core/preferences.h @@ -1,50 +1,35 @@ #pragma once -#include -#include - -#include "esphome/core/helpers.h" +#include "esphome/core/preference_backend.h" +// Include the concrete preferences manager for the active platform. +// Each header defines its manager class and provides the Preferences, +// ESPPreferences, and global_preferences declarations. +#ifdef USE_ESP32 +#include "esphome/components/esp32/preferences.h" +#elif defined(USE_ESP8266) +#include "esphome/components/esp8266/preferences.h" +#elif defined(USE_RP2040) +#include "esphome/components/rp2040/preferences.h" +#elif defined(USE_LIBRETINY) +#include "esphome/components/libretiny/preferences.h" +#elif defined(USE_HOST) +#include "esphome/components/host/preferences.h" +#elif defined(USE_ZEPHYR) && defined(CONFIG_SETTINGS) +#include "esphome/components/zephyr/preferences.h" +#else namespace esphome { - -class ESPPreferenceBackend { - public: - virtual bool save(const uint8_t *data, size_t len) = 0; - virtual bool load(uint8_t *data, size_t len) = 0; -}; - -class ESPPreferenceObject { - public: - ESPPreferenceObject() = default; - ESPPreferenceObject(ESPPreferenceBackend *backend) : backend_(backend) {} - - template bool save(const T *src) { - if (backend_ == nullptr) - return false; - return backend_->save(reinterpret_cast(src), sizeof(T)); - } - - template bool load(T *dest) { - if (backend_ == nullptr) - return false; - return backend_->load(reinterpret_cast(dest), sizeof(T)); - } - - protected: - ESPPreferenceBackend *backend_{nullptr}; -}; - -class ESPPreferences { - public: - virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash) = 0; - virtual ESPPreferenceObject make_preference(size_t length, uint32_t type) = 0; +struct Preferences : public PreferencesMixin { + using PreferencesMixin::make_preference; + ESPPreferenceObject make_preference(size_t, uint32_t, bool) { return {}; } + ESPPreferenceObject make_preference(size_t, uint32_t) { return {}; } /** * Commit pending writes to flash. * * @return true if write is successful. */ - virtual bool sync() = 0; + bool sync() { return false; } /** * Forget all unsaved changes and re-initialize the permanent preferences storage. @@ -52,19 +37,9 @@ class ESPPreferences { * * @return true if operation is successful. */ - virtual bool reset() = 0; - - template::value, bool> = true> - ESPPreferenceObject make_preference(uint32_t type, bool in_flash) { - return this->make_preference(sizeof(T), type, in_flash); - } - - template::value, bool> = true> - ESPPreferenceObject make_preference(uint32_t type) { - return this->make_preference(sizeof(T), type); - } + bool reset() { return false; } }; - +using ESPPreferences = Preferences; extern ESPPreferences *global_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) - } // namespace esphome +#endif