Compare commits

..

3 Commits

137 changed files with 1055 additions and 1340 deletions

View File

@@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning. # Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL - name: Initialize CodeQL
uses: github/codeql-action/init@6bc82e05fd0ea64601dd4b465378bbcf57de0314 # v4.32.1 uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
with: with:
languages: ${{ matrix.language }} languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }} build-mode: ${{ matrix.build-mode }}
@@ -86,6 +86,6 @@ jobs:
exit 1 exit 1
- name: Perform CodeQL Analysis - name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@6bc82e05fd0ea64601dd4b465378bbcf57de0314 # v4.32.1 uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
with: with:
category: "/language:${{matrix.language}}" category: "/language:${{matrix.language}}"

View File

@@ -4,8 +4,6 @@ from __future__ import annotations
from collections import defaultdict from collections import defaultdict
from collections.abc import Callable from collections.abc import Callable
import heapq
from operator import itemgetter
import sys import sys
from typing import TYPE_CHECKING from typing import TYPE_CHECKING
@@ -31,10 +29,6 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
) )
# Lower threshold for RAM symbols (RAM is more constrained) # Lower threshold for RAM symbols (RAM is more constrained)
RAM_SYMBOL_SIZE_THRESHOLD: int = 24 RAM_SYMBOL_SIZE_THRESHOLD: int = 24
# Number of top symbols to show in the largest symbols report
TOP_SYMBOLS_LIMIT: int = 30
# Width for symbol name display in top symbols report
COL_TOP_SYMBOL_NAME: int = 55
# Column width constants # Column width constants
COL_COMPONENT: int = 29 COL_COMPONENT: int = 29
@@ -153,37 +147,6 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
section_label = f" [{section[1:]}]" # .data -> [data], .bss -> [bss] section_label = f" [{section[1:]}]" # .data -> [data], .bss -> [bss]
return f"{demangled} ({size:,} B){section_label}" return f"{demangled} ({size:,} B){section_label}"
def _add_top_symbols(self, lines: list[str]) -> None:
"""Add a section showing the top largest symbols in the binary."""
# Collect all symbols from all components: (symbol, demangled, size, section, component)
all_symbols = [
(symbol, demangled, size, section, component)
for component, symbols in self._component_symbols.items()
for symbol, demangled, size, section in symbols
]
# Get top N symbols by size using heapq for efficiency
top_symbols = heapq.nlargest(
self.TOP_SYMBOLS_LIMIT, all_symbols, key=itemgetter(2)
)
lines.append("")
lines.append(f"Top {self.TOP_SYMBOLS_LIMIT} Largest Symbols:")
# Calculate truncation limit from column width (leaving room for "...")
truncate_limit = self.COL_TOP_SYMBOL_NAME - 3
for i, (_, demangled, size, section, component) in enumerate(top_symbols):
# Format section label
section_label = f"[{section[1:]}]" if section else ""
# Truncate demangled name if too long
demangled_display = (
f"{demangled[:truncate_limit]}..."
if len(demangled) > self.COL_TOP_SYMBOL_NAME
else demangled
)
lines.append(
f"{i + 1:>2}. {size:>7,} B {section_label:<8} {demangled_display:<{self.COL_TOP_SYMBOL_NAME}} {component}"
)
def generate_report(self, detailed: bool = False) -> str: def generate_report(self, detailed: bool = False) -> str:
"""Generate a formatted memory report.""" """Generate a formatted memory report."""
components = sorted( components = sorted(
@@ -285,9 +248,6 @@ class MemoryAnalyzerCLI(MemoryAnalyzer):
"RAM", "RAM",
) )
# Top largest symbols in the binary
self._add_top_symbols(lines)
# Add ESPHome core detailed analysis if there are core symbols # Add ESPHome core detailed analysis if there are core symbols
if self._esphome_core_symbols: if self._esphome_core_symbols:
self._add_section_header(lines, f"{_COMPONENT_CORE} Detailed Analysis") self._add_section_header(lines, f"{_COMPONENT_CORE} Detailed Analysis")

View File

@@ -45,7 +45,6 @@ service APIConnection {
rpc time_command (TimeCommandRequest) returns (void) {} rpc time_command (TimeCommandRequest) returns (void) {}
rpc update_command (UpdateCommandRequest) returns (void) {} rpc update_command (UpdateCommandRequest) returns (void) {}
rpc valve_command (ValveCommandRequest) returns (void) {} rpc valve_command (ValveCommandRequest) returns (void) {}
rpc water_heater_command (WaterHeaterCommandRequest) returns (void) {}
rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {} rpc subscribe_bluetooth_le_advertisements(SubscribeBluetoothLEAdvertisementsRequest) returns (void) {}
rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {} rpc bluetooth_device_request(BluetoothDeviceRequest) returns (void) {}

View File

@@ -1385,7 +1385,7 @@ uint16_t APIConnection::try_send_water_heater_info(EntityBase *entity, APIConnec
is_single); is_single);
} }
void APIConnection::water_heater_command(const WaterHeaterCommandRequest &msg) { void APIConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) {
ENTITY_COMMAND_MAKE_CALL(water_heater::WaterHeater, water_heater, water_heater) ENTITY_COMMAND_MAKE_CALL(water_heater::WaterHeater, water_heater, water_heater)
if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_MODE) if (msg.has_fields & enums::WATER_HEATER_COMMAND_HAS_MODE)
call.set_mode(static_cast<water_heater::WaterHeaterMode>(msg.mode)); call.set_mode(static_cast<water_heater::WaterHeaterMode>(msg.mode));

View File

@@ -170,7 +170,7 @@ class APIConnection final : public APIServerConnection {
#ifdef USE_WATER_HEATER #ifdef USE_WATER_HEATER
bool send_water_heater_state(water_heater::WaterHeater *water_heater); bool send_water_heater_state(water_heater::WaterHeater *water_heater);
void water_heater_command(const WaterHeaterCommandRequest &msg) override; void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override;
#endif #endif
#ifdef USE_IR_RF #ifdef USE_IR_RF

View File

@@ -746,11 +746,6 @@ void APIServerConnection::on_update_command_request(const UpdateCommandRequest &
#ifdef USE_VALVE #ifdef USE_VALVE
void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { this->valve_command(msg); } void APIServerConnection::on_valve_command_request(const ValveCommandRequest &msg) { this->valve_command(msg); }
#endif #endif
#ifdef USE_WATER_HEATER
void APIServerConnection::on_water_heater_command_request(const WaterHeaterCommandRequest &msg) {
this->water_heater_command(msg);
}
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request( void APIServerConnection::on_subscribe_bluetooth_le_advertisements_request(
const SubscribeBluetoothLEAdvertisementsRequest &msg) { const SubscribeBluetoothLEAdvertisementsRequest &msg) {

View File

@@ -303,9 +303,6 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_VALVE #ifdef USE_VALVE
virtual void valve_command(const ValveCommandRequest &msg) = 0; virtual void valve_command(const ValveCommandRequest &msg) = 0;
#endif #endif
#ifdef USE_WATER_HEATER
virtual void water_heater_command(const WaterHeaterCommandRequest &msg) = 0;
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0; virtual void subscribe_bluetooth_le_advertisements(const SubscribeBluetoothLEAdvertisementsRequest &msg) = 0;
#endif #endif
@@ -435,9 +432,6 @@ class APIServerConnection : public APIServerConnectionBase {
#ifdef USE_VALVE #ifdef USE_VALVE
void on_valve_command_request(const ValveCommandRequest &msg) override; void on_valve_command_request(const ValveCommandRequest &msg) override;
#endif #endif
#ifdef USE_WATER_HEATER
void on_water_heater_command_request(const WaterHeaterCommandRequest &msg) override;
#endif
#ifdef USE_BLUETOOTH_PROXY #ifdef USE_BLUETOOTH_PROXY
void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override; void on_subscribe_bluetooth_le_advertisements_request(const SubscribeBluetoothLEAdvertisementsRequest &msg) override;
#endif #endif

View File

@@ -211,7 +211,7 @@ void APIServer::loop() {
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER #ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
// Fire trigger after client is removed so api.connected reflects the true state // Fire trigger after client is removed so api.connected reflects the true state
this->client_disconnected_trigger_.trigger(client_name, client_peername); this->client_disconnected_trigger_->trigger(client_name, client_peername);
#endif #endif
// Don't increment client_index since we need to process the swapped element // Don't increment client_index since we need to process the swapped element
} }

View File

@@ -227,10 +227,12 @@ class APIServer : public Component,
#endif #endif
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER #ifdef USE_API_CLIENT_CONNECTED_TRIGGER
Trigger<std::string, std::string> *get_client_connected_trigger() { return &this->client_connected_trigger_; } Trigger<std::string, std::string> *get_client_connected_trigger() const { return this->client_connected_trigger_; }
#endif #endif
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER #ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
Trigger<std::string, std::string> *get_client_disconnected_trigger() { return &this->client_disconnected_trigger_; } Trigger<std::string, std::string> *get_client_disconnected_trigger() const {
return this->client_disconnected_trigger_;
}
#endif #endif
protected: protected:
@@ -251,10 +253,10 @@ class APIServer : public Component,
// Pointers and pointer-like types first (4 bytes each) // Pointers and pointer-like types first (4 bytes each)
std::unique_ptr<socket::Socket> socket_ = nullptr; std::unique_ptr<socket::Socket> socket_ = nullptr;
#ifdef USE_API_CLIENT_CONNECTED_TRIGGER #ifdef USE_API_CLIENT_CONNECTED_TRIGGER
Trigger<std::string, std::string> client_connected_trigger_; Trigger<std::string, std::string> *client_connected_trigger_ = new Trigger<std::string, std::string>();
#endif #endif
#ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER #ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER
Trigger<std::string, std::string> client_disconnected_trigger_; Trigger<std::string, std::string> *client_disconnected_trigger_ = new Trigger<std::string, std::string>();
#endif #endif
// 4-byte aligned types // 4-byte aligned types

View File

@@ -136,10 +136,12 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
void set_wants_response() { this->flags_.wants_response = true; } void set_wants_response() { this->flags_.wants_response = true; }
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
Trigger<JsonObjectConst, Ts...> *get_success_trigger_with_response() { return &this->success_trigger_with_response_; } Trigger<JsonObjectConst, Ts...> *get_success_trigger_with_response() const {
return this->success_trigger_with_response_;
}
#endif #endif
Trigger<Ts...> *get_success_trigger() { return &this->success_trigger_; } Trigger<Ts...> *get_success_trigger() const { return this->success_trigger_; }
Trigger<std::string, Ts...> *get_error_trigger() { return &this->error_trigger_; } Trigger<std::string, Ts...> *get_error_trigger() const { return this->error_trigger_; }
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
void play(const Ts &...x) override { void play(const Ts &...x) override {
@@ -185,14 +187,14 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
if (response.is_success()) { if (response.is_success()) {
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
if (this->flags_.wants_response) { if (this->flags_.wants_response) {
this->success_trigger_with_response_.trigger(response.get_json(), args...); this->success_trigger_with_response_->trigger(response.get_json(), args...);
} else } else
#endif #endif
{ {
this->success_trigger_.trigger(args...); this->success_trigger_->trigger(args...);
} }
} else { } else {
this->error_trigger_.trigger(response.get_error_message(), args...); this->error_trigger_->trigger(response.get_error_message(), args...);
} }
}, },
captured_args); captured_args);
@@ -249,10 +251,10 @@ template<typename... Ts> class HomeAssistantServiceCallAction : public Action<Ts
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES
#ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
TemplatableStringValue<Ts...> response_template_{""}; TemplatableStringValue<Ts...> response_template_{""};
Trigger<JsonObjectConst, Ts...> success_trigger_with_response_; Trigger<JsonObjectConst, Ts...> *success_trigger_with_response_ = new Trigger<JsonObjectConst, Ts...>();
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON
Trigger<Ts...> success_trigger_; Trigger<Ts...> *success_trigger_ = new Trigger<Ts...>();
Trigger<std::string, Ts...> error_trigger_; Trigger<std::string, Ts...> *error_trigger_ = new Trigger<std::string, Ts...>();
#endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES
struct Flags { struct Flags {

View File

@@ -6,7 +6,8 @@ namespace bang_bang {
static const char *const TAG = "bang_bang.climate"; static const char *const TAG = "bang_bang.climate";
BangBangClimate::BangBangClimate() = default; BangBangClimate::BangBangClimate()
: idle_trigger_(new Trigger<>()), cool_trigger_(new Trigger<>()), heat_trigger_(new Trigger<>()) {}
void BangBangClimate::setup() { void BangBangClimate::setup() {
this->sensor_->add_on_state_callback([this](float state) { this->sensor_->add_on_state_callback([this](float state) {
@@ -159,13 +160,13 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) {
switch (action) { switch (action) {
case climate::CLIMATE_ACTION_OFF: case climate::CLIMATE_ACTION_OFF:
case climate::CLIMATE_ACTION_IDLE: case climate::CLIMATE_ACTION_IDLE:
trig = &this->idle_trigger_; trig = this->idle_trigger_;
break; break;
case climate::CLIMATE_ACTION_COOLING: case climate::CLIMATE_ACTION_COOLING:
trig = &this->cool_trigger_; trig = this->cool_trigger_;
break; break;
case climate::CLIMATE_ACTION_HEATING: case climate::CLIMATE_ACTION_HEATING:
trig = &this->heat_trigger_; trig = this->heat_trigger_;
break; break;
default: default:
trig = nullptr; trig = nullptr;
@@ -203,9 +204,9 @@ void BangBangClimate::set_away_config(const BangBangClimateTargetTempConfig &awa
void BangBangClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } void BangBangClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; }
void BangBangClimate::set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; } void BangBangClimate::set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; }
Trigger<> *BangBangClimate::get_idle_trigger() { return &this->idle_trigger_; } Trigger<> *BangBangClimate::get_idle_trigger() const { return this->idle_trigger_; }
Trigger<> *BangBangClimate::get_cool_trigger() { return &this->cool_trigger_; } Trigger<> *BangBangClimate::get_cool_trigger() const { return this->cool_trigger_; }
Trigger<> *BangBangClimate::get_heat_trigger() { return &this->heat_trigger_; } Trigger<> *BangBangClimate::get_heat_trigger() const { return this->heat_trigger_; }
void BangBangClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; } void BangBangClimate::set_supports_cool(bool supports_cool) { this->supports_cool_ = supports_cool; }
void BangBangClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; } void BangBangClimate::set_supports_heat(bool supports_heat) { this->supports_heat_ = supports_heat; }

View File

@@ -30,9 +30,9 @@ class BangBangClimate : public climate::Climate, public Component {
void set_normal_config(const BangBangClimateTargetTempConfig &normal_config); void set_normal_config(const BangBangClimateTargetTempConfig &normal_config);
void set_away_config(const BangBangClimateTargetTempConfig &away_config); void set_away_config(const BangBangClimateTargetTempConfig &away_config);
Trigger<> *get_idle_trigger(); Trigger<> *get_idle_trigger() const;
Trigger<> *get_cool_trigger(); Trigger<> *get_cool_trigger() const;
Trigger<> *get_heat_trigger(); Trigger<> *get_heat_trigger() const;
protected: protected:
/// Override control to change settings of the climate device. /// Override control to change settings of the climate device.
@@ -57,13 +57,17 @@ class BangBangClimate : public climate::Climate, public Component {
* *
* In idle mode, the controller is assumed to have both heating and cooling disabled. * In idle mode, the controller is assumed to have both heating and cooling disabled.
*/ */
Trigger<> idle_trigger_; Trigger<> *idle_trigger_{nullptr};
/** The trigger to call when the controller should switch to cooling mode. /** The trigger to call when the controller should switch to cooling mode.
*/ */
Trigger<> cool_trigger_; Trigger<> *cool_trigger_{nullptr};
/** The trigger to call when the controller should switch to heating mode. /** The trigger to call when the controller should switch to heating mode.
*
* A null value for this attribute means that the controller has no heating action
* For example window blinds, where only cooling (blinds closed) and not-cooling
* (blinds open) is possible.
*/ */
Trigger<> heat_trigger_; Trigger<> *heat_trigger_{nullptr};
/** A reference to the trigger that was previously active. /** A reference to the trigger that was previously active.
* *
* This is so that the previous trigger can be stopped before enabling a new one. * This is so that the previous trigger can be stopped before enabling a new one.

View File

@@ -156,7 +156,7 @@ void CC1101Component::call_listeners_(const std::vector<uint8_t> &packet, float
for (auto &listener : this->listeners_) { for (auto &listener : this->listeners_) {
listener->on_packet(packet, freq_offset, rssi, lqi); listener->on_packet(packet, freq_offset, rssi, lqi);
} }
this->packet_trigger_.trigger(packet, freq_offset, rssi, lqi); this->packet_trigger_->trigger(packet, freq_offset, rssi, lqi);
} }
void CC1101Component::loop() { void CC1101Component::loop() {

View File

@@ -79,7 +79,7 @@ class CC1101Component : public Component,
// Packet mode operations // Packet mode operations
CC1101Error transmit_packet(const std::vector<uint8_t> &packet); CC1101Error transmit_packet(const std::vector<uint8_t> &packet);
void register_listener(CC1101Listener *listener) { this->listeners_.push_back(listener); } void register_listener(CC1101Listener *listener) { this->listeners_.push_back(listener); }
Trigger<std::vector<uint8_t>, float, float, uint8_t> *get_packet_trigger() { return &this->packet_trigger_; } Trigger<std::vector<uint8_t>, float, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; }
protected: protected:
uint16_t chip_id_{0}; uint16_t chip_id_{0};
@@ -96,7 +96,8 @@ class CC1101Component : public Component,
// Packet handling // Packet handling
void call_listeners_(const std::vector<uint8_t> &packet, float freq_offset, float rssi, uint8_t lqi); void call_listeners_(const std::vector<uint8_t> &packet, float freq_offset, float rssi, uint8_t lqi);
Trigger<std::vector<uint8_t>, float, float, uint8_t> packet_trigger_; Trigger<std::vector<uint8_t>, float, float, uint8_t> *packet_trigger_{
new Trigger<std::vector<uint8_t>, float, float, uint8_t>()};
std::vector<uint8_t> packet_; std::vector<uint8_t> packet_;
std::vector<CC1101Listener *> listeners_; std::vector<CC1101Listener *> listeners_;

View File

@@ -66,7 +66,7 @@ void CurrentBasedCover::loop() {
if (this->current_operation == COVER_OPERATION_OPENING) { if (this->current_operation == COVER_OPERATION_OPENING) {
if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction
this->direction_idle_(); this->direction_idle_();
this->malfunction_trigger_.trigger(); this->malfunction_trigger_->trigger();
ESP_LOGI(TAG, "'%s' - Malfunction detected during opening. Current flow detected in close circuit", ESP_LOGI(TAG, "'%s' - Malfunction detected during opening. Current flow detected in close circuit",
this->name_.c_str()); this->name_.c_str());
} else if (this->is_opening_blocked_()) { // Blocked } else if (this->is_opening_blocked_()) { // Blocked
@@ -87,7 +87,7 @@ void CurrentBasedCover::loop() {
} else if (this->current_operation == COVER_OPERATION_CLOSING) { } else if (this->current_operation == COVER_OPERATION_CLOSING) {
if (this->malfunction_detection_ && this->is_opening_()) { // Malfunction if (this->malfunction_detection_ && this->is_opening_()) { // Malfunction
this->direction_idle_(); this->direction_idle_();
this->malfunction_trigger_.trigger(); this->malfunction_trigger_->trigger();
ESP_LOGI(TAG, "'%s' - Malfunction detected during closing. Current flow detected in open circuit", ESP_LOGI(TAG, "'%s' - Malfunction detected during closing. Current flow detected in open circuit",
this->name_.c_str()); this->name_.c_str());
} else if (this->is_closing_blocked_()) { // Blocked } else if (this->is_closing_blocked_()) { // Blocked
@@ -221,15 +221,15 @@ void CurrentBasedCover::start_direction_(CoverOperation dir) {
Trigger<> *trig; Trigger<> *trig;
switch (dir) { switch (dir) {
case COVER_OPERATION_IDLE: case COVER_OPERATION_IDLE:
trig = &this->stop_trigger_; trig = this->stop_trigger_;
break; break;
case COVER_OPERATION_OPENING: case COVER_OPERATION_OPENING:
this->last_operation_ = dir; this->last_operation_ = dir;
trig = &this->open_trigger_; trig = this->open_trigger_;
break; break;
case COVER_OPERATION_CLOSING: case COVER_OPERATION_CLOSING:
this->last_operation_ = dir; this->last_operation_ = dir;
trig = &this->close_trigger_; trig = this->close_trigger_;
break; break;
default: default:
return; return;

View File

@@ -16,9 +16,9 @@ class CurrentBasedCover : public cover::Cover, public Component {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
Trigger<> *get_stop_trigger() { return &this->stop_trigger_; } Trigger<> *get_stop_trigger() const { return this->stop_trigger_; }
Trigger<> *get_open_trigger() { return &this->open_trigger_; } Trigger<> *get_open_trigger() const { return this->open_trigger_; }
void set_open_sensor(sensor::Sensor *open_sensor) { this->open_sensor_ = open_sensor; } void set_open_sensor(sensor::Sensor *open_sensor) { this->open_sensor_ = open_sensor; }
void set_open_moving_current_threshold(float open_moving_current_threshold) { void set_open_moving_current_threshold(float open_moving_current_threshold) {
this->open_moving_current_threshold_ = open_moving_current_threshold; this->open_moving_current_threshold_ = open_moving_current_threshold;
@@ -28,7 +28,7 @@ class CurrentBasedCover : public cover::Cover, public Component {
} }
void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; } void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; }
Trigger<> *get_close_trigger() { return &this->close_trigger_; } Trigger<> *get_close_trigger() const { return this->close_trigger_; }
void set_close_sensor(sensor::Sensor *close_sensor) { this->close_sensor_ = close_sensor; } void set_close_sensor(sensor::Sensor *close_sensor) { this->close_sensor_ = close_sensor; }
void set_close_moving_current_threshold(float close_moving_current_threshold) { void set_close_moving_current_threshold(float close_moving_current_threshold) {
this->close_moving_current_threshold_ = close_moving_current_threshold; this->close_moving_current_threshold_ = close_moving_current_threshold;
@@ -44,7 +44,7 @@ class CurrentBasedCover : public cover::Cover, public Component {
void set_malfunction_detection(bool malfunction_detection) { this->malfunction_detection_ = malfunction_detection; } void set_malfunction_detection(bool malfunction_detection) { this->malfunction_detection_ = malfunction_detection; }
void set_start_sensing_delay(uint32_t start_sensing_delay) { this->start_sensing_delay_ = start_sensing_delay; } void set_start_sensing_delay(uint32_t start_sensing_delay) { this->start_sensing_delay_ = start_sensing_delay; }
Trigger<> *get_malfunction_trigger() { return &this->malfunction_trigger_; } Trigger<> *get_malfunction_trigger() const { return this->malfunction_trigger_; }
cover::CoverTraits get_traits() override; cover::CoverTraits get_traits() override;
@@ -64,23 +64,23 @@ class CurrentBasedCover : public cover::Cover, public Component {
void recompute_position_(); void recompute_position_();
Trigger<> stop_trigger_; Trigger<> *stop_trigger_{new Trigger<>()};
sensor::Sensor *open_sensor_{nullptr}; sensor::Sensor *open_sensor_{nullptr};
Trigger<> open_trigger_; Trigger<> *open_trigger_{new Trigger<>()};
float open_moving_current_threshold_; float open_moving_current_threshold_;
float open_obstacle_current_threshold_{FLT_MAX}; float open_obstacle_current_threshold_{FLT_MAX};
uint32_t open_duration_; uint32_t open_duration_;
sensor::Sensor *close_sensor_{nullptr}; sensor::Sensor *close_sensor_{nullptr};
Trigger<> close_trigger_; Trigger<> *close_trigger_{new Trigger<>()};
float close_moving_current_threshold_; float close_moving_current_threshold_;
float close_obstacle_current_threshold_{FLT_MAX}; float close_obstacle_current_threshold_{FLT_MAX};
uint32_t close_duration_; uint32_t close_duration_;
uint32_t max_duration_{UINT32_MAX}; uint32_t max_duration_{UINT32_MAX};
bool malfunction_detection_{true}; bool malfunction_detection_{true};
Trigger<> malfunction_trigger_; Trigger<> *malfunction_trigger_{new Trigger<>()};
uint32_t start_sensing_delay_; uint32_t start_sensing_delay_;
float obstacle_rollback_; float obstacle_rollback_;

View File

@@ -141,15 +141,15 @@ void EndstopCover::start_direction_(CoverOperation dir) {
Trigger<> *trig; Trigger<> *trig;
switch (dir) { switch (dir) {
case COVER_OPERATION_IDLE: case COVER_OPERATION_IDLE:
trig = &this->stop_trigger_; trig = this->stop_trigger_;
break; break;
case COVER_OPERATION_OPENING: case COVER_OPERATION_OPENING:
this->last_operation_ = dir; this->last_operation_ = dir;
trig = &this->open_trigger_; trig = this->open_trigger_;
break; break;
case COVER_OPERATION_CLOSING: case COVER_OPERATION_CLOSING:
this->last_operation_ = dir; this->last_operation_ = dir;
trig = &this->close_trigger_; trig = this->close_trigger_;
break; break;
default: default:
return; return;

View File

@@ -15,9 +15,9 @@ class EndstopCover : public cover::Cover, public Component {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override; float get_setup_priority() const override;
Trigger<> *get_open_trigger() { return &this->open_trigger_; } Trigger<> *get_open_trigger() const { return this->open_trigger_; }
Trigger<> *get_close_trigger() { return &this->close_trigger_; } Trigger<> *get_close_trigger() const { return this->close_trigger_; }
Trigger<> *get_stop_trigger() { return &this->stop_trigger_; } Trigger<> *get_stop_trigger() const { return this->stop_trigger_; }
void set_open_endstop(binary_sensor::BinarySensor *open_endstop) { this->open_endstop_ = open_endstop; } void set_open_endstop(binary_sensor::BinarySensor *open_endstop) { this->open_endstop_ = open_endstop; }
void set_close_endstop(binary_sensor::BinarySensor *close_endstop) { this->close_endstop_ = close_endstop; } void set_close_endstop(binary_sensor::BinarySensor *close_endstop) { this->close_endstop_ = close_endstop; }
void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; } void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; }
@@ -39,11 +39,11 @@ class EndstopCover : public cover::Cover, public Component {
binary_sensor::BinarySensor *open_endstop_; binary_sensor::BinarySensor *open_endstop_;
binary_sensor::BinarySensor *close_endstop_; binary_sensor::BinarySensor *close_endstop_;
Trigger<> open_trigger_; Trigger<> *open_trigger_{new Trigger<>()};
uint32_t open_duration_; uint32_t open_duration_;
Trigger<> close_trigger_; Trigger<> *close_trigger_{new Trigger<>()};
uint32_t close_duration_; uint32_t close_duration_;
Trigger<> stop_trigger_; Trigger<> *stop_trigger_{new Trigger<>()};
uint32_t max_duration_{UINT32_MAX}; uint32_t max_duration_{UINT32_MAX};
Trigger<> *prev_command_trigger_{nullptr}; Trigger<> *prev_command_trigger_{nullptr};

View File

@@ -124,14 +124,10 @@ COMPILER_OPTIMIZATIONS = {
# - "sdmmc": driver -> esp_driver_sdmmc -> sdmmc dependency chain # - "sdmmc": driver -> esp_driver_sdmmc -> sdmmc dependency chain
DEFAULT_EXCLUDED_IDF_COMPONENTS = ( DEFAULT_EXCLUDED_IDF_COMPONENTS = (
"cmock", # Unit testing mock framework - ESPHome doesn't use IDF's testing "cmock", # Unit testing mock framework - ESPHome doesn't use IDF's testing
"driver", # Legacy driver shim - only needed by esp32_touch, esp32_can for legacy headers
"esp_adc", # ADC driver - only needed by adc component "esp_adc", # ADC driver - only needed by adc component
"esp_driver_dac", # DAC driver - only needed by esp32_dac component
"esp_driver_i2s", # I2S driver - only needed by i2s_audio component "esp_driver_i2s", # I2S driver - only needed by i2s_audio component
"esp_driver_mcpwm", # MCPWM driver - ESPHome doesn't use motor control PWM
"esp_driver_rmt", # RMT driver - only needed by remote_transmitter/receiver, neopixelbus "esp_driver_rmt", # RMT driver - only needed by remote_transmitter/receiver, neopixelbus
"esp_driver_touch_sens", # Touch sensor driver - only needed by esp32_touch "esp_driver_touch_sens", # Touch sensor driver - only needed by esp32_touch
"esp_driver_twai", # TWAI/CAN driver - only needed by esp32_can component
"esp_eth", # Ethernet driver - only needed by ethernet component "esp_eth", # Ethernet driver - only needed by ethernet component
"esp_hid", # HID host/device support - ESPHome doesn't implement HID functionality "esp_hid", # HID host/device support - ESPHome doesn't implement HID functionality
"esp_http_client", # HTTP client - only needed by http_request component "esp_http_client", # HTTP client - only needed by http_request component
@@ -142,11 +138,9 @@ DEFAULT_EXCLUDED_IDF_COMPONENTS = (
"espcoredump", # Core dump support - ESPHome has its own debug component "espcoredump", # Core dump support - ESPHome has its own debug component
"fatfs", # FAT filesystem - ESPHome doesn't use filesystem storage "fatfs", # FAT filesystem - ESPHome doesn't use filesystem storage
"mqtt", # ESP-IDF MQTT library - ESPHome has its own MQTT implementation "mqtt", # ESP-IDF MQTT library - ESPHome has its own MQTT implementation
"openthread", # Thread protocol - only needed by openthread component
"perfmon", # Xtensa performance monitor - ESPHome has its own debug component "perfmon", # Xtensa performance monitor - ESPHome has its own debug component
"protocomm", # Protocol communication for provisioning - unused by ESPHome "protocomm", # Protocol communication for provisioning - unused by ESPHome
"spiffs", # SPIFFS filesystem - ESPHome doesn't use filesystem storage (IDF only) "spiffs", # SPIFFS filesystem - ESPHome doesn't use filesystem storage (IDF only)
"ulp", # ULP coprocessor - not currently used by any ESPHome component
"unity", # Unit testing framework - ESPHome doesn't use IDF's testing "unity", # Unit testing framework - ESPHome doesn't use IDF's testing
"wear_levelling", # Flash wear levelling for fatfs - unused since fatfs unused "wear_levelling", # Flash wear levelling for fatfs - unused since fatfs unused
"wifi_provisioning", # WiFi provisioning - ESPHome uses its own improv implementation "wifi_provisioning", # WiFi provisioning - ESPHome uses its own improv implementation
@@ -1293,6 +1287,18 @@ async def to_code(config):
# Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms # Increase freertos tick speed from 100Hz to 1kHz so that delay() resolution is 1ms
add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000) add_idf_sdkconfig_option("CONFIG_FREERTOS_HZ", 1000)
# Reduce FreeRTOS max priorities from 25 to 16 to save RAM
# pxReadyTasksLists uses 20 bytes per priority level, so this saves 180 bytes
# All ESPHome tasks use relative priorities (configMAX_PRIORITIES - X) to scale automatically
# See https://github.com/espressif/esp-idf/issues/13041 for context
add_idf_sdkconfig_option("CONFIG_FREERTOS_MAX_PRIORITIES", 16)
# Set LWIP TCP/IP task priority to fit within reduced priority range (0-15)
# Default is 18, which would be invalid with MAX_PRIORITIES=16
# Priority 8 maintains the original hierarchy: I2S speaker (10) > LWIP (8) > mixer (6)
# This ensures audio I/O tasks aren't blocked by network, while network isn't starved by mixing
add_idf_sdkconfig_option("CONFIG_LWIP_TCPIP_TASK_PRIO", 8)
# Place non-ISR FreeRTOS functions into flash instead of IRAM # Place non-ISR FreeRTOS functions into flash instead of IRAM
# This saves up to 8KB of IRAM. ISR-safe functions (FromISR variants) stay in IRAM. # This saves up to 8KB of IRAM. ISR-safe functions (FromISR variants) stay in IRAM.
# In ESP-IDF 6.0 this becomes the default and CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH # In ESP-IDF 6.0 this becomes the default and CONFIG_FREERTOS_PLACE_FUNCTIONS_INTO_FLASH

View File

@@ -3,6 +3,7 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/task_priorities.h"
#include "preferences.h" #include "preferences.h"
#include <esp_clk_tree.h> #include <esp_clk_tree.h>
#include <esp_cpu.h> #include <esp_cpu.h>
@@ -66,10 +67,14 @@ void loop_task(void *pv_params) {
extern "C" void app_main() { extern "C" void app_main() {
initArduino(); initArduino();
esp32::setup_preferences(); esp32::setup_preferences();
// TASK_PRIORITY_APPLICATION: baseline priority for main loop - all component loops
// run here. Higher priority tasks (audio, network) preempt this when needed.
#if CONFIG_FREERTOS_UNICORE #if CONFIG_FREERTOS_UNICORE
xTaskCreate(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle); xTaskCreate(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, TASK_PRIORITY_APPLICATION,
&loop_task_handle);
#else #else
xTaskCreatePinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, 1, &loop_task_handle, 1); xTaskCreatePinnedToCore(loop_task, "loopTask", ESPHOME_LOOP_TASK_STACK_SIZE, nullptr, TASK_PRIORITY_APPLICATION,
&loop_task_handle, 1);
#endif #endif
} }

View File

@@ -203,11 +203,10 @@ class ESP32Preferences : public ESPPreferences {
} }
}; };
static ESP32Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void setup_preferences() { void setup_preferences() {
s_preferences.open(); auto *prefs = new ESP32Preferences(); // NOLINT(cppcoreguidelines-owning-memory)
global_preferences = &s_preferences; prefs->open();
global_preferences = prefs;
} }
} // namespace esp32 } // namespace esp32

View File

@@ -4,6 +4,7 @@
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/task_priorities.h"
#include <freertos/task.h> #include <freertos/task.h>
@@ -42,11 +43,13 @@ void ESP32Camera::setup() {
/* initialize RTOS */ /* initialize RTOS */
this->framebuffer_get_queue_ = xQueueCreate(1, sizeof(camera_fb_t *)); this->framebuffer_get_queue_ = xQueueCreate(1, sizeof(camera_fb_t *));
this->framebuffer_return_queue_ = xQueueCreate(1, sizeof(camera_fb_t *)); this->framebuffer_return_queue_ = xQueueCreate(1, sizeof(camera_fb_t *));
// TASK_PRIORITY_APPLICATION: same as main loop - camera capture is buffered,
// not real-time critical like audio
xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task, xTaskCreatePinnedToCore(&ESP32Camera::framebuffer_task,
"framebuffer_task", // name "framebuffer_task", // name
FRAMEBUFFER_TASK_STACK_SIZE, // stack size FRAMEBUFFER_TASK_STACK_SIZE, // stack size
this, // task pv params this, // task pv params
1, // priority TASK_PRIORITY_APPLICATION, // priority
nullptr, // handle nullptr, // handle
1 // core 1 // core
); );

View File

@@ -15,7 +15,6 @@ from esphome.components.esp32 import (
VARIANT_ESP32S2, VARIANT_ESP32S2,
VARIANT_ESP32S3, VARIANT_ESP32S3,
get_esp32_variant, get_esp32_variant,
include_builtin_idf_component,
) )
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import ( from esphome.const import (
@@ -122,10 +121,6 @@ def get_default_tx_enqueue_timeout(bit_rate):
async def to_code(config): async def to_code(config):
# Legacy driver component provides driver/twai.h header
include_builtin_idf_component("driver")
# Also enable esp_driver_twai for future migration to new API
include_builtin_idf_component("esp_driver_twai")
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await canbus.register_canbus(var, config) await canbus.register_canbus(var, config)

View File

@@ -1,12 +1,7 @@
from esphome import pins from esphome import pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import output from esphome.components import output
from esphome.components.esp32 import ( from esphome.components.esp32 import VARIANT_ESP32, VARIANT_ESP32S2, get_esp32_variant
VARIANT_ESP32,
VARIANT_ESP32S2,
get_esp32_variant,
include_builtin_idf_component,
)
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_NUMBER, CONF_PIN from esphome.const import CONF_ID, CONF_NUMBER, CONF_PIN
@@ -43,7 +38,6 @@ CONFIG_SCHEMA = output.FLOAT_OUTPUT_SCHEMA.extend(
async def to_code(config): async def to_code(config):
include_builtin_idf_component("esp_driver_dac")
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)
await output.register_output(var, config) await output.register_output(var, config)

View File

@@ -34,29 +34,14 @@ static const char *const ESP_HOSTED_VERSION_STR = STRINGIFY(ESP_HOSTED_VERSION_M
ESP_HOSTED_VERSION_MINOR_1) "." STRINGIFY(ESP_HOSTED_VERSION_PATCH_1); ESP_HOSTED_VERSION_MINOR_1) "." STRINGIFY(ESP_HOSTED_VERSION_PATCH_1);
#ifdef USE_ESP32_HOSTED_HTTP_UPDATE #ifdef USE_ESP32_HOSTED_HTTP_UPDATE
// Parse an integer from str, advancing ptr past the number
// Returns false if no digits were parsed
static bool parse_int(const char *&ptr, int &value) {
char *end;
value = static_cast<int>(strtol(ptr, &end, 10));
if (end == ptr)
return false;
ptr = end;
return true;
}
// Parse version string "major.minor.patch" into components // Parse version string "major.minor.patch" into components
// Returns true if at least major.minor was parsed // Returns true if parsing succeeded
static bool parse_version(const std::string &version_str, int &major, int &minor, int &patch) { static bool parse_version(const std::string &version_str, int &major, int &minor, int &patch) {
major = minor = patch = 0; major = minor = patch = 0;
const char *ptr = version_str.c_str(); if (sscanf(version_str.c_str(), "%d.%d.%d", &major, &minor, &patch) >= 2) {
return true;
if (!parse_int(ptr, major) || *ptr++ != '.' || !parse_int(ptr, minor)) }
return false; return false;
if (*ptr == '.')
parse_int(++ptr, patch);
return true;
} }
// Compare two versions, returns: // Compare two versions, returns:
@@ -211,14 +196,11 @@ bool Esp32HostedUpdate::fetch_manifest_() {
int read_or_error = container->read(buf, sizeof(buf)); int read_or_error = container->read(buf, sizeof(buf));
App.feed_wdt(); App.feed_wdt();
yield(); yield();
auto result = auto result = http_request::http_read_loop_result(read_or_error, last_data_time, read_timeout);
http_request::http_read_loop_result(read_or_error, last_data_time, read_timeout, container->is_read_complete());
if (result == http_request::HttpReadLoopResult::RETRY) if (result == http_request::HttpReadLoopResult::RETRY)
continue; continue;
// Note: COMPLETE is currently unreachable since the loop condition checks bytes_read < content_length,
// but this is defensive code in case chunked transfer encoding support is added in the future.
if (result != http_request::HttpReadLoopResult::DATA) if (result != http_request::HttpReadLoopResult::DATA)
break; // COMPLETE, ERROR, or TIMEOUT break; // ERROR or TIMEOUT
json_str.append(reinterpret_cast<char *>(buf), read_or_error); json_str.append(reinterpret_cast<char *>(buf), read_or_error);
} }
container->end(); container->end();
@@ -339,14 +321,9 @@ bool Esp32HostedUpdate::stream_firmware_to_coprocessor_() {
App.feed_wdt(); App.feed_wdt();
yield(); yield();
auto result = auto result = http_request::http_read_loop_result(read_or_error, last_data_time, read_timeout);
http_request::http_read_loop_result(read_or_error, last_data_time, read_timeout, container->is_read_complete());
if (result == http_request::HttpReadLoopResult::RETRY) if (result == http_request::HttpReadLoopResult::RETRY)
continue; continue;
// Note: COMPLETE is currently unreachable since the loop condition checks bytes_read < content_length,
// but this is defensive code in case chunked transfer encoding support is added in the future.
if (result == http_request::HttpReadLoopResult::COMPLETE)
break;
if (result != http_request::HttpReadLoopResult::DATA) { if (result != http_request::HttpReadLoopResult::DATA) {
if (result == http_request::HttpReadLoopResult::TIMEOUT) { if (result == http_request::HttpReadLoopResult::TIMEOUT) {
ESP_LOGE(TAG, "Timeout reading firmware data"); ESP_LOGE(TAG, "Timeout reading firmware data");

View File

@@ -269,8 +269,6 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config): async def to_code(config):
# Re-enable ESP-IDF's touch sensor driver (excluded by default to save compile time) # Re-enable ESP-IDF's touch sensor driver (excluded by default to save compile time)
include_builtin_idf_component("esp_driver_touch_sens") include_builtin_idf_component("esp_driver_touch_sens")
# Legacy driver component provides driver/touch_sensor.h header
include_builtin_idf_component("driver")
touch = cg.new_Pvariable(config[CONF_ID]) touch = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(touch, config) await cg.register_component(touch, config)

View File

@@ -17,6 +17,10 @@ namespace esphome::esp8266 {
static const char *const TAG = "esp8266.preferences"; static const char *const TAG = "esp8266.preferences";
static uint32_t *s_flash_storage = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200; static constexpr uint32_t ESP_RTC_USER_MEM_START = 0x60001200;
static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128; static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_WORDS = 128;
static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4; static constexpr uint32_t ESP_RTC_USER_MEM_SIZE_BYTES = ESP_RTC_USER_MEM_SIZE_WORDS * 4;
@@ -39,11 +43,6 @@ static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = 128;
static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = 64; static constexpr uint32_t ESP8266_FLASH_STORAGE_SIZE = 64;
#endif #endif
static uint32_t
s_flash_storage[ESP8266_FLASH_STORAGE_SIZE]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) { static inline bool esp_rtc_user_mem_read(uint32_t index, uint32_t *dest) {
if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) { if (index >= ESP_RTC_USER_MEM_SIZE_WORDS) {
return false; return false;
@@ -181,6 +180,7 @@ class ESP8266Preferences : public ESPPreferences {
uint32_t current_flash_offset = 0; // in words uint32_t current_flash_offset = 0; // in words
void setup() { void setup() {
s_flash_storage = new uint32_t[ESP8266_FLASH_STORAGE_SIZE]; // NOLINT
ESP_LOGVV(TAG, "Loading preferences from flash"); ESP_LOGVV(TAG, "Loading preferences from flash");
{ {
@@ -283,11 +283,10 @@ class ESP8266Preferences : public ESPPreferences {
} }
}; };
static ESP8266Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void setup_preferences() { void setup_preferences() {
s_preferences.setup(); auto *pref = new ESP8266Preferences(); // NOLINT(cppcoreguidelines-owning-memory)
global_preferences = &s_preferences; pref->setup();
global_preferences = pref;
} }
void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; } void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; }

View File

@@ -1,6 +1,6 @@
import logging import logging
from esphome import automation, pins from esphome import pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components.esp32 import ( from esphome.components.esp32 import (
VARIANT_ESP32, VARIANT_ESP32,
@@ -35,8 +35,6 @@ from esphome.const import (
CONF_MODE, CONF_MODE,
CONF_MOSI_PIN, CONF_MOSI_PIN,
CONF_NUMBER, CONF_NUMBER,
CONF_ON_CONNECT,
CONF_ON_DISCONNECT,
CONF_PAGE_ID, CONF_PAGE_ID,
CONF_PIN, CONF_PIN,
CONF_POLLING_INTERVAL, CONF_POLLING_INTERVAL,
@@ -239,8 +237,6 @@ BASE_SCHEMA = cv.Schema(
cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name,
cv.Optional(CONF_USE_ADDRESS): cv.string_strict, cv.Optional(CONF_USE_ADDRESS): cv.string_strict,
cv.Optional(CONF_MAC_ADDRESS): cv.mac_address, cv.Optional(CONF_MAC_ADDRESS): cv.mac_address,
cv.Optional(CONF_ON_CONNECT): automation.validate_automation(single=True),
cv.Optional(CONF_ON_DISCONNECT): automation.validate_automation(single=True),
} }
).extend(cv.COMPONENT_SCHEMA) ).extend(cv.COMPONENT_SCHEMA)
@@ -434,18 +430,6 @@ async def to_code(config):
if CORE.using_arduino: if CORE.using_arduino:
cg.add_library("WiFi", None) cg.add_library("WiFi", None)
if on_connect_config := config.get(CONF_ON_CONNECT):
cg.add_define("USE_ETHERNET_CONNECT_TRIGGER")
await automation.build_automation(
var.get_connect_trigger(), [], on_connect_config
)
if on_disconnect_config := config.get(CONF_ON_DISCONNECT):
cg.add_define("USE_ETHERNET_DISCONNECT_TRIGGER")
await automation.build_automation(
var.get_disconnect_trigger(), [], on_disconnect_config
)
CORE.add_job(final_step) CORE.add_job(final_step)

View File

@@ -309,9 +309,6 @@ void EthernetComponent::loop() {
this->dump_connect_params_(); this->dump_connect_params_();
this->status_clear_warning(); this->status_clear_warning();
#ifdef USE_ETHERNET_CONNECT_TRIGGER
this->connect_trigger_.trigger();
#endif
} else if (now - this->connect_begin_ > 15000) { } else if (now - this->connect_begin_ > 15000) {
ESP_LOGW(TAG, "Connecting failed; reconnecting"); ESP_LOGW(TAG, "Connecting failed; reconnecting");
this->start_connect_(); this->start_connect_();
@@ -321,16 +318,10 @@ void EthernetComponent::loop() {
if (!this->started_) { if (!this->started_) {
ESP_LOGI(TAG, "Stopped connection"); ESP_LOGI(TAG, "Stopped connection");
this->state_ = EthernetComponentState::STOPPED; this->state_ = EthernetComponentState::STOPPED;
#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
this->disconnect_trigger_.trigger();
#endif
} else if (!this->connected_) { } else if (!this->connected_) {
ESP_LOGW(TAG, "Connection lost; reconnecting"); ESP_LOGW(TAG, "Connection lost; reconnecting");
this->state_ = EthernetComponentState::CONNECTING; this->state_ = EthernetComponentState::CONNECTING;
this->start_connect_(); this->start_connect_();
#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
this->disconnect_trigger_.trigger();
#endif
} else { } else {
this->finish_connect_(); this->finish_connect_();
// When connected and stable, disable the loop to save CPU cycles // When connected and stable, disable the loop to save CPU cycles

View File

@@ -4,7 +4,6 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/automation.h"
#include "esphome/components/network/ip_address.h" #include "esphome/components/network/ip_address.h"
#ifdef USE_ESP32 #ifdef USE_ESP32
@@ -120,12 +119,6 @@ class EthernetComponent : public Component {
void add_ip_state_listener(EthernetIPStateListener *listener) { this->ip_state_listeners_.push_back(listener); } void add_ip_state_listener(EthernetIPStateListener *listener) { this->ip_state_listeners_.push_back(listener); }
#endif #endif
#ifdef USE_ETHERNET_CONNECT_TRIGGER
Trigger<> *get_connect_trigger() { return &this->connect_trigger_; }
#endif
#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
Trigger<> *get_disconnect_trigger() { return &this->disconnect_trigger_; }
#endif
protected: protected:
static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); static void eth_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
static void got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data); static void got_ip_event_handler(void *arg, esp_event_base_t event_base, int32_t event_id, void *event_data);
@@ -197,12 +190,6 @@ class EthernetComponent : public Component {
StaticVector<EthernetIPStateListener *, ESPHOME_ETHERNET_IP_STATE_LISTENERS> ip_state_listeners_; StaticVector<EthernetIPStateListener *, ESPHOME_ETHERNET_IP_STATE_LISTENERS> ip_state_listeners_;
#endif #endif
#ifdef USE_ETHERNET_CONNECT_TRIGGER
Trigger<> connect_trigger_;
#endif
#ifdef USE_ETHERNET_DISCONNECT_TRIGGER
Trigger<> disconnect_trigger_;
#endif
private: private:
// Stores a pointer to a string literal (static storage duration). // Stores a pointer to a string literal (static storage duration).
// ONLY set from Python-generated code with string literals - never dynamic strings. // ONLY set from Python-generated code with string literals - never dynamic strings.

View File

@@ -335,18 +335,18 @@ void FeedbackCover::start_direction_(CoverOperation dir) {
switch (dir) { switch (dir) {
case COVER_OPERATION_IDLE: case COVER_OPERATION_IDLE:
trig = &this->stop_trigger_; trig = this->stop_trigger_;
break; break;
case COVER_OPERATION_OPENING: case COVER_OPERATION_OPENING:
this->last_operation_ = dir; this->last_operation_ = dir;
trig = &this->open_trigger_; trig = this->open_trigger_;
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
obstacle = this->open_obstacle_; obstacle = this->open_obstacle_;
#endif #endif
break; break;
case COVER_OPERATION_CLOSING: case COVER_OPERATION_CLOSING:
this->last_operation_ = dir; this->last_operation_ = dir;
trig = &this->close_trigger_; trig = this->close_trigger_;
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
obstacle = this->close_obstacle_; obstacle = this->close_obstacle_;
#endif #endif

View File

@@ -17,9 +17,9 @@ class FeedbackCover : public cover::Cover, public Component {
void loop() override; void loop() override;
void dump_config() override; void dump_config() override;
Trigger<> *get_open_trigger() { return &this->open_trigger_; } Trigger<> *get_open_trigger() const { return this->open_trigger_; }
Trigger<> *get_close_trigger() { return &this->close_trigger_; } Trigger<> *get_close_trigger() const { return this->close_trigger_; }
Trigger<> *get_stop_trigger() { return &this->stop_trigger_; } Trigger<> *get_stop_trigger() const { return this->stop_trigger_; }
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
void set_open_endstop(binary_sensor::BinarySensor *open_endstop); void set_open_endstop(binary_sensor::BinarySensor *open_endstop);
@@ -61,9 +61,9 @@ class FeedbackCover : public cover::Cover, public Component {
binary_sensor::BinarySensor *close_obstacle_{nullptr}; binary_sensor::BinarySensor *close_obstacle_{nullptr};
#endif #endif
Trigger<> open_trigger_; Trigger<> *open_trigger_{new Trigger<>()};
Trigger<> close_trigger_; Trigger<> *close_trigger_{new Trigger<>()};
Trigger<> stop_trigger_; Trigger<> *stop_trigger_{new Trigger<>()};
uint32_t open_duration_{0}; uint32_t open_duration_{0};
uint32_t close_duration_{0}; uint32_t close_duration_{0};

View File

@@ -66,11 +66,10 @@ ESPPreferenceObject HostPreferences::make_preference(size_t length, uint32_t typ
return ESPPreferenceObject(backend); return ESPPreferenceObject(backend);
}; };
static HostPreferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void setup_preferences() { void setup_preferences() {
host_preferences = &s_preferences; auto *pref = new HostPreferences(); // NOLINT(cppcoreguidelines-owning-memory)
global_preferences = &s_preferences; host_preferences = pref;
global_preferences = pref;
} }
bool HostPreferenceBackend::save(const uint8_t *data, size_t len) { bool HostPreferenceBackend::save(const uint8_t *data, size_t len) {

View File

@@ -26,7 +26,6 @@ struct Header {
enum HttpStatus { enum HttpStatus {
HTTP_STATUS_OK = 200, HTTP_STATUS_OK = 200,
HTTP_STATUS_NO_CONTENT = 204, HTTP_STATUS_NO_CONTENT = 204,
HTTP_STATUS_RESET_CONTENT = 205,
HTTP_STATUS_PARTIAL_CONTENT = 206, HTTP_STATUS_PARTIAL_CONTENT = 206,
/* 3xx - Redirection */ /* 3xx - Redirection */
@@ -127,21 +126,19 @@ struct HttpReadResult {
/// Result of processing a non-blocking read with timeout (for manual loops) /// Result of processing a non-blocking read with timeout (for manual loops)
enum class HttpReadLoopResult : uint8_t { enum class HttpReadLoopResult : uint8_t {
DATA, ///< Data was read, process it DATA, ///< Data was read, process it
COMPLETE, ///< All content has been read, caller should exit loop RETRY, ///< No data yet, already delayed, caller should continue loop
RETRY, ///< No data yet, already delayed, caller should continue loop ERROR, ///< Read error, caller should exit loop
ERROR, ///< Read error, caller should exit loop TIMEOUT, ///< Timeout waiting for data, caller should exit loop
TIMEOUT, ///< Timeout waiting for data, caller should exit loop
}; };
/// Process a read result with timeout tracking and delay handling /// Process a read result with timeout tracking and delay handling
/// @param bytes_read_or_error Return value from read() - positive for bytes read, negative for error /// @param bytes_read_or_error Return value from read() - positive for bytes read, negative for error
/// @param last_data_time Time of last successful read, updated when data received /// @param last_data_time Time of last successful read, updated when data received
/// @param timeout_ms Maximum time to wait for data /// @param timeout_ms Maximum time to wait for data
/// @param is_read_complete Whether all expected content has been read (from HttpContainer::is_read_complete()) /// @return DATA if data received, RETRY if should continue loop, ERROR/TIMEOUT if should exit
/// @return How the caller should proceed - see HttpReadLoopResult enum inline HttpReadLoopResult http_read_loop_result(int bytes_read_or_error, uint32_t &last_data_time,
inline HttpReadLoopResult http_read_loop_result(int bytes_read_or_error, uint32_t &last_data_time, uint32_t timeout_ms, uint32_t timeout_ms) {
bool is_read_complete) {
if (bytes_read_or_error > 0) { if (bytes_read_or_error > 0) {
last_data_time = millis(); last_data_time = millis();
return HttpReadLoopResult::DATA; return HttpReadLoopResult::DATA;
@@ -149,10 +146,7 @@ inline HttpReadLoopResult http_read_loop_result(int bytes_read_or_error, uint32_
if (bytes_read_or_error < 0) { if (bytes_read_or_error < 0) {
return HttpReadLoopResult::ERROR; return HttpReadLoopResult::ERROR;
} }
// bytes_read_or_error == 0: either "no data yet" or "all content read" // bytes_read_or_error == 0: no data available yet
if (is_read_complete) {
return HttpReadLoopResult::COMPLETE;
}
if (millis() - last_data_time >= timeout_ms) { if (millis() - last_data_time >= timeout_ms) {
return HttpReadLoopResult::TIMEOUT; return HttpReadLoopResult::TIMEOUT;
} }
@@ -165,9 +159,9 @@ class HttpRequestComponent;
class HttpContainer : public Parented<HttpRequestComponent> { class HttpContainer : public Parented<HttpRequestComponent> {
public: public:
virtual ~HttpContainer() = default; virtual ~HttpContainer() = default;
size_t content_length{0}; size_t content_length;
int status_code{-1}; ///< -1 indicates no response received yet int status_code;
uint32_t duration_ms{0}; uint32_t duration_ms;
/** /**
* @brief Read data from the HTTP response body. * @brief Read data from the HTTP response body.
@@ -200,24 +194,9 @@ class HttpContainer : public Parented<HttpRequestComponent> {
virtual void end() = 0; virtual void end() = 0;
void set_secure(bool secure) { this->secure_ = secure; } void set_secure(bool secure) { this->secure_ = secure; }
void set_chunked(bool chunked) { this->is_chunked_ = chunked; }
size_t get_bytes_read() const { return this->bytes_read_; } size_t get_bytes_read() const { return this->bytes_read_; }
/// Check if all expected content has been read
/// For chunked responses, returns false (completion detected via read() returning error/EOF)
bool is_read_complete() const {
// Per RFC 9112, these responses have no body:
// - 1xx (Informational), 204 No Content, 205 Reset Content, 304 Not Modified
if ((this->status_code >= 100 && this->status_code < 200) || this->status_code == HTTP_STATUS_NO_CONTENT ||
this->status_code == HTTP_STATUS_RESET_CONTENT || this->status_code == HTTP_STATUS_NOT_MODIFIED) {
return true;
}
// For non-chunked responses, complete when bytes_read >= content_length
// This handles both Content-Length: 0 and Content-Length: N cases
return !this->is_chunked_ && this->bytes_read_ >= this->content_length;
}
/** /**
* @brief Get response headers. * @brief Get response headers.
* *
@@ -230,7 +209,6 @@ class HttpContainer : public Parented<HttpRequestComponent> {
protected: protected:
size_t bytes_read_{0}; size_t bytes_read_{0};
bool secure_{false}; bool secure_{false};
bool is_chunked_{false}; ///< True if response uses chunked transfer encoding
std::map<std::string, std::list<std::string>> response_headers_{}; std::map<std::string, std::list<std::string>> response_headers_{};
}; };
@@ -241,7 +219,7 @@ class HttpContainer : public Parented<HttpRequestComponent> {
/// @param total_size Total bytes to read /// @param total_size Total bytes to read
/// @param chunk_size Maximum bytes per read call /// @param chunk_size Maximum bytes per read call
/// @param timeout_ms Read timeout in milliseconds /// @param timeout_ms Read timeout in milliseconds
/// @return HttpReadResult with status and error_code on failure; use container->get_bytes_read() for total bytes read /// @return HttpReadResult with status and error_code on failure
inline HttpReadResult http_read_fully(HttpContainer *container, uint8_t *buffer, size_t total_size, size_t chunk_size, inline HttpReadResult http_read_fully(HttpContainer *container, uint8_t *buffer, size_t total_size, size_t chunk_size,
uint32_t timeout_ms) { uint32_t timeout_ms) {
size_t read_index = 0; size_t read_index = 0;
@@ -253,11 +231,9 @@ inline HttpReadResult http_read_fully(HttpContainer *container, uint8_t *buffer,
App.feed_wdt(); App.feed_wdt();
yield(); yield();
auto result = http_read_loop_result(read_bytes_or_error, last_data_time, timeout_ms, container->is_read_complete()); auto result = http_read_loop_result(read_bytes_or_error, last_data_time, timeout_ms);
if (result == HttpReadLoopResult::RETRY) if (result == HttpReadLoopResult::RETRY)
continue; continue;
if (result == HttpReadLoopResult::COMPLETE)
break; // Server sent less data than requested, but transfer is complete
if (result == HttpReadLoopResult::ERROR) if (result == HttpReadLoopResult::ERROR)
return {HttpReadStatus::ERROR, read_bytes_or_error}; return {HttpReadStatus::ERROR, read_bytes_or_error};
if (result == HttpReadLoopResult::TIMEOUT) if (result == HttpReadLoopResult::TIMEOUT)
@@ -356,13 +332,13 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
void set_json(std::function<void(Ts..., JsonObject)> json_func) { this->json_func_ = json_func; } void set_json(std::function<void(Ts..., JsonObject)> json_func) { this->json_func_ = json_func; }
#ifdef USE_HTTP_REQUEST_RESPONSE #ifdef USE_HTTP_REQUEST_RESPONSE
Trigger<std::shared_ptr<HttpContainer>, std::string &, Ts...> *get_success_trigger_with_response() { Trigger<std::shared_ptr<HttpContainer>, std::string &, Ts...> *get_success_trigger_with_response() const {
return &this->success_trigger_with_response_; return this->success_trigger_with_response_;
} }
#endif #endif
Trigger<std::shared_ptr<HttpContainer>, Ts...> *get_success_trigger() { return &this->success_trigger_; } Trigger<std::shared_ptr<HttpContainer>, Ts...> *get_success_trigger() const { return this->success_trigger_; }
Trigger<Ts...> *get_error_trigger() { return &this->error_trigger_; } Trigger<Ts...> *get_error_trigger() const { return this->error_trigger_; }
void set_max_response_buffer_size(size_t max_response_buffer_size) { void set_max_response_buffer_size(size_t max_response_buffer_size) {
this->max_response_buffer_size_ = max_response_buffer_size; this->max_response_buffer_size_ = max_response_buffer_size;
@@ -396,7 +372,7 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
auto captured_args = std::make_tuple(x...); auto captured_args = std::make_tuple(x...);
if (container == nullptr) { if (container == nullptr) {
std::apply([this](Ts... captured_args_inner) { this->error_trigger_.trigger(captured_args_inner...); }, std::apply([this](Ts... captured_args_inner) { this->error_trigger_->trigger(captured_args_inner...); },
captured_args); captured_args);
return; return;
} }
@@ -417,12 +393,11 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
int read_or_error = container->read(buf + read_index, std::min<size_t>(max_length - read_index, 512)); int read_or_error = container->read(buf + read_index, std::min<size_t>(max_length - read_index, 512));
App.feed_wdt(); App.feed_wdt();
yield(); yield();
auto result = auto result = http_read_loop_result(read_or_error, last_data_time, read_timeout);
http_read_loop_result(read_or_error, last_data_time, read_timeout, container->is_read_complete());
if (result == HttpReadLoopResult::RETRY) if (result == HttpReadLoopResult::RETRY)
continue; continue;
if (result != HttpReadLoopResult::DATA) if (result != HttpReadLoopResult::DATA)
break; // COMPLETE, ERROR, or TIMEOUT break; // ERROR or TIMEOUT
read_index += read_or_error; read_index += read_or_error;
} }
response_body.reserve(read_index); response_body.reserve(read_index);
@@ -431,14 +406,14 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
} }
std::apply( std::apply(
[this, &container, &response_body](Ts... captured_args_inner) { [this, &container, &response_body](Ts... captured_args_inner) {
this->success_trigger_with_response_.trigger(container, response_body, captured_args_inner...); this->success_trigger_with_response_->trigger(container, response_body, captured_args_inner...);
}, },
captured_args); captured_args);
} else } else
#endif #endif
{ {
std::apply([this, &container]( std::apply([this, &container](
Ts... captured_args_inner) { this->success_trigger_.trigger(container, captured_args_inner...); }, Ts... captured_args_inner) { this->success_trigger_->trigger(container, captured_args_inner...); },
captured_args); captured_args);
} }
container->end(); container->end();
@@ -458,10 +433,12 @@ template<typename... Ts> class HttpRequestSendAction : public Action<Ts...> {
std::map<const char *, TemplatableValue<std::string, Ts...>> json_{}; std::map<const char *, TemplatableValue<std::string, Ts...>> json_{};
std::function<void(Ts..., JsonObject)> json_func_{nullptr}; std::function<void(Ts..., JsonObject)> json_func_{nullptr};
#ifdef USE_HTTP_REQUEST_RESPONSE #ifdef USE_HTTP_REQUEST_RESPONSE
Trigger<std::shared_ptr<HttpContainer>, std::string &, Ts...> success_trigger_with_response_; Trigger<std::shared_ptr<HttpContainer>, std::string &, Ts...> *success_trigger_with_response_ =
new Trigger<std::shared_ptr<HttpContainer>, std::string &, Ts...>();
#endif #endif
Trigger<std::shared_ptr<HttpContainer>, Ts...> success_trigger_; Trigger<std::shared_ptr<HttpContainer>, Ts...> *success_trigger_ =
Trigger<Ts...> error_trigger_; new Trigger<std::shared_ptr<HttpContainer>, Ts...>();
Trigger<Ts...> *error_trigger_ = new Trigger<Ts...>();
size_t max_response_buffer_size_{SIZE_MAX}; size_t max_response_buffer_size_{SIZE_MAX};
}; };

View File

@@ -135,23 +135,9 @@ std::shared_ptr<HttpContainer> HttpRequestArduino::perform(const std::string &ur
// When cast to size_t, -1 becomes SIZE_MAX (4294967295 on 32-bit). // When cast to size_t, -1 becomes SIZE_MAX (4294967295 on 32-bit).
// The read() method handles this: bytes_read_ can never reach SIZE_MAX, so the // The read() method handles this: bytes_read_ can never reach SIZE_MAX, so the
// early return check (bytes_read_ >= content_length) will never trigger. // early return check (bytes_read_ >= content_length) will never trigger.
//
// TODO: Chunked transfer encoding is NOT properly supported on Arduino.
// The implementation in #7884 was incomplete - it only works correctly on ESP-IDF where
// esp_http_client_read() decodes chunks internally. On Arduino, using getStreamPtr()
// returns raw TCP data with chunk framing (e.g., "12a\r\n{json}\r\n0\r\n\r\n") instead
// of decoded content. This wasn't noticed because requests would complete and payloads
// were only examined on IDF. The long transfer times were also masked by the misleading
// "HTTP on Arduino version >= 3.1 is **very** slow" warning above. This causes two issues:
// 1. Response body is corrupted - contains chunk size headers mixed with data
// 2. Cannot detect end of transfer - connection stays open (keep-alive), causing timeout
// The proper fix would be to use getString() for chunked responses, which decodes chunks
// internally, but this buffers the entire response in memory.
int content_length = container->client_.getSize(); int content_length = container->client_.getSize();
ESP_LOGD(TAG, "Content-Length: %d", content_length); ESP_LOGD(TAG, "Content-Length: %d", content_length);
container->content_length = (size_t) content_length; container->content_length = (size_t) content_length;
// -1 (SIZE_MAX when cast to size_t) means chunked transfer encoding
container->set_chunked(content_length == -1);
container->duration_ms = millis() - start; container->duration_ms = millis() - start;
return container; return container;
@@ -192,9 +178,9 @@ int HttpContainerArduino::read(uint8_t *buf, size_t max_len) {
if (bufsize == 0) { if (bufsize == 0) {
this->duration_ms += (millis() - start); this->duration_ms += (millis() - start);
// Check if we've read all expected content (non-chunked only) // Check if we've read all expected content (only valid when content_length is known and not SIZE_MAX)
// For chunked encoding (content_length == SIZE_MAX), is_read_complete() returns false // For chunked encoding (content_length == SIZE_MAX), we can't use this check
if (this->is_read_complete()) { if (this->content_length > 0 && this->bytes_read_ >= this->content_length) {
return 0; // All content read successfully return 0; // All content read successfully
} }
// No data available - check if connection is still open // No data available - check if connection is still open

View File

@@ -160,7 +160,6 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
// esp_http_client_fetch_headers() returns 0 for chunked transfer encoding (no Content-Length header). // esp_http_client_fetch_headers() returns 0 for chunked transfer encoding (no Content-Length header).
// The read() method handles content_length == 0 specially to support chunked responses. // The read() method handles content_length == 0 specially to support chunked responses.
container->content_length = esp_http_client_fetch_headers(client); container->content_length = esp_http_client_fetch_headers(client);
container->set_chunked(esp_http_client_is_chunked_response(client));
container->feed_wdt(); container->feed_wdt();
container->status_code = esp_http_client_get_status_code(client); container->status_code = esp_http_client_get_status_code(client);
container->feed_wdt(); container->feed_wdt();
@@ -196,7 +195,6 @@ std::shared_ptr<HttpContainer> HttpRequestIDF::perform(const std::string &url, c
container->feed_wdt(); container->feed_wdt();
container->content_length = esp_http_client_fetch_headers(client); container->content_length = esp_http_client_fetch_headers(client);
container->set_chunked(esp_http_client_is_chunked_response(client));
container->feed_wdt(); container->feed_wdt();
container->status_code = esp_http_client_get_status_code(client); container->status_code = esp_http_client_get_status_code(client);
container->feed_wdt(); container->feed_wdt();
@@ -241,9 +239,10 @@ int HttpContainerIDF::read(uint8_t *buf, size_t max_len) {
const uint32_t start = millis(); const uint32_t start = millis();
watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout()); watchdog::WatchdogManager wdm(this->parent_->get_watchdog_timeout());
// Check if we've already read all expected content (non-chunked only) // Check if we've already read all expected content
// For chunked responses (content_length == 0), esp_http_client_read() handles EOF // Skip this check when content_length is 0 (chunked transfer encoding or unknown length)
if (this->is_read_complete()) { // For chunked responses, esp_http_client_read() will return 0 when all data is received
if (this->content_length > 0 && this->bytes_read_ >= this->content_length) {
return 0; // All content read successfully return 0; // All content read successfully
} }

View File

@@ -130,13 +130,9 @@ uint8_t OtaHttpRequestComponent::do_ota_() {
App.feed_wdt(); App.feed_wdt();
yield(); yield();
auto result = http_read_loop_result(bufsize_or_error, last_data_time, read_timeout, container->is_read_complete()); auto result = http_read_loop_result(bufsize_or_error, last_data_time, read_timeout);
if (result == HttpReadLoopResult::RETRY) if (result == HttpReadLoopResult::RETRY)
continue; continue;
// Note: COMPLETE is currently unreachable since the loop condition checks bytes_read < content_length,
// but this is defensive code in case chunked transfer encoding support is added for OTA in the future.
if (result == HttpReadLoopResult::COMPLETE)
break;
if (result != HttpReadLoopResult::DATA) { if (result != HttpReadLoopResult::DATA) {
if (result == HttpReadLoopResult::TIMEOUT) { if (result == HttpReadLoopResult::TIMEOUT) {
ESP_LOGE(TAG, "Timeout reading data"); ESP_LOGE(TAG, "Timeout reading data");

View File

@@ -2,6 +2,9 @@
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/version.h" #include "esphome/core/version.h"
#ifdef USE_ESP32
#include "esphome/core/task_priorities.h"
#endif
#include "esphome/components/json/json_util.h" #include "esphome/components/json/json_util.h"
#include "esphome/components/network/util.h" #include "esphome/components/network/util.h"
@@ -46,7 +49,9 @@ void HttpRequestUpdate::update() {
return; return;
} }
#ifdef USE_ESP32 #ifdef USE_ESP32
xTaskCreate(HttpRequestUpdate::update_task, "update_task", 8192, (void *) this, 1, &this->update_task_handle_); // TASK_PRIORITY_APPLICATION: same as main loop - update check is background work
xTaskCreate(HttpRequestUpdate::update_task, "update_task", 8192, (void *) this, TASK_PRIORITY_APPLICATION,
&this->update_task_handle_);
#else #else
this->update_task(this); this->update_task(this);
#endif #endif

View File

@@ -11,6 +11,7 @@
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/task_priorities.h"
#include "esphome/components/audio/audio.h" #include "esphome/components/audio/audio.h"
@@ -22,7 +23,6 @@ static const UBaseType_t MAX_LISTENERS = 16;
static const uint32_t READ_DURATION_MS = 16; static const uint32_t READ_DURATION_MS = 16;
static const size_t TASK_STACK_SIZE = 4096; static const size_t TASK_STACK_SIZE = 4096;
static const ssize_t TASK_PRIORITY = 23;
static const char *const TAG = "i2s_audio.microphone"; static const char *const TAG = "i2s_audio.microphone";
@@ -520,8 +520,10 @@ void I2SAudioMicrophone::loop() {
} }
if (this->task_handle_ == nullptr) { if (this->task_handle_ == nullptr) {
xTaskCreate(I2SAudioMicrophone::mic_task, "mic_task", TASK_STACK_SIZE, (void *) this, TASK_PRIORITY, // TASK_PRIORITY_AUDIO_CAPTURE: highest application priority - real-time audio
&this->task_handle_); // input cannot tolerate delays without dropping samples
xTaskCreate(I2SAudioMicrophone::mic_task, "mic_task", TASK_STACK_SIZE, (void *) this,
TASK_PRIORITY_AUDIO_CAPTURE, &this->task_handle_);
if (this->task_handle_ == nullptr) { if (this->task_handle_ == nullptr) {
ESP_LOGE(TAG, "Task failed to start, retrying in 1 second"); ESP_LOGE(TAG, "Task failed to start, retrying in 1 second");

View File

@@ -14,6 +14,7 @@
#include "esphome/core/application.h" #include "esphome/core/application.h"
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/task_priorities.h"
#include "esp_timer.h" #include "esp_timer.h"
@@ -24,7 +25,6 @@ static const uint32_t DMA_BUFFER_DURATION_MS = 15;
static const size_t DMA_BUFFERS_COUNT = 4; static const size_t DMA_BUFFERS_COUNT = 4;
static const size_t TASK_STACK_SIZE = 4096; static const size_t TASK_STACK_SIZE = 4096;
static const ssize_t TASK_PRIORITY = 19;
static const size_t I2S_EVENT_QUEUE_COUNT = DMA_BUFFERS_COUNT + 1; static const size_t I2S_EVENT_QUEUE_COUNT = DMA_BUFFERS_COUNT + 1;
@@ -151,8 +151,10 @@ void I2SAudioSpeaker::loop() {
} }
if (this->speaker_task_handle_ == nullptr) { if (this->speaker_task_handle_ == nullptr) {
xTaskCreate(I2SAudioSpeaker::speaker_task, "speaker_task", TASK_STACK_SIZE, (void *) this, TASK_PRIORITY, // TASK_PRIORITY_AUDIO_OUTPUT: high priority for real-time audio output,
&this->speaker_task_handle_); // below capture (TASK_PRIORITY_AUDIO_CAPTURE) but above network tasks
xTaskCreate(I2SAudioSpeaker::speaker_task, "speaker_task", TASK_STACK_SIZE, (void *) this,
TASK_PRIORITY_AUDIO_OUTPUT, &this->speaker_task_handle_);
if (this->speaker_task_handle_ == nullptr) { if (this->speaker_task_handle_ == nullptr) {
ESP_LOGE(TAG, "Task failed to start, retrying in 1 second"); ESP_LOGE(TAG, "Task failed to start, retrying in 1 second");

View File

@@ -189,11 +189,10 @@ class LibreTinyPreferences : public ESPPreferences {
} }
}; };
static LibreTinyPreferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void setup_preferences() { void setup_preferences() {
s_preferences.open(); auto *prefs = new LibreTinyPreferences(); // NOLINT(cppcoreguidelines-owning-memory)
global_preferences = &s_preferences; prefs->open();
global_preferences = prefs;
} }
} // namespace libretiny } // namespace libretiny

View File

@@ -138,20 +138,20 @@ class LambdaLightEffect : public LightEffect {
class AutomationLightEffect : public LightEffect { class AutomationLightEffect : public LightEffect {
public: public:
AutomationLightEffect(const char *name) : LightEffect(name) {} AutomationLightEffect(const char *name) : LightEffect(name) {}
void stop() override { this->trig_.stop_action(); } void stop() override { this->trig_->stop_action(); }
void apply() override { void apply() override {
if (!this->trig_.is_action_running()) { if (!this->trig_->is_action_running()) {
this->trig_.trigger(); this->trig_->trigger();
} }
} }
Trigger<> *get_trig() { return &this->trig_; } Trigger<> *get_trig() const { return trig_; }
/// Get the current effect index for use in automations. /// Get the current effect index for use in automations.
/// Useful for automations that need to know which effect is running. /// Useful for automations that need to know which effect is running.
uint32_t get_current_index() const { return this->get_index(); } uint32_t get_current_index() const { return this->get_index(); }
protected: protected:
Trigger<> trig_; Trigger<> *trig_{new Trigger<>};
}; };
struct StrobeLightEffectColor { struct StrobeLightEffectColor {

View File

@@ -128,7 +128,22 @@ void HOT Logger::log_vprintf_(uint8_t level, const char *tag, int line, const ch
// Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266. // Note: USE_STORE_LOG_STR_IN_FLASH is only defined for ESP8266.
// //
// This function handles format strings stored in flash memory (PROGMEM) to save RAM. // This function handles format strings stored in flash memory (PROGMEM) to save RAM.
// Uses vsnprintf_P to read the format string directly from flash without copying to RAM. // The buffer is used in a special way to avoid allocating extra memory:
//
// Memory layout during execution:
// Step 1: Copy format string from flash to buffer
// tx_buffer_: [format_string][null][.....................]
// tx_buffer_at_: ------------------^
// msg_start: saved here -----------^
//
// Step 2: format_log_to_buffer_with_terminator_ reads format string from beginning
// and writes formatted output starting at msg_start position
// tx_buffer_: [format_string][null][formatted_message][null]
// tx_buffer_at_: -------------------------------------^
//
// Step 3: Output the formatted message (starting at msg_start)
// write_msg_ and callbacks receive: this->tx_buffer_ + msg_start
// which points to: [formatted_message][null]
// //
void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format, void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __FlashStringHelper *format,
va_list args) { // NOLINT va_list args) { // NOLINT
@@ -138,25 +153,35 @@ void Logger::log_vprintf_(uint8_t level, const char *tag, int line, const __Flas
RecursionGuard guard(global_recursion_guard_); RecursionGuard guard(global_recursion_guard_);
this->tx_buffer_at_ = 0; this->tx_buffer_at_ = 0;
// Write header, format body directly from flash, and write footer // Copy format string from progmem
this->write_header_to_buffer_(level, tag, line, nullptr, this->tx_buffer_, &this->tx_buffer_at_, auto *format_pgm_p = reinterpret_cast<const uint8_t *>(format);
this->tx_buffer_size_); char ch = '.';
this->format_body_to_buffer_P_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_, while (this->tx_buffer_at_ < this->tx_buffer_size_ && ch != '\0') {
reinterpret_cast<PGM_P>(format), args); this->tx_buffer_[this->tx_buffer_at_++] = ch = (char) progmem_read_byte(format_pgm_p++);
this->write_footer_to_buffer_(this->tx_buffer_, &this->tx_buffer_at_, this->tx_buffer_size_); }
// Ensure null termination // Buffer full from copying format - RAII guard handles cleanup on return
uint16_t null_pos = this->tx_buffer_at_ >= this->tx_buffer_size_ ? this->tx_buffer_size_ - 1 : this->tx_buffer_at_; if (this->tx_buffer_at_ >= this->tx_buffer_size_) {
this->tx_buffer_[null_pos] = '\0'; return;
}
// Save the offset before calling format_log_to_buffer_with_terminator_
// since it will increment tx_buffer_at_ to the end of the formatted string
uint16_t msg_start = this->tx_buffer_at_;
this->format_log_to_buffer_with_terminator_(level, tag, line, this->tx_buffer_, args, this->tx_buffer_,
&this->tx_buffer_at_, this->tx_buffer_size_);
uint16_t msg_length =
this->tx_buffer_at_ - msg_start; // Don't subtract 1 - tx_buffer_at_ is already at the null terminator position
// Listeners get message first (before console write) // Listeners get message first (before console write)
#ifdef USE_LOG_LISTENERS #ifdef USE_LOG_LISTENERS
for (auto *listener : this->log_listeners_) for (auto *listener : this->log_listeners_)
listener->on_log(level, tag, this->tx_buffer_, this->tx_buffer_at_); listener->on_log(level, tag, this->tx_buffer_ + msg_start, msg_length);
#endif #endif
// Write to console // Write to console starting at the msg_start
this->write_tx_buffer_to_console_(); this->write_tx_buffer_to_console_(msg_start, &msg_length);
} }
#endif // USE_STORE_LOG_STR_IN_FLASH #endif // USE_STORE_LOG_STR_IN_FLASH

View File

@@ -597,39 +597,30 @@ class Logger : public Component {
*buffer_at = pos; *buffer_at = pos;
} }
// Helper to process vsnprintf return value and strip trailing newlines.
// Updates buffer_at with the formatted length, handling truncation:
// - When vsnprintf truncates (ret >= remaining), it writes (remaining - 1) chars + null terminator
// - When it doesn't truncate (ret < remaining), it writes ret chars + null terminator
__attribute__((always_inline)) static inline void process_vsnprintf_result(const char *buffer, uint16_t *buffer_at,
uint16_t remaining, int ret) {
if (ret < 0)
return; // Encoding error, do not increment buffer_at
*buffer_at += (ret >= remaining) ? (remaining - 1) : static_cast<uint16_t>(ret);
// Remove all trailing newlines right after formatting
while (*buffer_at > 0 && buffer[*buffer_at - 1] == '\n')
(*buffer_at)--;
}
inline void HOT format_body_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format, inline void HOT format_body_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, const char *format,
va_list args) { va_list args) {
// Check remaining capacity in the buffer // Get remaining capacity in the buffer
if (*buffer_at >= buffer_size) if (*buffer_at >= buffer_size)
return; return;
const uint16_t remaining = buffer_size - *buffer_at; const uint16_t remaining = buffer_size - *buffer_at;
process_vsnprintf_result(buffer, buffer_at, remaining, vsnprintf(buffer + *buffer_at, remaining, format, args));
}
#ifdef USE_STORE_LOG_STR_IN_FLASH const int ret = vsnprintf(buffer + *buffer_at, remaining, format, args);
// ESP8266 variant that reads format string directly from flash using vsnprintf_P
inline void HOT format_body_to_buffer_P_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size, PGM_P format, if (ret < 0) {
va_list args) { return; // Encoding error, do not increment buffer_at
if (*buffer_at >= buffer_size) }
return;
const uint16_t remaining = buffer_size - *buffer_at; // Update buffer_at with the formatted length (handle truncation)
process_vsnprintf_result(buffer, buffer_at, remaining, vsnprintf_P(buffer + *buffer_at, remaining, format, args)); // When vsnprintf truncates (ret >= remaining), it writes (remaining - 1) chars + null terminator
// When it doesn't truncate (ret < remaining), it writes ret chars + null terminator
uint16_t formatted_len = (ret >= remaining) ? (remaining - 1) : ret;
*buffer_at += formatted_len;
// Remove all trailing newlines right after formatting
while (*buffer_at > 0 && buffer[*buffer_at - 1] == '\n') {
(*buffer_at)--;
}
} }
#endif
inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) { inline void HOT write_footer_to_buffer_(char *buffer, uint16_t *buffer_at, uint16_t buffer_size) {
static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1; static constexpr uint16_t RESET_COLOR_LEN = sizeof(ESPHOME_LOG_RESET_COLOR) - 1;

View File

@@ -2,7 +2,6 @@
#include "esphome/core/defines.h" #include "esphome/core/defines.h"
#include "esphome/core/controller_registry.h" #include "esphome/core/controller_registry.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/progmem.h"
namespace esphome { namespace esphome {
namespace media_player { namespace media_player {
@@ -108,25 +107,25 @@ MediaPlayerCall &MediaPlayerCall::set_command(optional<MediaPlayerCommand> comma
this->command_ = command; this->command_ = command;
return *this; return *this;
} }
MediaPlayerCall &MediaPlayerCall::set_command(const char *command) { MediaPlayerCall &MediaPlayerCall::set_command(const std::string &command) {
if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("PLAY")) == 0) { if (str_equals_case_insensitive(command, "PLAY")) {
this->set_command(MEDIA_PLAYER_COMMAND_PLAY); this->set_command(MEDIA_PLAYER_COMMAND_PLAY);
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("PAUSE")) == 0) { } else if (str_equals_case_insensitive(command, "PAUSE")) {
this->set_command(MEDIA_PLAYER_COMMAND_PAUSE); this->set_command(MEDIA_PLAYER_COMMAND_PAUSE);
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("STOP")) == 0) { } else if (str_equals_case_insensitive(command, "STOP")) {
this->set_command(MEDIA_PLAYER_COMMAND_STOP); this->set_command(MEDIA_PLAYER_COMMAND_STOP);
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("MUTE")) == 0) { } else if (str_equals_case_insensitive(command, "MUTE")) {
this->set_command(MEDIA_PLAYER_COMMAND_MUTE); this->set_command(MEDIA_PLAYER_COMMAND_MUTE);
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("UNMUTE")) == 0) { } else if (str_equals_case_insensitive(command, "UNMUTE")) {
this->set_command(MEDIA_PLAYER_COMMAND_UNMUTE); this->set_command(MEDIA_PLAYER_COMMAND_UNMUTE);
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("TOGGLE")) == 0) { } else if (str_equals_case_insensitive(command, "TOGGLE")) {
this->set_command(MEDIA_PLAYER_COMMAND_TOGGLE); this->set_command(MEDIA_PLAYER_COMMAND_TOGGLE);
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("TURN_ON")) == 0) { } else if (str_equals_case_insensitive(command, "TURN_ON")) {
this->set_command(MEDIA_PLAYER_COMMAND_TURN_ON); this->set_command(MEDIA_PLAYER_COMMAND_TURN_ON);
} else if (ESPHOME_strcasecmp_P(command, ESPHOME_PSTR("TURN_OFF")) == 0) { } else if (str_equals_case_insensitive(command, "TURN_OFF")) {
this->set_command(MEDIA_PLAYER_COMMAND_TURN_OFF); this->set_command(MEDIA_PLAYER_COMMAND_TURN_OFF);
} else { } else {
ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command); ESP_LOGW(TAG, "'%s' - Unrecognized command %s", this->parent_->get_name().c_str(), command.c_str());
} }
return *this; return *this;
} }

View File

@@ -114,8 +114,7 @@ class MediaPlayerCall {
MediaPlayerCall &set_command(MediaPlayerCommand command); MediaPlayerCall &set_command(MediaPlayerCommand command);
MediaPlayerCall &set_command(optional<MediaPlayerCommand> command); MediaPlayerCall &set_command(optional<MediaPlayerCommand> command);
MediaPlayerCall &set_command(const char *command); MediaPlayerCall &set_command(const std::string &command);
MediaPlayerCall &set_command(const std::string &command) { return this->set_command(command.c_str()); }
MediaPlayerCall &set_media_url(const std::string &url); MediaPlayerCall &set_media_url(const std::string &url);

View File

@@ -6,6 +6,7 @@
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/task_priorities.h"
#include "esphome/components/audio/audio_transfer_buffer.h" #include "esphome/components/audio/audio_transfer_buffer.h"
@@ -25,7 +26,6 @@ static const size_t DATA_TIMEOUT_MS = 50;
static const uint32_t RING_BUFFER_DURATION_MS = 120; static const uint32_t RING_BUFFER_DURATION_MS = 120;
static const uint32_t INFERENCE_TASK_STACK_SIZE = 3072; static const uint32_t INFERENCE_TASK_STACK_SIZE = 3072;
static const UBaseType_t INFERENCE_TASK_PRIORITY = 3;
enum EventGroupBits : uint32_t { enum EventGroupBits : uint32_t {
COMMAND_STOP = (1 << 0), // Signals the inference task should stop COMMAND_STOP = (1 << 0), // Signals the inference task should stop
@@ -305,8 +305,10 @@ void MicroWakeWord::loop() {
return; return;
} }
// TASK_PRIORITY_INFERENCE: above main loop (TASK_PRIORITY_APPLICATION) but below
// protocol tasks (TASK_PRIORITY_PROTOCOL) - ML inference is background work
xTaskCreate(MicroWakeWord::inference_task, "mww", INFERENCE_TASK_STACK_SIZE, (void *) this, xTaskCreate(MicroWakeWord::inference_task, "mww", INFERENCE_TASK_STACK_SIZE, (void *) this,
INFERENCE_TASK_PRIORITY, &this->inference_task_handle_); TASK_PRIORITY_INFERENCE, &this->inference_task_handle_);
if (this->inference_task_handle_ == nullptr) { if (this->inference_task_handle_ == nullptr) {
FrontendFreeStateContents(&this->frontend_state_); // Deallocate frontend state FrontendFreeStateContents(&this->frontend_state_); // Deallocate frontend state
@@ -325,7 +327,7 @@ void MicroWakeWord::loop() {
ESP_LOGD(TAG, "Detected '%s' with sliding average probability is %.2f and max probability is %.2f", ESP_LOGD(TAG, "Detected '%s' with sliding average probability is %.2f and max probability is %.2f",
detection_event.wake_word->c_str(), (detection_event.average_probability / uint8_to_float_divisor), detection_event.wake_word->c_str(), (detection_event.average_probability / uint8_to_float_divisor),
(detection_event.max_probability / uint8_to_float_divisor)); (detection_event.max_probability / uint8_to_float_divisor));
this->wake_word_detected_trigger_.trigger(*detection_event.wake_word); this->wake_word_detected_trigger_->trigger(*detection_event.wake_word);
if (this->stop_after_detection_) { if (this->stop_after_detection_) {
this->stop(); this->stop();
} }

View File

@@ -60,7 +60,7 @@ class MicroWakeWord : public Component
void set_stop_after_detection(bool stop_after_detection) { this->stop_after_detection_ = stop_after_detection; } void set_stop_after_detection(bool stop_after_detection) { this->stop_after_detection_ = stop_after_detection; }
Trigger<std::string> *get_wake_word_detected_trigger() { return &this->wake_word_detected_trigger_; } Trigger<std::string> *get_wake_word_detected_trigger() const { return this->wake_word_detected_trigger_; }
void add_wake_word_model(WakeWordModel *model); void add_wake_word_model(WakeWordModel *model);
@@ -78,7 +78,7 @@ class MicroWakeWord : public Component
protected: protected:
microphone::MicrophoneSource *microphone_source_{nullptr}; microphone::MicrophoneSource *microphone_source_{nullptr};
Trigger<std::string> wake_word_detected_trigger_; Trigger<std::string> *wake_word_detected_trigger_ = new Trigger<std::string>();
State state_{State::STOPPED}; State state_{State::STOPPED};
std::weak_ptr<RingBuffer> ring_buffer_; std::weak_ptr<RingBuffer> ring_buffer_;

View File

@@ -1,6 +1,5 @@
#pragma once #pragma once
#include "esphome/core/automation.h"
#include "mixer_speaker.h" #include "mixer_speaker.h"
#ifdef USE_ESP32 #ifdef USE_ESP32

View File

@@ -5,6 +5,7 @@
#include "esphome/core/hal.h" #include "esphome/core/hal.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/task_priorities.h"
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
@@ -12,8 +13,6 @@
namespace esphome { namespace esphome {
namespace mixer_speaker { namespace mixer_speaker {
static const UBaseType_t MIXER_TASK_PRIORITY = 10;
static const uint32_t TRANSFER_BUFFER_DURATION_MS = 50; static const uint32_t TRANSFER_BUFFER_DURATION_MS = 50;
static const uint32_t TASK_DELAY_MS = 25; static const uint32_t TASK_DELAY_MS = 25;
@@ -385,8 +384,10 @@ esp_err_t MixerSpeaker::start_task_() {
} }
if (this->task_handle_ == nullptr) { if (this->task_handle_ == nullptr) {
// TASK_PRIORITY_AUDIO_MIXER: below I2S tasks (TASK_PRIORITY_AUDIO_OUTPUT) but
// above protocol tasks - mixing is buffered but feeds real-time output
this->task_handle_ = xTaskCreateStatic(audio_mixer_task, "mixer", TASK_STACK_SIZE, (void *) this, this->task_handle_ = xTaskCreateStatic(audio_mixer_task, "mixer", TASK_STACK_SIZE, (void *) this,
MIXER_TASK_PRIORITY, this->task_stack_buffer_, &this->task_stack_); TASK_PRIORITY_AUDIO_MIXER, this->task_stack_buffer_, &this->task_stack_);
} }
if (this->task_handle_ == nullptr) { if (this->task_handle_ == nullptr) {

View File

@@ -61,7 +61,10 @@ bool MQTTBackendESP32::initialize_() {
// Create the task only after MQTT client is initialized successfully // Create the task only after MQTT client is initialized successfully
// Use larger stack size when TLS is enabled // Use larger stack size when TLS is enabled
size_t stack_size = this->ca_certificate_.has_value() ? TASK_STACK_SIZE_TLS : TASK_STACK_SIZE; size_t stack_size = this->ca_certificate_.has_value() ? TASK_STACK_SIZE_TLS : TASK_STACK_SIZE;
xTaskCreate(esphome_mqtt_task, "esphome_mqtt", stack_size, (void *) this, TASK_PRIORITY, &this->task_handle_); // TASK_PRIORITY_PROTOCOL: above main loop (TASK_PRIORITY_APPLICATION) but below
// audio tasks - MQTT needs responsive scheduling for message handling
xTaskCreate(esphome_mqtt_task, "esphome_mqtt", stack_size, (void *) this, TASK_PRIORITY_PROTOCOL,
&this->task_handle_);
if (this->task_handle_ == nullptr) { if (this->task_handle_ == nullptr) {
ESP_LOGE(TAG, "Failed to create MQTT task"); ESP_LOGE(TAG, "Failed to create MQTT task");
// Clean up MQTT client since we can't start the async task // Clean up MQTT client since we can't start the async task

View File

@@ -14,6 +14,7 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/lock_free_queue.h" #include "esphome/core/lock_free_queue.h"
#include "esphome/core/event_pool.h" #include "esphome/core/event_pool.h"
#include "esphome/core/task_priorities.h"
namespace esphome::mqtt { namespace esphome::mqtt {
@@ -117,8 +118,7 @@ class MQTTBackendESP32 final : public MQTTBackend {
static const size_t MQTT_BUFFER_SIZE = 4096; static const size_t MQTT_BUFFER_SIZE = 4096;
static const size_t TASK_STACK_SIZE = 3072; static const size_t TASK_STACK_SIZE = 3072;
static const size_t TASK_STACK_SIZE_TLS = 4096; // Larger stack for TLS operations static const size_t TASK_STACK_SIZE_TLS = 4096; // Larger stack for TLS operations
static const ssize_t TASK_PRIORITY = 5; static const uint8_t MQTT_QUEUE_LENGTH = 30; // 30*12 bytes = 360
static const uint8_t MQTT_QUEUE_LENGTH = 30; // 30*12 bytes = 360
void set_keep_alive(uint16_t keep_alive) final { this->keep_alive_ = keep_alive; } void set_keep_alive(uint16_t keep_alive) final { this->keep_alive_ = keep_alive; }
void set_client_id(const char *client_id) final { this->client_id_ = client_id; } void set_client_id(const char *client_id) final { this->client_id_ = client_id; }
@@ -139,8 +139,7 @@ class MQTTBackendESP32 final : public MQTTBackend {
this->lwt_retain_ = retain; this->lwt_retain_ = retain;
} }
void set_server(network::IPAddress ip, uint16_t port) final { void set_server(network::IPAddress ip, uint16_t port) final {
char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; this->host_ = ip.str();
this->host_ = ip.str_to(ip_buf);
this->port_ = port; this->port_ = port;
} }
void set_server(const char *host, uint16_t port) final { void set_server(const char *host, uint16_t port) final {

View File

@@ -4,10 +4,8 @@ from typing import Any
from esphome import automation, pins from esphome import automation, pins
import esphome.codegen as cg import esphome.codegen as cg
from esphome.components import sensor from esphome.components import sensor
from esphome.components.esp32 import include_builtin_idf_component
import esphome.config_validation as cv import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_TRIGGER_ID, PLATFORM_ESP32, PLATFORM_ESP8266 from esphome.const import CONF_ID, CONF_TRIGGER_ID, PLATFORM_ESP32, PLATFORM_ESP8266
from esphome.core import CORE
from . import const, generate, schema, validate from . import const, generate, schema, validate
@@ -85,12 +83,6 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config: dict[str, Any]) -> None: async def to_code(config: dict[str, Any]) -> None:
if CORE.is_esp32:
# Re-enable ESP-IDF's legacy driver component (excluded by default to save compile time)
# Provides driver/timer.h header for hardware timer API
# TODO: Remove this once opentherm migrates to GPTimer API (driver/gptimer.h)
include_builtin_idf_component("driver")
var = cg.new_Pvariable(config[CONF_ID]) var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config) await cg.register_component(var, config)

View File

@@ -8,8 +8,6 @@
#include "opentherm.h" #include "opentherm.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include <cinttypes> #include <cinttypes>
// TODO: Migrate from legacy timer API (driver/timer.h) to GPTimer API (driver/gptimer.h)
// The legacy timer API is deprecated in ESP-IDF 5.x. See opentherm.h for details.
#ifdef USE_ESP32 #ifdef USE_ESP32
#include "driver/timer.h" #include "driver/timer.h"
#include "esp_err.h" #include "esp_err.h"

View File

@@ -12,10 +12,6 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
// TODO: Migrate from legacy timer API (driver/timer.h) to GPTimer API (driver/gptimer.h)
// The legacy timer API is deprecated in ESP-IDF 5.x. Migration would allow removing the
// "driver" IDF component dependency. See:
// https://docs.espressif.com/projects/esp-idf/en/latest/esp32/migration-guides/release-5.x/5.0/peripherals.html#id4
#ifdef USE_ESP32 #ifdef USE_ESP32
#include "driver/timer.h" #include "driver/timer.h"
#endif #endif

View File

@@ -4,7 +4,6 @@ from esphome.components.esp32 import (
VARIANT_ESP32C6, VARIANT_ESP32C6,
VARIANT_ESP32H2, VARIANT_ESP32H2,
add_idf_sdkconfig_option, add_idf_sdkconfig_option,
include_builtin_idf_component,
only_on_variant, only_on_variant,
require_vfs_select, require_vfs_select,
) )
@@ -173,9 +172,6 @@ FINAL_VALIDATE_SCHEMA = _final_validate
async def to_code(config): async def to_code(config):
# Re-enable openthread IDF component (excluded by default)
include_builtin_idf_component("openthread")
cg.add_define("USE_OPENTHREAD") cg.add_define("USE_OPENTHREAD")
# OpenThread SRP needs access to mDNS services after setup # OpenThread SRP needs access to mDNS services after setup

View File

@@ -10,6 +10,7 @@
#include "esp_task_wdt.h" #include "esp_task_wdt.h"
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/task_priorities.h"
#include "esp_err.h" #include "esp_err.h"
#include "esp_event.h" #include "esp_event.h"
@@ -39,12 +40,14 @@ void OpenThreadComponent::setup() {
ESP_ERROR_CHECK(esp_netif_init()); ESP_ERROR_CHECK(esp_netif_init());
ESP_ERROR_CHECK(esp_vfs_eventfd_register(&eventfd_config)); ESP_ERROR_CHECK(esp_vfs_eventfd_register(&eventfd_config));
// TASK_PRIORITY_PROTOCOL: same as USB host/MQTT - network protocol tasks need
// responsive scheduling but below audio tasks
xTaskCreate( xTaskCreate(
[](void *arg) { [](void *arg) {
static_cast<OpenThreadComponent *>(arg)->ot_main(); static_cast<OpenThreadComponent *>(arg)->ot_main();
vTaskDelete(nullptr); vTaskDelete(nullptr);
}, },
"ot_main", 10240, this, 5, nullptr); "ot_main", 10240, this, TASK_PRIORITY_PROTOCOL, nullptr);
} }
static esp_netif_t *init_openthread_netif(const esp_openthread_platform_config_t *config) { static esp_netif_t *init_openthread_netif(const esp_openthread_platform_config_t *config) {

View File

@@ -83,7 +83,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
uint32_t on_time, off_time; uint32_t on_time, off_time;
this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time); this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time);
this->target_time_ = 0; this->target_time_ = 0;
this->transmit_trigger_.trigger(); this->transmit_trigger_->trigger();
for (uint32_t i = 0; i < send_times; i++) { for (uint32_t i = 0; i < send_times; i++) {
InterruptLock lock; InterruptLock lock;
for (int32_t item : this->temp_.get_data()) { for (int32_t item : this->temp_.get_data()) {
@@ -102,7 +102,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
if (i + 1 < send_times) if (i + 1 < send_times)
this->target_time_ += send_wait; this->target_time_ += send_wait;
} }
this->complete_trigger_.trigger(); this->complete_trigger_->trigger();
} }
} // namespace remote_transmitter } // namespace remote_transmitter

View File

@@ -57,8 +57,8 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
void set_non_blocking(bool non_blocking) { this->non_blocking_ = non_blocking; } void set_non_blocking(bool non_blocking) { this->non_blocking_ = non_blocking; }
#endif #endif
Trigger<> *get_transmit_trigger() { return &this->transmit_trigger_; } Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; };
Trigger<> *get_complete_trigger() { return &this->complete_trigger_; } Trigger<> *get_complete_trigger() const { return this->complete_trigger_; };
protected: protected:
void send_internal(uint32_t send_times, uint32_t send_wait) override; void send_internal(uint32_t send_times, uint32_t send_wait) override;
@@ -96,8 +96,8 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase,
#endif #endif
uint8_t carrier_duty_percent_; uint8_t carrier_duty_percent_;
Trigger<> transmit_trigger_; Trigger<> *transmit_trigger_{new Trigger<>()};
Trigger<> complete_trigger_; Trigger<> *complete_trigger_{new Trigger<>()};
}; };
} // namespace remote_transmitter } // namespace remote_transmitter

View File

@@ -203,7 +203,7 @@ void RemoteTransmitterComponent::wait_for_rmt_() {
this->status_set_warning(); this->status_set_warning();
} }
this->complete_trigger_.trigger(); this->complete_trigger_->trigger();
} }
#if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1) #if ESP_IDF_VERSION >= ESP_IDF_VERSION_VAL(5, 5, 1)
@@ -264,7 +264,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
return; return;
} }
this->transmit_trigger_.trigger(); this->transmit_trigger_->trigger();
rmt_transmit_config_t config; rmt_transmit_config_t config;
memset(&config, 0, sizeof(config)); memset(&config, 0, sizeof(config));
@@ -333,7 +333,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
ESP_LOGE(TAG, "Empty data"); ESP_LOGE(TAG, "Empty data");
return; return;
} }
this->transmit_trigger_.trigger(); this->transmit_trigger_->trigger();
for (uint32_t i = 0; i < send_times; i++) { for (uint32_t i = 0; i < send_times; i++) {
rmt_transmit_config_t config; rmt_transmit_config_t config;
memset(&config, 0, sizeof(config)); memset(&config, 0, sizeof(config));
@@ -354,7 +354,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen
if (i + 1 < send_times) if (i + 1 < send_times)
delayMicroseconds(send_wait); delayMicroseconds(send_wait);
} }
this->complete_trigger_.trigger(); this->complete_trigger_->trigger();
} }
#endif #endif

View File

@@ -6,6 +6,7 @@
#include "esphome/core/helpers.h" #include "esphome/core/helpers.h"
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/task_priorities.h"
#include <algorithm> #include <algorithm>
#include <cstring> #include <cstring>
@@ -13,8 +14,6 @@
namespace esphome { namespace esphome {
namespace resampler { namespace resampler {
static const UBaseType_t RESAMPLER_TASK_PRIORITY = 1;
static const uint32_t TRANSFER_BUFFER_DURATION_MS = 50; static const uint32_t TRANSFER_BUFFER_DURATION_MS = 50;
static const uint32_t TASK_DELAY_MS = 20; static const uint32_t TASK_DELAY_MS = 20;
@@ -185,8 +184,10 @@ esp_err_t ResamplerSpeaker::start_task_() {
} }
if (this->task_handle_ == nullptr) { if (this->task_handle_ == nullptr) {
// TASK_PRIORITY_APPLICATION: same as main loop - resampling is buffered audio
// processing, not real-time I/O
this->task_handle_ = xTaskCreateStatic(resample_task, "sample", TASK_STACK_SIZE, (void *) this, this->task_handle_ = xTaskCreateStatic(resample_task, "sample", TASK_STACK_SIZE, (void *) this,
RESAMPLER_TASK_PRIORITY, this->task_stack_buffer_, &this->task_stack_); TASK_PRIORITY_APPLICATION, this->task_stack_buffer_, &this->task_stack_);
} }
if (this->task_handle_ == nullptr) { if (this->task_handle_ == nullptr) {

View File

@@ -18,12 +18,11 @@ namespace rp2040 {
static const char *const TAG = "rp2040.preferences"; static const char *const TAG = "rp2040.preferences";
static constexpr uint32_t RP2040_FLASH_STORAGE_SIZE = 512; static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static uint8_t *s_flash_storage = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_prevent_write = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) static const uint32_t RP2040_FLASH_STORAGE_SIZE = 512;
static uint8_t
s_flash_storage[RP2040_FLASH_STORAGE_SIZE]; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
static bool s_flash_dirty = false; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
// Stack buffer size for preferences - covers virtually all real-world preferences without heap allocation // Stack buffer size for preferences - covers virtually all real-world preferences without heap allocation
static constexpr size_t PREF_BUFFER_SIZE = 64; static constexpr size_t PREF_BUFFER_SIZE = 64;
@@ -92,6 +91,7 @@ class RP2040Preferences : public ESPPreferences {
RP2040Preferences() : eeprom_sector_(&_EEPROM_start) {} RP2040Preferences() : eeprom_sector_(&_EEPROM_start) {}
void setup() { void setup() {
s_flash_storage = new uint8_t[RP2040_FLASH_STORAGE_SIZE]; // NOLINT
ESP_LOGVV(TAG, "Loading preferences from flash"); ESP_LOGVV(TAG, "Loading preferences from flash");
memcpy(s_flash_storage, this->eeprom_sector_, RP2040_FLASH_STORAGE_SIZE); memcpy(s_flash_storage, this->eeprom_sector_, RP2040_FLASH_STORAGE_SIZE);
} }
@@ -149,11 +149,10 @@ class RP2040Preferences : public ESPPreferences {
uint8_t *eeprom_sector_; uint8_t *eeprom_sector_;
}; };
static RP2040Preferences s_preferences; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
void setup_preferences() { void setup_preferences() {
s_preferences.setup(); auto *prefs = new RP2040Preferences(); // NOLINT(cppcoreguidelines-owning-memory)
global_preferences = &s_preferences; prefs->setup();
global_preferences = prefs;
} }
void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; } void preferences_prevent_write(bool prevent) { s_prevent_write = prevent; }

View File

@@ -157,14 +157,8 @@ def _read_audio_file_and_type(file_config):
import puremagic import puremagic
try: file_type: str = puremagic.from_string(data)
file_type: str = puremagic.from_string(data) file_type = file_type.removeprefix(".")
file_type = file_type.removeprefix(".")
except puremagic.PureError as e:
raise cv.Invalid(
f"Unable to determine audio file type of '{path}'. "
f"Try re-encoding the file into a supported format. Details: {e}"
)
media_file_type = audio.AUDIO_FILE_TYPE_ENUM["NONE"] media_file_type = audio.AUDIO_FILE_TYPE_ENUM["NONE"]
if file_type in ("wav"): if file_type in ("wav"):

View File

@@ -3,6 +3,7 @@
#ifdef USE_ESP32 #ifdef USE_ESP32
#include "esphome/core/log.h" #include "esphome/core/log.h"
#include "esphome/core/task_priorities.h"
#include "esphome/components/audio/audio.h" #include "esphome/components/audio/audio.h"
#ifdef USE_OTA #ifdef USE_OTA
@@ -45,9 +46,6 @@ namespace speaker {
static const uint32_t MEDIA_CONTROLS_QUEUE_LENGTH = 20; static const uint32_t MEDIA_CONTROLS_QUEUE_LENGTH = 20;
static const UBaseType_t MEDIA_PIPELINE_TASK_PRIORITY = 1;
static const UBaseType_t ANNOUNCEMENT_PIPELINE_TASK_PRIORITY = 1;
static const char *const TAG = "speaker_media_player"; static const char *const TAG = "speaker_media_player";
void SpeakerMediaPlayer::setup() { void SpeakerMediaPlayer::setup() {
@@ -70,9 +68,10 @@ void SpeakerMediaPlayer::setup() {
ota::get_global_ota_callback()->add_global_state_listener(this); ota::get_global_ota_callback()->add_global_state_listener(this);
#endif #endif
this->announcement_pipeline_ = // TASK_PRIORITY_APPLICATION: same as main loop - media pipelines handle buffered
make_unique<AudioPipeline>(this->announcement_speaker_, this->buffer_size_, this->task_stack_in_psram_, "ann", // audio streaming, not real-time I/O, so they don't need elevated priority
ANNOUNCEMENT_PIPELINE_TASK_PRIORITY); this->announcement_pipeline_ = make_unique<AudioPipeline>(
this->announcement_speaker_, this->buffer_size_, this->task_stack_in_psram_, "ann", TASK_PRIORITY_APPLICATION);
if (this->announcement_pipeline_ == nullptr) { if (this->announcement_pipeline_ == nullptr) {
ESP_LOGE(TAG, "Failed to create announcement pipeline"); ESP_LOGE(TAG, "Failed to create announcement pipeline");
@@ -81,7 +80,7 @@ void SpeakerMediaPlayer::setup() {
if (!this->single_pipeline_()) { if (!this->single_pipeline_()) {
this->media_pipeline_ = make_unique<AudioPipeline>(this->media_speaker_, this->buffer_size_, this->media_pipeline_ = make_unique<AudioPipeline>(this->media_speaker_, this->buffer_size_,
this->task_stack_in_psram_, "med", MEDIA_PIPELINE_TASK_PRIORITY); this->task_stack_in_psram_, "med", TASK_PRIORITY_APPLICATION);
if (this->media_pipeline_ == nullptr) { if (this->media_pipeline_ == nullptr) {
ESP_LOGE(TAG, "Failed to create media pipeline"); ESP_LOGE(TAG, "Failed to create media pipeline");
@@ -519,9 +518,9 @@ void SpeakerMediaPlayer::set_mute_state_(bool mute_state) {
if (old_mute_state != mute_state) { if (old_mute_state != mute_state) {
if (mute_state) { if (mute_state) {
this->defer([this]() { this->mute_trigger_.trigger(); }); this->defer([this]() { this->mute_trigger_->trigger(); });
} else { } else {
this->defer([this]() { this->unmute_trigger_.trigger(); }); this->defer([this]() { this->unmute_trigger_->trigger(); });
} }
} }
} }
@@ -550,7 +549,7 @@ void SpeakerMediaPlayer::set_volume_(float volume, bool publish) {
this->set_mute_state_(false); this->set_mute_state_(false);
} }
this->defer([this, volume]() { this->volume_trigger_.trigger(volume); }); this->defer([this, volume]() { this->volume_trigger_->trigger(volume); });
} }
} // namespace speaker } // namespace speaker

View File

@@ -84,9 +84,9 @@ class SpeakerMediaPlayer : public Component,
this->media_format_ = media_format; this->media_format_ = media_format;
} }
Trigger<> *get_mute_trigger() { return &this->mute_trigger_; } Trigger<> *get_mute_trigger() const { return this->mute_trigger_; }
Trigger<> *get_unmute_trigger() { return &this->unmute_trigger_; } Trigger<> *get_unmute_trigger() const { return this->unmute_trigger_; }
Trigger<float> *get_volume_trigger() { return &this->volume_trigger_; } Trigger<float> *get_volume_trigger() const { return this->volume_trigger_; }
void play_file(audio::AudioFile *media_file, bool announcement, bool enqueue); void play_file(audio::AudioFile *media_file, bool announcement, bool enqueue);
@@ -154,9 +154,9 @@ class SpeakerMediaPlayer : public Component,
// Used to save volume/mute state for restoration on reboot // Used to save volume/mute state for restoration on reboot
ESPPreferenceObject pref_; ESPPreferenceObject pref_;
Trigger<> mute_trigger_; Trigger<> *mute_trigger_ = new Trigger<>();
Trigger<> unmute_trigger_; Trigger<> *unmute_trigger_ = new Trigger<>();
Trigger<float> volume_trigger_; Trigger<float> *volume_trigger_ = new Trigger<float>();
}; };
} // namespace speaker } // namespace speaker

View File

@@ -29,7 +29,7 @@ void SprinklerControllerNumber::setup() {
} }
void SprinklerControllerNumber::control(float value) { void SprinklerControllerNumber::control(float value) {
this->set_trigger_.trigger(value); this->set_trigger_->trigger(value);
this->publish_state(value); this->publish_state(value);
@@ -39,7 +39,8 @@ void SprinklerControllerNumber::control(float value) {
void SprinklerControllerNumber::dump_config() { LOG_NUMBER("", "Sprinkler Controller Number", this); } void SprinklerControllerNumber::dump_config() { LOG_NUMBER("", "Sprinkler Controller Number", this); }
SprinklerControllerSwitch::SprinklerControllerSwitch() = default; SprinklerControllerSwitch::SprinklerControllerSwitch()
: turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {}
void SprinklerControllerSwitch::loop() { void SprinklerControllerSwitch::loop() {
// Loop is only enabled when f_ has a value (see setup()) // Loop is only enabled when f_ has a value (see setup())
@@ -55,11 +56,11 @@ void SprinklerControllerSwitch::write_state(bool state) {
} }
if (state) { if (state) {
this->prev_trigger_ = &this->turn_on_trigger_; this->prev_trigger_ = this->turn_on_trigger_;
this->turn_on_trigger_.trigger(); this->turn_on_trigger_->trigger();
} else { } else {
this->prev_trigger_ = &this->turn_off_trigger_; this->prev_trigger_ = this->turn_off_trigger_;
this->turn_off_trigger_.trigger(); this->turn_off_trigger_->trigger();
} }
this->publish_state(state); this->publish_state(state);
@@ -68,6 +69,9 @@ void SprinklerControllerSwitch::write_state(bool state) {
void SprinklerControllerSwitch::set_state_lambda(std::function<optional<bool>()> &&f) { this->f_ = f; } void SprinklerControllerSwitch::set_state_lambda(std::function<optional<bool>()> &&f) { this->f_ = f; }
float SprinklerControllerSwitch::get_setup_priority() const { return setup_priority::HARDWARE; } float SprinklerControllerSwitch::get_setup_priority() const { return setup_priority::HARDWARE; }
Trigger<> *SprinklerControllerSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; }
Trigger<> *SprinklerControllerSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; }
void SprinklerControllerSwitch::setup() { void SprinklerControllerSwitch::setup() {
this->state = this->get_initial_state_with_restore_mode().value_or(false); this->state = this->get_initial_state_with_restore_mode().value_or(false);
// Disable loop if no state lambda is set - nothing to poll // Disable loop if no state lambda is set - nothing to poll

View File

@@ -76,7 +76,7 @@ class SprinklerControllerNumber : public number::Number, public Component {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::PROCESSOR; } float get_setup_priority() const override { return setup_priority::PROCESSOR; }
Trigger<float> *get_set_trigger() { return &this->set_trigger_; } Trigger<float> *get_set_trigger() const { return set_trigger_; }
void set_initial_value(float initial_value) { initial_value_ = initial_value; } void set_initial_value(float initial_value) { initial_value_ = initial_value; }
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
@@ -84,7 +84,7 @@ class SprinklerControllerNumber : public number::Number, public Component {
void control(float value) override; void control(float value) override;
float initial_value_{NAN}; float initial_value_{NAN};
bool restore_value_{true}; bool restore_value_{true};
Trigger<float> set_trigger_; Trigger<float> *set_trigger_ = new Trigger<float>();
ESPPreferenceObject pref_; ESPPreferenceObject pref_;
}; };
@@ -97,8 +97,8 @@ class SprinklerControllerSwitch : public switch_::Switch, public Component {
void dump_config() override; void dump_config() override;
void set_state_lambda(std::function<optional<bool>()> &&f); void set_state_lambda(std::function<optional<bool>()> &&f);
Trigger<> *get_turn_on_trigger() { return &this->turn_on_trigger_; } Trigger<> *get_turn_on_trigger() const;
Trigger<> *get_turn_off_trigger() { return &this->turn_off_trigger_; } Trigger<> *get_turn_off_trigger() const;
void loop() override; void loop() override;
float get_setup_priority() const override; float get_setup_priority() const override;
@@ -107,8 +107,8 @@ class SprinklerControllerSwitch : public switch_::Switch, public Component {
void write_state(bool state) override; void write_state(bool state) override;
optional<std::function<optional<bool>()>> f_; optional<std::function<optional<bool>()>> f_;
Trigger<> turn_on_trigger_; Trigger<> *turn_on_trigger_;
Trigger<> turn_off_trigger_; Trigger<> *turn_off_trigger_;
Trigger<> *prev_trigger_{nullptr}; Trigger<> *prev_trigger_{nullptr};
}; };

View File

@@ -213,7 +213,7 @@ CONFIG_SCHEMA = (
cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True), cv.Optional(CONF_ON_PACKET): automation.validate_automation(single=True),
cv.Optional(CONF_PA_POWER, default=17): cv.int_range(min=-3, max=22), cv.Optional(CONF_PA_POWER, default=17): cv.int_range(min=-3, max=22),
cv.Optional(CONF_PA_RAMP, default="40us"): cv.enum(RAMP), cv.Optional(CONF_PA_RAMP, default="40us"): cv.enum(RAMP),
cv.Optional(CONF_PAYLOAD_LENGTH, default=0): cv.int_range(min=0, max=255), cv.Optional(CONF_PAYLOAD_LENGTH, default=0): cv.int_range(min=0, max=256),
cv.Optional(CONF_PREAMBLE_DETECT, default=2): cv.int_range(min=0, max=4), cv.Optional(CONF_PREAMBLE_DETECT, default=2): cv.int_range(min=0, max=4),
cv.Optional(CONF_PREAMBLE_SIZE, default=8): cv.int_range(min=1, max=65535), cv.Optional(CONF_PREAMBLE_SIZE, default=8): cv.int_range(min=1, max=65535),
cv.Required(CONF_RST_PIN): pins.gpio_output_pin_schema, cv.Required(CONF_RST_PIN): pins.gpio_output_pin_schema,

View File

@@ -343,7 +343,7 @@ void SX126x::call_listeners_(const std::vector<uint8_t> &packet, float rssi, flo
for (auto &listener : this->listeners_) { for (auto &listener : this->listeners_) {
listener->on_packet(packet, rssi, snr); listener->on_packet(packet, rssi, snr);
} }
this->packet_trigger_.trigger(packet, rssi, snr); this->packet_trigger_->trigger(packet, rssi, snr);
} }
void SX126x::loop() { void SX126x::loop() {

View File

@@ -97,7 +97,7 @@ class SX126x : public Component,
void configure(); void configure();
SX126xError transmit_packet(const std::vector<uint8_t> &packet); SX126xError transmit_packet(const std::vector<uint8_t> &packet);
void register_listener(SX126xListener *listener) { this->listeners_.push_back(listener); } void register_listener(SX126xListener *listener) { this->listeners_.push_back(listener); }
Trigger<std::vector<uint8_t>, float, float> *get_packet_trigger() { return &this->packet_trigger_; } Trigger<std::vector<uint8_t>, float, float> *get_packet_trigger() const { return this->packet_trigger_; };
protected: protected:
void configure_fsk_ook_(); void configure_fsk_ook_();
@@ -111,7 +111,7 @@ class SX126x : public Component,
void read_register_(uint16_t reg, uint8_t *data, uint8_t size); void read_register_(uint16_t reg, uint8_t *data, uint8_t size);
void call_listeners_(const std::vector<uint8_t> &packet, float rssi, float snr); void call_listeners_(const std::vector<uint8_t> &packet, float rssi, float snr);
void wait_busy_(); void wait_busy_();
Trigger<std::vector<uint8_t>, float, float> packet_trigger_; Trigger<std::vector<uint8_t>, float, float> *packet_trigger_{new Trigger<std::vector<uint8_t>, float, float>()};
std::vector<SX126xListener *> listeners_; std::vector<SX126xListener *> listeners_;
std::vector<uint8_t> packet_; std::vector<uint8_t> packet_;
std::vector<uint8_t> sync_value_; std::vector<uint8_t> sync_value_;

View File

@@ -300,7 +300,7 @@ void SX127x::call_listeners_(const std::vector<uint8_t> &packet, float rssi, flo
for (auto &listener : this->listeners_) { for (auto &listener : this->listeners_) {
listener->on_packet(packet, rssi, snr); listener->on_packet(packet, rssi, snr);
} }
this->packet_trigger_.trigger(packet, rssi, snr); this->packet_trigger_->trigger(packet, rssi, snr);
} }
void SX127x::loop() { void SX127x::loop() {

View File

@@ -83,7 +83,7 @@ class SX127x : public Component,
void configure(); void configure();
SX127xError transmit_packet(const std::vector<uint8_t> &packet); SX127xError transmit_packet(const std::vector<uint8_t> &packet);
void register_listener(SX127xListener *listener) { this->listeners_.push_back(listener); } void register_listener(SX127xListener *listener) { this->listeners_.push_back(listener); }
Trigger<std::vector<uint8_t>, float, float> *get_packet_trigger() { return &this->packet_trigger_; } Trigger<std::vector<uint8_t>, float, float> *get_packet_trigger() const { return this->packet_trigger_; };
protected: protected:
void configure_fsk_ook_(); void configure_fsk_ook_();
@@ -94,7 +94,7 @@ class SX127x : public Component,
void write_register_(uint8_t reg, uint8_t value); void write_register_(uint8_t reg, uint8_t value);
void call_listeners_(const std::vector<uint8_t> &packet, float rssi, float snr); void call_listeners_(const std::vector<uint8_t> &packet, float rssi, float snr);
uint8_t read_register_(uint8_t reg); uint8_t read_register_(uint8_t reg);
Trigger<std::vector<uint8_t>, float, float> packet_trigger_; Trigger<std::vector<uint8_t>, float, float> *packet_trigger_{new Trigger<std::vector<uint8_t>, float, float>()};
std::vector<SX127xListener *> listeners_; std::vector<SX127xListener *> listeners_;
std::vector<uint8_t> packet_; std::vector<uint8_t> packet_;
std::vector<uint8_t> sync_value_; std::vector<uint8_t> sync_value_;

View File

@@ -7,7 +7,13 @@ using namespace esphome::cover;
static const char *const TAG = "template.cover"; static const char *const TAG = "template.cover";
TemplateCover::TemplateCover() = default; TemplateCover::TemplateCover()
: open_trigger_(new Trigger<>()),
close_trigger_(new Trigger<>),
stop_trigger_(new Trigger<>()),
toggle_trigger_(new Trigger<>()),
position_trigger_(new Trigger<float>()),
tilt_trigger_(new Trigger<float>()) {}
void TemplateCover::setup() { void TemplateCover::setup() {
switch (this->restore_mode_) { switch (this->restore_mode_) {
case COVER_NO_RESTORE: case COVER_NO_RESTORE:
@@ -56,22 +62,22 @@ void TemplateCover::loop() {
void TemplateCover::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void TemplateCover::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void TemplateCover::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } void TemplateCover::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
float TemplateCover::get_setup_priority() const { return setup_priority::HARDWARE; } float TemplateCover::get_setup_priority() const { return setup_priority::HARDWARE; }
Trigger<> *TemplateCover::get_open_trigger() { return &this->open_trigger_; } Trigger<> *TemplateCover::get_open_trigger() const { return this->open_trigger_; }
Trigger<> *TemplateCover::get_close_trigger() { return &this->close_trigger_; } Trigger<> *TemplateCover::get_close_trigger() const { return this->close_trigger_; }
Trigger<> *TemplateCover::get_stop_trigger() { return &this->stop_trigger_; } Trigger<> *TemplateCover::get_stop_trigger() const { return this->stop_trigger_; }
Trigger<> *TemplateCover::get_toggle_trigger() { return &this->toggle_trigger_; } Trigger<> *TemplateCover::get_toggle_trigger() const { return this->toggle_trigger_; }
void TemplateCover::dump_config() { LOG_COVER("", "Template Cover", this); } void TemplateCover::dump_config() { LOG_COVER("", "Template Cover", this); }
void TemplateCover::control(const CoverCall &call) { void TemplateCover::control(const CoverCall &call) {
if (call.get_stop()) { if (call.get_stop()) {
this->stop_prev_trigger_(); this->stop_prev_trigger_();
this->stop_trigger_.trigger(); this->stop_trigger_->trigger();
this->prev_command_trigger_ = &this->stop_trigger_; this->prev_command_trigger_ = this->stop_trigger_;
this->publish_state(); this->publish_state();
} }
if (call.get_toggle().has_value()) { if (call.get_toggle().has_value()) {
this->stop_prev_trigger_(); this->stop_prev_trigger_();
this->toggle_trigger_.trigger(); this->toggle_trigger_->trigger();
this->prev_command_trigger_ = &this->toggle_trigger_; this->prev_command_trigger_ = this->toggle_trigger_;
this->publish_state(); this->publish_state();
} }
if (call.get_position().has_value()) { if (call.get_position().has_value()) {
@@ -79,13 +85,13 @@ void TemplateCover::control(const CoverCall &call) {
this->stop_prev_trigger_(); this->stop_prev_trigger_();
if (pos == COVER_OPEN) { if (pos == COVER_OPEN) {
this->open_trigger_.trigger(); this->open_trigger_->trigger();
this->prev_command_trigger_ = &this->open_trigger_; this->prev_command_trigger_ = this->open_trigger_;
} else if (pos == COVER_CLOSED) { } else if (pos == COVER_CLOSED) {
this->close_trigger_.trigger(); this->close_trigger_->trigger();
this->prev_command_trigger_ = &this->close_trigger_; this->prev_command_trigger_ = this->close_trigger_;
} else { } else {
this->position_trigger_.trigger(pos); this->position_trigger_->trigger(pos);
} }
if (this->optimistic_) { if (this->optimistic_) {
@@ -95,7 +101,7 @@ void TemplateCover::control(const CoverCall &call) {
if (call.get_tilt().has_value()) { if (call.get_tilt().has_value()) {
auto tilt = *call.get_tilt(); auto tilt = *call.get_tilt();
this->tilt_trigger_.trigger(tilt); this->tilt_trigger_->trigger(tilt);
if (this->optimistic_) { if (this->optimistic_) {
this->tilt = tilt; this->tilt = tilt;
@@ -113,8 +119,8 @@ CoverTraits TemplateCover::get_traits() {
traits.set_supports_tilt(this->has_tilt_); traits.set_supports_tilt(this->has_tilt_);
return traits; return traits;
} }
Trigger<float> *TemplateCover::get_position_trigger() { return &this->position_trigger_; } Trigger<float> *TemplateCover::get_position_trigger() const { return this->position_trigger_; }
Trigger<float> *TemplateCover::get_tilt_trigger() { return &this->tilt_trigger_; } Trigger<float> *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; }
void TemplateCover::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; } void TemplateCover::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; }
void TemplateCover::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; } void TemplateCover::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; }
void TemplateCover::set_has_position(bool has_position) { this->has_position_ = has_position; } void TemplateCover::set_has_position(bool has_position) { this->has_position_ = has_position; }

View File

@@ -19,12 +19,12 @@ class TemplateCover final : public cover::Cover, public Component {
template<typename F> void set_state_lambda(F &&f) { this->state_f_.set(std::forward<F>(f)); } template<typename F> void set_state_lambda(F &&f) { this->state_f_.set(std::forward<F>(f)); }
template<typename F> void set_tilt_lambda(F &&f) { this->tilt_f_.set(std::forward<F>(f)); } template<typename F> void set_tilt_lambda(F &&f) { this->tilt_f_.set(std::forward<F>(f)); }
Trigger<> *get_open_trigger(); Trigger<> *get_open_trigger() const;
Trigger<> *get_close_trigger(); Trigger<> *get_close_trigger() const;
Trigger<> *get_stop_trigger(); Trigger<> *get_stop_trigger() const;
Trigger<> *get_toggle_trigger(); Trigger<> *get_toggle_trigger() const;
Trigger<float> *get_position_trigger(); Trigger<float> *get_position_trigger() const;
Trigger<float> *get_tilt_trigger(); Trigger<float> *get_tilt_trigger() const;
void set_optimistic(bool optimistic); void set_optimistic(bool optimistic);
void set_assumed_state(bool assumed_state); void set_assumed_state(bool assumed_state);
void set_has_stop(bool has_stop); void set_has_stop(bool has_stop);
@@ -49,16 +49,16 @@ class TemplateCover final : public cover::Cover, public Component {
TemplateLambda<float> tilt_f_; TemplateLambda<float> tilt_f_;
bool assumed_state_{false}; bool assumed_state_{false};
bool optimistic_{false}; bool optimistic_{false};
Trigger<> open_trigger_; Trigger<> *open_trigger_;
Trigger<> close_trigger_; Trigger<> *close_trigger_;
bool has_stop_{false}; bool has_stop_{false};
bool has_toggle_{false}; bool has_toggle_{false};
Trigger<> stop_trigger_; Trigger<> *stop_trigger_;
Trigger<> toggle_trigger_; Trigger<> *toggle_trigger_;
Trigger<> *prev_command_trigger_{nullptr}; Trigger<> *prev_command_trigger_{nullptr};
Trigger<float> position_trigger_; Trigger<float> *position_trigger_;
bool has_position_{false}; bool has_position_{false};
Trigger<float> tilt_trigger_; Trigger<float> *tilt_trigger_;
bool has_tilt_{false}; bool has_tilt_{false};
}; };

View File

@@ -62,7 +62,7 @@ void TemplateDate::control(const datetime::DateCall &call) {
if (has_day) if (has_day)
value.day_of_month = *call.get_day(); value.day_of_month = *call.get_day();
this->set_trigger_.trigger(value); this->set_trigger_->trigger(value);
if (this->optimistic_) { if (this->optimistic_) {
if (has_year) if (has_year)

View File

@@ -22,7 +22,7 @@ class TemplateDate final : public datetime::DateEntity, public PollingComponent
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; } float get_setup_priority() const override { return setup_priority::HARDWARE; }
Trigger<ESPTime> *get_set_trigger() { return &this->set_trigger_; } Trigger<ESPTime> *get_set_trigger() const { return this->set_trigger_; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; } void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; }
@@ -34,7 +34,7 @@ class TemplateDate final : public datetime::DateEntity, public PollingComponent
bool optimistic_{false}; bool optimistic_{false};
ESPTime initial_value_{}; ESPTime initial_value_{};
bool restore_value_{false}; bool restore_value_{false};
Trigger<ESPTime> set_trigger_; Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
TemplateLambda<ESPTime> f_; TemplateLambda<ESPTime> f_;
ESPPreferenceObject pref_; ESPPreferenceObject pref_;

View File

@@ -80,7 +80,7 @@ void TemplateDateTime::control(const datetime::DateTimeCall &call) {
if (has_second) if (has_second)
value.second = *call.get_second(); value.second = *call.get_second();
this->set_trigger_.trigger(value); this->set_trigger_->trigger(value);
if (this->optimistic_) { if (this->optimistic_) {
if (has_year) if (has_year)

View File

@@ -22,7 +22,7 @@ class TemplateDateTime final : public datetime::DateTimeEntity, public PollingCo
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; } float get_setup_priority() const override { return setup_priority::HARDWARE; }
Trigger<ESPTime> *get_set_trigger() { return &this->set_trigger_; } Trigger<ESPTime> *get_set_trigger() const { return this->set_trigger_; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; } void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; }
@@ -34,7 +34,7 @@ class TemplateDateTime final : public datetime::DateTimeEntity, public PollingCo
bool optimistic_{false}; bool optimistic_{false};
ESPTime initial_value_{}; ESPTime initial_value_{};
bool restore_value_{false}; bool restore_value_{false};
Trigger<ESPTime> set_trigger_; Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
TemplateLambda<ESPTime> f_; TemplateLambda<ESPTime> f_;
ESPPreferenceObject pref_; ESPPreferenceObject pref_;

View File

@@ -62,7 +62,7 @@ void TemplateTime::control(const datetime::TimeCall &call) {
if (has_second) if (has_second)
value.second = *call.get_second(); value.second = *call.get_second();
this->set_trigger_.trigger(value); this->set_trigger_->trigger(value);
if (this->optimistic_) { if (this->optimistic_) {
if (has_hour) if (has_hour)

View File

@@ -22,7 +22,7 @@ class TemplateTime final : public datetime::TimeEntity, public PollingComponent
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; } float get_setup_priority() const override { return setup_priority::HARDWARE; }
Trigger<ESPTime> *get_set_trigger() { return &this->set_trigger_; } Trigger<ESPTime> *get_set_trigger() const { return this->set_trigger_; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; } void set_initial_value(ESPTime initial_value) { this->initial_value_ = initial_value; }
@@ -34,7 +34,7 @@ class TemplateTime final : public datetime::TimeEntity, public PollingComponent
bool optimistic_{false}; bool optimistic_{false};
ESPTime initial_value_{}; ESPTime initial_value_{};
bool restore_value_{false}; bool restore_value_{false};
Trigger<ESPTime> set_trigger_; Trigger<ESPTime> *set_trigger_ = new Trigger<ESPTime>();
TemplateLambda<ESPTime> f_; TemplateLambda<ESPTime> f_;
ESPPreferenceObject pref_; ESPPreferenceObject pref_;

View File

@@ -7,7 +7,8 @@ using namespace esphome::lock;
static const char *const TAG = "template.lock"; static const char *const TAG = "template.lock";
TemplateLock::TemplateLock() = default; TemplateLock::TemplateLock()
: lock_trigger_(new Trigger<>()), unlock_trigger_(new Trigger<>()), open_trigger_(new Trigger<>()) {}
void TemplateLock::setup() { void TemplateLock::setup() {
if (!this->f_.has_value()) if (!this->f_.has_value())
@@ -27,11 +28,11 @@ void TemplateLock::control(const lock::LockCall &call) {
auto state = *call.get_state(); auto state = *call.get_state();
if (state == LOCK_STATE_LOCKED) { if (state == LOCK_STATE_LOCKED) {
this->prev_trigger_ = &this->lock_trigger_; this->prev_trigger_ = this->lock_trigger_;
this->lock_trigger_.trigger(); this->lock_trigger_->trigger();
} else if (state == LOCK_STATE_UNLOCKED) { } else if (state == LOCK_STATE_UNLOCKED) {
this->prev_trigger_ = &this->unlock_trigger_; this->prev_trigger_ = this->unlock_trigger_;
this->unlock_trigger_.trigger(); this->unlock_trigger_->trigger();
} }
if (this->optimistic_) if (this->optimistic_)
@@ -41,11 +42,14 @@ void TemplateLock::open_latch() {
if (this->prev_trigger_ != nullptr) { if (this->prev_trigger_ != nullptr) {
this->prev_trigger_->stop_action(); this->prev_trigger_->stop_action();
} }
this->prev_trigger_ = &this->open_trigger_; this->prev_trigger_ = this->open_trigger_;
this->open_trigger_.trigger(); this->open_trigger_->trigger();
} }
void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; } float TemplateLock::get_setup_priority() const { return setup_priority::HARDWARE; }
Trigger<> *TemplateLock::get_lock_trigger() const { return this->lock_trigger_; }
Trigger<> *TemplateLock::get_unlock_trigger() const { return this->unlock_trigger_; }
Trigger<> *TemplateLock::get_open_trigger() const { return this->open_trigger_; }
void TemplateLock::dump_config() { void TemplateLock::dump_config() {
LOG_LOCK("", "Template Lock", this); LOG_LOCK("", "Template Lock", this);
ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_));

View File

@@ -15,9 +15,9 @@ class TemplateLock final : public lock::Lock, public Component {
void dump_config() override; void dump_config() override;
template<typename F> void set_state_lambda(F &&f) { this->f_.set(std::forward<F>(f)); } template<typename F> void set_state_lambda(F &&f) { this->f_.set(std::forward<F>(f)); }
Trigger<> *get_lock_trigger() { return &this->lock_trigger_; } Trigger<> *get_lock_trigger() const;
Trigger<> *get_unlock_trigger() { return &this->unlock_trigger_; } Trigger<> *get_unlock_trigger() const;
Trigger<> *get_open_trigger() { return &this->open_trigger_; } Trigger<> *get_open_trigger() const;
void set_optimistic(bool optimistic); void set_optimistic(bool optimistic);
void loop() override; void loop() override;
@@ -29,9 +29,9 @@ class TemplateLock final : public lock::Lock, public Component {
TemplateLambda<lock::LockState> f_; TemplateLambda<lock::LockState> f_;
bool optimistic_{false}; bool optimistic_{false};
Trigger<> lock_trigger_; Trigger<> *lock_trigger_;
Trigger<> unlock_trigger_; Trigger<> *unlock_trigger_;
Trigger<> open_trigger_; Trigger<> *open_trigger_;
Trigger<> *prev_trigger_{nullptr}; Trigger<> *prev_trigger_{nullptr};
}; };

View File

@@ -36,7 +36,7 @@ void TemplateNumber::update() {
} }
void TemplateNumber::control(float value) { void TemplateNumber::control(float value) {
this->set_trigger_.trigger(value); this->set_trigger_->trigger(value);
if (this->optimistic_) if (this->optimistic_)
this->publish_state(value); this->publish_state(value);

View File

@@ -17,7 +17,7 @@ class TemplateNumber final : public number::Number, public PollingComponent {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; } float get_setup_priority() const override { return setup_priority::HARDWARE; }
Trigger<float> *get_set_trigger() { return &this->set_trigger_; } Trigger<float> *get_set_trigger() const { return set_trigger_; }
void set_optimistic(bool optimistic) { optimistic_ = optimistic; } void set_optimistic(bool optimistic) { optimistic_ = optimistic; }
void set_initial_value(float initial_value) { initial_value_ = initial_value; } void set_initial_value(float initial_value) { initial_value_ = initial_value; }
void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; } void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
@@ -27,7 +27,7 @@ class TemplateNumber final : public number::Number, public PollingComponent {
bool optimistic_{false}; bool optimistic_{false};
float initial_value_{NAN}; float initial_value_{NAN};
bool restore_value_{false}; bool restore_value_{false};
Trigger<float> set_trigger_; Trigger<float> *set_trigger_ = new Trigger<float>();
TemplateLambda<float> f_; TemplateLambda<float> f_;
ESPPreferenceObject pref_; ESPPreferenceObject pref_;

View File

@@ -8,22 +8,22 @@ namespace esphome::template_ {
class TemplateBinaryOutput final : public output::BinaryOutput { class TemplateBinaryOutput final : public output::BinaryOutput {
public: public:
Trigger<bool> *get_trigger() { return &this->trigger_; } Trigger<bool> *get_trigger() const { return trigger_; }
protected: protected:
void write_state(bool state) override { this->trigger_.trigger(state); } void write_state(bool state) override { this->trigger_->trigger(state); }
Trigger<bool> trigger_; Trigger<bool> *trigger_ = new Trigger<bool>();
}; };
class TemplateFloatOutput final : public output::FloatOutput { class TemplateFloatOutput final : public output::FloatOutput {
public: public:
Trigger<float> *get_trigger() { return &this->trigger_; } Trigger<float> *get_trigger() const { return trigger_; }
protected: protected:
void write_state(float state) override { this->trigger_.trigger(state); } void write_state(float state) override { this->trigger_->trigger(state); }
Trigger<float> trigger_; Trigger<float> *trigger_ = new Trigger<float>();
}; };
} // namespace esphome::template_ } // namespace esphome::template_

View File

@@ -10,65 +10,35 @@ from esphome.const import (
CONF_OPTIONS, CONF_OPTIONS,
CONF_RESTORE_VALUE, CONF_RESTORE_VALUE,
CONF_SET_ACTION, CONF_SET_ACTION,
CONF_UPDATE_INTERVAL,
SCHEDULER_DONT_RUN,
) )
from esphome.core import TimePeriodMilliseconds
from esphome.cpp_generator import TemplateArguments
from .. import template_ns from .. import template_ns
TemplateSelect = template_ns.class_( TemplateSelect = template_ns.class_(
"TemplateSelect", select.Select, cg.PollingComponent "TemplateSelect", select.Select, cg.PollingComponent
) )
TemplateSelectWithSetAction = template_ns.class_(
"TemplateSelectWithSetAction", TemplateSelect
)
def validate(config): def validate(config):
errors = []
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
if config[CONF_OPTIMISTIC]: if config[CONF_OPTIMISTIC]:
errors.append( raise cv.Invalid("optimistic cannot be used with lambda")
cv.Invalid(
"optimistic cannot be used with lambda", path=[CONF_OPTIMISTIC]
)
)
if CONF_INITIAL_OPTION in config: if CONF_INITIAL_OPTION in config:
errors.append( raise cv.Invalid("initial_value cannot be used with lambda")
cv.Invalid(
"initial_value cannot be used with lambda",
path=[CONF_INITIAL_OPTION],
)
)
if CONF_RESTORE_VALUE in config: if CONF_RESTORE_VALUE in config:
errors.append( raise cv.Invalid("restore_value cannot be used with lambda")
cv.Invalid(
"restore_value cannot be used with lambda",
path=[CONF_RESTORE_VALUE],
)
)
elif CONF_INITIAL_OPTION in config: elif CONF_INITIAL_OPTION in config:
if config[CONF_INITIAL_OPTION] not in config[CONF_OPTIONS]: if config[CONF_INITIAL_OPTION] not in config[CONF_OPTIONS]:
errors.append( raise cv.Invalid(
cv.Invalid( f"initial_option '{config[CONF_INITIAL_OPTION]}' is not a valid option [{', '.join(config[CONF_OPTIONS])}]"
f"initial_option '{config[CONF_INITIAL_OPTION]}' is not a valid option [{', '.join(config[CONF_OPTIONS])}]",
path=[CONF_INITIAL_OPTION],
)
) )
else: else:
config[CONF_INITIAL_OPTION] = config[CONF_OPTIONS][0] config[CONF_INITIAL_OPTION] = config[CONF_OPTIONS][0]
if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config: if not config[CONF_OPTIMISTIC] and CONF_SET_ACTION not in config:
errors.append( raise cv.Invalid(
cv.Invalid( "Either optimistic mode must be enabled, or set_action must be set, to handle the option being set."
"Either optimistic mode must be enabled, or set_action must be set, to handle the option being set."
)
) )
if errors:
raise cv.MultipleInvalid(errors)
return config return config
@@ -92,34 +62,29 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config): async def to_code(config):
var_id = config[CONF_ID] var = cg.new_Pvariable(config[CONF_ID])
if CONF_SET_ACTION in config: await cg.register_component(var, config)
var_id.type = TemplateSelectWithSetAction await select.register_select(var, config, options=config[CONF_OPTIONS])
has_lambda = CONF_LAMBDA in config
optimistic = config.get(CONF_OPTIMISTIC, False)
restore_value = config.get(CONF_RESTORE_VALUE, False)
options = config[CONF_OPTIONS]
initial_option = config.get(CONF_INITIAL_OPTION, 0)
initial_option_index = options.index(initial_option) if not has_lambda else 0
var = cg.new_Pvariable(
var_id,
TemplateArguments(has_lambda, optimistic, restore_value, initial_option_index),
)
component_config = config.copy()
if not has_lambda:
# No point in polling if not using a lambda
component_config[CONF_UPDATE_INTERVAL] = TimePeriodMilliseconds(
milliseconds=SCHEDULER_DONT_RUN
)
await cg.register_component(var, component_config)
await select.register_select(var, config, options=options)
if CONF_LAMBDA in config: if CONF_LAMBDA in config:
lambda_ = await cg.process_lambda( template_ = await cg.process_lambda(
config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string) config[CONF_LAMBDA], [], return_type=cg.optional.template(cg.std_string)
) )
cg.add(var.set_lambda(lambda_)) cg.add(var.set_template(template_))
else:
# Only set if non-default to avoid bloating setup() function
if config[CONF_OPTIMISTIC]:
cg.add(var.set_optimistic(True))
initial_option_index = config[CONF_OPTIONS].index(config[CONF_INITIAL_OPTION])
# Only set if non-zero to avoid bloating setup() function
# (initial_option_index_ is zero-initialized in the header)
if initial_option_index != 0:
cg.add(var.set_initial_option_index(initial_option_index))
# Only set if True (default is False)
if config.get(CONF_RESTORE_VALUE):
cg.add(var.set_restore_value(True))
if CONF_SET_ACTION in config: if CONF_SET_ACTION in config:
await automation.build_automation( await automation.build_automation(

View File

@@ -3,17 +3,63 @@
namespace esphome::template_ { namespace esphome::template_ {
void dump_config_helper(BaseTemplateSelect *sel_comp, bool optimistic, bool has_lambda, static const char *const TAG = "template.select";
const size_t initial_option_index, bool restore_value) {
LOG_SELECT("", "Template Select", sel_comp); void TemplateSelect::setup() {
if (has_lambda) { if (this->f_.has_value())
LOG_UPDATE_INTERVAL(sel_comp); return;
size_t index = this->initial_option_index_;
if (this->restore_value_) {
this->pref_ = this->make_entity_preference<size_t>();
size_t restored_index;
if (this->pref_.load(&restored_index) && this->has_index(restored_index)) {
index = restored_index;
ESP_LOGD(TAG, "State from restore: %s", this->option_at(index));
} else {
ESP_LOGD(TAG, "State from initial (could not load or invalid stored index): %s", this->option_at(index));
}
} else { } else {
ESP_LOGCONFIG(TAG, ESP_LOGD(TAG, "State from initial: %s", this->option_at(index));
" Optimistic: %s\n" }
" Initial Option: %s\n"
" Restore Value: %s", this->publish_state(index);
YESNO(optimistic), sel_comp->option_at(initial_option_index), YESNO(restore_value)); }
void TemplateSelect::update() {
if (!this->f_.has_value())
return;
auto val = this->f_();
if (val.has_value()) {
if (!this->has_option(*val)) {
ESP_LOGE(TAG, "Lambda returned an invalid option: %s", (*val).c_str());
return;
}
this->publish_state(*val);
} }
} }
void TemplateSelect::control(size_t index) {
this->set_trigger_->trigger(StringRef(this->option_at(index)));
if (this->optimistic_)
this->publish_state(index);
if (this->restore_value_)
this->pref_.save(&index);
}
void TemplateSelect::dump_config() {
LOG_SELECT("", "Template Select", this);
LOG_UPDATE_INTERVAL(this);
if (this->f_.has_value())
return;
ESP_LOGCONFIG(TAG,
" Optimistic: %s\n"
" Initial Option: %s\n"
" Restore Value: %s",
YESNO(this->optimistic_), this->option_at(this->initial_option_index_), YESNO(this->restore_value_));
}
} // namespace esphome::template_ } // namespace esphome::template_

View File

@@ -8,83 +8,30 @@
#include "esphome/core/template_lambda.h" #include "esphome/core/template_lambda.h"
namespace esphome::template_ { namespace esphome::template_ {
static const char *const TAG = "template.select";
struct Empty {};
class BaseTemplateSelect : public select::Select, public PollingComponent {};
void dump_config_helper(BaseTemplateSelect *sel_comp, bool optimistic, bool has_lambda, size_t initial_option_index, class TemplateSelect final : public select::Select, public PollingComponent {
bool restore_value);
/// Base template select class - used when no set_action is configured
template<bool HAS_LAMBDA, bool OPTIMISTIC, bool RESTORE_VALUE, size_t INITIAL_OPTION_INDEX>
class TemplateSelect : public BaseTemplateSelect {
public: public:
template<typename F> void set_lambda(F &&f) { template<typename F> void set_template(F &&f) { this->f_.set(std::forward<F>(f)); }
if constexpr (HAS_LAMBDA) {
this->f_.set(std::forward<F>(f));
}
}
void setup() override { void setup() override;
if constexpr (!HAS_LAMBDA) { void update() override;
size_t index = INITIAL_OPTION_INDEX; void dump_config() override;
if constexpr (RESTORE_VALUE) {
this->pref_ = this->template make_entity_preference<size_t>();
if (this->pref_.load(&index) && this->has_index(index)) {
esph_log_d(TAG, "State from restore: %s", this->option_at(index));
} else {
index = INITIAL_OPTION_INDEX;
esph_log_d(TAG, "State from initial (no valid stored index): %s", this->option_at(INITIAL_OPTION_INDEX));
}
} else {
esph_log_d(TAG, "State from initial: %s", this->option_at(INITIAL_OPTION_INDEX));
}
this->publish_state(index);
}
}
void update() override {
if constexpr (HAS_LAMBDA) {
auto val = this->f_();
if (val.has_value()) {
if (!this->has_option(*val)) {
esph_log_e(TAG, "Lambda returned an invalid option: %s", (*val).c_str());
return;
}
this->publish_state(*val);
}
}
}
void dump_config() override {
dump_config_helper(this, OPTIMISTIC, HAS_LAMBDA, INITIAL_OPTION_INDEX, RESTORE_VALUE);
};
float get_setup_priority() const override { return setup_priority::HARDWARE; } float get_setup_priority() const override { return setup_priority::HARDWARE; }
protected: Trigger<StringRef> *get_set_trigger() const { return this->set_trigger_; }
void control(size_t index) override { void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
if constexpr (OPTIMISTIC) void set_initial_option_index(size_t initial_option_index) { this->initial_option_index_ = initial_option_index; }
this->publish_state(index); void set_restore_value(bool restore_value) { this->restore_value_ = restore_value; }
if constexpr (RESTORE_VALUE)
this->pref_.save(&index);
}
[[no_unique_address]] std::conditional_t<HAS_LAMBDA, TemplateLambda<std::string>, Empty> f_{};
[[no_unique_address]] std::conditional_t<RESTORE_VALUE, ESPPreferenceObject, Empty> pref_{};
};
/// Template select with set_action trigger - only instantiated when set_action is configured
template<bool HAS_LAMBDA, bool OPTIMISTIC, bool RESTORE_VALUE, size_t INITIAL_OPTION_INDEX>
class TemplateSelectWithSetAction final
: public TemplateSelect<HAS_LAMBDA, OPTIMISTIC, RESTORE_VALUE, INITIAL_OPTION_INDEX> {
public:
Trigger<StringRef> *get_set_trigger() { return &this->set_trigger_; }
protected: protected:
void control(size_t index) override { void control(size_t index) override;
this->set_trigger_.trigger(StringRef(this->option_at(index))); bool optimistic_ = false;
TemplateSelect<HAS_LAMBDA, OPTIMISTIC, RESTORE_VALUE, INITIAL_OPTION_INDEX>::control(index); size_t initial_option_index_{0};
} bool restore_value_ = false;
Trigger<StringRef> set_trigger_; Trigger<StringRef> *set_trigger_ = new Trigger<StringRef>();
TemplateLambda<std::string> f_;
ESPPreferenceObject pref_;
}; };
} // namespace esphome::template_ } // namespace esphome::template_

View File

@@ -5,7 +5,7 @@ namespace esphome::template_ {
static const char *const TAG = "template.switch"; static const char *const TAG = "template.switch";
TemplateSwitch::TemplateSwitch() = default; TemplateSwitch::TemplateSwitch() : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {}
void TemplateSwitch::loop() { void TemplateSwitch::loop() {
auto s = this->f_(); auto s = this->f_();
@@ -19,11 +19,11 @@ void TemplateSwitch::write_state(bool state) {
} }
if (state) { if (state) {
this->prev_trigger_ = &this->turn_on_trigger_; this->prev_trigger_ = this->turn_on_trigger_;
this->turn_on_trigger_.trigger(); this->turn_on_trigger_->trigger();
} else { } else {
this->prev_trigger_ = &this->turn_off_trigger_; this->prev_trigger_ = this->turn_off_trigger_;
this->turn_off_trigger_.trigger(); this->turn_off_trigger_->trigger();
} }
if (this->optimistic_) if (this->optimistic_)
@@ -32,8 +32,8 @@ void TemplateSwitch::write_state(bool state) {
void TemplateSwitch::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void TemplateSwitch::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
bool TemplateSwitch::assumed_state() { return this->assumed_state_; } bool TemplateSwitch::assumed_state() { return this->assumed_state_; }
float TemplateSwitch::get_setup_priority() const { return setup_priority::HARDWARE - 2.0f; } float TemplateSwitch::get_setup_priority() const { return setup_priority::HARDWARE - 2.0f; }
Trigger<> *TemplateSwitch::get_turn_on_trigger() { return &this->turn_on_trigger_; } Trigger<> *TemplateSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; }
Trigger<> *TemplateSwitch::get_turn_off_trigger() { return &this->turn_off_trigger_; } Trigger<> *TemplateSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; }
void TemplateSwitch::setup() { void TemplateSwitch::setup() {
if (!this->f_.has_value()) if (!this->f_.has_value())
this->disable_loop(); this->disable_loop();

View File

@@ -15,8 +15,8 @@ class TemplateSwitch final : public switch_::Switch, public Component {
void dump_config() override; void dump_config() override;
template<typename F> void set_state_lambda(F &&f) { this->f_.set(std::forward<F>(f)); } template<typename F> void set_state_lambda(F &&f) { this->f_.set(std::forward<F>(f)); }
Trigger<> *get_turn_on_trigger(); Trigger<> *get_turn_on_trigger() const;
Trigger<> *get_turn_off_trigger(); Trigger<> *get_turn_off_trigger() const;
void set_optimistic(bool optimistic); void set_optimistic(bool optimistic);
void set_assumed_state(bool assumed_state); void set_assumed_state(bool assumed_state);
void loop() override; void loop() override;
@@ -31,9 +31,9 @@ class TemplateSwitch final : public switch_::Switch, public Component {
TemplateLambda<bool> f_; TemplateLambda<bool> f_;
bool optimistic_{false}; bool optimistic_{false};
bool assumed_state_{false}; bool assumed_state_{false};
Trigger<> turn_on_trigger_; Trigger<> *turn_on_trigger_;
Trigger<> turn_off_trigger_; Trigger<> *turn_off_trigger_;
Trigger<> *prev_trigger_{nullptr}; // Points to one of the above Trigger<> *prev_trigger_{nullptr};
}; };
} // namespace esphome::template_ } // namespace esphome::template_

View File

@@ -47,7 +47,7 @@ void TemplateText::update() {
} }
void TemplateText::control(const std::string &value) { void TemplateText::control(const std::string &value) {
this->set_trigger_.trigger(value); this->set_trigger_->trigger(value);
if (this->optimistic_) if (this->optimistic_)
this->publish_state(value); this->publish_state(value);

View File

@@ -68,7 +68,7 @@ class TemplateText final : public text::Text, public PollingComponent {
void dump_config() override; void dump_config() override;
float get_setup_priority() const override { return setup_priority::HARDWARE; } float get_setup_priority() const override { return setup_priority::HARDWARE; }
Trigger<std::string> *get_set_trigger() { return &this->set_trigger_; } Trigger<std::string> *get_set_trigger() const { return this->set_trigger_; }
void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; }
void set_initial_value(const char *initial_value) { this->initial_value_ = initial_value; } void set_initial_value(const char *initial_value) { this->initial_value_ = initial_value; }
/// Prevent accidental use of std::string which would dangle /// Prevent accidental use of std::string which would dangle
@@ -79,7 +79,7 @@ class TemplateText final : public text::Text, public PollingComponent {
void control(const std::string &value) override; void control(const std::string &value) override;
bool optimistic_ = false; bool optimistic_ = false;
const char *initial_value_{nullptr}; const char *initial_value_{nullptr};
Trigger<std::string> set_trigger_; Trigger<std::string> *set_trigger_ = new Trigger<std::string>();
TemplateLambda<std::string> f_{}; TemplateLambda<std::string> f_{};
TemplateTextSaverBase *pref_ = nullptr; TemplateTextSaverBase *pref_ = nullptr;

View File

@@ -7,7 +7,12 @@ using namespace esphome::valve;
static const char *const TAG = "template.valve"; static const char *const TAG = "template.valve";
TemplateValve::TemplateValve() = default; TemplateValve::TemplateValve()
: open_trigger_(new Trigger<>()),
close_trigger_(new Trigger<>),
stop_trigger_(new Trigger<>()),
toggle_trigger_(new Trigger<>()),
position_trigger_(new Trigger<float>()) {}
void TemplateValve::setup() { void TemplateValve::setup() {
switch (this->restore_mode_) { switch (this->restore_mode_) {
@@ -51,10 +56,10 @@ void TemplateValve::set_optimistic(bool optimistic) { this->optimistic_ = optimi
void TemplateValve::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } void TemplateValve::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; }
float TemplateValve::get_setup_priority() const { return setup_priority::HARDWARE; } float TemplateValve::get_setup_priority() const { return setup_priority::HARDWARE; }
Trigger<> *TemplateValve::get_open_trigger() { return &this->open_trigger_; } Trigger<> *TemplateValve::get_open_trigger() const { return this->open_trigger_; }
Trigger<> *TemplateValve::get_close_trigger() { return &this->close_trigger_; } Trigger<> *TemplateValve::get_close_trigger() const { return this->close_trigger_; }
Trigger<> *TemplateValve::get_stop_trigger() { return &this->stop_trigger_; } Trigger<> *TemplateValve::get_stop_trigger() const { return this->stop_trigger_; }
Trigger<> *TemplateValve::get_toggle_trigger() { return &this->toggle_trigger_; } Trigger<> *TemplateValve::get_toggle_trigger() const { return this->toggle_trigger_; }
void TemplateValve::dump_config() { void TemplateValve::dump_config() {
LOG_VALVE("", "Template Valve", this); LOG_VALVE("", "Template Valve", this);
@@ -67,14 +72,14 @@ void TemplateValve::dump_config() {
void TemplateValve::control(const ValveCall &call) { void TemplateValve::control(const ValveCall &call) {
if (call.get_stop()) { if (call.get_stop()) {
this->stop_prev_trigger_(); this->stop_prev_trigger_();
this->stop_trigger_.trigger(); this->stop_trigger_->trigger();
this->prev_command_trigger_ = &this->stop_trigger_; this->prev_command_trigger_ = this->stop_trigger_;
this->publish_state(); this->publish_state();
} }
if (call.get_toggle().has_value()) { if (call.get_toggle().has_value()) {
this->stop_prev_trigger_(); this->stop_prev_trigger_();
this->toggle_trigger_.trigger(); this->toggle_trigger_->trigger();
this->prev_command_trigger_ = &this->toggle_trigger_; this->prev_command_trigger_ = this->toggle_trigger_;
this->publish_state(); this->publish_state();
} }
if (call.get_position().has_value()) { if (call.get_position().has_value()) {
@@ -82,13 +87,13 @@ void TemplateValve::control(const ValveCall &call) {
this->stop_prev_trigger_(); this->stop_prev_trigger_();
if (pos == VALVE_OPEN) { if (pos == VALVE_OPEN) {
this->open_trigger_.trigger(); this->open_trigger_->trigger();
this->prev_command_trigger_ = &this->open_trigger_; this->prev_command_trigger_ = this->open_trigger_;
} else if (pos == VALVE_CLOSED) { } else if (pos == VALVE_CLOSED) {
this->close_trigger_.trigger(); this->close_trigger_->trigger();
this->prev_command_trigger_ = &this->close_trigger_; this->prev_command_trigger_ = this->close_trigger_;
} else { } else {
this->position_trigger_.trigger(pos); this->position_trigger_->trigger(pos);
} }
if (this->optimistic_) { if (this->optimistic_) {
@@ -108,7 +113,7 @@ ValveTraits TemplateValve::get_traits() {
return traits; return traits;
} }
Trigger<float> *TemplateValve::get_position_trigger() { return &this->position_trigger_; } Trigger<float> *TemplateValve::get_position_trigger() const { return this->position_trigger_; }
void TemplateValve::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; } void TemplateValve::set_has_stop(bool has_stop) { this->has_stop_ = has_stop; }
void TemplateValve::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; } void TemplateValve::set_has_toggle(bool has_toggle) { this->has_toggle_ = has_toggle; }

View File

@@ -18,11 +18,11 @@ class TemplateValve final : public valve::Valve, public Component {
TemplateValve(); TemplateValve();
template<typename F> void set_state_lambda(F &&f) { this->state_f_.set(std::forward<F>(f)); } template<typename F> void set_state_lambda(F &&f) { this->state_f_.set(std::forward<F>(f)); }
Trigger<> *get_open_trigger(); Trigger<> *get_open_trigger() const;
Trigger<> *get_close_trigger(); Trigger<> *get_close_trigger() const;
Trigger<> *get_stop_trigger(); Trigger<> *get_stop_trigger() const;
Trigger<> *get_toggle_trigger(); Trigger<> *get_toggle_trigger() const;
Trigger<float> *get_position_trigger(); Trigger<float> *get_position_trigger() const;
void set_optimistic(bool optimistic); void set_optimistic(bool optimistic);
void set_assumed_state(bool assumed_state); void set_assumed_state(bool assumed_state);
void set_has_stop(bool has_stop); void set_has_stop(bool has_stop);
@@ -45,14 +45,14 @@ class TemplateValve final : public valve::Valve, public Component {
TemplateLambda<float> state_f_; TemplateLambda<float> state_f_;
bool assumed_state_{false}; bool assumed_state_{false};
bool optimistic_{false}; bool optimistic_{false};
Trigger<> open_trigger_; Trigger<> *open_trigger_;
Trigger<> close_trigger_; Trigger<> *close_trigger_;
bool has_stop_{false}; bool has_stop_{false};
bool has_toggle_{false}; bool has_toggle_{false};
Trigger<> stop_trigger_; Trigger<> *stop_trigger_;
Trigger<> toggle_trigger_; Trigger<> *toggle_trigger_;
Trigger<> *prev_command_trigger_{nullptr}; Trigger<> *prev_command_trigger_{nullptr};
Trigger<float> position_trigger_; Trigger<float> *position_trigger_;
bool has_position_{false}; bool has_position_{false};
}; };

View File

@@ -5,7 +5,7 @@ namespace esphome::template_ {
static const char *const TAG = "template.water_heater"; static const char *const TAG = "template.water_heater";
TemplateWaterHeater::TemplateWaterHeater() = default; TemplateWaterHeater::TemplateWaterHeater() : set_trigger_(new Trigger<>()) {}
void TemplateWaterHeater::setup() { void TemplateWaterHeater::setup() {
if (this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE || if (this->restore_mode_ == TemplateWaterHeaterRestoreMode::WATER_HEATER_RESTORE ||
@@ -78,7 +78,7 @@ void TemplateWaterHeater::control(const water_heater::WaterHeaterCall &call) {
} }
} }
this->set_trigger_.trigger(); this->set_trigger_->trigger();
if (this->optimistic_) { if (this->optimistic_) {
this->publish_state(); this->publish_state();

View File

@@ -28,7 +28,7 @@ class TemplateWaterHeater : public Component, public water_heater::WaterHeater {
this->supported_modes_ = modes; this->supported_modes_ = modes;
} }
Trigger<> *get_set_trigger() { return &this->set_trigger_; } Trigger<> *get_set_trigger() const { return this->set_trigger_; }
void setup() override; void setup() override;
void loop() override; void loop() override;
@@ -42,7 +42,7 @@ class TemplateWaterHeater : public Component, public water_heater::WaterHeater {
water_heater::WaterHeaterTraits traits() override; water_heater::WaterHeaterTraits traits() override;
// Ordered to minimize padding on 32-bit: 4-byte members first, then smaller // Ordered to minimize padding on 32-bit: 4-byte members first, then smaller
Trigger<> set_trigger_; Trigger<> *set_trigger_;
TemplateLambda<float> current_temperature_f_; TemplateLambda<float> current_temperature_f_;
TemplateLambda<water_heater::WaterHeaterMode> mode_f_; TemplateLambda<water_heater::WaterHeaterMode> mode_f_;
TemplateWaterHeaterRestoreMode restore_mode_{WATER_HEATER_NO_RESTORE}; TemplateWaterHeaterRestoreMode restore_mode_{WATER_HEATER_NO_RESTORE};

View File

@@ -499,7 +499,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
} }
bool action_ready = false; bool action_ready = false;
Trigger<> *trig = &this->idle_action_trigger_, *trig_fan = nullptr; Trigger<> *trig = this->idle_action_trigger_, *trig_fan = nullptr;
switch (action) { switch (action) {
case climate::CLIMATE_ACTION_OFF: case climate::CLIMATE_ACTION_OFF:
case climate::CLIMATE_ACTION_IDLE: case climate::CLIMATE_ACTION_IDLE:
@@ -529,10 +529,10 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME); this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME);
if (this->supports_fan_with_cooling_) { if (this->supports_fan_with_cooling_) {
this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON); this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON);
trig_fan = &this->fan_only_action_trigger_; trig_fan = this->fan_only_action_trigger_;
} }
this->cooling_max_runtime_exceeded_ = false; this->cooling_max_runtime_exceeded_ = false;
trig = &this->cool_action_trigger_; trig = this->cool_action_trigger_;
ESP_LOGVV(TAG, "Switching to COOLING action"); ESP_LOGVV(TAG, "Switching to COOLING action");
action_ready = true; action_ready = true;
} }
@@ -543,10 +543,10 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
this->start_timer_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME); this->start_timer_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME);
if (this->supports_fan_with_heating_) { if (this->supports_fan_with_heating_) {
this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON); this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON);
trig_fan = &this->fan_only_action_trigger_; trig_fan = this->fan_only_action_trigger_;
} }
this->heating_max_runtime_exceeded_ = false; this->heating_max_runtime_exceeded_ = false;
trig = &this->heat_action_trigger_; trig = this->heat_action_trigger_;
ESP_LOGVV(TAG, "Switching to HEATING action"); ESP_LOGVV(TAG, "Switching to HEATING action");
action_ready = true; action_ready = true;
} }
@@ -558,7 +558,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
} else { } else {
this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON); this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON);
} }
trig = &this->fan_only_action_trigger_; trig = this->fan_only_action_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_ONLY action"); ESP_LOGVV(TAG, "Switching to FAN_ONLY action");
action_ready = true; action_ready = true;
} }
@@ -567,7 +567,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
if (this->drying_action_ready_()) { if (this->drying_action_ready_()) {
this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_ON); this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_ON);
this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON); this->start_timer_(thermostat::THERMOSTAT_TIMER_FANNING_ON);
trig = &this->dry_action_trigger_; trig = this->dry_action_trigger_;
ESP_LOGVV(TAG, "Switching to DRYING action"); ESP_LOGVV(TAG, "Switching to DRYING action");
action_ready = true; action_ready = true;
} }
@@ -586,7 +586,9 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu
} }
this->action = action; this->action = action;
this->prev_action_trigger_ = trig; this->prev_action_trigger_ = trig;
trig->trigger(); if (trig != nullptr) {
trig->trigger();
}
// if enabled, call the fan_only action with cooling/heating actions // if enabled, call the fan_only action with cooling/heating actions
if (trig_fan != nullptr) { if (trig_fan != nullptr) {
ESP_LOGVV(TAG, "Calling FAN_ONLY action with HEATING/COOLING action"); ESP_LOGVV(TAG, "Calling FAN_ONLY action with HEATING/COOLING action");
@@ -632,14 +634,14 @@ void ThermostatClimate::trigger_supplemental_action_() {
if (!this->timer_active_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME)) { if (!this->timer_active_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME)) {
this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME); this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_MAX_RUN_TIME);
} }
trig = &this->supplemental_cool_action_trigger_; trig = this->supplemental_cool_action_trigger_;
ESP_LOGVV(TAG, "Calling supplemental COOLING action"); ESP_LOGVV(TAG, "Calling supplemental COOLING action");
break; break;
case climate::CLIMATE_ACTION_HEATING: case climate::CLIMATE_ACTION_HEATING:
if (!this->timer_active_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME)) { if (!this->timer_active_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME)) {
this->start_timer_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME); this->start_timer_(thermostat::THERMOSTAT_TIMER_HEATING_MAX_RUN_TIME);
} }
trig = &this->supplemental_heat_action_trigger_; trig = this->supplemental_heat_action_trigger_;
ESP_LOGVV(TAG, "Calling supplemental HEATING action"); ESP_LOGVV(TAG, "Calling supplemental HEATING action");
break; break;
default: default:
@@ -658,24 +660,24 @@ void ThermostatClimate::switch_to_humidity_control_action_(HumidificationAction
return; return;
} }
Trigger<> *trig = &this->humidity_control_off_action_trigger_; Trigger<> *trig = this->humidity_control_off_action_trigger_;
switch (action) { switch (action) {
case THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF: case THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF:
// trig = &this->humidity_control_off_action_trigger_; // trig = this->humidity_control_off_action_trigger_;
ESP_LOGVV(TAG, "Switching to HUMIDIFICATION_OFF action"); ESP_LOGVV(TAG, "Switching to HUMIDIFICATION_OFF action");
break; break;
case THERMOSTAT_HUMIDITY_CONTROL_ACTION_DEHUMIDIFY: case THERMOSTAT_HUMIDITY_CONTROL_ACTION_DEHUMIDIFY:
trig = &this->humidity_control_dehumidify_action_trigger_; trig = this->humidity_control_dehumidify_action_trigger_;
ESP_LOGVV(TAG, "Switching to DEHUMIDIFY action"); ESP_LOGVV(TAG, "Switching to DEHUMIDIFY action");
break; break;
case THERMOSTAT_HUMIDITY_CONTROL_ACTION_HUMIDIFY: case THERMOSTAT_HUMIDITY_CONTROL_ACTION_HUMIDIFY:
trig = &this->humidity_control_humidify_action_trigger_; trig = this->humidity_control_humidify_action_trigger_;
ESP_LOGVV(TAG, "Switching to HUMIDIFY action"); ESP_LOGVV(TAG, "Switching to HUMIDIFY action");
break; break;
case THERMOSTAT_HUMIDITY_CONTROL_ACTION_NONE: case THERMOSTAT_HUMIDITY_CONTROL_ACTION_NONE:
default: default:
action = THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF; action = THERMOSTAT_HUMIDITY_CONTROL_ACTION_OFF;
// trig = &this->humidity_control_off_action_trigger_; // trig = this->humidity_control_off_action_trigger_;
} }
if (this->prev_humidity_control_trigger_ != nullptr) { if (this->prev_humidity_control_trigger_ != nullptr) {
@@ -684,7 +686,9 @@ void ThermostatClimate::switch_to_humidity_control_action_(HumidificationAction
} }
this->humidification_action = action; this->humidification_action = action;
this->prev_humidity_control_trigger_ = trig; this->prev_humidity_control_trigger_ = trig;
trig->trigger(); if (trig != nullptr) {
trig->trigger();
}
} }
void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bool publish_state) { void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bool publish_state) {
@@ -699,60 +703,62 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bo
this->publish_state(); this->publish_state();
if (this->fan_mode_ready_()) { if (this->fan_mode_ready_()) {
Trigger<> *trig = &this->fan_mode_auto_trigger_; Trigger<> *trig = this->fan_mode_auto_trigger_;
switch (fan_mode) { switch (fan_mode) {
case climate::CLIMATE_FAN_ON: case climate::CLIMATE_FAN_ON:
trig = &this->fan_mode_on_trigger_; trig = this->fan_mode_on_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_ON mode"); ESP_LOGVV(TAG, "Switching to FAN_ON mode");
break; break;
case climate::CLIMATE_FAN_OFF: case climate::CLIMATE_FAN_OFF:
trig = &this->fan_mode_off_trigger_; trig = this->fan_mode_off_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_OFF mode"); ESP_LOGVV(TAG, "Switching to FAN_OFF mode");
break; break;
case climate::CLIMATE_FAN_AUTO: case climate::CLIMATE_FAN_AUTO:
// trig = &this->fan_mode_auto_trigger_; // trig = this->fan_mode_auto_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_AUTO mode"); ESP_LOGVV(TAG, "Switching to FAN_AUTO mode");
break; break;
case climate::CLIMATE_FAN_LOW: case climate::CLIMATE_FAN_LOW:
trig = &this->fan_mode_low_trigger_; trig = this->fan_mode_low_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_LOW mode"); ESP_LOGVV(TAG, "Switching to FAN_LOW mode");
break; break;
case climate::CLIMATE_FAN_MEDIUM: case climate::CLIMATE_FAN_MEDIUM:
trig = &this->fan_mode_medium_trigger_; trig = this->fan_mode_medium_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_MEDIUM mode"); ESP_LOGVV(TAG, "Switching to FAN_MEDIUM mode");
break; break;
case climate::CLIMATE_FAN_HIGH: case climate::CLIMATE_FAN_HIGH:
trig = &this->fan_mode_high_trigger_; trig = this->fan_mode_high_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_HIGH mode"); ESP_LOGVV(TAG, "Switching to FAN_HIGH mode");
break; break;
case climate::CLIMATE_FAN_MIDDLE: case climate::CLIMATE_FAN_MIDDLE:
trig = &this->fan_mode_middle_trigger_; trig = this->fan_mode_middle_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_MIDDLE mode"); ESP_LOGVV(TAG, "Switching to FAN_MIDDLE mode");
break; break;
case climate::CLIMATE_FAN_FOCUS: case climate::CLIMATE_FAN_FOCUS:
trig = &this->fan_mode_focus_trigger_; trig = this->fan_mode_focus_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_FOCUS mode"); ESP_LOGVV(TAG, "Switching to FAN_FOCUS mode");
break; break;
case climate::CLIMATE_FAN_DIFFUSE: case climate::CLIMATE_FAN_DIFFUSE:
trig = &this->fan_mode_diffuse_trigger_; trig = this->fan_mode_diffuse_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_DIFFUSE mode"); ESP_LOGVV(TAG, "Switching to FAN_DIFFUSE mode");
break; break;
case climate::CLIMATE_FAN_QUIET: case climate::CLIMATE_FAN_QUIET:
trig = &this->fan_mode_quiet_trigger_; trig = this->fan_mode_quiet_trigger_;
ESP_LOGVV(TAG, "Switching to FAN_QUIET mode"); ESP_LOGVV(TAG, "Switching to FAN_QUIET mode");
break; break;
default: default:
// we cannot report an invalid mode back to HA (even if it asked for one) // we cannot report an invalid mode back to HA (even if it asked for one)
// and must assume some valid value // and must assume some valid value
fan_mode = climate::CLIMATE_FAN_AUTO; fan_mode = climate::CLIMATE_FAN_AUTO;
// trig = &this->fan_mode_auto_trigger_; // trig = this->fan_mode_auto_trigger_;
} }
if (this->prev_fan_mode_trigger_ != nullptr) { if (this->prev_fan_mode_trigger_ != nullptr) {
this->prev_fan_mode_trigger_->stop_action(); this->prev_fan_mode_trigger_->stop_action();
this->prev_fan_mode_trigger_ = nullptr; this->prev_fan_mode_trigger_ = nullptr;
} }
this->start_timer_(thermostat::THERMOSTAT_TIMER_FAN_MODE); this->start_timer_(thermostat::THERMOSTAT_TIMER_FAN_MODE);
trig->trigger(); if (trig != nullptr) {
trig->trigger();
}
this->prev_fan_mode_ = fan_mode; this->prev_fan_mode_ = fan_mode;
this->prev_fan_mode_trigger_ = trig; this->prev_fan_mode_trigger_ = trig;
} }
@@ -769,25 +775,25 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_
this->prev_mode_trigger_->stop_action(); this->prev_mode_trigger_->stop_action();
this->prev_mode_trigger_ = nullptr; this->prev_mode_trigger_ = nullptr;
} }
Trigger<> *trig = &this->off_mode_trigger_; Trigger<> *trig = this->off_mode_trigger_;
switch (mode) { switch (mode) {
case climate::CLIMATE_MODE_AUTO: case climate::CLIMATE_MODE_AUTO:
trig = &this->auto_mode_trigger_; trig = this->auto_mode_trigger_;
break; break;
case climate::CLIMATE_MODE_HEAT_COOL: case climate::CLIMATE_MODE_HEAT_COOL:
trig = &this->heat_cool_mode_trigger_; trig = this->heat_cool_mode_trigger_;
break; break;
case climate::CLIMATE_MODE_COOL: case climate::CLIMATE_MODE_COOL:
trig = &this->cool_mode_trigger_; trig = this->cool_mode_trigger_;
break; break;
case climate::CLIMATE_MODE_HEAT: case climate::CLIMATE_MODE_HEAT:
trig = &this->heat_mode_trigger_; trig = this->heat_mode_trigger_;
break; break;
case climate::CLIMATE_MODE_FAN_ONLY: case climate::CLIMATE_MODE_FAN_ONLY:
trig = &this->fan_only_mode_trigger_; trig = this->fan_only_mode_trigger_;
break; break;
case climate::CLIMATE_MODE_DRY: case climate::CLIMATE_MODE_DRY:
trig = &this->dry_mode_trigger_; trig = this->dry_mode_trigger_;
break; break;
case climate::CLIMATE_MODE_OFF: case climate::CLIMATE_MODE_OFF:
default: default:
@@ -796,7 +802,9 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_
mode = climate::CLIMATE_MODE_OFF; mode = climate::CLIMATE_MODE_OFF;
// trig = this->off_mode_trigger_; // trig = this->off_mode_trigger_;
} }
trig->trigger(); if (trig != nullptr) {
trig->trigger();
}
this->mode = mode; this->mode = mode;
this->prev_mode_ = mode; this->prev_mode_ = mode;
this->prev_mode_trigger_ = trig; this->prev_mode_trigger_ = trig;
@@ -816,27 +824,29 @@ void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mo
this->prev_swing_mode_trigger_->stop_action(); this->prev_swing_mode_trigger_->stop_action();
this->prev_swing_mode_trigger_ = nullptr; this->prev_swing_mode_trigger_ = nullptr;
} }
Trigger<> *trig = &this->swing_mode_off_trigger_; Trigger<> *trig = this->swing_mode_off_trigger_;
switch (swing_mode) { switch (swing_mode) {
case climate::CLIMATE_SWING_BOTH: case climate::CLIMATE_SWING_BOTH:
trig = &this->swing_mode_both_trigger_; trig = this->swing_mode_both_trigger_;
break; break;
case climate::CLIMATE_SWING_HORIZONTAL: case climate::CLIMATE_SWING_HORIZONTAL:
trig = &this->swing_mode_horizontal_trigger_; trig = this->swing_mode_horizontal_trigger_;
break; break;
case climate::CLIMATE_SWING_OFF: case climate::CLIMATE_SWING_OFF:
// trig = &this->swing_mode_off_trigger_; // trig = this->swing_mode_off_trigger_;
break; break;
case climate::CLIMATE_SWING_VERTICAL: case climate::CLIMATE_SWING_VERTICAL:
trig = &this->swing_mode_vertical_trigger_; trig = this->swing_mode_vertical_trigger_;
break; break;
default: default:
// we cannot report an invalid mode back to HA (even if it asked for one) // we cannot report an invalid mode back to HA (even if it asked for one)
// and must assume some valid value // and must assume some valid value
swing_mode = climate::CLIMATE_SWING_OFF; swing_mode = climate::CLIMATE_SWING_OFF;
// trig = &this->swing_mode_off_trigger_; // trig = this->swing_mode_off_trigger_;
}
if (trig != nullptr) {
trig->trigger();
} }
trig->trigger();
this->swing_mode = swing_mode; this->swing_mode = swing_mode;
this->prev_swing_mode_ = swing_mode; this->prev_swing_mode_ = swing_mode;
this->prev_swing_mode_trigger_ = trig; this->prev_swing_mode_trigger_ = trig;
@@ -1014,8 +1024,10 @@ void ThermostatClimate::check_humidity_change_trigger_() {
this->prev_target_humidity_ = this->target_humidity; this->prev_target_humidity_ = this->target_humidity;
} }
// trigger the action // trigger the action
Trigger<> *trig = &this->humidity_change_trigger_; Trigger<> *trig = this->humidity_change_trigger_;
trig->trigger(); if (trig != nullptr) {
trig->trigger();
}
} }
void ThermostatClimate::check_temperature_change_trigger_() { void ThermostatClimate::check_temperature_change_trigger_() {
@@ -1038,8 +1050,10 @@ void ThermostatClimate::check_temperature_change_trigger_() {
} }
} }
// trigger the action // trigger the action
Trigger<> *trig = &this->temperature_change_trigger_; Trigger<> *trig = this->temperature_change_trigger_;
trig->trigger(); if (trig != nullptr) {
trig->trigger();
}
} }
bool ThermostatClimate::cooling_required_() { bool ThermostatClimate::cooling_required_() {
@@ -1188,10 +1202,12 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) {
if (config != nullptr) { if (config != nullptr) {
ESP_LOGV(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset))); ESP_LOGV(TAG, "Preset %s requested", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
if (this->change_preset_internal_(*config) || (!this->preset.has_value()) || this->preset.value() != preset) { if (this->change_preset_internal_(*config) || (!this->preset.has_value()) || this->preset.value() != preset) {
// Fire preset changed trigger // Fire any preset changed trigger if defined
Trigger<> *trig = &this->preset_change_trigger_; Trigger<> *trig = this->preset_change_trigger_;
this->set_preset_(preset); this->set_preset_(preset);
trig->trigger(); if (trig != nullptr) {
trig->trigger();
}
this->refresh(); this->refresh();
ESP_LOGI(TAG, "Preset %s applied", LOG_STR_ARG(climate::climate_preset_to_string(preset))); ESP_LOGI(TAG, "Preset %s applied", LOG_STR_ARG(climate::climate_preset_to_string(preset)));
@@ -1218,11 +1234,13 @@ void ThermostatClimate::change_custom_preset_(const char *custom_preset, size_t
ESP_LOGV(TAG, "Custom preset %s requested", custom_preset); ESP_LOGV(TAG, "Custom preset %s requested", custom_preset);
if (this->change_preset_internal_(*config) || !this->has_custom_preset() || if (this->change_preset_internal_(*config) || !this->has_custom_preset() ||
this->get_custom_preset() != custom_preset) { this->get_custom_preset() != custom_preset) {
// Fire preset changed trigger // Fire any preset changed trigger if defined
Trigger<> *trig = &this->preset_change_trigger_; Trigger<> *trig = this->preset_change_trigger_;
// Use the base class method which handles pointer lookup and preset reset internally // Use the base class method which handles pointer lookup and preset reset internally
this->set_custom_preset_(custom_preset); this->set_custom_preset_(custom_preset);
trig->trigger(); if (trig != nullptr) {
trig->trigger();
}
this->refresh(); this->refresh();
ESP_LOGI(TAG, "Custom preset %s applied", custom_preset); ESP_LOGI(TAG, "Custom preset %s applied", custom_preset);
@@ -1287,7 +1305,41 @@ void ThermostatClimate::set_custom_preset_config(std::initializer_list<CustomPre
this->custom_preset_config_ = presets; this->custom_preset_config_ = presets;
} }
ThermostatClimate::ThermostatClimate() = default; ThermostatClimate::ThermostatClimate()
: cool_action_trigger_(new Trigger<>()),
supplemental_cool_action_trigger_(new Trigger<>()),
cool_mode_trigger_(new Trigger<>()),
dry_action_trigger_(new Trigger<>()),
dry_mode_trigger_(new Trigger<>()),
heat_action_trigger_(new Trigger<>()),
supplemental_heat_action_trigger_(new Trigger<>()),
heat_mode_trigger_(new Trigger<>()),
heat_cool_mode_trigger_(new Trigger<>()),
auto_mode_trigger_(new Trigger<>()),
idle_action_trigger_(new Trigger<>()),
off_mode_trigger_(new Trigger<>()),
fan_only_action_trigger_(new Trigger<>()),
fan_only_mode_trigger_(new Trigger<>()),
fan_mode_on_trigger_(new Trigger<>()),
fan_mode_off_trigger_(new Trigger<>()),
fan_mode_auto_trigger_(new Trigger<>()),
fan_mode_low_trigger_(new Trigger<>()),
fan_mode_medium_trigger_(new Trigger<>()),
fan_mode_high_trigger_(new Trigger<>()),
fan_mode_middle_trigger_(new Trigger<>()),
fan_mode_focus_trigger_(new Trigger<>()),
fan_mode_diffuse_trigger_(new Trigger<>()),
fan_mode_quiet_trigger_(new Trigger<>()),
swing_mode_both_trigger_(new Trigger<>()),
swing_mode_off_trigger_(new Trigger<>()),
swing_mode_horizontal_trigger_(new Trigger<>()),
swing_mode_vertical_trigger_(new Trigger<>()),
humidity_change_trigger_(new Trigger<>()),
temperature_change_trigger_(new Trigger<>()),
preset_change_trigger_(new Trigger<>()),
humidity_control_dehumidify_action_trigger_(new Trigger<>()),
humidity_control_humidify_action_trigger_(new Trigger<>()),
humidity_control_off_action_trigger_(new Trigger<>()) {}
void ThermostatClimate::set_default_preset(const char *custom_preset) { void ThermostatClimate::set_default_preset(const char *custom_preset) {
// Find the preset in custom_preset_config_ and store pointer from there // Find the preset in custom_preset_config_ and store pointer from there
@@ -1461,49 +1513,49 @@ void ThermostatClimate::set_supports_humidification(bool supports_humidification
} }
} }
Trigger<> *ThermostatClimate::get_cool_action_trigger() { return &this->cool_action_trigger_; } Trigger<> *ThermostatClimate::get_cool_action_trigger() const { return this->cool_action_trigger_; }
Trigger<> *ThermostatClimate::get_supplemental_cool_action_trigger() { Trigger<> *ThermostatClimate::get_supplemental_cool_action_trigger() const {
return &this->supplemental_cool_action_trigger_; return this->supplemental_cool_action_trigger_;
} }
Trigger<> *ThermostatClimate::get_dry_action_trigger() { return &this->dry_action_trigger_; } Trigger<> *ThermostatClimate::get_dry_action_trigger() const { return this->dry_action_trigger_; }
Trigger<> *ThermostatClimate::get_fan_only_action_trigger() { return &this->fan_only_action_trigger_; } Trigger<> *ThermostatClimate::get_fan_only_action_trigger() const { return this->fan_only_action_trigger_; }
Trigger<> *ThermostatClimate::get_heat_action_trigger() { return &this->heat_action_trigger_; } Trigger<> *ThermostatClimate::get_heat_action_trigger() const { return this->heat_action_trigger_; }
Trigger<> *ThermostatClimate::get_supplemental_heat_action_trigger() { Trigger<> *ThermostatClimate::get_supplemental_heat_action_trigger() const {
return &this->supplemental_heat_action_trigger_; return this->supplemental_heat_action_trigger_;
} }
Trigger<> *ThermostatClimate::get_idle_action_trigger() { return &this->idle_action_trigger_; } Trigger<> *ThermostatClimate::get_idle_action_trigger() const { return this->idle_action_trigger_; }
Trigger<> *ThermostatClimate::get_auto_mode_trigger() { return &this->auto_mode_trigger_; } Trigger<> *ThermostatClimate::get_auto_mode_trigger() const { return this->auto_mode_trigger_; }
Trigger<> *ThermostatClimate::get_cool_mode_trigger() { return &this->cool_mode_trigger_; } Trigger<> *ThermostatClimate::get_cool_mode_trigger() const { return this->cool_mode_trigger_; }
Trigger<> *ThermostatClimate::get_dry_mode_trigger() { return &this->dry_mode_trigger_; } Trigger<> *ThermostatClimate::get_dry_mode_trigger() const { return this->dry_mode_trigger_; }
Trigger<> *ThermostatClimate::get_fan_only_mode_trigger() { return &this->fan_only_mode_trigger_; } Trigger<> *ThermostatClimate::get_fan_only_mode_trigger() const { return this->fan_only_mode_trigger_; }
Trigger<> *ThermostatClimate::get_heat_mode_trigger() { return &this->heat_mode_trigger_; } Trigger<> *ThermostatClimate::get_heat_mode_trigger() const { return this->heat_mode_trigger_; }
Trigger<> *ThermostatClimate::get_heat_cool_mode_trigger() { return &this->heat_cool_mode_trigger_; } Trigger<> *ThermostatClimate::get_heat_cool_mode_trigger() const { return this->heat_cool_mode_trigger_; }
Trigger<> *ThermostatClimate::get_off_mode_trigger() { return &this->off_mode_trigger_; } Trigger<> *ThermostatClimate::get_off_mode_trigger() const { return this->off_mode_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_on_trigger() { return &this->fan_mode_on_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_on_trigger() const { return this->fan_mode_on_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_off_trigger() { return &this->fan_mode_off_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_off_trigger() const { return this->fan_mode_off_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_auto_trigger() { return &this->fan_mode_auto_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_auto_trigger() const { return this->fan_mode_auto_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_low_trigger() { return &this->fan_mode_low_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_low_trigger() const { return this->fan_mode_low_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_medium_trigger() { return &this->fan_mode_medium_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_medium_trigger() const { return this->fan_mode_medium_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_high_trigger() { return &this->fan_mode_high_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_high_trigger() const { return this->fan_mode_high_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_middle_trigger() { return &this->fan_mode_middle_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_middle_trigger() const { return this->fan_mode_middle_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_focus_trigger() { return &this->fan_mode_focus_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_focus_trigger() const { return this->fan_mode_focus_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_diffuse_trigger() { return &this->fan_mode_diffuse_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_diffuse_trigger() const { return this->fan_mode_diffuse_trigger_; }
Trigger<> *ThermostatClimate::get_fan_mode_quiet_trigger() { return &this->fan_mode_quiet_trigger_; } Trigger<> *ThermostatClimate::get_fan_mode_quiet_trigger() const { return this->fan_mode_quiet_trigger_; }
Trigger<> *ThermostatClimate::get_swing_mode_both_trigger() { return &this->swing_mode_both_trigger_; } Trigger<> *ThermostatClimate::get_swing_mode_both_trigger() const { return this->swing_mode_both_trigger_; }
Trigger<> *ThermostatClimate::get_swing_mode_off_trigger() { return &this->swing_mode_off_trigger_; } Trigger<> *ThermostatClimate::get_swing_mode_off_trigger() const { return this->swing_mode_off_trigger_; }
Trigger<> *ThermostatClimate::get_swing_mode_horizontal_trigger() { return &this->swing_mode_horizontal_trigger_; } Trigger<> *ThermostatClimate::get_swing_mode_horizontal_trigger() const { return this->swing_mode_horizontal_trigger_; }
Trigger<> *ThermostatClimate::get_swing_mode_vertical_trigger() { return &this->swing_mode_vertical_trigger_; } Trigger<> *ThermostatClimate::get_swing_mode_vertical_trigger() const { return this->swing_mode_vertical_trigger_; }
Trigger<> *ThermostatClimate::get_humidity_change_trigger() { return &this->humidity_change_trigger_; } Trigger<> *ThermostatClimate::get_humidity_change_trigger() const { return this->humidity_change_trigger_; }
Trigger<> *ThermostatClimate::get_temperature_change_trigger() { return &this->temperature_change_trigger_; } Trigger<> *ThermostatClimate::get_temperature_change_trigger() const { return this->temperature_change_trigger_; }
Trigger<> *ThermostatClimate::get_preset_change_trigger() { return &this->preset_change_trigger_; } Trigger<> *ThermostatClimate::get_preset_change_trigger() const { return this->preset_change_trigger_; }
Trigger<> *ThermostatClimate::get_humidity_control_dehumidify_action_trigger() { Trigger<> *ThermostatClimate::get_humidity_control_dehumidify_action_trigger() const {
return &this->humidity_control_dehumidify_action_trigger_; return this->humidity_control_dehumidify_action_trigger_;
} }
Trigger<> *ThermostatClimate::get_humidity_control_humidify_action_trigger() { Trigger<> *ThermostatClimate::get_humidity_control_humidify_action_trigger() const {
return &this->humidity_control_humidify_action_trigger_; return this->humidity_control_humidify_action_trigger_;
} }
Trigger<> *ThermostatClimate::get_humidity_control_off_action_trigger() { Trigger<> *ThermostatClimate::get_humidity_control_off_action_trigger() const {
return &this->humidity_control_off_action_trigger_; return this->humidity_control_off_action_trigger_;
} }
void ThermostatClimate::dump_config() { void ThermostatClimate::dump_config() {

View File

@@ -146,40 +146,40 @@ class ThermostatClimate : public climate::Climate, public Component {
void set_preset_config(std::initializer_list<PresetEntry> presets); void set_preset_config(std::initializer_list<PresetEntry> presets);
void set_custom_preset_config(std::initializer_list<CustomPresetEntry> presets); void set_custom_preset_config(std::initializer_list<CustomPresetEntry> presets);
Trigger<> *get_cool_action_trigger(); Trigger<> *get_cool_action_trigger() const;
Trigger<> *get_supplemental_cool_action_trigger(); Trigger<> *get_supplemental_cool_action_trigger() const;
Trigger<> *get_dry_action_trigger(); Trigger<> *get_dry_action_trigger() const;
Trigger<> *get_fan_only_action_trigger(); Trigger<> *get_fan_only_action_trigger() const;
Trigger<> *get_heat_action_trigger(); Trigger<> *get_heat_action_trigger() const;
Trigger<> *get_supplemental_heat_action_trigger(); Trigger<> *get_supplemental_heat_action_trigger() const;
Trigger<> *get_idle_action_trigger(); Trigger<> *get_idle_action_trigger() const;
Trigger<> *get_auto_mode_trigger(); Trigger<> *get_auto_mode_trigger() const;
Trigger<> *get_cool_mode_trigger(); Trigger<> *get_cool_mode_trigger() const;
Trigger<> *get_dry_mode_trigger(); Trigger<> *get_dry_mode_trigger() const;
Trigger<> *get_fan_only_mode_trigger(); Trigger<> *get_fan_only_mode_trigger() const;
Trigger<> *get_heat_mode_trigger(); Trigger<> *get_heat_mode_trigger() const;
Trigger<> *get_heat_cool_mode_trigger(); Trigger<> *get_heat_cool_mode_trigger() const;
Trigger<> *get_off_mode_trigger(); Trigger<> *get_off_mode_trigger() const;
Trigger<> *get_fan_mode_on_trigger(); Trigger<> *get_fan_mode_on_trigger() const;
Trigger<> *get_fan_mode_off_trigger(); Trigger<> *get_fan_mode_off_trigger() const;
Trigger<> *get_fan_mode_auto_trigger(); Trigger<> *get_fan_mode_auto_trigger() const;
Trigger<> *get_fan_mode_low_trigger(); Trigger<> *get_fan_mode_low_trigger() const;
Trigger<> *get_fan_mode_medium_trigger(); Trigger<> *get_fan_mode_medium_trigger() const;
Trigger<> *get_fan_mode_high_trigger(); Trigger<> *get_fan_mode_high_trigger() const;
Trigger<> *get_fan_mode_middle_trigger(); Trigger<> *get_fan_mode_middle_trigger() const;
Trigger<> *get_fan_mode_focus_trigger(); Trigger<> *get_fan_mode_focus_trigger() const;
Trigger<> *get_fan_mode_diffuse_trigger(); Trigger<> *get_fan_mode_diffuse_trigger() const;
Trigger<> *get_fan_mode_quiet_trigger(); Trigger<> *get_fan_mode_quiet_trigger() const;
Trigger<> *get_swing_mode_both_trigger(); Trigger<> *get_swing_mode_both_trigger() const;
Trigger<> *get_swing_mode_horizontal_trigger(); Trigger<> *get_swing_mode_horizontal_trigger() const;
Trigger<> *get_swing_mode_off_trigger(); Trigger<> *get_swing_mode_off_trigger() const;
Trigger<> *get_swing_mode_vertical_trigger(); Trigger<> *get_swing_mode_vertical_trigger() const;
Trigger<> *get_humidity_change_trigger(); Trigger<> *get_humidity_change_trigger() const;
Trigger<> *get_temperature_change_trigger(); Trigger<> *get_temperature_change_trigger() const;
Trigger<> *get_preset_change_trigger(); Trigger<> *get_preset_change_trigger() const;
Trigger<> *get_humidity_control_dehumidify_action_trigger(); Trigger<> *get_humidity_control_dehumidify_action_trigger() const;
Trigger<> *get_humidity_control_humidify_action_trigger(); Trigger<> *get_humidity_control_humidify_action_trigger() const;
Trigger<> *get_humidity_control_off_action_trigger(); Trigger<> *get_humidity_control_off_action_trigger() const;
/// Get current hysteresis values /// Get current hysteresis values
float cool_deadband(); float cool_deadband();
float cool_overrun(); float cool_overrun();
@@ -417,65 +417,115 @@ class ThermostatClimate : public climate::Climate, public Component {
/// The sensor used for getting the current humidity /// The sensor used for getting the current humidity
sensor::Sensor *humidity_sensor_{nullptr}; sensor::Sensor *humidity_sensor_{nullptr};
/// Trigger for cooling action/mode /// The trigger to call when the controller should switch to cooling action/mode.
Trigger<> cool_action_trigger_; ///
Trigger<> supplemental_cool_action_trigger_; /// A null value for this attribute means that the controller has no cooling action
Trigger<> cool_mode_trigger_; /// For example electric heat, where only heating (power on) and not-heating
/// (power off) is possible.
Trigger<> *cool_action_trigger_{nullptr};
Trigger<> *supplemental_cool_action_trigger_{nullptr};
Trigger<> *cool_mode_trigger_{nullptr};
/// Trigger for dry (dehumidification) mode /// The trigger to call when the controller should switch to dry (dehumidification) mode.
Trigger<> dry_action_trigger_; ///
Trigger<> dry_mode_trigger_; /// In dry mode, the controller is assumed to have both heating and cooling disabled,
/// although the system may use its cooling mechanism to achieve drying.
Trigger<> *dry_action_trigger_{nullptr};
Trigger<> *dry_mode_trigger_{nullptr};
/// Trigger for heating action/mode /// The trigger to call when the controller should switch to heating action/mode.
Trigger<> heat_action_trigger_; ///
Trigger<> supplemental_heat_action_trigger_; /// A null value for this attribute means that the controller has no heating action
Trigger<> heat_mode_trigger_; /// For example window blinds, where only cooling (blinds closed) and not-cooling
/// (blinds open) is possible.
Trigger<> *heat_action_trigger_{nullptr};
Trigger<> *supplemental_heat_action_trigger_{nullptr};
Trigger<> *heat_mode_trigger_{nullptr};
/// Trigger for heat/cool mode /// The trigger to call when the controller should switch to heat/cool mode.
Trigger<> heat_cool_mode_trigger_; ///
/// In heat/cool mode, the controller will enable heating/cooling as necessary and switch
/// to idle when the temperature is within the thresholds/set points.
Trigger<> *heat_cool_mode_trigger_{nullptr};
/// Trigger for auto mode /// The trigger to call when the controller should switch to auto mode.
Trigger<> auto_mode_trigger_; ///
/// In auto mode, the controller will enable heating/cooling as supported/necessary and switch
/// to idle when the temperature is within the thresholds/set points.
Trigger<> *auto_mode_trigger_{nullptr};
/// Trigger for idle action/off mode /// The trigger to call when the controller should switch to idle action/off mode.
Trigger<> idle_action_trigger_; ///
Trigger<> off_mode_trigger_; /// In these actions/modes, the controller is assumed to have both heating and cooling disabled.
Trigger<> *idle_action_trigger_{nullptr};
Trigger<> *off_mode_trigger_{nullptr};
/// Trigger for fan-only action/mode /// The trigger to call when the controller should switch to fan-only action/mode.
Trigger<> fan_only_action_trigger_; ///
Trigger<> fan_only_mode_trigger_; /// In fan-only mode, the controller is assumed to have both heating and cooling disabled.
/// The system should activate the fan only.
Trigger<> *fan_only_action_trigger_{nullptr};
Trigger<> *fan_only_mode_trigger_{nullptr};
/// Fan mode triggers /// The trigger to call when the controller should switch on the fan.
Trigger<> fan_mode_on_trigger_; Trigger<> *fan_mode_on_trigger_{nullptr};
Trigger<> fan_mode_off_trigger_;
Trigger<> fan_mode_auto_trigger_;
Trigger<> fan_mode_low_trigger_;
Trigger<> fan_mode_medium_trigger_;
Trigger<> fan_mode_high_trigger_;
Trigger<> fan_mode_middle_trigger_;
Trigger<> fan_mode_focus_trigger_;
Trigger<> fan_mode_diffuse_trigger_;
Trigger<> fan_mode_quiet_trigger_;
/// Swing mode triggers /// The trigger to call when the controller should switch off the fan.
Trigger<> swing_mode_both_trigger_; Trigger<> *fan_mode_off_trigger_{nullptr};
Trigger<> swing_mode_off_trigger_;
Trigger<> swing_mode_horizontal_trigger_;
Trigger<> swing_mode_vertical_trigger_;
/// Trigger for target humidity changes /// The trigger to call when the controller should switch the fan to "auto" mode.
Trigger<> humidity_change_trigger_; Trigger<> *fan_mode_auto_trigger_{nullptr};
/// Trigger for target temperature changes /// The trigger to call when the controller should switch the fan to "low" speed.
Trigger<> temperature_change_trigger_; Trigger<> *fan_mode_low_trigger_{nullptr};
/// Trigger for preset mode changes /// The trigger to call when the controller should switch the fan to "medium" speed.
Trigger<> preset_change_trigger_; Trigger<> *fan_mode_medium_trigger_{nullptr};
/// Humidity control triggers /// The trigger to call when the controller should switch the fan to "high" speed.
Trigger<> humidity_control_dehumidify_action_trigger_; Trigger<> *fan_mode_high_trigger_{nullptr};
Trigger<> humidity_control_humidify_action_trigger_;
Trigger<> humidity_control_off_action_trigger_; /// The trigger to call when the controller should switch the fan to "middle" position.
Trigger<> *fan_mode_middle_trigger_{nullptr};
/// The trigger to call when the controller should switch the fan to "focus" position.
Trigger<> *fan_mode_focus_trigger_{nullptr};
/// The trigger to call when the controller should switch the fan to "diffuse" position.
Trigger<> *fan_mode_diffuse_trigger_{nullptr};
/// The trigger to call when the controller should switch the fan to "quiet" position.
Trigger<> *fan_mode_quiet_trigger_{nullptr};
/// The trigger to call when the controller should switch the swing mode to "both".
Trigger<> *swing_mode_both_trigger_{nullptr};
/// The trigger to call when the controller should switch the swing mode to "off".
Trigger<> *swing_mode_off_trigger_{nullptr};
/// The trigger to call when the controller should switch the swing mode to "horizontal".
Trigger<> *swing_mode_horizontal_trigger_{nullptr};
/// The trigger to call when the controller should switch the swing mode to "vertical".
Trigger<> *swing_mode_vertical_trigger_{nullptr};
/// The trigger to call when the target humidity changes.
Trigger<> *humidity_change_trigger_{nullptr};
/// The trigger to call when the target temperature(s) change(es).
Trigger<> *temperature_change_trigger_{nullptr};
/// The trigger to call when the preset mode changes
Trigger<> *preset_change_trigger_{nullptr};
/// The trigger to call when dehumidification is required
Trigger<> *humidity_control_dehumidify_action_trigger_{nullptr};
/// The trigger to call when humidification is required
Trigger<> *humidity_control_humidify_action_trigger_{nullptr};
/// The trigger to call when (de)humidification should stop
Trigger<> *humidity_control_off_action_trigger_{nullptr};
/// A reference to the trigger that was previously active. /// A reference to the trigger that was previously active.
/// ///

View File

@@ -62,7 +62,7 @@ class RealTimeClock : public PollingComponent {
void apply_timezone_(); void apply_timezone_();
#endif #endif
LazyCallbackManager<void()> time_sync_callback_; CallbackManager<void()> time_sync_callback_;
}; };
template<typename... Ts> class TimeHasTimeCondition : public Condition<Ts...> { template<typename... Ts> class TimeHasTimeCondition : public Condition<Ts...> {

View File

@@ -132,15 +132,15 @@ void TimeBasedCover::start_direction_(CoverOperation dir) {
Trigger<> *trig; Trigger<> *trig;
switch (dir) { switch (dir) {
case COVER_OPERATION_IDLE: case COVER_OPERATION_IDLE:
trig = &this->stop_trigger_; trig = this->stop_trigger_;
break; break;
case COVER_OPERATION_OPENING: case COVER_OPERATION_OPENING:
this->last_operation_ = dir; this->last_operation_ = dir;
trig = &this->open_trigger_; trig = this->open_trigger_;
break; break;
case COVER_OPERATION_CLOSING: case COVER_OPERATION_CLOSING:
this->last_operation_ = dir; this->last_operation_ = dir;
trig = &this->close_trigger_; trig = this->close_trigger_;
break; break;
default: default:
return; return;

Some files were not shown because too many files have changed in this diff Show More