diff --git a/.clang-tidy.hash b/.clang-tidy.hash index 1cb5f98c28..ab354259e3 100644 --- a/.clang-tidy.hash +++ b/.clang-tidy.hash @@ -1 +1 @@ -cf3d341206b4184ec8b7fe85141aef4fe4696aa720c3f8a06d4e57930574bdab +069fa9526c52f7c580a9ec17c7678d12f142221387e9b561c18f95394d4629a3 diff --git a/CODEOWNERS b/CODEOWNERS index b7675f9406..1d165a6f57 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -134,6 +134,7 @@ esphome/components/dfplayer/* @glmnet esphome/components/dfrobot_sen0395/* @niklasweber esphome/components/dht/* @OttoWinter esphome/components/display_menu_base/* @numo68 +esphome/components/dlms_meter/* @SimonFischer04 esphome/components/dps310/* @kbx81 esphome/components/ds1307/* @badbadc0ffee esphome/components/ds2484/* @mrk-its diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index ed97c3b9a2..c56449455d 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -211,7 +211,7 @@ void APIServer::loop() { #ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER // 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 // Don't increment client_index since we need to process the swapped element } diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 93421ef801..6ab3cdc576 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -227,12 +227,10 @@ class APIServer : public Component, #endif #ifdef USE_API_CLIENT_CONNECTED_TRIGGER - Trigger *get_client_connected_trigger() const { return this->client_connected_trigger_; } + Trigger *get_client_connected_trigger() { return &this->client_connected_trigger_; } #endif #ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER - Trigger *get_client_disconnected_trigger() const { - return this->client_disconnected_trigger_; - } + Trigger *get_client_disconnected_trigger() { return &this->client_disconnected_trigger_; } #endif protected: @@ -253,10 +251,10 @@ class APIServer : public Component, // Pointers and pointer-like types first (4 bytes each) std::unique_ptr socket_ = nullptr; #ifdef USE_API_CLIENT_CONNECTED_TRIGGER - Trigger *client_connected_trigger_ = new Trigger(); + Trigger client_connected_trigger_; #endif #ifdef USE_API_CLIENT_DISCONNECTED_TRIGGER - Trigger *client_disconnected_trigger_ = new Trigger(); + Trigger client_disconnected_trigger_; #endif // 4-byte aligned types diff --git a/esphome/components/api/homeassistant_service.h b/esphome/components/api/homeassistant_service.h index 9bffe18764..8ee23c75fe 100644 --- a/esphome/components/api/homeassistant_service.h +++ b/esphome/components/api/homeassistant_service.h @@ -136,12 +136,10 @@ template class HomeAssistantServiceCallAction : public Actionflags_.wants_response = true; } #ifdef USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - Trigger *get_success_trigger_with_response() const { - return this->success_trigger_with_response_; - } + Trigger *get_success_trigger_with_response() { return &this->success_trigger_with_response_; } #endif - Trigger *get_success_trigger() const { return this->success_trigger_; } - Trigger *get_error_trigger() const { return this->error_trigger_; } + Trigger *get_success_trigger() { return &this->success_trigger_; } + Trigger *get_error_trigger() { return &this->error_trigger_; } #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES void play(const Ts &...x) override { @@ -187,14 +185,14 @@ template class HomeAssistantServiceCallAction : public Actionflags_.wants_response) { - this->success_trigger_with_response_->trigger(response.get_json(), args...); + this->success_trigger_with_response_.trigger(response.get_json(), args...); } else #endif { - this->success_trigger_->trigger(args...); + this->success_trigger_.trigger(args...); } } else { - this->error_trigger_->trigger(response.get_error_message(), args...); + this->error_trigger_.trigger(response.get_error_message(), args...); } }, captured_args); @@ -251,10 +249,10 @@ template class HomeAssistantServiceCallAction : public Action response_template_{""}; - Trigger *success_trigger_with_response_ = new Trigger(); + Trigger success_trigger_with_response_; #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES_JSON - Trigger *success_trigger_ = new Trigger(); - Trigger *error_trigger_ = new Trigger(); + Trigger success_trigger_; + Trigger error_trigger_; #endif // USE_API_HOMEASSISTANT_ACTION_RESPONSES struct Flags { diff --git a/esphome/components/bang_bang/bang_bang_climate.cpp b/esphome/components/bang_bang/bang_bang_climate.cpp index f26377a38a..6871e9df5d 100644 --- a/esphome/components/bang_bang/bang_bang_climate.cpp +++ b/esphome/components/bang_bang/bang_bang_climate.cpp @@ -6,8 +6,7 @@ namespace bang_bang { static const char *const TAG = "bang_bang.climate"; -BangBangClimate::BangBangClimate() - : idle_trigger_(new Trigger<>()), cool_trigger_(new Trigger<>()), heat_trigger_(new Trigger<>()) {} +BangBangClimate::BangBangClimate() = default; void BangBangClimate::setup() { this->sensor_->add_on_state_callback([this](float state) { @@ -160,13 +159,13 @@ void BangBangClimate::switch_to_action_(climate::ClimateAction action) { switch (action) { case climate::CLIMATE_ACTION_OFF: case climate::CLIMATE_ACTION_IDLE: - trig = this->idle_trigger_; + trig = &this->idle_trigger_; break; case climate::CLIMATE_ACTION_COOLING: - trig = this->cool_trigger_; + trig = &this->cool_trigger_; break; case climate::CLIMATE_ACTION_HEATING: - trig = this->heat_trigger_; + trig = &this->heat_trigger_; break; default: trig = nullptr; @@ -204,9 +203,9 @@ void BangBangClimate::set_away_config(const BangBangClimateTargetTempConfig &awa void BangBangClimate::set_sensor(sensor::Sensor *sensor) { this->sensor_ = sensor; } void BangBangClimate::set_humidity_sensor(sensor::Sensor *humidity_sensor) { this->humidity_sensor_ = humidity_sensor; } -Trigger<> *BangBangClimate::get_idle_trigger() const { return this->idle_trigger_; } -Trigger<> *BangBangClimate::get_cool_trigger() const { return this->cool_trigger_; } -Trigger<> *BangBangClimate::get_heat_trigger() const { return this->heat_trigger_; } +Trigger<> *BangBangClimate::get_idle_trigger() { return &this->idle_trigger_; } +Trigger<> *BangBangClimate::get_cool_trigger() { return &this->cool_trigger_; } +Trigger<> *BangBangClimate::get_heat_trigger() { return &this->heat_trigger_; } 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; } diff --git a/esphome/components/bang_bang/bang_bang_climate.h b/esphome/components/bang_bang/bang_bang_climate.h index 2e7da93a07..d0ddef2848 100644 --- a/esphome/components/bang_bang/bang_bang_climate.h +++ b/esphome/components/bang_bang/bang_bang_climate.h @@ -30,9 +30,9 @@ class BangBangClimate : public climate::Climate, public Component { void set_normal_config(const BangBangClimateTargetTempConfig &normal_config); void set_away_config(const BangBangClimateTargetTempConfig &away_config); - Trigger<> *get_idle_trigger() const; - Trigger<> *get_cool_trigger() const; - Trigger<> *get_heat_trigger() const; + Trigger<> *get_idle_trigger(); + Trigger<> *get_cool_trigger(); + Trigger<> *get_heat_trigger(); protected: /// Override control to change settings of the climate device. @@ -57,17 +57,13 @@ class BangBangClimate : public climate::Climate, public Component { * * In idle mode, the controller is assumed to have both heating and cooling disabled. */ - Trigger<> *idle_trigger_{nullptr}; + Trigger<> idle_trigger_; /** The trigger to call when the controller should switch to cooling mode. */ - Trigger<> *cool_trigger_{nullptr}; + Trigger<> cool_trigger_; /** 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_{nullptr}; + Trigger<> heat_trigger_; /** A reference to the trigger that was previously active. * * This is so that the previous trigger can be stopped before enabling a new one. diff --git a/esphome/components/cc1101/cc1101.cpp b/esphome/components/cc1101/cc1101.cpp index 46cd89e0e8..b6973da78d 100644 --- a/esphome/components/cc1101/cc1101.cpp +++ b/esphome/components/cc1101/cc1101.cpp @@ -156,7 +156,7 @@ void CC1101Component::call_listeners_(const std::vector &packet, float for (auto &listener : this->listeners_) { 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() { diff --git a/esphome/components/cc1101/cc1101.h b/esphome/components/cc1101/cc1101.h index 6e3f01af90..e55071e7e3 100644 --- a/esphome/components/cc1101/cc1101.h +++ b/esphome/components/cc1101/cc1101.h @@ -79,7 +79,7 @@ class CC1101Component : public Component, // Packet mode operations CC1101Error transmit_packet(const std::vector &packet); void register_listener(CC1101Listener *listener) { this->listeners_.push_back(listener); } - Trigger, float, float, uint8_t> *get_packet_trigger() const { return this->packet_trigger_; } + Trigger, float, float, uint8_t> *get_packet_trigger() { return &this->packet_trigger_; } protected: uint16_t chip_id_{0}; @@ -96,8 +96,7 @@ class CC1101Component : public Component, // Packet handling void call_listeners_(const std::vector &packet, float freq_offset, float rssi, uint8_t lqi); - Trigger, float, float, uint8_t> *packet_trigger_{ - new Trigger, float, float, uint8_t>()}; + Trigger, float, float, uint8_t> packet_trigger_; std::vector packet_; std::vector listeners_; diff --git a/esphome/components/current_based/current_based_cover.cpp b/esphome/components/current_based/current_based_cover.cpp index cb3f65c9cd..402cf9fee7 100644 --- a/esphome/components/current_based/current_based_cover.cpp +++ b/esphome/components/current_based/current_based_cover.cpp @@ -66,7 +66,7 @@ void CurrentBasedCover::loop() { if (this->current_operation == COVER_OPERATION_OPENING) { if (this->malfunction_detection_ && this->is_closing_()) { // Malfunction 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", this->name_.c_str()); } else if (this->is_opening_blocked_()) { // Blocked @@ -87,7 +87,7 @@ void CurrentBasedCover::loop() { } else if (this->current_operation == COVER_OPERATION_CLOSING) { if (this->malfunction_detection_ && this->is_opening_()) { // Malfunction 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", this->name_.c_str()); } else if (this->is_closing_blocked_()) { // Blocked @@ -221,15 +221,15 @@ void CurrentBasedCover::start_direction_(CoverOperation dir) { Trigger<> *trig; switch (dir) { case COVER_OPERATION_IDLE: - trig = this->stop_trigger_; + trig = &this->stop_trigger_; break; case COVER_OPERATION_OPENING: this->last_operation_ = dir; - trig = this->open_trigger_; + trig = &this->open_trigger_; break; case COVER_OPERATION_CLOSING: this->last_operation_ = dir; - trig = this->close_trigger_; + trig = &this->close_trigger_; break; default: return; diff --git a/esphome/components/current_based/current_based_cover.h b/esphome/components/current_based/current_based_cover.h index b172e762b0..f7993f1550 100644 --- a/esphome/components/current_based/current_based_cover.h +++ b/esphome/components/current_based/current_based_cover.h @@ -16,9 +16,9 @@ class CurrentBasedCover : public cover::Cover, public Component { void dump_config() override; float get_setup_priority() const override; - Trigger<> *get_stop_trigger() const { return this->stop_trigger_; } + Trigger<> *get_stop_trigger() { return &this->stop_trigger_; } - Trigger<> *get_open_trigger() const { return this->open_trigger_; } + Trigger<> *get_open_trigger() { return &this->open_trigger_; } void set_open_sensor(sensor::Sensor *open_sensor) { this->open_sensor_ = open_sensor; } void set_open_moving_current_threshold(float 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; } - Trigger<> *get_close_trigger() const { return this->close_trigger_; } + Trigger<> *get_close_trigger() { return &this->close_trigger_; } void set_close_sensor(sensor::Sensor *close_sensor) { this->close_sensor_ = close_sensor; } void set_close_moving_current_threshold(float 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_start_sensing_delay(uint32_t start_sensing_delay) { this->start_sensing_delay_ = start_sensing_delay; } - Trigger<> *get_malfunction_trigger() const { return this->malfunction_trigger_; } + Trigger<> *get_malfunction_trigger() { return &this->malfunction_trigger_; } cover::CoverTraits get_traits() override; @@ -64,23 +64,23 @@ class CurrentBasedCover : public cover::Cover, public Component { void recompute_position_(); - Trigger<> *stop_trigger_{new Trigger<>()}; + Trigger<> stop_trigger_; sensor::Sensor *open_sensor_{nullptr}; - Trigger<> *open_trigger_{new Trigger<>()}; + Trigger<> open_trigger_; float open_moving_current_threshold_; float open_obstacle_current_threshold_{FLT_MAX}; uint32_t open_duration_; sensor::Sensor *close_sensor_{nullptr}; - Trigger<> *close_trigger_{new Trigger<>()}; + Trigger<> close_trigger_; float close_moving_current_threshold_; float close_obstacle_current_threshold_{FLT_MAX}; uint32_t close_duration_; uint32_t max_duration_{UINT32_MAX}; bool malfunction_detection_{true}; - Trigger<> *malfunction_trigger_{new Trigger<>()}; + Trigger<> malfunction_trigger_; uint32_t start_sensing_delay_; float obstacle_rollback_; diff --git a/esphome/components/dlms_meter/__init__.py b/esphome/components/dlms_meter/__init__.py new file mode 100644 index 0000000000..c22ab7b552 --- /dev/null +++ b/esphome/components/dlms_meter/__init__.py @@ -0,0 +1,57 @@ +import esphome.codegen as cg +from esphome.components import uart +import esphome.config_validation as cv +from esphome.const import CONF_ID, PLATFORM_ESP32, PLATFORM_ESP8266 + +CODEOWNERS = ["@SimonFischer04"] +DEPENDENCIES = ["uart"] + +CONF_DLMS_METER_ID = "dlms_meter_id" +CONF_DECRYPTION_KEY = "decryption_key" +CONF_PROVIDER = "provider" + +PROVIDERS = {"generic": 0, "netznoe": 1} + +dlms_meter_component_ns = cg.esphome_ns.namespace("dlms_meter") +DlmsMeterComponent = dlms_meter_component_ns.class_( + "DlmsMeterComponent", cg.Component, uart.UARTDevice +) + + +def validate_key(value): + value = cv.string_strict(value) + if len(value) != 32: + raise cv.Invalid("Decryption key must be 32 hex characters (16 bytes)") + try: + return [int(value[i : i + 2], 16) for i in range(0, 32, 2)] + except ValueError as exc: + raise cv.Invalid("Decryption key must be hex values from 00 to FF") from exc + + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(DlmsMeterComponent), + cv.Required(CONF_DECRYPTION_KEY): validate_key, + cv.Optional(CONF_PROVIDER, default="generic"): cv.enum( + PROVIDERS, lower=True + ), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA), + cv.only_on([PLATFORM_ESP8266, PLATFORM_ESP32]), +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "dlms_meter", baud_rate=2400, require_rx=True +) + + +async def to_code(config): + var = cg.new_Pvariable(config[CONF_ID]) + await cg.register_component(var, config) + await uart.register_uart_device(var, config) + key = ", ".join(str(b) for b in config[CONF_DECRYPTION_KEY]) + cg.add(var.set_decryption_key(cg.RawExpression(f"{{{key}}}"))) + cg.add(var.set_provider(PROVIDERS[config[CONF_PROVIDER]])) diff --git a/esphome/components/dlms_meter/dlms.h b/esphome/components/dlms_meter/dlms.h new file mode 100644 index 0000000000..a3d8f62ce6 --- /dev/null +++ b/esphome/components/dlms_meter/dlms.h @@ -0,0 +1,71 @@ +#pragma once + +#include + +namespace esphome::dlms_meter { + +/* ++-------------------------------+ +| Ciphering Service | ++-------------------------------+ +| System Title Length | ++-------------------------------+ +| | +| | +| | +| System | +| Title | +| | +| | +| | ++-------------------------------+ +| Length | (1 or 3 Bytes) ++-------------------------------+ +| Security Control Byte | ++-------------------------------+ +| | +| Frame | +| Counter | +| | ++-------------------------------+ +| | +~ ~ + Encrypted Payload +~ ~ +| | ++-------------------------------+ + +Ciphering Service: 0xDB (General-Glo-Ciphering) +System Title Length: 0x08 +System Title: Unique ID of meter +Length: 1 Byte=Length <= 127, 3 Bytes=Length > 127 (0x82 & 2 Bytes length) +Security Control Byte: +- Bit 3…0: Security_Suite_Id +- Bit 4: "A" subfield: indicates that authentication is applied +- Bit 5: "E" subfield: indicates that encryption is applied +- Bit 6: Key_Set subfield: 0 = Unicast, 1 = Broadcast +- Bit 7: Indicates the use of compression. + */ + +static constexpr uint8_t DLMS_HEADER_LENGTH = 16; +static constexpr uint8_t DLMS_HEADER_EXT_OFFSET = 2; // Extra offset for extended length header +static constexpr uint8_t DLMS_CIPHER_OFFSET = 0; +static constexpr uint8_t DLMS_SYST_OFFSET = 1; +static constexpr uint8_t DLMS_LENGTH_OFFSET = 10; +static constexpr uint8_t TWO_BYTE_LENGTH = 0x82; +static constexpr uint8_t DLMS_LENGTH_CORRECTION = 5; // Header bytes included in length field +static constexpr uint8_t DLMS_SECBYTE_OFFSET = 11; +static constexpr uint8_t DLMS_FRAMECOUNTER_OFFSET = 12; +static constexpr uint8_t DLMS_FRAMECOUNTER_LENGTH = 4; +static constexpr uint8_t DLMS_PAYLOAD_OFFSET = 16; +static constexpr uint8_t GLO_CIPHERING = 0xDB; +static constexpr uint8_t DATA_NOTIFICATION = 0x0F; +static constexpr uint8_t TIMESTAMP_DATETIME = 0x0C; +static constexpr uint16_t MAX_MESSAGE_LENGTH = 512; // Maximum size of message (when having 2 bytes length in header). + +// Provider specific quirks +static constexpr uint8_t NETZ_NOE_MAGIC_BYTE = 0x81; // Magic length byte used by Netz NOE +static constexpr uint8_t NETZ_NOE_EXPECTED_MESSAGE_LENGTH = 0xF8; +static constexpr uint8_t NETZ_NOE_EXPECTED_SECURITY_CONTROL_BYTE = 0x20; + +} // namespace esphome::dlms_meter diff --git a/esphome/components/dlms_meter/dlms_meter.cpp b/esphome/components/dlms_meter/dlms_meter.cpp new file mode 100644 index 0000000000..6aa465143e --- /dev/null +++ b/esphome/components/dlms_meter/dlms_meter.cpp @@ -0,0 +1,468 @@ +#include "dlms_meter.h" + +#include + +#if defined(USE_ESP8266_FRAMEWORK_ARDUINO) +#include +#elif defined(USE_ESP32) +#include "mbedtls/esp_config.h" +#include "mbedtls/gcm.h" +#endif + +namespace esphome::dlms_meter { + +static constexpr const char *TAG = "dlms_meter"; + +void DlmsMeterComponent::dump_config() { + const char *provider_name = this->provider_ == PROVIDER_NETZNOE ? "Netz NOE" : "Generic"; + ESP_LOGCONFIG(TAG, + "DLMS Meter:\n" + " Provider: %s\n" + " Read Timeout: %u ms", + provider_name, this->read_timeout_); +#define DLMS_METER_LOG_SENSOR(s) LOG_SENSOR(" ", #s, this->s##_sensor_); + DLMS_METER_SENSOR_LIST(DLMS_METER_LOG_SENSOR, ) +#define DLMS_METER_LOG_TEXT_SENSOR(s) LOG_TEXT_SENSOR(" ", #s, this->s##_text_sensor_); + DLMS_METER_TEXT_SENSOR_LIST(DLMS_METER_LOG_TEXT_SENSOR, ) +} + +void DlmsMeterComponent::loop() { + // Read while data is available, netznoe uses two frames so allow 2x max frame length + while (this->available()) { + if (this->receive_buffer_.size() >= MBUS_MAX_FRAME_LENGTH * 2) { + ESP_LOGW(TAG, "Receive buffer full, dropping remaining bytes"); + break; + } + uint8_t c; + this->read_byte(&c); + this->receive_buffer_.push_back(c); + this->last_read_ = millis(); + } + + if (!this->receive_buffer_.empty() && millis() - this->last_read_ > this->read_timeout_) { + this->mbus_payload_.clear(); + if (!this->parse_mbus_(this->mbus_payload_)) + return; + + uint16_t message_length; + uint8_t systitle_length; + uint16_t header_offset; + if (!this->parse_dlms_(this->mbus_payload_, message_length, systitle_length, header_offset)) + return; + + if (message_length < DECODER_START_OFFSET || message_length > MAX_MESSAGE_LENGTH) { + ESP_LOGE(TAG, "DLMS: Message length invalid: %u", message_length); + this->receive_buffer_.clear(); + return; + } + + // Decrypt in place and then decode the OBIS codes + if (!this->decrypt_(this->mbus_payload_, message_length, systitle_length, header_offset)) + return; + this->decode_obis_(&this->mbus_payload_[header_offset + DLMS_PAYLOAD_OFFSET], message_length); + } +} + +bool DlmsMeterComponent::parse_mbus_(std::vector &mbus_payload) { + ESP_LOGV(TAG, "Parsing M-Bus frames"); + uint16_t frame_offset = 0; // Offset is used if the M-Bus message is split into multiple frames + + while (frame_offset < this->receive_buffer_.size()) { + // Ensure enough bytes remain for the minimal intro header before accessing indices + if (this->receive_buffer_.size() - frame_offset < MBUS_HEADER_INTRO_LENGTH) { + ESP_LOGE(TAG, "MBUS: Not enough data for frame header (need %d, have %d)", MBUS_HEADER_INTRO_LENGTH, + (this->receive_buffer_.size() - frame_offset)); + this->receive_buffer_.clear(); + return false; + } + + // Check start bytes + if (this->receive_buffer_[frame_offset + MBUS_START1_OFFSET] != START_BYTE_LONG_FRAME || + this->receive_buffer_[frame_offset + MBUS_START2_OFFSET] != START_BYTE_LONG_FRAME) { + ESP_LOGE(TAG, "MBUS: Start bytes do not match"); + this->receive_buffer_.clear(); + return false; + } + + // Both length bytes must be identical + if (this->receive_buffer_[frame_offset + MBUS_LENGTH1_OFFSET] != + this->receive_buffer_[frame_offset + MBUS_LENGTH2_OFFSET]) { + ESP_LOGE(TAG, "MBUS: Length bytes do not match"); + this->receive_buffer_.clear(); + return false; + } + + uint8_t frame_length = this->receive_buffer_[frame_offset + MBUS_LENGTH1_OFFSET]; // Get length of this frame + + // Check if received data is enough for the given frame length + if (this->receive_buffer_.size() - frame_offset < + frame_length + 3) { // length field inside packet does not account for second start- + checksum- + stop- byte + ESP_LOGE(TAG, "MBUS: Frame too big for received data"); + this->receive_buffer_.clear(); + return false; + } + + // Ensure we have full frame (header + payload + checksum + stop byte) before accessing stop byte + size_t required_total = + frame_length + MBUS_HEADER_INTRO_LENGTH + MBUS_FOOTER_LENGTH; // payload + header + 2 footer bytes + if (this->receive_buffer_.size() - frame_offset < required_total) { + ESP_LOGE(TAG, "MBUS: Incomplete frame (need %d, have %d)", (unsigned int) required_total, + this->receive_buffer_.size() - frame_offset); + this->receive_buffer_.clear(); + return false; + } + + if (this->receive_buffer_[frame_offset + frame_length + MBUS_HEADER_INTRO_LENGTH + MBUS_FOOTER_LENGTH - 1] != + STOP_BYTE) { + ESP_LOGE(TAG, "MBUS: Invalid stop byte"); + this->receive_buffer_.clear(); + return false; + } + + // Verify checksum: sum of all bytes starting at MBUS_HEADER_INTRO_LENGTH, take last byte + uint8_t checksum = 0; // use uint8_t so only the 8 least significant bits are stored + for (uint16_t i = 0; i < frame_length; i++) { + checksum += this->receive_buffer_[frame_offset + MBUS_HEADER_INTRO_LENGTH + i]; + } + if (checksum != this->receive_buffer_[frame_offset + frame_length + MBUS_HEADER_INTRO_LENGTH]) { + ESP_LOGE(TAG, "MBUS: Invalid checksum: %x != %x", checksum, + this->receive_buffer_[frame_offset + frame_length + MBUS_HEADER_INTRO_LENGTH]); + this->receive_buffer_.clear(); + return false; + } + + mbus_payload.insert(mbus_payload.end(), &this->receive_buffer_[frame_offset + MBUS_FULL_HEADER_LENGTH], + &this->receive_buffer_[frame_offset + MBUS_HEADER_INTRO_LENGTH + frame_length]); + + frame_offset += MBUS_HEADER_INTRO_LENGTH + frame_length + MBUS_FOOTER_LENGTH; + } + return true; +} + +bool DlmsMeterComponent::parse_dlms_(const std::vector &mbus_payload, uint16_t &message_length, + uint8_t &systitle_length, uint16_t &header_offset) { + ESP_LOGV(TAG, "Parsing DLMS header"); + if (mbus_payload.size() < DLMS_HEADER_LENGTH + DLMS_HEADER_EXT_OFFSET) { + ESP_LOGE(TAG, "DLMS: Payload too short"); + this->receive_buffer_.clear(); + return false; + } + + if (mbus_payload[DLMS_CIPHER_OFFSET] != GLO_CIPHERING) { // Only general-glo-ciphering is supported (0xDB) + ESP_LOGE(TAG, "DLMS: Unsupported cipher"); + this->receive_buffer_.clear(); + return false; + } + + systitle_length = mbus_payload[DLMS_SYST_OFFSET]; + + if (systitle_length != 0x08) { // Only system titles with length of 8 are supported + ESP_LOGE(TAG, "DLMS: Unsupported system title length"); + this->receive_buffer_.clear(); + return false; + } + + message_length = mbus_payload[DLMS_LENGTH_OFFSET]; + header_offset = 0; + + if (this->provider_ == PROVIDER_NETZNOE) { + // for some reason EVN seems to set the standard "length" field to 0x81 and then the actual length is in the next + // byte. Check some bytes to see if received data still matches expectation + if (message_length == NETZ_NOE_MAGIC_BYTE && + mbus_payload[DLMS_LENGTH_OFFSET + 1] == NETZ_NOE_EXPECTED_MESSAGE_LENGTH && + mbus_payload[DLMS_LENGTH_OFFSET + 2] == NETZ_NOE_EXPECTED_SECURITY_CONTROL_BYTE) { + message_length = mbus_payload[DLMS_LENGTH_OFFSET + 1]; + header_offset = 1; + } else { + ESP_LOGE(TAG, "Wrong Length - Security Control Byte sequence detected for provider EVN"); + } + } else { + if (message_length == TWO_BYTE_LENGTH) { + message_length = encode_uint16(mbus_payload[DLMS_LENGTH_OFFSET + 1], mbus_payload[DLMS_LENGTH_OFFSET + 2]); + header_offset = DLMS_HEADER_EXT_OFFSET; + } + } + if (message_length < DLMS_LENGTH_CORRECTION) { + ESP_LOGE(TAG, "DLMS: Message length too short: %u", message_length); + this->receive_buffer_.clear(); + return false; + } + message_length -= DLMS_LENGTH_CORRECTION; // Correct message length due to part of header being included in length + + if (mbus_payload.size() - DLMS_HEADER_LENGTH - header_offset != message_length) { + ESP_LOGV(TAG, "DLMS: Length mismatch - payload=%d, header=%d, offset=%d, message=%d", mbus_payload.size(), + DLMS_HEADER_LENGTH, header_offset, message_length); + ESP_LOGE(TAG, "DLMS: Message has invalid length"); + this->receive_buffer_.clear(); + return false; + } + + if (mbus_payload[header_offset + DLMS_SECBYTE_OFFSET] != 0x21 && + mbus_payload[header_offset + DLMS_SECBYTE_OFFSET] != + 0x20) { // Only certain security suite is supported (0x21 || 0x20) + ESP_LOGE(TAG, "DLMS: Unsupported security control byte"); + this->receive_buffer_.clear(); + return false; + } + + return true; +} + +bool DlmsMeterComponent::decrypt_(std::vector &mbus_payload, uint16_t message_length, uint8_t systitle_length, + uint16_t header_offset) { + ESP_LOGV(TAG, "Decrypting payload"); + uint8_t iv[12]; // Reserve space for the IV, always 12 bytes + // Copy system title to IV (System title is before length; no header offset needed!) + // Add 1 to the offset in order to skip the system title length byte + memcpy(&iv[0], &mbus_payload[DLMS_SYST_OFFSET + 1], systitle_length); + memcpy(&iv[8], &mbus_payload[header_offset + DLMS_FRAMECOUNTER_OFFSET], + DLMS_FRAMECOUNTER_LENGTH); // Copy frame counter to IV + + uint8_t *payload_ptr = &mbus_payload[header_offset + DLMS_PAYLOAD_OFFSET]; + +#if defined(USE_ESP8266_FRAMEWORK_ARDUINO) + br_gcm_context gcm_ctx; + br_aes_ct_ctr_keys bc; + br_aes_ct_ctr_init(&bc, this->decryption_key_.data(), this->decryption_key_.size()); + br_gcm_init(&gcm_ctx, &bc.vtable, br_ghash_ctmul32); + br_gcm_reset(&gcm_ctx, iv, sizeof(iv)); + br_gcm_flip(&gcm_ctx); + br_gcm_run(&gcm_ctx, 0, payload_ptr, message_length); +#elif defined(USE_ESP32) + size_t outlen = 0; + mbedtls_gcm_context gcm_ctx; + mbedtls_gcm_init(&gcm_ctx); + mbedtls_gcm_setkey(&gcm_ctx, MBEDTLS_CIPHER_ID_AES, this->decryption_key_.data(), this->decryption_key_.size() * 8); + mbedtls_gcm_starts(&gcm_ctx, MBEDTLS_GCM_DECRYPT, iv, sizeof(iv)); + auto ret = mbedtls_gcm_update(&gcm_ctx, payload_ptr, message_length, payload_ptr, message_length, &outlen); + mbedtls_gcm_free(&gcm_ctx); + if (ret != 0) { + ESP_LOGE(TAG, "Decryption failed with error: %d", ret); + this->receive_buffer_.clear(); + return false; + } +#else +#error "Invalid Platform" +#endif + + if (payload_ptr[0] != DATA_NOTIFICATION || payload_ptr[5] != TIMESTAMP_DATETIME) { + ESP_LOGE(TAG, "OBIS: Packet was decrypted but data is invalid"); + this->receive_buffer_.clear(); + return false; + } + ESP_LOGV(TAG, "Decrypted payload: %d bytes", message_length); + return true; +} + +void DlmsMeterComponent::decode_obis_(uint8_t *plaintext, uint16_t message_length) { + ESP_LOGV(TAG, "Decoding payload"); + MeterData data{}; + uint16_t current_position = DECODER_START_OFFSET; + bool power_factor_found = false; + + while (current_position + OBIS_CODE_OFFSET <= message_length) { + if (plaintext[current_position + OBIS_TYPE_OFFSET] != DataType::OCTET_STRING) { + ESP_LOGE(TAG, "OBIS: Unsupported OBIS header type: %x", plaintext[current_position + OBIS_TYPE_OFFSET]); + this->receive_buffer_.clear(); + return; + } + + uint8_t obis_code_length = plaintext[current_position + OBIS_LENGTH_OFFSET]; + if (obis_code_length != OBIS_CODE_LENGTH_STANDARD && obis_code_length != OBIS_CODE_LENGTH_EXTENDED) { + ESP_LOGE(TAG, "OBIS: Unsupported OBIS header length: %x", obis_code_length); + this->receive_buffer_.clear(); + return; + } + if (current_position + OBIS_CODE_OFFSET + obis_code_length > message_length) { + ESP_LOGE(TAG, "OBIS: Buffer too short for OBIS code"); + this->receive_buffer_.clear(); + return; + } + + uint8_t *obis_code = &plaintext[current_position + OBIS_CODE_OFFSET]; + uint8_t obis_medium = obis_code[OBIS_A]; + uint16_t obis_cd = encode_uint16(obis_code[OBIS_C], obis_code[OBIS_D]); + + bool timestamp_found = false; + bool meter_number_found = false; + if (this->provider_ == PROVIDER_NETZNOE) { + // Do not advance Position when reading the Timestamp at DECODER_START_OFFSET + if ((obis_code_length == OBIS_CODE_LENGTH_EXTENDED) && (current_position == DECODER_START_OFFSET)) { + timestamp_found = true; + } else if (power_factor_found) { + meter_number_found = true; + power_factor_found = false; + } else { + current_position += obis_code_length + OBIS_CODE_OFFSET; // Advance past code and position + } + } else { + current_position += obis_code_length + OBIS_CODE_OFFSET; // Advance past code, position and type + } + if (!timestamp_found && !meter_number_found && obis_medium != Medium::ELECTRICITY && + obis_medium != Medium::ABSTRACT) { + ESP_LOGE(TAG, "OBIS: Unsupported OBIS medium: %x", obis_medium); + this->receive_buffer_.clear(); + return; + } + + if (current_position >= message_length) { + ESP_LOGE(TAG, "OBIS: Buffer too short for data type"); + this->receive_buffer_.clear(); + return; + } + + float value = 0.0f; + uint8_t value_size = 0; + uint8_t data_type = plaintext[current_position]; + current_position++; + + switch (data_type) { + case DataType::DOUBLE_LONG_UNSIGNED: { + value_size = 4; + if (current_position + value_size > message_length) { + ESP_LOGE(TAG, "OBIS: Buffer too short for DOUBLE_LONG_UNSIGNED"); + this->receive_buffer_.clear(); + return; + } + value = encode_uint32(plaintext[current_position + 0], plaintext[current_position + 1], + plaintext[current_position + 2], plaintext[current_position + 3]); + current_position += value_size; + break; + } + case DataType::LONG_UNSIGNED: { + value_size = 2; + if (current_position + value_size > message_length) { + ESP_LOGE(TAG, "OBIS: Buffer too short for LONG_UNSIGNED"); + this->receive_buffer_.clear(); + return; + } + value = encode_uint16(plaintext[current_position + 0], plaintext[current_position + 1]); + current_position += value_size; + break; + } + case DataType::OCTET_STRING: { + uint8_t data_length = plaintext[current_position]; + current_position++; // Advance past string length + if (current_position + data_length > message_length) { + ESP_LOGE(TAG, "OBIS: Buffer too short for OCTET_STRING"); + this->receive_buffer_.clear(); + return; + } + // Handle timestamp (normal OBIS code or NETZNOE special case) + if (obis_cd == OBIS_TIMESTAMP || timestamp_found) { + if (data_length < 8) { + ESP_LOGE(TAG, "OBIS: Timestamp data too short: %u", data_length); + this->receive_buffer_.clear(); + return; + } + uint16_t year = encode_uint16(plaintext[current_position + 0], plaintext[current_position + 1]); + uint8_t month = plaintext[current_position + 2]; + uint8_t day = plaintext[current_position + 3]; + uint8_t hour = plaintext[current_position + 5]; + uint8_t minute = plaintext[current_position + 6]; + uint8_t second = plaintext[current_position + 7]; + if (year > 9999 || month > 12 || day > 31 || hour > 23 || minute > 59 || second > 59) { + ESP_LOGE(TAG, "Invalid timestamp values: %04u-%02u-%02uT%02u:%02u:%02uZ", year, month, day, hour, minute, + second); + this->receive_buffer_.clear(); + return; + } + snprintf(data.timestamp, sizeof(data.timestamp), "%04u-%02u-%02uT%02u:%02u:%02uZ", year, month, day, hour, + minute, second); + } else if (meter_number_found) { + snprintf(data.meternumber, sizeof(data.meternumber), "%.*s", data_length, &plaintext[current_position]); + } + current_position += data_length; + break; + } + default: + ESP_LOGE(TAG, "OBIS: Unsupported OBIS data type: %x", data_type); + this->receive_buffer_.clear(); + return; + } + + // Skip break after data + if (this->provider_ == PROVIDER_NETZNOE) { + // Don't skip the break on the first timestamp, as there's none + if (!timestamp_found) { + current_position += 2; + } + } else { + current_position += 2; + } + + // Check for additional data (scaler-unit structure) + if (current_position < message_length && plaintext[current_position] == DataType::INTEGER) { + // Apply scaler: real_value = raw_value × 10^scaler + if (current_position + 1 < message_length) { + int8_t scaler = static_cast(plaintext[current_position + 1]); + if (scaler != 0) { + value *= powf(10.0f, scaler); + } + } + + // on EVN Meters there is no additional break + if (this->provider_ == PROVIDER_NETZNOE) { + current_position += 4; + } else { + current_position += 6; + } + } + + // Handle numeric values (LONG_UNSIGNED and DOUBLE_LONG_UNSIGNED) + if (value_size > 0) { + switch (obis_cd) { + case OBIS_VOLTAGE_L1: + data.voltage_l1 = value; + break; + case OBIS_VOLTAGE_L2: + data.voltage_l2 = value; + break; + case OBIS_VOLTAGE_L3: + data.voltage_l3 = value; + break; + case OBIS_CURRENT_L1: + data.current_l1 = value; + break; + case OBIS_CURRENT_L2: + data.current_l2 = value; + break; + case OBIS_CURRENT_L3: + data.current_l3 = value; + break; + case OBIS_ACTIVE_POWER_PLUS: + data.active_power_plus = value; + break; + case OBIS_ACTIVE_POWER_MINUS: + data.active_power_minus = value; + break; + case OBIS_ACTIVE_ENERGY_PLUS: + data.active_energy_plus = value; + break; + case OBIS_ACTIVE_ENERGY_MINUS: + data.active_energy_minus = value; + break; + case OBIS_REACTIVE_ENERGY_PLUS: + data.reactive_energy_plus = value; + break; + case OBIS_REACTIVE_ENERGY_MINUS: + data.reactive_energy_minus = value; + break; + case OBIS_POWER_FACTOR: + data.power_factor = value; + power_factor_found = true; + break; + default: + ESP_LOGW(TAG, "Unsupported OBIS code 0x%04X", obis_cd); + } + } + } + + this->receive_buffer_.clear(); + + ESP_LOGI(TAG, "Received valid data"); + this->publish_sensors(data); + this->status_clear_warning(); +} + +} // namespace esphome::dlms_meter diff --git a/esphome/components/dlms_meter/dlms_meter.h b/esphome/components/dlms_meter/dlms_meter.h new file mode 100644 index 0000000000..c50e6f6b4d --- /dev/null +++ b/esphome/components/dlms_meter/dlms_meter.h @@ -0,0 +1,96 @@ +#pragma once + +#include "esphome/core/component.h" +#include "esphome/core/defines.h" +#include "esphome/core/log.h" +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif +#ifdef USE_TEXT_SENSOR +#include "esphome/components/text_sensor/text_sensor.h" +#endif +#include "esphome/components/uart/uart.h" + +#include "mbus.h" +#include "dlms.h" +#include "obis.h" + +#include +#include + +namespace esphome::dlms_meter { + +#ifndef DLMS_METER_SENSOR_LIST +#define DLMS_METER_SENSOR_LIST(F, SEP) +#endif + +#ifndef DLMS_METER_TEXT_SENSOR_LIST +#define DLMS_METER_TEXT_SENSOR_LIST(F, SEP) +#endif + +struct MeterData { + float voltage_l1 = 0.0f; // Voltage L1 + float voltage_l2 = 0.0f; // Voltage L2 + float voltage_l3 = 0.0f; // Voltage L3 + float current_l1 = 0.0f; // Current L1 + float current_l2 = 0.0f; // Current L2 + float current_l3 = 0.0f; // Current L3 + float active_power_plus = 0.0f; // Active power taken from grid + float active_power_minus = 0.0f; // Active power put into grid + float active_energy_plus = 0.0f; // Active energy taken from grid + float active_energy_minus = 0.0f; // Active energy put into grid + float reactive_energy_plus = 0.0f; // Reactive energy taken from grid + float reactive_energy_minus = 0.0f; // Reactive energy put into grid + char timestamp[27]{}; // Text sensor for the timestamp value + + // Netz NOE + float power_factor = 0.0f; // Power Factor + char meternumber[13]{}; // Text sensor for the meterNumber value +}; + +// Provider constants +enum Providers : uint32_t { PROVIDER_GENERIC = 0x00, PROVIDER_NETZNOE = 0x01 }; + +class DlmsMeterComponent : public Component, public uart::UARTDevice { + public: + DlmsMeterComponent() = default; + + void dump_config() override; + void loop() override; + + void set_decryption_key(const std::array &key) { this->decryption_key_ = key; } + void set_provider(uint32_t provider) { this->provider_ = provider; } + + void publish_sensors(MeterData &data) { +#define DLMS_METER_PUBLISH_SENSOR(s) \ + if (this->s##_sensor_ != nullptr) \ + s##_sensor_->publish_state(data.s); + DLMS_METER_SENSOR_LIST(DLMS_METER_PUBLISH_SENSOR, ) + +#define DLMS_METER_PUBLISH_TEXT_SENSOR(s) \ + if (this->s##_text_sensor_ != nullptr) \ + s##_text_sensor_->publish_state(data.s); + DLMS_METER_TEXT_SENSOR_LIST(DLMS_METER_PUBLISH_TEXT_SENSOR, ) + } + + DLMS_METER_SENSOR_LIST(SUB_SENSOR, ) + DLMS_METER_TEXT_SENSOR_LIST(SUB_TEXT_SENSOR, ) + + protected: + bool parse_mbus_(std::vector &mbus_payload); + bool parse_dlms_(const std::vector &mbus_payload, uint16_t &message_length, uint8_t &systitle_length, + uint16_t &header_offset); + bool decrypt_(std::vector &mbus_payload, uint16_t message_length, uint8_t systitle_length, + uint16_t header_offset); + void decode_obis_(uint8_t *plaintext, uint16_t message_length); + + std::vector receive_buffer_; // Stores the packet currently being received + std::vector mbus_payload_; // Parsed M-Bus payload, reused to avoid heap churn + uint32_t last_read_ = 0; // Timestamp when data was last read + uint32_t read_timeout_ = 1000; // Time to wait after last byte before considering data complete + + uint32_t provider_ = PROVIDER_GENERIC; // Provider of the meter / your grid operator + std::array decryption_key_; +}; + +} // namespace esphome::dlms_meter diff --git a/esphome/components/dlms_meter/mbus.h b/esphome/components/dlms_meter/mbus.h new file mode 100644 index 0000000000..293d43a55b --- /dev/null +++ b/esphome/components/dlms_meter/mbus.h @@ -0,0 +1,69 @@ +#pragma once + +#include + +namespace esphome::dlms_meter { + +/* ++----------------------------------------------------+ - +| Start Character [0x68] | \ ++----------------------------------------------------+ | +| Data Length (L) | | ++----------------------------------------------------+ | +| Data Length Repeat (L) | | ++----------------------------------------------------+ > M-Bus Data link layer +| Start Character Repeat [0x68] | | ++----------------------------------------------------+ | +| Control/Function Field (C) | | ++----------------------------------------------------+ | +| Address Field (A) | / ++----------------------------------------------------+ - +| Control Information Field (CI) | \ ++----------------------------------------------------+ | +| Source Transport Service Access Point (STSAP) | > DLMS/COSEM M-Bus transport layer ++----------------------------------------------------+ | +| Destination Transport Service Access Point (DTSAP) | / ++----------------------------------------------------+ - +| | \ +~ ~ | + Data > DLMS/COSEM Application Layer +~ ~ | +| | / ++----------------------------------------------------+ - +| Checksum | \ ++----------------------------------------------------+ > M-Bus Data link layer +| Stop Character [0x16] | / ++----------------------------------------------------+ - + +Data_Length = L - C - A - CI +Each line (except Data) is one Byte + +Possible Values found in publicly available docs: +- C: 0x53/0x73 (SND_UD) +- A: FF (Broadcast) +- CI: 0x00-0x1F/0x60/0x61/0x7C/0x7D +- STSAP: 0x01 (Management Logical Device ID 1 of the meter) +- DTSAP: 0x67 (Consumer Information Push Client ID 103) + */ + +// MBUS start bytes for different telegram formats: +// - Single Character: 0xE5 (length=1) +// - Short Frame: 0x10 (length=5) +// - Control Frame: 0x68 (length=9) +// - Long Frame: 0x68 (length=9+data_length) +// This component currently only uses Long Frame. +static constexpr uint8_t START_BYTE_SINGLE_CHARACTER = 0xE5; +static constexpr uint8_t START_BYTE_SHORT_FRAME = 0x10; +static constexpr uint8_t START_BYTE_CONTROL_FRAME = 0x68; +static constexpr uint8_t START_BYTE_LONG_FRAME = 0x68; +static constexpr uint8_t MBUS_HEADER_INTRO_LENGTH = 4; // Header length for the intro (0x68, length, length, 0x68) +static constexpr uint8_t MBUS_FULL_HEADER_LENGTH = 9; // Total header length +static constexpr uint8_t MBUS_FOOTER_LENGTH = 2; // Footer after frame +static constexpr uint8_t MBUS_MAX_FRAME_LENGTH = 250; // Maximum size of frame +static constexpr uint8_t MBUS_START1_OFFSET = 0; // Offset of first start byte +static constexpr uint8_t MBUS_LENGTH1_OFFSET = 1; // Offset of first length byte +static constexpr uint8_t MBUS_LENGTH2_OFFSET = 2; // Offset of (duplicated) second length byte +static constexpr uint8_t MBUS_START2_OFFSET = 3; // Offset of (duplicated) second start byte +static constexpr uint8_t STOP_BYTE = 0x16; + +} // namespace esphome::dlms_meter diff --git a/esphome/components/dlms_meter/obis.h b/esphome/components/dlms_meter/obis.h new file mode 100644 index 0000000000..1bb960e61e --- /dev/null +++ b/esphome/components/dlms_meter/obis.h @@ -0,0 +1,94 @@ +#pragma once + +#include + +namespace esphome::dlms_meter { + +// Data types as per specification +enum DataType { + NULL_DATA = 0x00, + BOOLEAN = 0x03, + BIT_STRING = 0x04, + DOUBLE_LONG = 0x05, + DOUBLE_LONG_UNSIGNED = 0x06, + OCTET_STRING = 0x09, + VISIBLE_STRING = 0x0A, + UTF8_STRING = 0x0C, + BINARY_CODED_DECIMAL = 0x0D, + INTEGER = 0x0F, + LONG = 0x10, + UNSIGNED = 0x11, + LONG_UNSIGNED = 0x12, + LONG64 = 0x14, + LONG64_UNSIGNED = 0x15, + ENUM = 0x16, + FLOAT32 = 0x17, + FLOAT64 = 0x18, + DATE_TIME = 0x19, + DATE = 0x1A, + TIME = 0x1B, + + ARRAY = 0x01, + STRUCTURE = 0x02, + COMPACT_ARRAY = 0x13 +}; + +enum Medium { + ABSTRACT = 0x00, + ELECTRICITY = 0x01, + HEAT_COST_ALLOCATOR = 0x04, + COOLING = 0x05, + HEAT = 0x06, + GAS = 0x07, + COLD_WATER = 0x08, + HOT_WATER = 0x09, + OIL = 0x10, + COMPRESSED_AIR = 0x11, + NITROGEN = 0x12 +}; + +// Data structure +static constexpr uint8_t DECODER_START_OFFSET = 20; // Skip header, timestamp and break block +static constexpr uint8_t OBIS_TYPE_OFFSET = 0; +static constexpr uint8_t OBIS_LENGTH_OFFSET = 1; +static constexpr uint8_t OBIS_CODE_OFFSET = 2; +static constexpr uint8_t OBIS_CODE_LENGTH_STANDARD = 0x06; // 6-byte OBIS code (A.B.C.D.E.F) +static constexpr uint8_t OBIS_CODE_LENGTH_EXTENDED = 0x0C; // 12-byte extended OBIS code +static constexpr uint8_t OBIS_A = 0; +static constexpr uint8_t OBIS_B = 1; +static constexpr uint8_t OBIS_C = 2; +static constexpr uint8_t OBIS_D = 3; +static constexpr uint8_t OBIS_E = 4; +static constexpr uint8_t OBIS_F = 5; + +// Metadata +static constexpr uint16_t OBIS_TIMESTAMP = 0x0100; +static constexpr uint16_t OBIS_SERIAL_NUMBER = 0x6001; +static constexpr uint16_t OBIS_DEVICE_NAME = 0x2A00; + +// Voltage +static constexpr uint16_t OBIS_VOLTAGE_L1 = 0x2007; +static constexpr uint16_t OBIS_VOLTAGE_L2 = 0x3407; +static constexpr uint16_t OBIS_VOLTAGE_L3 = 0x4807; + +// Current +static constexpr uint16_t OBIS_CURRENT_L1 = 0x1F07; +static constexpr uint16_t OBIS_CURRENT_L2 = 0x3307; +static constexpr uint16_t OBIS_CURRENT_L3 = 0x4707; + +// Power +static constexpr uint16_t OBIS_ACTIVE_POWER_PLUS = 0x0107; +static constexpr uint16_t OBIS_ACTIVE_POWER_MINUS = 0x0207; + +// Active energy +static constexpr uint16_t OBIS_ACTIVE_ENERGY_PLUS = 0x0108; +static constexpr uint16_t OBIS_ACTIVE_ENERGY_MINUS = 0x0208; + +// Reactive energy +static constexpr uint16_t OBIS_REACTIVE_ENERGY_PLUS = 0x0308; +static constexpr uint16_t OBIS_REACTIVE_ENERGY_MINUS = 0x0408; + +// Netz NOE specific +static constexpr uint16_t OBIS_POWER_FACTOR = 0x0D07; + +} // namespace esphome::dlms_meter diff --git a/esphome/components/dlms_meter/sensor/__init__.py b/esphome/components/dlms_meter/sensor/__init__.py new file mode 100644 index 0000000000..27fd44f008 --- /dev/null +++ b/esphome/components/dlms_meter/sensor/__init__.py @@ -0,0 +1,124 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + CONF_ID, + DEVICE_CLASS_CURRENT, + DEVICE_CLASS_ENERGY, + DEVICE_CLASS_POWER, + DEVICE_CLASS_POWER_FACTOR, + DEVICE_CLASS_VOLTAGE, + STATE_CLASS_MEASUREMENT, + STATE_CLASS_TOTAL_INCREASING, + UNIT_AMPERE, + UNIT_VOLT, + UNIT_WATT, + UNIT_WATT_HOURS, +) + +from .. import CONF_DLMS_METER_ID, DlmsMeterComponent + +AUTO_LOAD = ["dlms_meter"] + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_DLMS_METER_ID): cv.use_id(DlmsMeterComponent), + cv.Optional("voltage_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("voltage_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_VOLT, + accuracy_decimals=1, + device_class=DEVICE_CLASS_VOLTAGE, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_l1"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_l2"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("current_l3"): sensor.sensor_schema( + unit_of_measurement=UNIT_AMPERE, + accuracy_decimals=2, + device_class=DEVICE_CLASS_CURRENT, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("active_power_plus"): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("active_power_minus"): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT, + accuracy_decimals=0, + device_class=DEVICE_CLASS_POWER, + state_class=STATE_CLASS_MEASUREMENT, + ), + cv.Optional("active_energy_plus"): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("active_energy_minus"): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("reactive_energy_plus"): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + cv.Optional("reactive_energy_minus"): sensor.sensor_schema( + unit_of_measurement=UNIT_WATT_HOURS, + accuracy_decimals=0, + device_class=DEVICE_CLASS_ENERGY, + state_class=STATE_CLASS_TOTAL_INCREASING, + ), + # Netz NOE + cv.Optional("power_factor"): sensor.sensor_schema( + accuracy_decimals=3, + device_class=DEVICE_CLASS_POWER_FACTOR, + state_class=STATE_CLASS_MEASUREMENT, + ), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_DLMS_METER_ID]) + + sensors = [] + for key, conf in config.items(): + if not isinstance(conf, dict): + continue + id = conf[CONF_ID] + if id and id.type == sensor.Sensor: + sens = await sensor.new_sensor(conf) + cg.add(getattr(hub, f"set_{key}_sensor")(sens)) + sensors.append(f"F({key})") + + if sensors: + cg.add_define( + "DLMS_METER_SENSOR_LIST(F, sep)", cg.RawExpression(" sep ".join(sensors)) + ) diff --git a/esphome/components/dlms_meter/text_sensor/__init__.py b/esphome/components/dlms_meter/text_sensor/__init__.py new file mode 100644 index 0000000000..4d2373f4f9 --- /dev/null +++ b/esphome/components/dlms_meter/text_sensor/__init__.py @@ -0,0 +1,37 @@ +import esphome.codegen as cg +from esphome.components import text_sensor +import esphome.config_validation as cv +from esphome.const import CONF_ID + +from .. import CONF_DLMS_METER_ID, DlmsMeterComponent + +AUTO_LOAD = ["dlms_meter"] + +CONFIG_SCHEMA = cv.Schema( + { + cv.GenerateID(CONF_DLMS_METER_ID): cv.use_id(DlmsMeterComponent), + cv.Optional("timestamp"): text_sensor.text_sensor_schema(), + # Netz NOE + cv.Optional("meternumber"): text_sensor.text_sensor_schema(), + } +).extend(cv.COMPONENT_SCHEMA) + + +async def to_code(config): + hub = await cg.get_variable(config[CONF_DLMS_METER_ID]) + + text_sensors = [] + for key, conf in config.items(): + if not isinstance(conf, dict): + continue + id = conf[CONF_ID] + if id and id.type == text_sensor.TextSensor: + sens = await text_sensor.new_text_sensor(conf) + cg.add(getattr(hub, f"set_{key}_text_sensor")(sens)) + text_sensors.append(f"F({key})") + + if text_sensors: + cg.add_define( + "DLMS_METER_TEXT_SENSOR_LIST(F, sep)", + cg.RawExpression(" sep ".join(text_sensors)), + ) diff --git a/esphome/components/endstop/endstop_cover.cpp b/esphome/components/endstop/endstop_cover.cpp index 2c281ea2e6..e28f024136 100644 --- a/esphome/components/endstop/endstop_cover.cpp +++ b/esphome/components/endstop/endstop_cover.cpp @@ -141,15 +141,15 @@ void EndstopCover::start_direction_(CoverOperation dir) { Trigger<> *trig; switch (dir) { case COVER_OPERATION_IDLE: - trig = this->stop_trigger_; + trig = &this->stop_trigger_; break; case COVER_OPERATION_OPENING: this->last_operation_ = dir; - trig = this->open_trigger_; + trig = &this->open_trigger_; break; case COVER_OPERATION_CLOSING: this->last_operation_ = dir; - trig = this->close_trigger_; + trig = &this->close_trigger_; break; default: return; diff --git a/esphome/components/endstop/endstop_cover.h b/esphome/components/endstop/endstop_cover.h index 6ae15de8c1..6f72b2b805 100644 --- a/esphome/components/endstop/endstop_cover.h +++ b/esphome/components/endstop/endstop_cover.h @@ -15,9 +15,9 @@ class EndstopCover : public cover::Cover, public Component { void dump_config() override; float get_setup_priority() const override; - Trigger<> *get_open_trigger() const { return this->open_trigger_; } - Trigger<> *get_close_trigger() const { return this->close_trigger_; } - Trigger<> *get_stop_trigger() const { return this->stop_trigger_; } + Trigger<> *get_open_trigger() { return &this->open_trigger_; } + Trigger<> *get_close_trigger() { return &this->close_trigger_; } + Trigger<> *get_stop_trigger() { return &this->stop_trigger_; } 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_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 *close_endstop_; - Trigger<> *open_trigger_{new Trigger<>()}; + Trigger<> open_trigger_; uint32_t open_duration_; - Trigger<> *close_trigger_{new Trigger<>()}; + Trigger<> close_trigger_; uint32_t close_duration_; - Trigger<> *stop_trigger_{new Trigger<>()}; + Trigger<> stop_trigger_; uint32_t max_duration_{UINT32_MAX}; Trigger<> *prev_command_trigger_{nullptr}; diff --git a/esphome/components/esp32/__init__.py b/esphome/components/esp32/__init__.py index 513d7d6fe7..cd448be9dc 100644 --- a/esphome/components/esp32/__init__.py +++ b/esphome/components/esp32/__init__.py @@ -1540,6 +1540,10 @@ async def to_code(config): # Disable dynamic log level control to save memory add_idf_sdkconfig_option("CONFIG_LOG_DYNAMIC_LEVEL_CONTROL", False) + # Disable per-tag log level filtering since dynamic level control is disabled above + # This saves ~250 bytes of RAM (tag cache) and associated code + add_idf_sdkconfig_option("CONFIG_LOG_TAG_LEVEL_IMPL_NONE", True) + # Reduce PHY TX power in the event of a brownout add_idf_sdkconfig_option("CONFIG_ESP_PHY_REDUCE_TX_POWER", True) diff --git a/esphome/components/ethernet/__init__.py b/esphome/components/ethernet/__init__.py index 215ff46748..38489ceb2b 100644 --- a/esphome/components/ethernet/__init__.py +++ b/esphome/components/ethernet/__init__.py @@ -1,6 +1,6 @@ import logging -from esphome import pins +from esphome import automation, pins import esphome.codegen as cg from esphome.components.esp32 import ( VARIANT_ESP32, @@ -35,6 +35,8 @@ from esphome.const import ( CONF_MODE, CONF_MOSI_PIN, CONF_NUMBER, + CONF_ON_CONNECT, + CONF_ON_DISCONNECT, CONF_PAGE_ID, CONF_PIN, CONF_POLLING_INTERVAL, @@ -237,6 +239,8 @@ BASE_SCHEMA = cv.Schema( cv.Optional(CONF_DOMAIN, default=".local"): cv.domain_name, cv.Optional(CONF_USE_ADDRESS): cv.string_strict, 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) @@ -427,6 +431,18 @@ async def to_code(config): # Add LAN867x 10BASE-T1S PHY support component add_idf_component(name="espressif/lan867x", ref="2.0.0") + 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) diff --git a/esphome/components/ethernet/ethernet_component.cpp b/esphome/components/ethernet/ethernet_component.cpp index 70f8ce1204..af7fed608b 100644 --- a/esphome/components/ethernet/ethernet_component.cpp +++ b/esphome/components/ethernet/ethernet_component.cpp @@ -309,6 +309,9 @@ void EthernetComponent::loop() { this->dump_connect_params_(); this->status_clear_warning(); +#ifdef USE_ETHERNET_CONNECT_TRIGGER + this->connect_trigger_.trigger(); +#endif } else if (now - this->connect_begin_ > 15000) { ESP_LOGW(TAG, "Connecting failed; reconnecting"); this->start_connect_(); @@ -318,10 +321,16 @@ void EthernetComponent::loop() { if (!this->started_) { ESP_LOGI(TAG, "Stopped connection"); this->state_ = EthernetComponentState::STOPPED; +#ifdef USE_ETHERNET_DISCONNECT_TRIGGER + this->disconnect_trigger_.trigger(); +#endif } else if (!this->connected_) { ESP_LOGW(TAG, "Connection lost; reconnecting"); this->state_ = EthernetComponentState::CONNECTING; this->start_connect_(); +#ifdef USE_ETHERNET_DISCONNECT_TRIGGER + this->disconnect_trigger_.trigger(); +#endif } else { this->finish_connect_(); // When connected and stable, disable the loop to save CPU cycles diff --git a/esphome/components/ethernet/ethernet_component.h b/esphome/components/ethernet/ethernet_component.h index 34380047d1..5a2869c5a7 100644 --- a/esphome/components/ethernet/ethernet_component.h +++ b/esphome/components/ethernet/ethernet_component.h @@ -4,6 +4,7 @@ #include "esphome/core/defines.h" #include "esphome/core/hal.h" #include "esphome/core/helpers.h" +#include "esphome/core/automation.h" #include "esphome/components/network/ip_address.h" #ifdef USE_ESP32 @@ -119,6 +120,12 @@ class EthernetComponent : public Component { void add_ip_state_listener(EthernetIPStateListener *listener) { this->ip_state_listeners_.push_back(listener); } #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: 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); @@ -190,6 +197,12 @@ class EthernetComponent : public Component { StaticVector ip_state_listeners_; #endif +#ifdef USE_ETHERNET_CONNECT_TRIGGER + Trigger<> connect_trigger_; +#endif +#ifdef USE_ETHERNET_DISCONNECT_TRIGGER + Trigger<> disconnect_trigger_; +#endif private: // Stores a pointer to a string literal (static storage duration). // ONLY set from Python-generated code with string literals - never dynamic strings. diff --git a/esphome/components/feedback/feedback_cover.cpp b/esphome/components/feedback/feedback_cover.cpp index e419ee6229..ffb19fa091 100644 --- a/esphome/components/feedback/feedback_cover.cpp +++ b/esphome/components/feedback/feedback_cover.cpp @@ -335,18 +335,18 @@ void FeedbackCover::start_direction_(CoverOperation dir) { switch (dir) { case COVER_OPERATION_IDLE: - trig = this->stop_trigger_; + trig = &this->stop_trigger_; break; case COVER_OPERATION_OPENING: this->last_operation_ = dir; - trig = this->open_trigger_; + trig = &this->open_trigger_; #ifdef USE_BINARY_SENSOR obstacle = this->open_obstacle_; #endif break; case COVER_OPERATION_CLOSING: this->last_operation_ = dir; - trig = this->close_trigger_; + trig = &this->close_trigger_; #ifdef USE_BINARY_SENSOR obstacle = this->close_obstacle_; #endif diff --git a/esphome/components/feedback/feedback_cover.h b/esphome/components/feedback/feedback_cover.h index 199d3b520a..6be8939413 100644 --- a/esphome/components/feedback/feedback_cover.h +++ b/esphome/components/feedback/feedback_cover.h @@ -17,9 +17,9 @@ class FeedbackCover : public cover::Cover, public Component { void loop() override; void dump_config() override; - Trigger<> *get_open_trigger() const { return this->open_trigger_; } - Trigger<> *get_close_trigger() const { return this->close_trigger_; } - Trigger<> *get_stop_trigger() const { return this->stop_trigger_; } + Trigger<> *get_open_trigger() { return &this->open_trigger_; } + Trigger<> *get_close_trigger() { return &this->close_trigger_; } + Trigger<> *get_stop_trigger() { return &this->stop_trigger_; } #ifdef USE_BINARY_SENSOR 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}; #endif - Trigger<> *open_trigger_{new Trigger<>()}; - Trigger<> *close_trigger_{new Trigger<>()}; - Trigger<> *stop_trigger_{new Trigger<>()}; + Trigger<> open_trigger_; + Trigger<> close_trigger_; + Trigger<> stop_trigger_; uint32_t open_duration_{0}; uint32_t close_duration_{0}; diff --git a/esphome/components/http_request/http_request.h b/esphome/components/http_request/http_request.h index fb39ca504c..79098a6b72 100644 --- a/esphome/components/http_request/http_request.h +++ b/esphome/components/http_request/http_request.h @@ -332,13 +332,13 @@ template class HttpRequestSendAction : public Action { void set_json(std::function json_func) { this->json_func_ = json_func; } #ifdef USE_HTTP_REQUEST_RESPONSE - Trigger, std::string &, Ts...> *get_success_trigger_with_response() const { - return this->success_trigger_with_response_; + Trigger, std::string &, Ts...> *get_success_trigger_with_response() { + return &this->success_trigger_with_response_; } #endif - Trigger, Ts...> *get_success_trigger() const { return this->success_trigger_; } + Trigger, Ts...> *get_success_trigger() { return &this->success_trigger_; } - Trigger *get_error_trigger() const { return this->error_trigger_; } + Trigger *get_error_trigger() { return &this->error_trigger_; } void set_max_response_buffer_size(size_t max_response_buffer_size) { this->max_response_buffer_size_ = max_response_buffer_size; @@ -372,7 +372,7 @@ template class HttpRequestSendAction : public Action { auto captured_args = std::make_tuple(x...); 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); return; } @@ -406,14 +406,14 @@ template class HttpRequestSendAction : public Action { } std::apply( [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); } else #endif { 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); } container->end(); @@ -433,12 +433,10 @@ template class HttpRequestSendAction : public Action { std::map> json_{}; std::function json_func_{nullptr}; #ifdef USE_HTTP_REQUEST_RESPONSE - Trigger, std::string &, Ts...> *success_trigger_with_response_ = - new Trigger, std::string &, Ts...>(); + Trigger, std::string &, Ts...> success_trigger_with_response_; #endif - Trigger, Ts...> *success_trigger_ = - new Trigger, Ts...>(); - Trigger *error_trigger_ = new Trigger(); + Trigger, Ts...> success_trigger_; + Trigger error_trigger_; size_t max_response_buffer_size_{SIZE_MAX}; }; diff --git a/esphome/components/i2c/i2c.cpp b/esphome/components/i2c/i2c.cpp index c1e7336ce4..b9b5d79428 100644 --- a/esphome/components/i2c/i2c.cpp +++ b/esphome/components/i2c/i2c.cpp @@ -11,12 +11,6 @@ namespace i2c { static const char *const TAG = "i2c"; void I2CBus::i2c_scan_() { - // suppress logs from the IDF I2C library during the scan -#if defined(USE_ESP32) && defined(USE_LOGGER) - auto previous = esp_log_level_get("*"); - esp_log_level_set("*", ESP_LOG_NONE); -#endif - for (uint8_t address = 8; address != 120; address++) { auto err = write_readv(address, nullptr, 0, nullptr, 0); if (err == ERROR_OK) { @@ -27,9 +21,6 @@ void I2CBus::i2c_scan_() { // it takes 16sec to scan on nrf52. It prevents board reset. arch_feed_wdt(); } -#if defined(USE_ESP32) && defined(USE_LOGGER) - esp_log_level_set("*", previous); -#endif } ErrorCode I2CDevice::read_register(uint8_t a_register, uint8_t *data, size_t len) { diff --git a/esphome/components/light/base_light_effects.h b/esphome/components/light/base_light_effects.h index 2eeae574e7..cdb9f1f666 100644 --- a/esphome/components/light/base_light_effects.h +++ b/esphome/components/light/base_light_effects.h @@ -138,20 +138,20 @@ class LambdaLightEffect : public LightEffect { class AutomationLightEffect : public LightEffect { public: AutomationLightEffect(const char *name) : LightEffect(name) {} - void stop() override { this->trig_->stop_action(); } + void stop() override { this->trig_.stop_action(); } void apply() override { - if (!this->trig_->is_action_running()) { - this->trig_->trigger(); + if (!this->trig_.is_action_running()) { + this->trig_.trigger(); } } - Trigger<> *get_trig() const { return trig_; } + Trigger<> *get_trig() { return &this->trig_; } /// Get the current effect index for use in automations. /// Useful for automations that need to know which effect is running. uint32_t get_current_index() const { return this->get_index(); } protected: - Trigger<> *trig_{new Trigger<>}; + Trigger<> trig_; }; struct StrobeLightEffectColor { diff --git a/esphome/components/logger/logger_esp32.cpp b/esphome/components/logger/logger_esp32.cpp index 32ef752462..9defb6c166 100644 --- a/esphome/components/logger/logger_esp32.cpp +++ b/esphome/components/logger/logger_esp32.cpp @@ -114,9 +114,6 @@ void Logger::pre_setup() { global_logger = this; esp_log_set_vprintf(esp_idf_log_vprintf_); - if (ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE) { - esp_log_level_set("*", ESP_LOG_VERBOSE); - } ESP_LOGI(TAG, "Log initialized"); } diff --git a/esphome/components/max7219/display.py b/esphome/components/max7219/display.py index a434125148..abb20702bd 100644 --- a/esphome/components/max7219/display.py +++ b/esphome/components/max7219/display.py @@ -28,11 +28,10 @@ CONFIG_SCHEMA = ( async def to_code(config): - var = cg.new_Pvariable(config[CONF_ID]) + var = cg.new_Pvariable(config[CONF_ID], config[CONF_NUM_CHIPS]) await spi.register_spi_device(var, config, write_only=True) await display.register_display(var, config) - cg.add(var.set_num_chips(config[CONF_NUM_CHIPS])) cg.add(var.set_intensity(config[CONF_INTENSITY])) cg.add(var.set_reverse(config[CONF_REVERSE_ENABLE])) diff --git a/esphome/components/max7219/max7219.cpp b/esphome/components/max7219/max7219.cpp index 157b317c02..d701e6fc86 100644 --- a/esphome/components/max7219/max7219.cpp +++ b/esphome/components/max7219/max7219.cpp @@ -3,8 +3,7 @@ #include "esphome/core/helpers.h" #include "esphome/core/log.h" -namespace esphome { -namespace max7219 { +namespace esphome::max7219 { static const char *const TAG = "max7219"; @@ -115,12 +114,14 @@ const uint8_t MAX7219_ASCII_TO_RAW[95] PROGMEM = { }; float MAX7219Component::get_setup_priority() const { return setup_priority::PROCESSOR; } + +MAX7219Component::MAX7219Component(uint8_t num_chips) : num_chips_(num_chips) { + this->buffer_ = new uint8_t[this->num_chips_ * 8]; // NOLINT + memset(this->buffer_, 0, this->num_chips_ * 8); +} + void MAX7219Component::setup() { this->spi_setup(); - this->buffer_ = new uint8_t[this->num_chips_ * 8]; // NOLINT - for (uint8_t i = 0; i < this->num_chips_ * 8; i++) - this->buffer_[i] = 0; - // let's assume the user has all 8 digits connected, only important in daisy chained setups anyway this->send_to_all_(MAX7219_REGISTER_SCAN_LIMIT, 7); // let's use our own ASCII -> led pattern encoding @@ -229,7 +230,6 @@ void MAX7219Component::set_intensity(uint8_t intensity) { this->intensity_ = intensity; } } -void MAX7219Component::set_num_chips(uint8_t num_chips) { this->num_chips_ = num_chips; } uint8_t MAX7219Component::strftime(uint8_t pos, const char *format, ESPTime time) { char buffer[64]; @@ -240,5 +240,4 @@ uint8_t MAX7219Component::strftime(uint8_t pos, const char *format, ESPTime time } uint8_t MAX7219Component::strftime(const char *format, ESPTime time) { return this->strftime(0, format, time); } -} // namespace max7219 -} // namespace esphome +} // namespace esphome::max7219 diff --git a/esphome/components/max7219/max7219.h b/esphome/components/max7219/max7219.h index 58d871d54c..ef38628f28 100644 --- a/esphome/components/max7219/max7219.h +++ b/esphome/components/max7219/max7219.h @@ -6,8 +6,7 @@ #include "esphome/components/spi/spi.h" #include "esphome/components/display/display.h" -namespace esphome { -namespace max7219 { +namespace esphome::max7219 { class MAX7219Component; @@ -17,6 +16,8 @@ class MAX7219Component : public PollingComponent, public spi::SPIDevice { public: + explicit MAX7219Component(uint8_t num_chips); + void set_writer(max7219_writer_t &&writer); void setup() override; @@ -30,7 +31,6 @@ class MAX7219Component : public PollingComponent, void display(); void set_intensity(uint8_t intensity); - void set_num_chips(uint8_t num_chips); void set_reverse(bool reverse) { this->reverse_ = reverse; }; /// Evaluate the printf-format and print the result at the given position. @@ -56,10 +56,9 @@ class MAX7219Component : public PollingComponent, uint8_t intensity_{15}; // Intensity of the display from 0 to 15 (most) bool intensity_changed_{}; // True if we need to re-send the intensity uint8_t num_chips_{1}; - uint8_t *buffer_; + uint8_t *buffer_{nullptr}; bool reverse_{false}; max7219_writer_t writer_{}; }; -} // namespace max7219 -} // namespace esphome +} // namespace esphome::max7219 diff --git a/esphome/components/micro_wake_word/micro_wake_word.cpp b/esphome/components/micro_wake_word/micro_wake_word.cpp index d7e80efc84..b93bf1b556 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.cpp +++ b/esphome/components/micro_wake_word/micro_wake_word.cpp @@ -325,7 +325,7 @@ void MicroWakeWord::loop() { 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.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_) { this->stop(); } diff --git a/esphome/components/micro_wake_word/micro_wake_word.h b/esphome/components/micro_wake_word/micro_wake_word.h index b427e4dfcb..44d5d89372 100644 --- a/esphome/components/micro_wake_word/micro_wake_word.h +++ b/esphome/components/micro_wake_word/micro_wake_word.h @@ -60,7 +60,7 @@ class MicroWakeWord : public Component void set_stop_after_detection(bool stop_after_detection) { this->stop_after_detection_ = stop_after_detection; } - Trigger *get_wake_word_detected_trigger() const { return this->wake_word_detected_trigger_; } + Trigger *get_wake_word_detected_trigger() { return &this->wake_word_detected_trigger_; } void add_wake_word_model(WakeWordModel *model); @@ -78,7 +78,7 @@ class MicroWakeWord : public Component protected: microphone::MicrophoneSource *microphone_source_{nullptr}; - Trigger *wake_word_detected_trigger_ = new Trigger(); + Trigger wake_word_detected_trigger_; State state_{State::STOPPED}; std::weak_ptr ring_buffer_; diff --git a/esphome/components/mipi_spi/mipi_spi.cpp b/esphome/components/mipi_spi/mipi_spi.cpp index 272915b4e1..90f6324511 100644 --- a/esphome/components/mipi_spi/mipi_spi.cpp +++ b/esphome/components/mipi_spi/mipi_spi.cpp @@ -1,6 +1,39 @@ #include "mipi_spi.h" #include "esphome/core/log.h" -namespace esphome { -namespace mipi_spi {} // namespace mipi_spi -} // namespace esphome +namespace esphome::mipi_spi { + +void internal_dump_config(const char *model, int width, int height, int offset_width, int offset_height, uint8_t madctl, + bool invert_colors, int display_bits, bool is_big_endian, const optional &brightness, + GPIOPin *cs, GPIOPin *reset, GPIOPin *dc, int spi_mode, uint32_t data_rate, int bus_width) { + ESP_LOGCONFIG(TAG, + "MIPI_SPI Display\n" + " Model: %s\n" + " Width: %d\n" + " Height: %d\n" + " Swap X/Y: %s\n" + " Mirror X: %s\n" + " Mirror Y: %s\n" + " Invert colors: %s\n" + " Color order: %s\n" + " Display pixels: %d bits\n" + " Endianness: %s\n" + " SPI Mode: %d\n" + " SPI Data rate: %uMHz\n" + " SPI Bus width: %d", + model, width, height, YESNO(madctl & MADCTL_MV), YESNO(madctl & (MADCTL_MX | MADCTL_XFLIP)), + YESNO(madctl & (MADCTL_MY | MADCTL_YFLIP)), YESNO(invert_colors), (madctl & MADCTL_BGR) ? "BGR" : "RGB", + display_bits, is_big_endian ? "Big" : "Little", spi_mode, static_cast(data_rate / 1000000), + bus_width); + LOG_PIN(" CS Pin: ", cs); + LOG_PIN(" Reset Pin: ", reset); + LOG_PIN(" DC Pin: ", dc); + if (offset_width != 0) + ESP_LOGCONFIG(TAG, " Offset width: %d", offset_width); + if (offset_height != 0) + ESP_LOGCONFIG(TAG, " Offset height: %d", offset_height); + if (brightness.has_value()) + ESP_LOGCONFIG(TAG, " Brightness: %u", brightness.value()); +} + +} // namespace esphome::mipi_spi diff --git a/esphome/components/mipi_spi/mipi_spi.h b/esphome/components/mipi_spi/mipi_spi.h index fd5bc97596..083ff9507f 100644 --- a/esphome/components/mipi_spi/mipi_spi.h +++ b/esphome/components/mipi_spi/mipi_spi.h @@ -63,6 +63,11 @@ enum BusType { BUS_TYPE_SINGLE_16 = 16, // Single bit bus, but 16 bits per transfer }; +// Helper function for dump_config - defined in mipi_spi.cpp to allow use of LOG_PIN macro +void internal_dump_config(const char *model, int width, int height, int offset_width, int offset_height, uint8_t madctl, + bool invert_colors, int display_bits, bool is_big_endian, const optional &brightness, + GPIOPin *cs, GPIOPin *reset, GPIOPin *dc, int spi_mode, uint32_t data_rate, int bus_width); + /** * Base class for MIPI SPI displays. * All the methods are defined here in the header file, as it is not possible to define templated methods in a cpp file. @@ -201,37 +206,9 @@ class MipiSpi : public display::Display, } void dump_config() override { - esph_log_config(TAG, - "MIPI_SPI Display\n" - " Model: %s\n" - " Width: %u\n" - " Height: %u", - this->model_, WIDTH, HEIGHT); - if constexpr (OFFSET_WIDTH != 0) - esph_log_config(TAG, " Offset width: %u", OFFSET_WIDTH); - if constexpr (OFFSET_HEIGHT != 0) - esph_log_config(TAG, " Offset height: %u", OFFSET_HEIGHT); - esph_log_config(TAG, - " Swap X/Y: %s\n" - " Mirror X: %s\n" - " Mirror Y: %s\n" - " Invert colors: %s\n" - " Color order: %s\n" - " Display pixels: %d bits\n" - " Endianness: %s\n", - YESNO(this->madctl_ & MADCTL_MV), YESNO(this->madctl_ & (MADCTL_MX | MADCTL_XFLIP)), - YESNO(this->madctl_ & (MADCTL_MY | MADCTL_YFLIP)), YESNO(this->invert_colors_), - this->madctl_ & MADCTL_BGR ? "BGR" : "RGB", DISPLAYPIXEL * 8, IS_BIG_ENDIAN ? "Big" : "Little"); - if (this->brightness_.has_value()) - esph_log_config(TAG, " Brightness: %u", this->brightness_.value()); - log_pin(TAG, " CS Pin: ", this->cs_); - log_pin(TAG, " Reset Pin: ", this->reset_pin_); - log_pin(TAG, " DC Pin: ", this->dc_pin_); - esph_log_config(TAG, - " SPI Mode: %d\n" - " SPI Data rate: %dMHz\n" - " SPI Bus width: %d", - this->mode_, static_cast(this->data_rate_ / 1000000), BUS_TYPE); + internal_dump_config(this->model_, WIDTH, HEIGHT, OFFSET_WIDTH, OFFSET_HEIGHT, this->madctl_, this->invert_colors_, + DISPLAYPIXEL * 8, IS_BIG_ENDIAN, this->brightness_, this->cs_, this->reset_pin_, this->dc_pin_, + this->mode_, this->data_rate_, BUS_TYPE); } protected: diff --git a/esphome/components/mixer/speaker/automation.h b/esphome/components/mixer/speaker/automation.h index 2234936628..2fb2f49373 100644 --- a/esphome/components/mixer/speaker/automation.h +++ b/esphome/components/mixer/speaker/automation.h @@ -1,5 +1,6 @@ #pragma once +#include "esphome/core/automation.h" #include "mixer_speaker.h" #ifdef USE_ESP32 diff --git a/esphome/components/mqtt/mqtt_backend_esp32.h b/esphome/components/mqtt/mqtt_backend_esp32.h index bd2d2a67b2..adba0cf004 100644 --- a/esphome/components/mqtt/mqtt_backend_esp32.h +++ b/esphome/components/mqtt/mqtt_backend_esp32.h @@ -139,7 +139,8 @@ class MQTTBackendESP32 final : public MQTTBackend { this->lwt_retain_ = retain; } void set_server(network::IPAddress ip, uint16_t port) final { - this->host_ = ip.str(); + char ip_buf[network::IP_ADDRESS_BUFFER_SIZE]; + this->host_ = ip.str_to(ip_buf); this->port_ = port; } void set_server(const char *host, uint16_t port) final { diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index e7364f3406..a284b162dd 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -643,10 +643,34 @@ static bool topic_match(const char *message, const char *subscription) { } void MQTTClientComponent::on_message(const std::string &topic, const std::string &payload) { - for (auto &subscription : this->subscriptions_) { - if (topic_match(topic.c_str(), subscription.topic.c_str())) - subscription.callback(topic, payload); - } +#ifdef USE_ESP8266 + // IMPORTANT: This defer is REQUIRED to prevent stack overflow crashes on ESP8266. + // + // On ESP8266, this callback is invoked directly from the lwIP/AsyncTCP network stack + // which runs in the "sys" context with a very limited stack (~4KB). By the time we + // reach this function, the stack is already partially consumed by the network + // processing chain: tcp_input -> AsyncClient::_recv -> AsyncMqttClient::_onMessage -> here. + // + // MQTT subscription callbacks can trigger arbitrary user actions (automations, HTTP + // requests, sensor updates, etc.) which may have deep call stacks of their own. + // For example, an HTTP request action requires: DNS lookup -> TCP connect -> TLS + // handshake (if HTTPS) -> request formatting. This easily overflows the remaining + // system stack space, causing a LoadStoreAlignmentCause exception or silent corruption. + // + // By deferring to the main loop, we ensure callbacks execute with a fresh, full-size + // stack in the normal application context rather than the constrained network task. + // + // DO NOT REMOVE THIS DEFER without understanding the above. It may appear to work + // in simple tests but will cause crashes with complex automations. + this->defer([this, topic, payload]() { +#endif + for (auto &subscription : this->subscriptions_) { + if (topic_match(topic.c_str(), subscription.topic.c_str())) + subscription.callback(topic, payload); + } +#ifdef USE_ESP8266 + }); +#endif } // Setters diff --git a/esphome/components/pmsx003/pmsx003.cpp b/esphome/components/pmsx003/pmsx003.cpp index bb167033d1..114ecf435e 100644 --- a/esphome/components/pmsx003/pmsx003.cpp +++ b/esphome/components/pmsx003/pmsx003.cpp @@ -2,21 +2,20 @@ #include "esphome/core/log.h" #include "esphome/core/application.h" -namespace esphome { -namespace pmsx003 { +namespace esphome::pmsx003 { static const char *const TAG = "pmsx003"; static const uint8_t START_CHARACTER_1 = 0x42; static const uint8_t START_CHARACTER_2 = 0x4D; -static const uint16_t PMS_STABILISING_MS = 30000; // time taken for the sensor to become stable after power on in ms +static const uint16_t STABILISING_MS = 30000; // time taken for the sensor to become stable after power on in ms -static const uint16_t PMS_CMD_MEASUREMENT_MODE_PASSIVE = - 0x0000; // use `PMS_CMD_MANUAL_MEASUREMENT` to trigger a measurement -static const uint16_t PMS_CMD_MEASUREMENT_MODE_ACTIVE = 0x0001; // automatically perform measurements -static const uint16_t PMS_CMD_SLEEP_MODE_SLEEP = 0x0000; // go to sleep mode -static const uint16_t PMS_CMD_SLEEP_MODE_WAKEUP = 0x0001; // wake up from sleep mode +static const uint16_t CMD_MEASUREMENT_MODE_PASSIVE = + 0x0000; // use `Command::MANUAL_MEASUREMENT` to trigger a measurement +static const uint16_t CMD_MEASUREMENT_MODE_ACTIVE = 0x0001; // automatically perform measurements +static const uint16_t CMD_SLEEP_MODE_SLEEP = 0x0000; // go to sleep mode +static const uint16_t CMD_SLEEP_MODE_WAKEUP = 0x0001; // wake up from sleep mode void PMSX003Component::setup() {} @@ -42,7 +41,7 @@ void PMSX003Component::dump_config() { LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); LOG_SENSOR(" ", "Humidity", this->humidity_sensor_); - if (this->update_interval_ <= PMS_STABILISING_MS) { + if (this->update_interval_ <= STABILISING_MS) { ESP_LOGCONFIG(TAG, " Mode: active continuous (sensor default)"); } else { ESP_LOGCONFIG(TAG, " Mode: passive with sleep/wake cycles"); @@ -55,44 +54,44 @@ void PMSX003Component::loop() { const uint32_t now = App.get_loop_component_start_time(); // Initialize sensor mode on first loop - if (this->initialised_ == 0) { - if (this->update_interval_ > PMS_STABILISING_MS) { + if (!this->initialised_) { + if (this->update_interval_ > STABILISING_MS) { // Long update interval: use passive mode with sleep/wake cycles - this->send_command_(PMS_CMD_MEASUREMENT_MODE, PMS_CMD_MEASUREMENT_MODE_PASSIVE); - this->send_command_(PMS_CMD_SLEEP_MODE, PMS_CMD_SLEEP_MODE_WAKEUP); + this->send_command_(Command::MEASUREMENT_MODE, CMD_MEASUREMENT_MODE_PASSIVE); + this->send_command_(Command::SLEEP_MODE, CMD_SLEEP_MODE_WAKEUP); } else { // Short/zero update interval: use active continuous mode - this->send_command_(PMS_CMD_MEASUREMENT_MODE, PMS_CMD_MEASUREMENT_MODE_ACTIVE); + this->send_command_(Command::MEASUREMENT_MODE, CMD_MEASUREMENT_MODE_ACTIVE); } - this->initialised_ = 1; + this->initialised_ = true; } // If we update less often than it takes the device to stabilise, spin the fan down // rather than running it constantly. It does take some time to stabilise, so we // need to keep track of what state we're in. - if (this->update_interval_ > PMS_STABILISING_MS) { + if (this->update_interval_ > STABILISING_MS) { switch (this->state_) { - case PMSX003_STATE_IDLE: + case State::IDLE: // Power on the sensor now so it'll be ready when we hit the update time - if (now - this->last_update_ < (this->update_interval_ - PMS_STABILISING_MS)) + if (now - this->last_update_ < (this->update_interval_ - STABILISING_MS)) return; - this->state_ = PMSX003_STATE_STABILISING; - this->send_command_(PMS_CMD_SLEEP_MODE, PMS_CMD_SLEEP_MODE_WAKEUP); + this->state_ = State::STABILISING; + this->send_command_(Command::SLEEP_MODE, CMD_SLEEP_MODE_WAKEUP); this->fan_on_time_ = now; return; - case PMSX003_STATE_STABILISING: + case State::STABILISING: // wait for the sensor to be stable - if (now - this->fan_on_time_ < PMS_STABILISING_MS) + if (now - this->fan_on_time_ < STABILISING_MS) return; // consume any command responses that are in the serial buffer while (this->available()) this->read_byte(&this->data_[0]); // Trigger a new read - this->send_command_(PMS_CMD_MANUAL_MEASUREMENT, 0); - this->state_ = PMSX003_STATE_WAITING; + this->send_command_(Command::MANUAL_MEASUREMENT, 0); + this->state_ = State::WAITING; break; - case PMSX003_STATE_WAITING: + case State::WAITING: // Just go ahead and read stuff break; } @@ -180,27 +179,31 @@ optional PMSX003Component::check_byte_() { } bool PMSX003Component::check_payload_length_(uint16_t payload_length) { + // https://avaldebe.github.io/PyPMS/sensors/Plantower/ switch (this->type_) { - case PMSX003_TYPE_X003: - // The expected payload length is typically 28 bytes. - // However, a 20-byte payload check was already present in the code. - // No official documentation was found confirming this. - // Retaining this check to avoid breaking existing behavior. + case Type::PMS1003: + return payload_length == 28; // 2*13+2 + case Type::PMS3003: // Data 7/8/9 not set/reserved + return payload_length == 20; // 2*9+2 + case Type::PMSX003: // Data 13 not set/reserved + // Deprecated: Length 20 is for PMS3003 backwards compatibility return payload_length == 28 || payload_length == 20; // 2*13+2 - case PMSX003_TYPE_5003T: - case PMSX003_TYPE_5003S: - return payload_length == 28; // 2*13+2 (Data 13 not set/reserved) - case PMSX003_TYPE_5003ST: - return payload_length == 36; // 2*17+2 (Data 16 not set/reserved) + case Type::PMS5003S: + case Type::PMS5003T: // Data 13 not set/reserved + return payload_length == 28; // 2*13+2 + case Type::PMS5003ST: // Data 16 not set/reserved + return payload_length == 36; // 2*17+2 + case Type::PMS9003M: + return payload_length == 28; // 2*13+2 } return false; } -void PMSX003Component::send_command_(PMSX0003Command cmd, uint16_t data) { +void PMSX003Component::send_command_(Command cmd, uint16_t data) { uint8_t send_data[7] = { START_CHARACTER_1, // Start Byte 1 START_CHARACTER_2, // Start Byte 2 - cmd, // Command + static_cast(cmd), // Command uint8_t((data >> 8) & 0xFF), // Data 1 uint8_t((data >> 0) & 0xFF), // Data 2 0, // Verify Byte 1 @@ -265,7 +268,7 @@ void PMSX003Component::parse_data_() { if (this->pm_particles_25um_sensor_ != nullptr) this->pm_particles_25um_sensor_->publish_state(pm_particles_25um); - if (this->type_ == PMSX003_TYPE_5003T) { + if (this->type_ == Type::PMS5003T) { ESP_LOGD(TAG, "Got PM0.3 Particles: %u Count/0.1L, PM0.5 Particles: %u Count/0.1L, PM1.0 Particles: %u Count/0.1L, " "PM2.5 Particles %u Count/0.1L", @@ -289,7 +292,7 @@ void PMSX003Component::parse_data_() { } // Formaldehyde - if (this->type_ == PMSX003_TYPE_5003ST || this->type_ == PMSX003_TYPE_5003S) { + if (this->type_ == Type::PMS5003S || this->type_ == Type::PMS5003ST) { const uint16_t formaldehyde = this->get_16_bit_uint_(28); ESP_LOGD(TAG, "Got Formaldehyde: %u µg/m^3", formaldehyde); @@ -299,8 +302,8 @@ void PMSX003Component::parse_data_() { } // Temperature and Humidity - if (this->type_ == PMSX003_TYPE_5003ST || this->type_ == PMSX003_TYPE_5003T) { - const uint8_t temperature_offset = (this->type_ == PMSX003_TYPE_5003T) ? 24 : 30; + if (this->type_ == Type::PMS5003T || this->type_ == Type::PMS5003ST) { + const uint8_t temperature_offset = (this->type_ == Type::PMS5003T) ? 24 : 30; const float temperature = static_cast(this->get_16_bit_uint_(temperature_offset)) / 10.0f; const float humidity = this->get_16_bit_uint_(temperature_offset + 2) / 10.0f; @@ -314,22 +317,22 @@ void PMSX003Component::parse_data_() { } // Firmware Version and Error Code - if (this->type_ == PMSX003_TYPE_5003ST) { - const uint8_t firmware_version = this->data_[36]; - const uint8_t error_code = this->data_[37]; + if (this->type_ == Type::PMS1003 || this->type_ == Type::PMS5003ST || this->type_ == Type::PMS9003M) { + const uint8_t firmware_error_code_offset = (this->type_ == Type::PMS5003ST) ? 36 : 28; + const uint8_t firmware_version = this->data_[firmware_error_code_offset]; + const uint8_t error_code = this->data_[firmware_error_code_offset + 1]; ESP_LOGD(TAG, "Got Firmware Version: 0x%02X, Error Code: 0x%02X", firmware_version, error_code); } // Spin down the sensor again if we aren't going to need it until more time has // passed than it takes to stabilise - if (this->update_interval_ > PMS_STABILISING_MS) { - this->send_command_(PMS_CMD_SLEEP_MODE, PMS_CMD_SLEEP_MODE_SLEEP); - this->state_ = PMSX003_STATE_IDLE; + if (this->update_interval_ > STABILISING_MS) { + this->send_command_(Command::SLEEP_MODE, CMD_SLEEP_MODE_SLEEP); + this->state_ = State::IDLE; } this->status_clear_warning(); } -} // namespace pmsx003 -} // namespace esphome +} // namespace esphome::pmsx003 diff --git a/esphome/components/pmsx003/pmsx003.h b/esphome/components/pmsx003/pmsx003.h index f48121800e..d559f2dec0 100644 --- a/esphome/components/pmsx003/pmsx003.h +++ b/esphome/components/pmsx003/pmsx003.h @@ -5,27 +5,28 @@ #include "esphome/components/sensor/sensor.h" #include "esphome/components/uart/uart.h" -namespace esphome { -namespace pmsx003 { +namespace esphome::pmsx003 { -enum PMSX0003Command : uint8_t { - PMS_CMD_MEASUREMENT_MODE = - 0xE1, // Data Options: `PMS_CMD_MEASUREMENT_MODE_PASSIVE`, `PMS_CMD_MEASUREMENT_MODE_ACTIVE` - PMS_CMD_MANUAL_MEASUREMENT = 0xE2, - PMS_CMD_SLEEP_MODE = 0xE4, // Data Options: `PMS_CMD_SLEEP_MODE_SLEEP`, `PMS_CMD_SLEEP_MODE_WAKEUP` +enum class Type : uint8_t { + PMS1003 = 0, + PMS3003, + PMSX003, // PMS5003, PMS6003, PMS7003, PMSA003 (NOT PMSA003I - see `pmsa003i` component) + PMS5003S, + PMS5003T, + PMS5003ST, + PMS9003M, }; -enum PMSX003Type { - PMSX003_TYPE_X003 = 0, - PMSX003_TYPE_5003T, - PMSX003_TYPE_5003ST, - PMSX003_TYPE_5003S, +enum class Command : uint8_t { + MEASUREMENT_MODE = 0xE1, // Data Options: `CMD_MEASUREMENT_MODE_PASSIVE`, `CMD_MEASUREMENT_MODE_ACTIVE` + MANUAL_MEASUREMENT = 0xE2, + SLEEP_MODE = 0xE4, // Data Options: `CMD_SLEEP_MODE_SLEEP`, `CMD_SLEEP_MODE_WAKEUP` }; -enum PMSX003State { - PMSX003_STATE_IDLE = 0, - PMSX003_STATE_STABILISING, - PMSX003_STATE_WAITING, +enum class State : uint8_t { + IDLE = 0, + STABILISING, + WAITING, }; class PMSX003Component : public uart::UARTDevice, public Component { @@ -37,7 +38,7 @@ class PMSX003Component : public uart::UARTDevice, public Component { void set_update_interval(uint32_t update_interval) { this->update_interval_ = update_interval; } - void set_type(PMSX003Type type) { this->type_ = type; } + void set_type(Type type) { this->type_ = type; } void set_pm_1_0_std_sensor(sensor::Sensor *pm_1_0_std_sensor) { this->pm_1_0_std_sensor_ = pm_1_0_std_sensor; } void set_pm_2_5_std_sensor(sensor::Sensor *pm_2_5_std_sensor) { this->pm_2_5_std_sensor_ = pm_2_5_std_sensor; } @@ -77,20 +78,20 @@ class PMSX003Component : public uart::UARTDevice, public Component { optional check_byte_(); void parse_data_(); bool check_payload_length_(uint16_t payload_length); - void send_command_(PMSX0003Command cmd, uint16_t data); + void send_command_(Command cmd, uint16_t data); uint16_t get_16_bit_uint_(uint8_t start_index) const { return encode_uint16(this->data_[start_index], this->data_[start_index + 1]); } + Type type_; + State state_{State::IDLE}; + bool initialised_{false}; uint8_t data_[64]; uint8_t data_index_{0}; - uint8_t initialised_{0}; uint32_t fan_on_time_{0}; uint32_t last_update_{0}; uint32_t last_transmission_{0}; uint32_t update_interval_{0}; - PMSX003State state_{PMSX003_STATE_IDLE}; - PMSX003Type type_; // "Standard Particle" sensor::Sensor *pm_1_0_std_sensor_{nullptr}; @@ -118,5 +119,4 @@ class PMSX003Component : public uart::UARTDevice, public Component { sensor::Sensor *humidity_sensor_{nullptr}; }; -} // namespace pmsx003 -} // namespace esphome +} // namespace esphome::pmsx003 diff --git a/esphome/components/pmsx003/sensor.py b/esphome/components/pmsx003/sensor.py index bebd3a01ee..cdcedc85ac 100644 --- a/esphome/components/pmsx003/sensor.py +++ b/esphome/components/pmsx003/sensor.py @@ -40,34 +40,128 @@ pmsx003_ns = cg.esphome_ns.namespace("pmsx003") PMSX003Component = pmsx003_ns.class_("PMSX003Component", uart.UARTDevice, cg.Component) PMSX003Sensor = pmsx003_ns.class_("PMSX003Sensor", sensor.Sensor) -TYPE_PMSX003 = "PMSX003" +TYPE_PMS1003 = "PMS1003" +TYPE_PMS3003 = "PMS3003" +TYPE_PMSX003 = "PMSX003" # PMS5003, PMS6003, PMS7003, PMSA003 (NOT PMSA003I - see `pmsa003i` component) +TYPE_PMS5003S = "PMS5003S" TYPE_PMS5003T = "PMS5003T" TYPE_PMS5003ST = "PMS5003ST" -TYPE_PMS5003S = "PMS5003S" +TYPE_PMS9003M = "PMS9003M" -PMSX003Type = pmsx003_ns.enum("PMSX003Type") +Type = pmsx003_ns.enum("Type", is_class=True) PMSX003_TYPES = { - TYPE_PMSX003: PMSX003Type.PMSX003_TYPE_X003, - TYPE_PMS5003T: PMSX003Type.PMSX003_TYPE_5003T, - TYPE_PMS5003ST: PMSX003Type.PMSX003_TYPE_5003ST, - TYPE_PMS5003S: PMSX003Type.PMSX003_TYPE_5003S, + TYPE_PMS1003: Type.PMS1003, + TYPE_PMS3003: Type.PMS3003, + TYPE_PMSX003: Type.PMSX003, + TYPE_PMS5003S: Type.PMS5003S, + TYPE_PMS5003T: Type.PMS5003T, + TYPE_PMS5003ST: Type.PMS5003ST, + TYPE_PMS9003M: Type.PMS9003M, } SENSORS_TO_TYPE = { - CONF_PM_1_0: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], - CONF_PM_2_5: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], - CONF_PM_10_0: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], - CONF_PM_1_0_STD: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], - CONF_PM_2_5_STD: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], - CONF_PM_10_0_STD: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], - CONF_PM_0_3UM: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], - CONF_PM_0_5UM: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], - CONF_PM_1_0UM: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], - CONF_PM_2_5UM: [TYPE_PMSX003, TYPE_PMS5003T, TYPE_PMS5003ST, TYPE_PMS5003S], - CONF_PM_5_0UM: [TYPE_PMSX003, TYPE_PMS5003ST, TYPE_PMS5003S], - CONF_PM_10_0UM: [TYPE_PMSX003, TYPE_PMS5003ST, TYPE_PMS5003S], - CONF_FORMALDEHYDE: [TYPE_PMS5003ST, TYPE_PMS5003S], + CONF_PM_1_0_STD: [ + TYPE_PMS1003, + TYPE_PMS3003, + TYPE_PMSX003, + TYPE_PMS5003S, + TYPE_PMS5003T, + TYPE_PMS5003ST, + TYPE_PMS9003M, + ], + CONF_PM_2_5_STD: [ + TYPE_PMS1003, + TYPE_PMS3003, + TYPE_PMSX003, + TYPE_PMS5003S, + TYPE_PMS5003T, + TYPE_PMS5003ST, + TYPE_PMS9003M, + ], + CONF_PM_10_0_STD: [ + TYPE_PMS1003, + TYPE_PMS3003, + TYPE_PMSX003, + TYPE_PMS5003S, + TYPE_PMS5003T, + TYPE_PMS5003ST, + TYPE_PMS9003M, + ], + CONF_PM_1_0: [ + TYPE_PMS1003, + TYPE_PMS3003, + TYPE_PMSX003, + TYPE_PMS5003S, + TYPE_PMS5003T, + TYPE_PMS5003ST, + TYPE_PMS9003M, + ], + CONF_PM_2_5: [ + TYPE_PMS1003, + TYPE_PMS3003, + TYPE_PMSX003, + TYPE_PMS5003S, + TYPE_PMS5003T, + TYPE_PMS5003ST, + TYPE_PMS9003M, + ], + CONF_PM_10_0: [ + TYPE_PMS1003, + TYPE_PMS3003, + TYPE_PMSX003, + TYPE_PMS5003S, + TYPE_PMS5003T, + TYPE_PMS5003ST, + TYPE_PMS9003M, + ], + CONF_PM_0_3UM: [ + TYPE_PMS1003, + TYPE_PMSX003, + TYPE_PMS5003S, + TYPE_PMS5003T, + TYPE_PMS5003ST, + TYPE_PMS9003M, + ], + CONF_PM_0_5UM: [ + TYPE_PMS1003, + TYPE_PMSX003, + TYPE_PMS5003S, + TYPE_PMS5003T, + TYPE_PMS5003ST, + TYPE_PMS9003M, + ], + CONF_PM_1_0UM: [ + TYPE_PMS1003, + TYPE_PMSX003, + TYPE_PMS5003S, + TYPE_PMS5003T, + TYPE_PMS5003ST, + TYPE_PMS9003M, + ], + CONF_PM_2_5UM: [ + TYPE_PMS1003, + TYPE_PMSX003, + TYPE_PMS5003S, + TYPE_PMS5003T, + TYPE_PMS5003ST, + TYPE_PMS9003M, + ], + CONF_PM_5_0UM: [ + TYPE_PMS1003, + TYPE_PMSX003, + TYPE_PMS5003S, + TYPE_PMS5003ST, + TYPE_PMS9003M, + ], + CONF_PM_10_0UM: [ + TYPE_PMS1003, + TYPE_PMSX003, + TYPE_PMS5003S, + TYPE_PMS5003ST, + TYPE_PMS9003M, + ], + CONF_FORMALDEHYDE: [TYPE_PMS5003S, TYPE_PMS5003ST], CONF_TEMPERATURE: [TYPE_PMS5003T, TYPE_PMS5003ST], CONF_HUMIDITY: [TYPE_PMS5003T, TYPE_PMS5003ST], } diff --git a/esphome/components/remote_transmitter/remote_transmitter.cpp b/esphome/components/remote_transmitter/remote_transmitter.cpp index f20789fb9f..d35541e2e1 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter.cpp @@ -83,7 +83,7 @@ void RemoteTransmitterComponent::send_internal(uint32_t send_times, uint32_t sen uint32_t on_time, off_time; this->calculate_on_off_time_(this->temp_.get_carrier_frequency(), &on_time, &off_time); this->target_time_ = 0; - this->transmit_trigger_->trigger(); + this->transmit_trigger_.trigger(); for (uint32_t i = 0; i < send_times; i++) { InterruptLock lock; 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) this->target_time_ += send_wait; } - this->complete_trigger_->trigger(); + this->complete_trigger_.trigger(); } } // namespace remote_transmitter diff --git a/esphome/components/remote_transmitter/remote_transmitter.h b/esphome/components/remote_transmitter/remote_transmitter.h index dd6a849e4c..65bd2ac8b2 100644 --- a/esphome/components/remote_transmitter/remote_transmitter.h +++ b/esphome/components/remote_transmitter/remote_transmitter.h @@ -57,8 +57,8 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, void set_non_blocking(bool non_blocking) { this->non_blocking_ = non_blocking; } #endif - Trigger<> *get_transmit_trigger() const { return this->transmit_trigger_; }; - Trigger<> *get_complete_trigger() const { return this->complete_trigger_; }; + Trigger<> *get_transmit_trigger() { return &this->transmit_trigger_; } + Trigger<> *get_complete_trigger() { return &this->complete_trigger_; } protected: void send_internal(uint32_t send_times, uint32_t send_wait) override; @@ -96,8 +96,8 @@ class RemoteTransmitterComponent : public remote_base::RemoteTransmitterBase, #endif uint8_t carrier_duty_percent_; - Trigger<> *transmit_trigger_{new Trigger<>()}; - Trigger<> *complete_trigger_{new Trigger<>()}; + Trigger<> transmit_trigger_; + Trigger<> complete_trigger_; }; } // namespace remote_transmitter diff --git a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp index 59c85c99a8..89d97895b2 100644 --- a/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp +++ b/esphome/components/remote_transmitter/remote_transmitter_esp32.cpp @@ -203,7 +203,7 @@ void RemoteTransmitterComponent::wait_for_rmt_() { this->status_set_warning(); } - this->complete_trigger_->trigger(); + this->complete_trigger_.trigger(); } #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; } - this->transmit_trigger_->trigger(); + this->transmit_trigger_.trigger(); rmt_transmit_config_t 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"); return; } - this->transmit_trigger_->trigger(); + this->transmit_trigger_.trigger(); for (uint32_t i = 0; i < send_times; i++) { rmt_transmit_config_t 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) delayMicroseconds(send_wait); } - this->complete_trigger_->trigger(); + this->complete_trigger_.trigger(); } #endif diff --git a/esphome/components/speaker/media_player/speaker_media_player.cpp b/esphome/components/speaker/media_player/speaker_media_player.cpp index 172bc980a8..94f555c26e 100644 --- a/esphome/components/speaker/media_player/speaker_media_player.cpp +++ b/esphome/components/speaker/media_player/speaker_media_player.cpp @@ -519,9 +519,9 @@ void SpeakerMediaPlayer::set_mute_state_(bool mute_state) { if (old_mute_state != mute_state) { if (mute_state) { - this->defer([this]() { this->mute_trigger_->trigger(); }); + this->defer([this]() { this->mute_trigger_.trigger(); }); } else { - this->defer([this]() { this->unmute_trigger_->trigger(); }); + this->defer([this]() { this->unmute_trigger_.trigger(); }); } } } @@ -550,7 +550,7 @@ void SpeakerMediaPlayer::set_volume_(float volume, bool publish) { 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 diff --git a/esphome/components/speaker/media_player/speaker_media_player.h b/esphome/components/speaker/media_player/speaker_media_player.h index 065926d0cf..722f98ceea 100644 --- a/esphome/components/speaker/media_player/speaker_media_player.h +++ b/esphome/components/speaker/media_player/speaker_media_player.h @@ -84,9 +84,9 @@ class SpeakerMediaPlayer : public Component, this->media_format_ = media_format; } - Trigger<> *get_mute_trigger() const { return this->mute_trigger_; } - Trigger<> *get_unmute_trigger() const { return this->unmute_trigger_; } - Trigger *get_volume_trigger() const { return this->volume_trigger_; } + Trigger<> *get_mute_trigger() { return &this->mute_trigger_; } + Trigger<> *get_unmute_trigger() { return &this->unmute_trigger_; } + Trigger *get_volume_trigger() { return &this->volume_trigger_; } 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 ESPPreferenceObject pref_; - Trigger<> *mute_trigger_ = new Trigger<>(); - Trigger<> *unmute_trigger_ = new Trigger<>(); - Trigger *volume_trigger_ = new Trigger(); + Trigger<> mute_trigger_; + Trigger<> unmute_trigger_; + Trigger volume_trigger_; }; } // namespace speaker diff --git a/esphome/components/sprinkler/sprinkler.cpp b/esphome/components/sprinkler/sprinkler.cpp index 2a60eb042b..eae6ecbf31 100644 --- a/esphome/components/sprinkler/sprinkler.cpp +++ b/esphome/components/sprinkler/sprinkler.cpp @@ -29,7 +29,7 @@ void SprinklerControllerNumber::setup() { } void SprinklerControllerNumber::control(float value) { - this->set_trigger_->trigger(value); + this->set_trigger_.trigger(value); this->publish_state(value); @@ -39,8 +39,7 @@ void SprinklerControllerNumber::control(float value) { void SprinklerControllerNumber::dump_config() { LOG_NUMBER("", "Sprinkler Controller Number", this); } -SprinklerControllerSwitch::SprinklerControllerSwitch() - : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {} +SprinklerControllerSwitch::SprinklerControllerSwitch() = default; void SprinklerControllerSwitch::loop() { // Loop is only enabled when f_ has a value (see setup()) @@ -56,11 +55,11 @@ void SprinklerControllerSwitch::write_state(bool state) { } if (state) { - this->prev_trigger_ = this->turn_on_trigger_; - this->turn_on_trigger_->trigger(); + this->prev_trigger_ = &this->turn_on_trigger_; + this->turn_on_trigger_.trigger(); } else { - this->prev_trigger_ = this->turn_off_trigger_; - this->turn_off_trigger_->trigger(); + this->prev_trigger_ = &this->turn_off_trigger_; + this->turn_off_trigger_.trigger(); } this->publish_state(state); @@ -69,9 +68,6 @@ void SprinklerControllerSwitch::write_state(bool state) { void SprinklerControllerSwitch::set_state_lambda(std::function()> &&f) { this->f_ = f; } 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() { this->state = this->get_initial_state_with_restore_mode().value_or(false); // Disable loop if no state lambda is set - nothing to poll diff --git a/esphome/components/sprinkler/sprinkler.h b/esphome/components/sprinkler/sprinkler.h index 04efa28031..a3cdef5b1a 100644 --- a/esphome/components/sprinkler/sprinkler.h +++ b/esphome/components/sprinkler/sprinkler.h @@ -76,7 +76,7 @@ class SprinklerControllerNumber : public number::Number, public Component { void dump_config() override; float get_setup_priority() const override { return setup_priority::PROCESSOR; } - Trigger *get_set_trigger() const { return set_trigger_; } + Trigger *get_set_trigger() { return &this->set_trigger_; } void set_initial_value(float initial_value) { initial_value_ = initial_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; float initial_value_{NAN}; bool restore_value_{true}; - Trigger *set_trigger_ = new Trigger(); + Trigger set_trigger_; ESPPreferenceObject pref_; }; @@ -97,8 +97,8 @@ class SprinklerControllerSwitch : public switch_::Switch, public Component { void dump_config() override; void set_state_lambda(std::function()> &&f); - Trigger<> *get_turn_on_trigger() const; - Trigger<> *get_turn_off_trigger() const; + Trigger<> *get_turn_on_trigger() { return &this->turn_on_trigger_; } + Trigger<> *get_turn_off_trigger() { return &this->turn_off_trigger_; } void loop() override; float get_setup_priority() const override; @@ -107,8 +107,8 @@ class SprinklerControllerSwitch : public switch_::Switch, public Component { void write_state(bool state) override; optional()>> f_; - Trigger<> *turn_on_trigger_; - Trigger<> *turn_off_trigger_; + Trigger<> turn_on_trigger_; + Trigger<> turn_off_trigger_; Trigger<> *prev_trigger_{nullptr}; }; diff --git a/esphome/components/sx126x/sx126x.cpp b/esphome/components/sx126x/sx126x.cpp index 707d6f1fbf..64cd24b171 100644 --- a/esphome/components/sx126x/sx126x.cpp +++ b/esphome/components/sx126x/sx126x.cpp @@ -343,7 +343,7 @@ void SX126x::call_listeners_(const std::vector &packet, float rssi, flo for (auto &listener : this->listeners_) { listener->on_packet(packet, rssi, snr); } - this->packet_trigger_->trigger(packet, rssi, snr); + this->packet_trigger_.trigger(packet, rssi, snr); } void SX126x::loop() { diff --git a/esphome/components/sx126x/sx126x.h b/esphome/components/sx126x/sx126x.h index 850d7d4c77..a758d63795 100644 --- a/esphome/components/sx126x/sx126x.h +++ b/esphome/components/sx126x/sx126x.h @@ -97,7 +97,7 @@ class SX126x : public Component, void configure(); SX126xError transmit_packet(const std::vector &packet); void register_listener(SX126xListener *listener) { this->listeners_.push_back(listener); } - Trigger, float, float> *get_packet_trigger() const { return this->packet_trigger_; }; + Trigger, float, float> *get_packet_trigger() { return &this->packet_trigger_; } protected: 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 call_listeners_(const std::vector &packet, float rssi, float snr); void wait_busy_(); - Trigger, float, float> *packet_trigger_{new Trigger, float, float>()}; + Trigger, float, float> packet_trigger_; std::vector listeners_; std::vector packet_; std::vector sync_value_; diff --git a/esphome/components/sx127x/sx127x.cpp b/esphome/components/sx127x/sx127x.cpp index 3185574b1a..caf68b6d51 100644 --- a/esphome/components/sx127x/sx127x.cpp +++ b/esphome/components/sx127x/sx127x.cpp @@ -300,7 +300,7 @@ void SX127x::call_listeners_(const std::vector &packet, float rssi, flo for (auto &listener : this->listeners_) { listener->on_packet(packet, rssi, snr); } - this->packet_trigger_->trigger(packet, rssi, snr); + this->packet_trigger_.trigger(packet, rssi, snr); } void SX127x::loop() { diff --git a/esphome/components/sx127x/sx127x.h b/esphome/components/sx127x/sx127x.h index 0600b51201..be7b6d8d9f 100644 --- a/esphome/components/sx127x/sx127x.h +++ b/esphome/components/sx127x/sx127x.h @@ -83,7 +83,7 @@ class SX127x : public Component, void configure(); SX127xError transmit_packet(const std::vector &packet); void register_listener(SX127xListener *listener) { this->listeners_.push_back(listener); } - Trigger, float, float> *get_packet_trigger() const { return this->packet_trigger_; }; + Trigger, float, float> *get_packet_trigger() { return &this->packet_trigger_; } protected: void configure_fsk_ook_(); @@ -94,7 +94,7 @@ class SX127x : public Component, void write_register_(uint8_t reg, uint8_t value); void call_listeners_(const std::vector &packet, float rssi, float snr); uint8_t read_register_(uint8_t reg); - Trigger, float, float> *packet_trigger_{new Trigger, float, float>()}; + Trigger, float, float> packet_trigger_; std::vector listeners_; std::vector packet_; std::vector sync_value_; diff --git a/esphome/components/template/cover/template_cover.cpp b/esphome/components/template/cover/template_cover.cpp index 9c8a8fc9bc..7f5d68623f 100644 --- a/esphome/components/template/cover/template_cover.cpp +++ b/esphome/components/template/cover/template_cover.cpp @@ -7,13 +7,7 @@ using namespace esphome::cover; static const char *const TAG = "template.cover"; -TemplateCover::TemplateCover() - : open_trigger_(new Trigger<>()), - close_trigger_(new Trigger<>), - stop_trigger_(new Trigger<>()), - toggle_trigger_(new Trigger<>()), - position_trigger_(new Trigger()), - tilt_trigger_(new Trigger()) {} +TemplateCover::TemplateCover() = default; void TemplateCover::setup() { switch (this->restore_mode_) { case COVER_NO_RESTORE: @@ -62,22 +56,22 @@ void TemplateCover::loop() { void TemplateCover::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void TemplateCover::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } float TemplateCover::get_setup_priority() const { return setup_priority::HARDWARE; } -Trigger<> *TemplateCover::get_open_trigger() const { return this->open_trigger_; } -Trigger<> *TemplateCover::get_close_trigger() const { return this->close_trigger_; } -Trigger<> *TemplateCover::get_stop_trigger() const { return this->stop_trigger_; } -Trigger<> *TemplateCover::get_toggle_trigger() const { return this->toggle_trigger_; } +Trigger<> *TemplateCover::get_open_trigger() { return &this->open_trigger_; } +Trigger<> *TemplateCover::get_close_trigger() { return &this->close_trigger_; } +Trigger<> *TemplateCover::get_stop_trigger() { return &this->stop_trigger_; } +Trigger<> *TemplateCover::get_toggle_trigger() { return &this->toggle_trigger_; } void TemplateCover::dump_config() { LOG_COVER("", "Template Cover", this); } void TemplateCover::control(const CoverCall &call) { if (call.get_stop()) { this->stop_prev_trigger_(); - this->stop_trigger_->trigger(); - this->prev_command_trigger_ = this->stop_trigger_; + this->stop_trigger_.trigger(); + this->prev_command_trigger_ = &this->stop_trigger_; this->publish_state(); } if (call.get_toggle().has_value()) { this->stop_prev_trigger_(); - this->toggle_trigger_->trigger(); - this->prev_command_trigger_ = this->toggle_trigger_; + this->toggle_trigger_.trigger(); + this->prev_command_trigger_ = &this->toggle_trigger_; this->publish_state(); } if (call.get_position().has_value()) { @@ -85,13 +79,13 @@ void TemplateCover::control(const CoverCall &call) { this->stop_prev_trigger_(); if (pos == COVER_OPEN) { - this->open_trigger_->trigger(); - this->prev_command_trigger_ = this->open_trigger_; + this->open_trigger_.trigger(); + this->prev_command_trigger_ = &this->open_trigger_; } else if (pos == COVER_CLOSED) { - this->close_trigger_->trigger(); - this->prev_command_trigger_ = this->close_trigger_; + this->close_trigger_.trigger(); + this->prev_command_trigger_ = &this->close_trigger_; } else { - this->position_trigger_->trigger(pos); + this->position_trigger_.trigger(pos); } if (this->optimistic_) { @@ -101,7 +95,7 @@ void TemplateCover::control(const CoverCall &call) { if (call.get_tilt().has_value()) { auto tilt = *call.get_tilt(); - this->tilt_trigger_->trigger(tilt); + this->tilt_trigger_.trigger(tilt); if (this->optimistic_) { this->tilt = tilt; @@ -119,8 +113,8 @@ CoverTraits TemplateCover::get_traits() { traits.set_supports_tilt(this->has_tilt_); return traits; } -Trigger *TemplateCover::get_position_trigger() const { return this->position_trigger_; } -Trigger *TemplateCover::get_tilt_trigger() const { return this->tilt_trigger_; } +Trigger *TemplateCover::get_position_trigger() { return &this->position_trigger_; } +Trigger *TemplateCover::get_tilt_trigger() { return &this->tilt_trigger_; } 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_position(bool has_position) { this->has_position_ = has_position; } diff --git a/esphome/components/template/cover/template_cover.h b/esphome/components/template/cover/template_cover.h index 9c4a787283..20c092cda7 100644 --- a/esphome/components/template/cover/template_cover.h +++ b/esphome/components/template/cover/template_cover.h @@ -19,12 +19,12 @@ class TemplateCover final : public cover::Cover, public Component { template void set_state_lambda(F &&f) { this->state_f_.set(std::forward(f)); } template void set_tilt_lambda(F &&f) { this->tilt_f_.set(std::forward(f)); } - Trigger<> *get_open_trigger() const; - Trigger<> *get_close_trigger() const; - Trigger<> *get_stop_trigger() const; - Trigger<> *get_toggle_trigger() const; - Trigger *get_position_trigger() const; - Trigger *get_tilt_trigger() const; + Trigger<> *get_open_trigger(); + Trigger<> *get_close_trigger(); + Trigger<> *get_stop_trigger(); + Trigger<> *get_toggle_trigger(); + Trigger *get_position_trigger(); + Trigger *get_tilt_trigger(); void set_optimistic(bool optimistic); void set_assumed_state(bool assumed_state); void set_has_stop(bool has_stop); @@ -49,16 +49,16 @@ class TemplateCover final : public cover::Cover, public Component { TemplateLambda tilt_f_; bool assumed_state_{false}; bool optimistic_{false}; - Trigger<> *open_trigger_; - Trigger<> *close_trigger_; + Trigger<> open_trigger_; + Trigger<> close_trigger_; bool has_stop_{false}; bool has_toggle_{false}; - Trigger<> *stop_trigger_; - Trigger<> *toggle_trigger_; + Trigger<> stop_trigger_; + Trigger<> toggle_trigger_; Trigger<> *prev_command_trigger_{nullptr}; - Trigger *position_trigger_; + Trigger position_trigger_; bool has_position_{false}; - Trigger *tilt_trigger_; + Trigger tilt_trigger_; bool has_tilt_{false}; }; diff --git a/esphome/components/template/datetime/template_date.cpp b/esphome/components/template/datetime/template_date.cpp index be1d875a7e..8a5f11b876 100644 --- a/esphome/components/template/datetime/template_date.cpp +++ b/esphome/components/template/datetime/template_date.cpp @@ -62,7 +62,7 @@ void TemplateDate::control(const datetime::DateCall &call) { if (has_day) value.day_of_month = *call.get_day(); - this->set_trigger_->trigger(value); + this->set_trigger_.trigger(value); if (this->optimistic_) { if (has_year) diff --git a/esphome/components/template/datetime/template_date.h b/esphome/components/template/datetime/template_date.h index 0379a9bc67..acf823a34d 100644 --- a/esphome/components/template/datetime/template_date.h +++ b/esphome/components/template/datetime/template_date.h @@ -22,7 +22,7 @@ class TemplateDate final : public datetime::DateEntity, public PollingComponent void dump_config() override; float get_setup_priority() const override { return setup_priority::HARDWARE; } - Trigger *get_set_trigger() const { return this->set_trigger_; } + Trigger *get_set_trigger() { return &this->set_trigger_; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } 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}; ESPTime initial_value_{}; bool restore_value_{false}; - Trigger *set_trigger_ = new Trigger(); + Trigger set_trigger_; TemplateLambda f_; ESPPreferenceObject pref_; diff --git a/esphome/components/template/datetime/template_datetime.cpp b/esphome/components/template/datetime/template_datetime.cpp index e134f2b654..269a1d06ca 100644 --- a/esphome/components/template/datetime/template_datetime.cpp +++ b/esphome/components/template/datetime/template_datetime.cpp @@ -80,7 +80,7 @@ void TemplateDateTime::control(const datetime::DateTimeCall &call) { if (has_second) value.second = *call.get_second(); - this->set_trigger_->trigger(value); + this->set_trigger_.trigger(value); if (this->optimistic_) { if (has_year) diff --git a/esphome/components/template/datetime/template_datetime.h b/esphome/components/template/datetime/template_datetime.h index b7eb490933..575065a3dd 100644 --- a/esphome/components/template/datetime/template_datetime.h +++ b/esphome/components/template/datetime/template_datetime.h @@ -22,7 +22,7 @@ class TemplateDateTime final : public datetime::DateTimeEntity, public PollingCo void dump_config() override; float get_setup_priority() const override { return setup_priority::HARDWARE; } - Trigger *get_set_trigger() const { return this->set_trigger_; } + Trigger *get_set_trigger() { return &this->set_trigger_; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } 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}; ESPTime initial_value_{}; bool restore_value_{false}; - Trigger *set_trigger_ = new Trigger(); + Trigger set_trigger_; TemplateLambda f_; ESPPreferenceObject pref_; diff --git a/esphome/components/template/datetime/template_time.cpp b/esphome/components/template/datetime/template_time.cpp index 586e126e3b..9c81687116 100644 --- a/esphome/components/template/datetime/template_time.cpp +++ b/esphome/components/template/datetime/template_time.cpp @@ -62,7 +62,7 @@ void TemplateTime::control(const datetime::TimeCall &call) { if (has_second) value.second = *call.get_second(); - this->set_trigger_->trigger(value); + this->set_trigger_.trigger(value); if (this->optimistic_) { if (has_hour) diff --git a/esphome/components/template/datetime/template_time.h b/esphome/components/template/datetime/template_time.h index cb83b1b3e5..924b53cc71 100644 --- a/esphome/components/template/datetime/template_time.h +++ b/esphome/components/template/datetime/template_time.h @@ -22,7 +22,7 @@ class TemplateTime final : public datetime::TimeEntity, public PollingComponent void dump_config() override; float get_setup_priority() const override { return setup_priority::HARDWARE; } - Trigger *get_set_trigger() const { return this->set_trigger_; } + Trigger *get_set_trigger() { return &this->set_trigger_; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } 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}; ESPTime initial_value_{}; bool restore_value_{false}; - Trigger *set_trigger_ = new Trigger(); + Trigger set_trigger_; TemplateLambda f_; ESPPreferenceObject pref_; diff --git a/esphome/components/template/lock/template_lock.cpp b/esphome/components/template/lock/template_lock.cpp index de8f9b762c..dbc4501ce7 100644 --- a/esphome/components/template/lock/template_lock.cpp +++ b/esphome/components/template/lock/template_lock.cpp @@ -7,8 +7,7 @@ using namespace esphome::lock; static const char *const TAG = "template.lock"; -TemplateLock::TemplateLock() - : lock_trigger_(new Trigger<>()), unlock_trigger_(new Trigger<>()), open_trigger_(new Trigger<>()) {} +TemplateLock::TemplateLock() = default; void TemplateLock::setup() { if (!this->f_.has_value()) @@ -28,11 +27,11 @@ void TemplateLock::control(const lock::LockCall &call) { auto state = *call.get_state(); if (state == LOCK_STATE_LOCKED) { - this->prev_trigger_ = this->lock_trigger_; - this->lock_trigger_->trigger(); + this->prev_trigger_ = &this->lock_trigger_; + this->lock_trigger_.trigger(); } else if (state == LOCK_STATE_UNLOCKED) { - this->prev_trigger_ = this->unlock_trigger_; - this->unlock_trigger_->trigger(); + this->prev_trigger_ = &this->unlock_trigger_; + this->unlock_trigger_.trigger(); } if (this->optimistic_) @@ -42,14 +41,11 @@ void TemplateLock::open_latch() { if (this->prev_trigger_ != nullptr) { this->prev_trigger_->stop_action(); } - this->prev_trigger_ = this->open_trigger_; - this->open_trigger_->trigger(); + this->prev_trigger_ = &this->open_trigger_; + this->open_trigger_.trigger(); } void TemplateLock::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } 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() { LOG_LOCK("", "Template Lock", this); ESP_LOGCONFIG(TAG, " Optimistic: %s", YESNO(this->optimistic_)); diff --git a/esphome/components/template/lock/template_lock.h b/esphome/components/template/lock/template_lock.h index f4396c2c5d..03e3e86d88 100644 --- a/esphome/components/template/lock/template_lock.h +++ b/esphome/components/template/lock/template_lock.h @@ -15,9 +15,9 @@ class TemplateLock final : public lock::Lock, public Component { void dump_config() override; template void set_state_lambda(F &&f) { this->f_.set(std::forward(f)); } - Trigger<> *get_lock_trigger() const; - Trigger<> *get_unlock_trigger() const; - Trigger<> *get_open_trigger() const; + Trigger<> *get_lock_trigger() { return &this->lock_trigger_; } + Trigger<> *get_unlock_trigger() { return &this->unlock_trigger_; } + Trigger<> *get_open_trigger() { return &this->open_trigger_; } void set_optimistic(bool optimistic); void loop() override; @@ -29,9 +29,9 @@ class TemplateLock final : public lock::Lock, public Component { TemplateLambda f_; bool optimistic_{false}; - Trigger<> *lock_trigger_; - Trigger<> *unlock_trigger_; - Trigger<> *open_trigger_; + Trigger<> lock_trigger_; + Trigger<> unlock_trigger_; + Trigger<> open_trigger_; Trigger<> *prev_trigger_{nullptr}; }; diff --git a/esphome/components/template/number/template_number.cpp b/esphome/components/template/number/template_number.cpp index 885265cf5d..64c2deb281 100644 --- a/esphome/components/template/number/template_number.cpp +++ b/esphome/components/template/number/template_number.cpp @@ -36,7 +36,7 @@ void TemplateNumber::update() { } void TemplateNumber::control(float value) { - this->set_trigger_->trigger(value); + this->set_trigger_.trigger(value); if (this->optimistic_) this->publish_state(value); diff --git a/esphome/components/template/number/template_number.h b/esphome/components/template/number/template_number.h index 42c27fc3ca..e51e858ccf 100644 --- a/esphome/components/template/number/template_number.h +++ b/esphome/components/template/number/template_number.h @@ -17,7 +17,7 @@ class TemplateNumber final : public number::Number, public PollingComponent { void dump_config() override; float get_setup_priority() const override { return setup_priority::HARDWARE; } - Trigger *get_set_trigger() const { return set_trigger_; } + Trigger *get_set_trigger() { return &this->set_trigger_; } void set_optimistic(bool optimistic) { optimistic_ = optimistic; } void set_initial_value(float initial_value) { initial_value_ = initial_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}; float initial_value_{NAN}; bool restore_value_{false}; - Trigger *set_trigger_ = new Trigger(); + Trigger set_trigger_; TemplateLambda f_; ESPPreferenceObject pref_; diff --git a/esphome/components/template/output/template_output.h b/esphome/components/template/output/template_output.h index e536660b02..6fe8e53855 100644 --- a/esphome/components/template/output/template_output.h +++ b/esphome/components/template/output/template_output.h @@ -8,22 +8,22 @@ namespace esphome::template_ { class TemplateBinaryOutput final : public output::BinaryOutput { public: - Trigger *get_trigger() const { return trigger_; } + Trigger *get_trigger() { return &this->trigger_; } protected: - void write_state(bool state) override { this->trigger_->trigger(state); } + void write_state(bool state) override { this->trigger_.trigger(state); } - Trigger *trigger_ = new Trigger(); + Trigger trigger_; }; class TemplateFloatOutput final : public output::FloatOutput { public: - Trigger *get_trigger() const { return trigger_; } + Trigger *get_trigger() { return &this->trigger_; } protected: - void write_state(float state) override { this->trigger_->trigger(state); } + void write_state(float state) override { this->trigger_.trigger(state); } - Trigger *trigger_ = new Trigger(); + Trigger trigger_; }; } // namespace esphome::template_ diff --git a/esphome/components/template/switch/template_switch.cpp b/esphome/components/template/switch/template_switch.cpp index cfa8798e75..05288b2d4e 100644 --- a/esphome/components/template/switch/template_switch.cpp +++ b/esphome/components/template/switch/template_switch.cpp @@ -5,7 +5,7 @@ namespace esphome::template_ { static const char *const TAG = "template.switch"; -TemplateSwitch::TemplateSwitch() : turn_on_trigger_(new Trigger<>()), turn_off_trigger_(new Trigger<>()) {} +TemplateSwitch::TemplateSwitch() = default; void TemplateSwitch::loop() { auto s = this->f_(); @@ -19,11 +19,11 @@ void TemplateSwitch::write_state(bool state) { } if (state) { - this->prev_trigger_ = this->turn_on_trigger_; - this->turn_on_trigger_->trigger(); + this->prev_trigger_ = &this->turn_on_trigger_; + this->turn_on_trigger_.trigger(); } else { - this->prev_trigger_ = this->turn_off_trigger_; - this->turn_off_trigger_->trigger(); + this->prev_trigger_ = &this->turn_off_trigger_; + this->turn_off_trigger_.trigger(); } if (this->optimistic_) @@ -32,8 +32,8 @@ void TemplateSwitch::write_state(bool state) { void TemplateSwitch::set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } bool TemplateSwitch::assumed_state() { return this->assumed_state_; } float TemplateSwitch::get_setup_priority() const { return setup_priority::HARDWARE - 2.0f; } -Trigger<> *TemplateSwitch::get_turn_on_trigger() const { return this->turn_on_trigger_; } -Trigger<> *TemplateSwitch::get_turn_off_trigger() const { return this->turn_off_trigger_; } +Trigger<> *TemplateSwitch::get_turn_on_trigger() { return &this->turn_on_trigger_; } +Trigger<> *TemplateSwitch::get_turn_off_trigger() { return &this->turn_off_trigger_; } void TemplateSwitch::setup() { if (!this->f_.has_value()) this->disable_loop(); diff --git a/esphome/components/template/switch/template_switch.h b/esphome/components/template/switch/template_switch.h index 91b7b396f6..1714b4f72b 100644 --- a/esphome/components/template/switch/template_switch.h +++ b/esphome/components/template/switch/template_switch.h @@ -15,8 +15,8 @@ class TemplateSwitch final : public switch_::Switch, public Component { void dump_config() override; template void set_state_lambda(F &&f) { this->f_.set(std::forward(f)); } - Trigger<> *get_turn_on_trigger() const; - Trigger<> *get_turn_off_trigger() const; + Trigger<> *get_turn_on_trigger(); + Trigger<> *get_turn_off_trigger(); void set_optimistic(bool optimistic); void set_assumed_state(bool assumed_state); void loop() override; @@ -31,9 +31,9 @@ class TemplateSwitch final : public switch_::Switch, public Component { TemplateLambda f_; bool optimistic_{false}; bool assumed_state_{false}; - Trigger<> *turn_on_trigger_; - Trigger<> *turn_off_trigger_; - Trigger<> *prev_trigger_{nullptr}; + Trigger<> turn_on_trigger_; + Trigger<> turn_off_trigger_; + Trigger<> *prev_trigger_{nullptr}; // Points to one of the above }; } // namespace esphome::template_ diff --git a/esphome/components/template/text/template_text.cpp b/esphome/components/template/text/template_text.cpp index 70b8dce312..af134e6ed4 100644 --- a/esphome/components/template/text/template_text.cpp +++ b/esphome/components/template/text/template_text.cpp @@ -47,7 +47,7 @@ void TemplateText::update() { } void TemplateText::control(const std::string &value) { - this->set_trigger_->trigger(value); + this->set_trigger_.trigger(value); if (this->optimistic_) this->publish_state(value); diff --git a/esphome/components/template/text/template_text.h b/esphome/components/template/text/template_text.h index e5e5e4f4a8..88c6afdf2c 100644 --- a/esphome/components/template/text/template_text.h +++ b/esphome/components/template/text/template_text.h @@ -68,7 +68,7 @@ class TemplateText final : public text::Text, public PollingComponent { void dump_config() override; float get_setup_priority() const override { return setup_priority::HARDWARE; } - Trigger *get_set_trigger() const { return this->set_trigger_; } + Trigger *get_set_trigger() { return &this->set_trigger_; } void set_optimistic(bool optimistic) { this->optimistic_ = optimistic; } void set_initial_value(const char *initial_value) { this->initial_value_ = initial_value; } /// 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; bool optimistic_ = false; const char *initial_value_{nullptr}; - Trigger *set_trigger_ = new Trigger(); + Trigger set_trigger_; TemplateLambda f_{}; TemplateTextSaverBase *pref_ = nullptr; diff --git a/esphome/components/template/valve/template_valve.cpp b/esphome/components/template/valve/template_valve.cpp index 4e772f9253..2817e1a132 100644 --- a/esphome/components/template/valve/template_valve.cpp +++ b/esphome/components/template/valve/template_valve.cpp @@ -7,12 +7,7 @@ using namespace esphome::valve; static const char *const TAG = "template.valve"; -TemplateValve::TemplateValve() - : open_trigger_(new Trigger<>()), - close_trigger_(new Trigger<>), - stop_trigger_(new Trigger<>()), - toggle_trigger_(new Trigger<>()), - position_trigger_(new Trigger()) {} +TemplateValve::TemplateValve() = default; void TemplateValve::setup() { switch (this->restore_mode_) { @@ -56,10 +51,10 @@ void TemplateValve::set_optimistic(bool optimistic) { this->optimistic_ = optimi void TemplateValve::set_assumed_state(bool assumed_state) { this->assumed_state_ = assumed_state; } float TemplateValve::get_setup_priority() const { return setup_priority::HARDWARE; } -Trigger<> *TemplateValve::get_open_trigger() const { return this->open_trigger_; } -Trigger<> *TemplateValve::get_close_trigger() const { return this->close_trigger_; } -Trigger<> *TemplateValve::get_stop_trigger() const { return this->stop_trigger_; } -Trigger<> *TemplateValve::get_toggle_trigger() const { return this->toggle_trigger_; } +Trigger<> *TemplateValve::get_open_trigger() { return &this->open_trigger_; } +Trigger<> *TemplateValve::get_close_trigger() { return &this->close_trigger_; } +Trigger<> *TemplateValve::get_stop_trigger() { return &this->stop_trigger_; } +Trigger<> *TemplateValve::get_toggle_trigger() { return &this->toggle_trigger_; } void TemplateValve::dump_config() { LOG_VALVE("", "Template Valve", this); @@ -72,14 +67,14 @@ void TemplateValve::dump_config() { void TemplateValve::control(const ValveCall &call) { if (call.get_stop()) { this->stop_prev_trigger_(); - this->stop_trigger_->trigger(); - this->prev_command_trigger_ = this->stop_trigger_; + this->stop_trigger_.trigger(); + this->prev_command_trigger_ = &this->stop_trigger_; this->publish_state(); } if (call.get_toggle().has_value()) { this->stop_prev_trigger_(); - this->toggle_trigger_->trigger(); - this->prev_command_trigger_ = this->toggle_trigger_; + this->toggle_trigger_.trigger(); + this->prev_command_trigger_ = &this->toggle_trigger_; this->publish_state(); } if (call.get_position().has_value()) { @@ -87,13 +82,13 @@ void TemplateValve::control(const ValveCall &call) { this->stop_prev_trigger_(); if (pos == VALVE_OPEN) { - this->open_trigger_->trigger(); - this->prev_command_trigger_ = this->open_trigger_; + this->open_trigger_.trigger(); + this->prev_command_trigger_ = &this->open_trigger_; } else if (pos == VALVE_CLOSED) { - this->close_trigger_->trigger(); - this->prev_command_trigger_ = this->close_trigger_; + this->close_trigger_.trigger(); + this->prev_command_trigger_ = &this->close_trigger_; } else { - this->position_trigger_->trigger(pos); + this->position_trigger_.trigger(pos); } if (this->optimistic_) { @@ -113,7 +108,7 @@ ValveTraits TemplateValve::get_traits() { return traits; } -Trigger *TemplateValve::get_position_trigger() const { return this->position_trigger_; } +Trigger *TemplateValve::get_position_trigger() { return &this->position_trigger_; } 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; } diff --git a/esphome/components/template/valve/template_valve.h b/esphome/components/template/valve/template_valve.h index 4205682a2a..76c4630aa0 100644 --- a/esphome/components/template/valve/template_valve.h +++ b/esphome/components/template/valve/template_valve.h @@ -18,11 +18,11 @@ class TemplateValve final : public valve::Valve, public Component { TemplateValve(); template void set_state_lambda(F &&f) { this->state_f_.set(std::forward(f)); } - Trigger<> *get_open_trigger() const; - Trigger<> *get_close_trigger() const; - Trigger<> *get_stop_trigger() const; - Trigger<> *get_toggle_trigger() const; - Trigger *get_position_trigger() const; + Trigger<> *get_open_trigger(); + Trigger<> *get_close_trigger(); + Trigger<> *get_stop_trigger(); + Trigger<> *get_toggle_trigger(); + Trigger *get_position_trigger(); void set_optimistic(bool optimistic); void set_assumed_state(bool assumed_state); void set_has_stop(bool has_stop); @@ -45,14 +45,14 @@ class TemplateValve final : public valve::Valve, public Component { TemplateLambda state_f_; bool assumed_state_{false}; bool optimistic_{false}; - Trigger<> *open_trigger_; - Trigger<> *close_trigger_; + Trigger<> open_trigger_; + Trigger<> close_trigger_; bool has_stop_{false}; bool has_toggle_{false}; - Trigger<> *stop_trigger_; - Trigger<> *toggle_trigger_; + Trigger<> stop_trigger_; + Trigger<> toggle_trigger_; Trigger<> *prev_command_trigger_{nullptr}; - Trigger *position_trigger_; + Trigger position_trigger_; bool has_position_{false}; }; diff --git a/esphome/components/template/water_heater/template_water_heater.cpp b/esphome/components/template/water_heater/template_water_heater.cpp index e89c96ca48..f888edb1df 100644 --- a/esphome/components/template/water_heater/template_water_heater.cpp +++ b/esphome/components/template/water_heater/template_water_heater.cpp @@ -5,7 +5,7 @@ namespace esphome::template_ { static const char *const TAG = "template.water_heater"; -TemplateWaterHeater::TemplateWaterHeater() : set_trigger_(new Trigger<>()) {} +TemplateWaterHeater::TemplateWaterHeater() = default; void TemplateWaterHeater::setup() { 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_) { this->publish_state(); diff --git a/esphome/components/template/water_heater/template_water_heater.h b/esphome/components/template/water_heater/template_water_heater.h index c2a2dcbb23..f1cf00a115 100644 --- a/esphome/components/template/water_heater/template_water_heater.h +++ b/esphome/components/template/water_heater/template_water_heater.h @@ -28,7 +28,7 @@ class TemplateWaterHeater : public Component, public water_heater::WaterHeater { this->supported_modes_ = modes; } - Trigger<> *get_set_trigger() const { return this->set_trigger_; } + Trigger<> *get_set_trigger() { return &this->set_trigger_; } void setup() override; void loop() override; @@ -42,7 +42,7 @@ class TemplateWaterHeater : public Component, public water_heater::WaterHeater { water_heater::WaterHeaterTraits traits() override; // Ordered to minimize padding on 32-bit: 4-byte members first, then smaller - Trigger<> *set_trigger_; + Trigger<> set_trigger_; TemplateLambda current_temperature_f_; TemplateLambda mode_f_; TemplateWaterHeaterRestoreMode restore_mode_{WATER_HEATER_NO_RESTORE}; diff --git a/esphome/components/thermostat/thermostat_climate.cpp b/esphome/components/thermostat/thermostat_climate.cpp index 44087969b5..c666419701 100644 --- a/esphome/components/thermostat/thermostat_climate.cpp +++ b/esphome/components/thermostat/thermostat_climate.cpp @@ -499,7 +499,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu } bool action_ready = false; - Trigger<> *trig = this->idle_action_trigger_, *trig_fan = nullptr; + Trigger<> *trig = &this->idle_action_trigger_, *trig_fan = nullptr; switch (action) { case climate::CLIMATE_ACTION_OFF: 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); if (this->supports_fan_with_cooling_) { 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; - trig = this->cool_action_trigger_; + trig = &this->cool_action_trigger_; ESP_LOGVV(TAG, "Switching to COOLING action"); 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); if (this->supports_fan_with_heating_) { 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; - trig = this->heat_action_trigger_; + trig = &this->heat_action_trigger_; ESP_LOGVV(TAG, "Switching to HEATING action"); action_ready = true; } @@ -558,7 +558,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu } else { 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"); action_ready = true; } @@ -567,7 +567,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu if (this->drying_action_ready_()) { this->start_timer_(thermostat::THERMOSTAT_TIMER_COOLING_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"); action_ready = true; } @@ -586,9 +586,7 @@ void ThermostatClimate::switch_to_action_(climate::ClimateAction action, bool pu } this->action = action; this->prev_action_trigger_ = trig; - if (trig != nullptr) { - trig->trigger(); - } + trig->trigger(); // if enabled, call the fan_only action with cooling/heating actions if (trig_fan != nullptr) { ESP_LOGVV(TAG, "Calling FAN_ONLY action with HEATING/COOLING action"); @@ -634,14 +632,14 @@ void ThermostatClimate::trigger_supplemental_action_() { if (!this->timer_active_(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"); break; case climate::CLIMATE_ACTION_HEATING: if (!this->timer_active_(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"); break; default: @@ -660,24 +658,24 @@ void ThermostatClimate::switch_to_humidity_control_action_(HumidificationAction return; } - Trigger<> *trig = this->humidity_control_off_action_trigger_; + Trigger<> *trig = &this->humidity_control_off_action_trigger_; switch (action) { 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"); break; 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"); break; 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"); break; case THERMOSTAT_HUMIDITY_CONTROL_ACTION_NONE: default: 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) { @@ -686,9 +684,7 @@ void ThermostatClimate::switch_to_humidity_control_action_(HumidificationAction } this->humidification_action = action; this->prev_humidity_control_trigger_ = trig; - if (trig != nullptr) { - trig->trigger(); - } + trig->trigger(); } void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bool publish_state) { @@ -703,62 +699,60 @@ void ThermostatClimate::switch_to_fan_mode_(climate::ClimateFanMode fan_mode, bo this->publish_state(); if (this->fan_mode_ready_()) { - Trigger<> *trig = this->fan_mode_auto_trigger_; + Trigger<> *trig = &this->fan_mode_auto_trigger_; switch (fan_mode) { 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"); break; 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"); break; 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"); break; 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"); break; 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"); break; 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"); break; 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"); break; 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"); break; 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"); break; 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"); break; default: // we cannot report an invalid mode back to HA (even if it asked for one) // and must assume some valid value 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) { this->prev_fan_mode_trigger_->stop_action(); this->prev_fan_mode_trigger_ = nullptr; } this->start_timer_(thermostat::THERMOSTAT_TIMER_FAN_MODE); - if (trig != nullptr) { - trig->trigger(); - } + trig->trigger(); this->prev_fan_mode_ = fan_mode; this->prev_fan_mode_trigger_ = trig; } @@ -775,25 +769,25 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_ this->prev_mode_trigger_->stop_action(); this->prev_mode_trigger_ = nullptr; } - Trigger<> *trig = this->off_mode_trigger_; + Trigger<> *trig = &this->off_mode_trigger_; switch (mode) { case climate::CLIMATE_MODE_AUTO: - trig = this->auto_mode_trigger_; + trig = &this->auto_mode_trigger_; break; case climate::CLIMATE_MODE_HEAT_COOL: - trig = this->heat_cool_mode_trigger_; + trig = &this->heat_cool_mode_trigger_; break; case climate::CLIMATE_MODE_COOL: - trig = this->cool_mode_trigger_; + trig = &this->cool_mode_trigger_; break; case climate::CLIMATE_MODE_HEAT: - trig = this->heat_mode_trigger_; + trig = &this->heat_mode_trigger_; break; case climate::CLIMATE_MODE_FAN_ONLY: - trig = this->fan_only_mode_trigger_; + trig = &this->fan_only_mode_trigger_; break; case climate::CLIMATE_MODE_DRY: - trig = this->dry_mode_trigger_; + trig = &this->dry_mode_trigger_; break; case climate::CLIMATE_MODE_OFF: default: @@ -802,9 +796,7 @@ void ThermostatClimate::switch_to_mode_(climate::ClimateMode mode, bool publish_ mode = climate::CLIMATE_MODE_OFF; // trig = this->off_mode_trigger_; } - if (trig != nullptr) { - trig->trigger(); - } + trig->trigger(); this->mode = mode; this->prev_mode_ = mode; this->prev_mode_trigger_ = trig; @@ -824,29 +816,27 @@ void ThermostatClimate::switch_to_swing_mode_(climate::ClimateSwingMode swing_mo this->prev_swing_mode_trigger_->stop_action(); this->prev_swing_mode_trigger_ = nullptr; } - Trigger<> *trig = this->swing_mode_off_trigger_; + Trigger<> *trig = &this->swing_mode_off_trigger_; switch (swing_mode) { case climate::CLIMATE_SWING_BOTH: - trig = this->swing_mode_both_trigger_; + trig = &this->swing_mode_both_trigger_; break; case climate::CLIMATE_SWING_HORIZONTAL: - trig = this->swing_mode_horizontal_trigger_; + trig = &this->swing_mode_horizontal_trigger_; break; case climate::CLIMATE_SWING_OFF: - // trig = this->swing_mode_off_trigger_; + // trig = &this->swing_mode_off_trigger_; break; case climate::CLIMATE_SWING_VERTICAL: - trig = this->swing_mode_vertical_trigger_; + trig = &this->swing_mode_vertical_trigger_; break; default: // we cannot report an invalid mode back to HA (even if it asked for one) // and must assume some valid value swing_mode = climate::CLIMATE_SWING_OFF; - // trig = this->swing_mode_off_trigger_; - } - if (trig != nullptr) { - trig->trigger(); + // trig = &this->swing_mode_off_trigger_; } + trig->trigger(); this->swing_mode = swing_mode; this->prev_swing_mode_ = swing_mode; this->prev_swing_mode_trigger_ = trig; @@ -1024,10 +1014,8 @@ void ThermostatClimate::check_humidity_change_trigger_() { this->prev_target_humidity_ = this->target_humidity; } // trigger the action - Trigger<> *trig = this->humidity_change_trigger_; - if (trig != nullptr) { - trig->trigger(); - } + Trigger<> *trig = &this->humidity_change_trigger_; + trig->trigger(); } void ThermostatClimate::check_temperature_change_trigger_() { @@ -1050,10 +1038,8 @@ void ThermostatClimate::check_temperature_change_trigger_() { } } // trigger the action - Trigger<> *trig = this->temperature_change_trigger_; - if (trig != nullptr) { - trig->trigger(); - } + Trigger<> *trig = &this->temperature_change_trigger_; + trig->trigger(); } bool ThermostatClimate::cooling_required_() { @@ -1202,12 +1188,10 @@ void ThermostatClimate::change_preset_(climate::ClimatePreset preset) { if (config != nullptr) { 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) { - // Fire any preset changed trigger if defined - Trigger<> *trig = this->preset_change_trigger_; + // Fire preset changed trigger + Trigger<> *trig = &this->preset_change_trigger_; this->set_preset_(preset); - if (trig != nullptr) { - trig->trigger(); - } + trig->trigger(); this->refresh(); ESP_LOGI(TAG, "Preset %s applied", LOG_STR_ARG(climate::climate_preset_to_string(preset))); @@ -1234,13 +1218,11 @@ void ThermostatClimate::change_custom_preset_(const char *custom_preset, size_t ESP_LOGV(TAG, "Custom preset %s requested", custom_preset); if (this->change_preset_internal_(*config) || !this->has_custom_preset() || this->get_custom_preset() != custom_preset) { - // Fire any preset changed trigger if defined - Trigger<> *trig = this->preset_change_trigger_; + // Fire preset changed trigger + Trigger<> *trig = &this->preset_change_trigger_; // Use the base class method which handles pointer lookup and preset reset internally this->set_custom_preset_(custom_preset); - if (trig != nullptr) { - trig->trigger(); - } + trig->trigger(); this->refresh(); ESP_LOGI(TAG, "Custom preset %s applied", custom_preset); @@ -1305,41 +1287,7 @@ void ThermostatClimate::set_custom_preset_config(std::initializer_listcustom_preset_config_ = presets; } -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<>()) {} +ThermostatClimate::ThermostatClimate() = default; void ThermostatClimate::set_default_preset(const char *custom_preset) { // Find the preset in custom_preset_config_ and store pointer from there @@ -1513,49 +1461,49 @@ void ThermostatClimate::set_supports_humidification(bool supports_humidification } } -Trigger<> *ThermostatClimate::get_cool_action_trigger() const { return this->cool_action_trigger_; } -Trigger<> *ThermostatClimate::get_supplemental_cool_action_trigger() const { - return this->supplemental_cool_action_trigger_; +Trigger<> *ThermostatClimate::get_cool_action_trigger() { return &this->cool_action_trigger_; } +Trigger<> *ThermostatClimate::get_supplemental_cool_action_trigger() { + return &this->supplemental_cool_action_trigger_; } -Trigger<> *ThermostatClimate::get_dry_action_trigger() const { return this->dry_action_trigger_; } -Trigger<> *ThermostatClimate::get_fan_only_action_trigger() const { return this->fan_only_action_trigger_; } -Trigger<> *ThermostatClimate::get_heat_action_trigger() const { return this->heat_action_trigger_; } -Trigger<> *ThermostatClimate::get_supplemental_heat_action_trigger() const { - return this->supplemental_heat_action_trigger_; +Trigger<> *ThermostatClimate::get_dry_action_trigger() { return &this->dry_action_trigger_; } +Trigger<> *ThermostatClimate::get_fan_only_action_trigger() { return &this->fan_only_action_trigger_; } +Trigger<> *ThermostatClimate::get_heat_action_trigger() { return &this->heat_action_trigger_; } +Trigger<> *ThermostatClimate::get_supplemental_heat_action_trigger() { + return &this->supplemental_heat_action_trigger_; } -Trigger<> *ThermostatClimate::get_idle_action_trigger() const { return this->idle_action_trigger_; } -Trigger<> *ThermostatClimate::get_auto_mode_trigger() const { return this->auto_mode_trigger_; } -Trigger<> *ThermostatClimate::get_cool_mode_trigger() const { return this->cool_mode_trigger_; } -Trigger<> *ThermostatClimate::get_dry_mode_trigger() const { return this->dry_mode_trigger_; } -Trigger<> *ThermostatClimate::get_fan_only_mode_trigger() const { return this->fan_only_mode_trigger_; } -Trigger<> *ThermostatClimate::get_heat_mode_trigger() const { return this->heat_mode_trigger_; } -Trigger<> *ThermostatClimate::get_heat_cool_mode_trigger() const { return this->heat_cool_mode_trigger_; } -Trigger<> *ThermostatClimate::get_off_mode_trigger() const { return this->off_mode_trigger_; } -Trigger<> *ThermostatClimate::get_fan_mode_on_trigger() const { return this->fan_mode_on_trigger_; } -Trigger<> *ThermostatClimate::get_fan_mode_off_trigger() const { return this->fan_mode_off_trigger_; } -Trigger<> *ThermostatClimate::get_fan_mode_auto_trigger() const { return this->fan_mode_auto_trigger_; } -Trigger<> *ThermostatClimate::get_fan_mode_low_trigger() const { return this->fan_mode_low_trigger_; } -Trigger<> *ThermostatClimate::get_fan_mode_medium_trigger() const { return this->fan_mode_medium_trigger_; } -Trigger<> *ThermostatClimate::get_fan_mode_high_trigger() const { return this->fan_mode_high_trigger_; } -Trigger<> *ThermostatClimate::get_fan_mode_middle_trigger() const { return this->fan_mode_middle_trigger_; } -Trigger<> *ThermostatClimate::get_fan_mode_focus_trigger() const { return this->fan_mode_focus_trigger_; } -Trigger<> *ThermostatClimate::get_fan_mode_diffuse_trigger() const { return this->fan_mode_diffuse_trigger_; } -Trigger<> *ThermostatClimate::get_fan_mode_quiet_trigger() const { return this->fan_mode_quiet_trigger_; } -Trigger<> *ThermostatClimate::get_swing_mode_both_trigger() const { return this->swing_mode_both_trigger_; } -Trigger<> *ThermostatClimate::get_swing_mode_off_trigger() const { return this->swing_mode_off_trigger_; } -Trigger<> *ThermostatClimate::get_swing_mode_horizontal_trigger() const { return this->swing_mode_horizontal_trigger_; } -Trigger<> *ThermostatClimate::get_swing_mode_vertical_trigger() const { return this->swing_mode_vertical_trigger_; } -Trigger<> *ThermostatClimate::get_humidity_change_trigger() const { return this->humidity_change_trigger_; } -Trigger<> *ThermostatClimate::get_temperature_change_trigger() const { return this->temperature_change_trigger_; } -Trigger<> *ThermostatClimate::get_preset_change_trigger() const { return this->preset_change_trigger_; } -Trigger<> *ThermostatClimate::get_humidity_control_dehumidify_action_trigger() const { - return this->humidity_control_dehumidify_action_trigger_; +Trigger<> *ThermostatClimate::get_idle_action_trigger() { return &this->idle_action_trigger_; } +Trigger<> *ThermostatClimate::get_auto_mode_trigger() { return &this->auto_mode_trigger_; } +Trigger<> *ThermostatClimate::get_cool_mode_trigger() { return &this->cool_mode_trigger_; } +Trigger<> *ThermostatClimate::get_dry_mode_trigger() { return &this->dry_mode_trigger_; } +Trigger<> *ThermostatClimate::get_fan_only_mode_trigger() { return &this->fan_only_mode_trigger_; } +Trigger<> *ThermostatClimate::get_heat_mode_trigger() { return &this->heat_mode_trigger_; } +Trigger<> *ThermostatClimate::get_heat_cool_mode_trigger() { return &this->heat_cool_mode_trigger_; } +Trigger<> *ThermostatClimate::get_off_mode_trigger() { return &this->off_mode_trigger_; } +Trigger<> *ThermostatClimate::get_fan_mode_on_trigger() { return &this->fan_mode_on_trigger_; } +Trigger<> *ThermostatClimate::get_fan_mode_off_trigger() { return &this->fan_mode_off_trigger_; } +Trigger<> *ThermostatClimate::get_fan_mode_auto_trigger() { return &this->fan_mode_auto_trigger_; } +Trigger<> *ThermostatClimate::get_fan_mode_low_trigger() { return &this->fan_mode_low_trigger_; } +Trigger<> *ThermostatClimate::get_fan_mode_medium_trigger() { return &this->fan_mode_medium_trigger_; } +Trigger<> *ThermostatClimate::get_fan_mode_high_trigger() { return &this->fan_mode_high_trigger_; } +Trigger<> *ThermostatClimate::get_fan_mode_middle_trigger() { return &this->fan_mode_middle_trigger_; } +Trigger<> *ThermostatClimate::get_fan_mode_focus_trigger() { return &this->fan_mode_focus_trigger_; } +Trigger<> *ThermostatClimate::get_fan_mode_diffuse_trigger() { return &this->fan_mode_diffuse_trigger_; } +Trigger<> *ThermostatClimate::get_fan_mode_quiet_trigger() { return &this->fan_mode_quiet_trigger_; } +Trigger<> *ThermostatClimate::get_swing_mode_both_trigger() { return &this->swing_mode_both_trigger_; } +Trigger<> *ThermostatClimate::get_swing_mode_off_trigger() { return &this->swing_mode_off_trigger_; } +Trigger<> *ThermostatClimate::get_swing_mode_horizontal_trigger() { return &this->swing_mode_horizontal_trigger_; } +Trigger<> *ThermostatClimate::get_swing_mode_vertical_trigger() { return &this->swing_mode_vertical_trigger_; } +Trigger<> *ThermostatClimate::get_humidity_change_trigger() { return &this->humidity_change_trigger_; } +Trigger<> *ThermostatClimate::get_temperature_change_trigger() { return &this->temperature_change_trigger_; } +Trigger<> *ThermostatClimate::get_preset_change_trigger() { return &this->preset_change_trigger_; } +Trigger<> *ThermostatClimate::get_humidity_control_dehumidify_action_trigger() { + return &this->humidity_control_dehumidify_action_trigger_; } -Trigger<> *ThermostatClimate::get_humidity_control_humidify_action_trigger() const { - return this->humidity_control_humidify_action_trigger_; +Trigger<> *ThermostatClimate::get_humidity_control_humidify_action_trigger() { + return &this->humidity_control_humidify_action_trigger_; } -Trigger<> *ThermostatClimate::get_humidity_control_off_action_trigger() const { - return this->humidity_control_off_action_trigger_; +Trigger<> *ThermostatClimate::get_humidity_control_off_action_trigger() { + return &this->humidity_control_off_action_trigger_; } void ThermostatClimate::dump_config() { diff --git a/esphome/components/thermostat/thermostat_climate.h b/esphome/components/thermostat/thermostat_climate.h index d37c9a68a6..4268d5c582 100644 --- a/esphome/components/thermostat/thermostat_climate.h +++ b/esphome/components/thermostat/thermostat_climate.h @@ -146,40 +146,40 @@ class ThermostatClimate : public climate::Climate, public Component { void set_preset_config(std::initializer_list presets); void set_custom_preset_config(std::initializer_list presets); - Trigger<> *get_cool_action_trigger() const; - Trigger<> *get_supplemental_cool_action_trigger() const; - Trigger<> *get_dry_action_trigger() const; - Trigger<> *get_fan_only_action_trigger() const; - Trigger<> *get_heat_action_trigger() const; - Trigger<> *get_supplemental_heat_action_trigger() const; - Trigger<> *get_idle_action_trigger() const; - Trigger<> *get_auto_mode_trigger() const; - Trigger<> *get_cool_mode_trigger() const; - Trigger<> *get_dry_mode_trigger() const; - Trigger<> *get_fan_only_mode_trigger() const; - Trigger<> *get_heat_mode_trigger() const; - Trigger<> *get_heat_cool_mode_trigger() const; - Trigger<> *get_off_mode_trigger() const; - Trigger<> *get_fan_mode_on_trigger() const; - Trigger<> *get_fan_mode_off_trigger() const; - Trigger<> *get_fan_mode_auto_trigger() const; - Trigger<> *get_fan_mode_low_trigger() const; - Trigger<> *get_fan_mode_medium_trigger() const; - Trigger<> *get_fan_mode_high_trigger() const; - Trigger<> *get_fan_mode_middle_trigger() const; - Trigger<> *get_fan_mode_focus_trigger() const; - Trigger<> *get_fan_mode_diffuse_trigger() const; - Trigger<> *get_fan_mode_quiet_trigger() const; - Trigger<> *get_swing_mode_both_trigger() const; - Trigger<> *get_swing_mode_horizontal_trigger() const; - Trigger<> *get_swing_mode_off_trigger() const; - Trigger<> *get_swing_mode_vertical_trigger() const; - Trigger<> *get_humidity_change_trigger() const; - Trigger<> *get_temperature_change_trigger() const; - Trigger<> *get_preset_change_trigger() const; - Trigger<> *get_humidity_control_dehumidify_action_trigger() const; - Trigger<> *get_humidity_control_humidify_action_trigger() const; - Trigger<> *get_humidity_control_off_action_trigger() const; + Trigger<> *get_cool_action_trigger(); + Trigger<> *get_supplemental_cool_action_trigger(); + Trigger<> *get_dry_action_trigger(); + Trigger<> *get_fan_only_action_trigger(); + Trigger<> *get_heat_action_trigger(); + Trigger<> *get_supplemental_heat_action_trigger(); + Trigger<> *get_idle_action_trigger(); + Trigger<> *get_auto_mode_trigger(); + Trigger<> *get_cool_mode_trigger(); + Trigger<> *get_dry_mode_trigger(); + Trigger<> *get_fan_only_mode_trigger(); + Trigger<> *get_heat_mode_trigger(); + Trigger<> *get_heat_cool_mode_trigger(); + Trigger<> *get_off_mode_trigger(); + Trigger<> *get_fan_mode_on_trigger(); + Trigger<> *get_fan_mode_off_trigger(); + Trigger<> *get_fan_mode_auto_trigger(); + Trigger<> *get_fan_mode_low_trigger(); + Trigger<> *get_fan_mode_medium_trigger(); + Trigger<> *get_fan_mode_high_trigger(); + Trigger<> *get_fan_mode_middle_trigger(); + Trigger<> *get_fan_mode_focus_trigger(); + Trigger<> *get_fan_mode_diffuse_trigger(); + Trigger<> *get_fan_mode_quiet_trigger(); + Trigger<> *get_swing_mode_both_trigger(); + Trigger<> *get_swing_mode_horizontal_trigger(); + Trigger<> *get_swing_mode_off_trigger(); + Trigger<> *get_swing_mode_vertical_trigger(); + Trigger<> *get_humidity_change_trigger(); + Trigger<> *get_temperature_change_trigger(); + Trigger<> *get_preset_change_trigger(); + Trigger<> *get_humidity_control_dehumidify_action_trigger(); + Trigger<> *get_humidity_control_humidify_action_trigger(); + Trigger<> *get_humidity_control_off_action_trigger(); /// Get current hysteresis values float cool_deadband(); float cool_overrun(); @@ -417,115 +417,65 @@ class ThermostatClimate : public climate::Climate, public Component { /// The sensor used for getting the current humidity sensor::Sensor *humidity_sensor_{nullptr}; - /// The trigger to call when the controller should switch to cooling action/mode. - /// - /// A null value for this attribute means that the controller has no cooling action - /// 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 cooling action/mode + Trigger<> cool_action_trigger_; + Trigger<> supplemental_cool_action_trigger_; + Trigger<> cool_mode_trigger_; - /// The trigger to call when the controller should switch to dry (dehumidification) mode. - /// - /// 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 dry (dehumidification) mode + Trigger<> dry_action_trigger_; + Trigger<> dry_mode_trigger_; - /// The trigger to call when the controller should switch to heating action/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_action_trigger_{nullptr}; - Trigger<> *supplemental_heat_action_trigger_{nullptr}; - Trigger<> *heat_mode_trigger_{nullptr}; + /// Trigger for heating action/mode + Trigger<> heat_action_trigger_; + Trigger<> supplemental_heat_action_trigger_; + Trigger<> heat_mode_trigger_; - /// The trigger to call when the controller should switch to heat/cool mode. - /// - /// 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 heat/cool mode + Trigger<> heat_cool_mode_trigger_; - /// The trigger to call when the controller should switch to auto mode. - /// - /// 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 auto mode + Trigger<> auto_mode_trigger_; - /// The trigger to call when the controller should switch to idle action/off mode. - /// - /// 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 idle action/off mode + Trigger<> idle_action_trigger_; + Trigger<> off_mode_trigger_; - /// The trigger to call when the controller should switch to fan-only action/mode. - /// - /// 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}; + /// Trigger for fan-only action/mode + Trigger<> fan_only_action_trigger_; + Trigger<> fan_only_mode_trigger_; - /// The trigger to call when the controller should switch on the fan. - Trigger<> *fan_mode_on_trigger_{nullptr}; + /// Fan mode triggers + Trigger<> fan_mode_on_trigger_; + 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_; - /// The trigger to call when the controller should switch off the fan. - Trigger<> *fan_mode_off_trigger_{nullptr}; + /// Swing mode triggers + Trigger<> swing_mode_both_trigger_; + Trigger<> swing_mode_off_trigger_; + Trigger<> swing_mode_horizontal_trigger_; + Trigger<> swing_mode_vertical_trigger_; - /// The trigger to call when the controller should switch the fan to "auto" mode. - Trigger<> *fan_mode_auto_trigger_{nullptr}; + /// Trigger for target humidity changes + Trigger<> humidity_change_trigger_; - /// The trigger to call when the controller should switch the fan to "low" speed. - Trigger<> *fan_mode_low_trigger_{nullptr}; + /// Trigger for target temperature changes + Trigger<> temperature_change_trigger_; - /// The trigger to call when the controller should switch the fan to "medium" speed. - Trigger<> *fan_mode_medium_trigger_{nullptr}; + /// Trigger for preset mode changes + Trigger<> preset_change_trigger_; - /// The trigger to call when the controller should switch the fan to "high" speed. - Trigger<> *fan_mode_high_trigger_{nullptr}; - - /// 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}; + /// Humidity control triggers + Trigger<> humidity_control_dehumidify_action_trigger_; + Trigger<> humidity_control_humidify_action_trigger_; + Trigger<> humidity_control_off_action_trigger_; /// A reference to the trigger that was previously active. /// diff --git a/esphome/components/time_based/time_based_cover.cpp b/esphome/components/time_based/time_based_cover.cpp index 1eb591fe6e..0aef4b8e85 100644 --- a/esphome/components/time_based/time_based_cover.cpp +++ b/esphome/components/time_based/time_based_cover.cpp @@ -132,15 +132,15 @@ void TimeBasedCover::start_direction_(CoverOperation dir) { Trigger<> *trig; switch (dir) { case COVER_OPERATION_IDLE: - trig = this->stop_trigger_; + trig = &this->stop_trigger_; break; case COVER_OPERATION_OPENING: this->last_operation_ = dir; - trig = this->open_trigger_; + trig = &this->open_trigger_; break; case COVER_OPERATION_CLOSING: this->last_operation_ = dir; - trig = this->close_trigger_; + trig = &this->close_trigger_; break; default: return; diff --git a/esphome/components/time_based/time_based_cover.h b/esphome/components/time_based/time_based_cover.h index 42cf66c2ab..d2457cae7a 100644 --- a/esphome/components/time_based/time_based_cover.h +++ b/esphome/components/time_based/time_based_cover.h @@ -14,9 +14,9 @@ class TimeBasedCover : public cover::Cover, public Component { void dump_config() override; float get_setup_priority() const override; - Trigger<> *get_open_trigger() const { return this->open_trigger_; } - Trigger<> *get_close_trigger() const { return this->close_trigger_; } - Trigger<> *get_stop_trigger() const { return this->stop_trigger_; } + Trigger<> *get_open_trigger() { return &this->open_trigger_; } + Trigger<> *get_close_trigger() { return &this->close_trigger_; } + Trigger<> *get_stop_trigger() { return &this->stop_trigger_; } void set_open_duration(uint32_t open_duration) { this->open_duration_ = open_duration; } void set_close_duration(uint32_t close_duration) { this->close_duration_ = close_duration; } cover::CoverTraits get_traits() override; @@ -34,11 +34,11 @@ class TimeBasedCover : public cover::Cover, public Component { void recompute_position_(); - Trigger<> *open_trigger_{new Trigger<>()}; + Trigger<> open_trigger_; uint32_t open_duration_; - Trigger<> *close_trigger_{new Trigger<>()}; + Trigger<> close_trigger_; uint32_t close_duration_; - Trigger<> *stop_trigger_{new Trigger<>()}; + Trigger<> stop_trigger_; Trigger<> *prev_command_trigger_{nullptr}; uint32_t last_recompute_time_{0}; diff --git a/esphome/components/udp/__init__.py b/esphome/components/udp/__init__.py index 9be196d420..8252e35023 100644 --- a/esphome/components/udp/__init__.py +++ b/esphome/components/udp/__init__.py @@ -12,8 +12,8 @@ from esphome.components.packet_transport import ( ) import esphome.config_validation as cv from esphome.const import CONF_DATA, CONF_ID, CONF_PORT, CONF_TRIGGER_ID -from esphome.core import ID, Lambda -from esphome.cpp_generator import ExpressionStatement, MockObj +from esphome.core import ID +from esphome.cpp_generator import literal CODEOWNERS = ["@clydebarrow"] DEPENDENCIES = ["network"] @@ -24,6 +24,8 @@ udp_ns = cg.esphome_ns.namespace("udp") UDPComponent = udp_ns.class_("UDPComponent", cg.Component) UDPWriteAction = udp_ns.class_("UDPWriteAction", automation.Action) trigger_args = cg.std_vector.template(cg.uint8) +trigger_argname = "data" +trigger_argtype = [(trigger_args, trigger_argname)] CONF_ADDRESSES = "addresses" CONF_LISTEN_ADDRESS = "listen_address" @@ -111,13 +113,14 @@ async def to_code(config): cg.add(var.set_addresses([str(addr) for addr in config[CONF_ADDRESSES]])) if on_receive := config.get(CONF_ON_RECEIVE): on_receive = on_receive[0] - trigger = cg.new_Pvariable(on_receive[CONF_TRIGGER_ID]) + trigger_id = cg.new_Pvariable(on_receive[CONF_TRIGGER_ID]) trigger = await automation.build_automation( - trigger, [(trigger_args, "data")], on_receive + trigger_id, trigger_argtype, on_receive ) - trigger = Lambda(str(ExpressionStatement(trigger.trigger(MockObj("data"))))) - trigger = await cg.process_lambda(trigger, [(trigger_args, "data")]) - cg.add(var.add_listener(trigger)) + trigger_lambda = await cg.process_lambda( + trigger.trigger(literal(trigger_argname)), trigger_argtype + ) + cg.add(var.add_listener(trigger_lambda)) cg.add(var.set_should_listen()) diff --git a/esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp b/esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp index 5c91150f30..224d6e3ab1 100644 --- a/esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp +++ b/esphome/components/usb_cdc_acm/usb_cdc_acm_esp32.cpp @@ -155,8 +155,9 @@ void USBCDCACMInstance::setup() { return; } - // Use a larger stack size for (very) verbose logging - const size_t stack_size = esp_log_level_get(TAG) > ESP_LOG_DEBUG ? USB_TX_TASK_STACK_SIZE_VV : USB_TX_TASK_STACK_SIZE; + // Use a larger stack size for very verbose logging + constexpr size_t stack_size = + ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE ? USB_TX_TASK_STACK_SIZE_VV : USB_TX_TASK_STACK_SIZE; // Create a simple, unique task name per interface char task_name[] = "usb_tx_0"; diff --git a/esphome/components/voice_assistant/voice_assistant.cpp b/esphome/components/voice_assistant/voice_assistant.cpp index e2516d5fb8..7f5fbe62e1 100644 --- a/esphome/components/voice_assistant/voice_assistant.cpp +++ b/esphome/components/voice_assistant/voice_assistant.cpp @@ -197,7 +197,7 @@ void VoiceAssistant::loop() { switch (this->state_) { case State::IDLE: { if (this->continuous_ && this->desired_state_ == State::IDLE) { - this->idle_trigger_->trigger(); + this->idle_trigger_.trigger(); this->set_state_(State::START_MICROPHONE, State::START_PIPELINE); } else { this->deallocate_buffers_(); @@ -254,7 +254,7 @@ void VoiceAssistant::loop() { if (this->api_client_ == nullptr || !this->api_client_->send_message(msg, api::VoiceAssistantRequest::MESSAGE_TYPE)) { ESP_LOGW(TAG, "Could not request start"); - this->error_trigger_->trigger("not-connected", "Could not request start"); + this->error_trigger_.trigger("not-connected", "Could not request start"); this->continuous_ = false; this->set_state_(State::IDLE, State::IDLE); break; @@ -384,7 +384,7 @@ void VoiceAssistant::loop() { this->wait_for_stream_end_ = false; this->stream_ended_ = false; - this->tts_stream_end_trigger_->trigger(); + this->tts_stream_end_trigger_.trigger(); } #endif if (this->continue_conversation_) { @@ -425,7 +425,7 @@ void VoiceAssistant::client_subscription(api::APIConnection *client, bool subscr return; } this->api_client_ = nullptr; - this->client_disconnected_trigger_->trigger(); + this->client_disconnected_trigger_.trigger(); return; } @@ -440,7 +440,7 @@ void VoiceAssistant::client_subscription(api::APIConnection *client, bool subscr } this->api_client_ = client; - this->client_connected_trigger_->trigger(); + this->client_connected_trigger_.trigger(); } static const LogString *voice_assistant_state_to_string(State state) { @@ -491,7 +491,7 @@ void VoiceAssistant::set_state_(State state, State desired_state) { void VoiceAssistant::failed_to_start() { ESP_LOGE(TAG, "Failed to start server. See Home Assistant logs for more details."); - this->error_trigger_->trigger("failed-to-start", "Failed to start server. See Home Assistant logs for more details."); + this->error_trigger_.trigger("failed-to-start", "Failed to start server. See Home Assistant logs for more details."); this->set_state_(State::STOP_MICROPHONE, State::IDLE); } @@ -637,18 +637,18 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } } #endif - this->defer([this]() { this->start_trigger_->trigger(); }); + this->defer([this]() { this->start_trigger_.trigger(); }); break; case api::enums::VOICE_ASSISTANT_WAKE_WORD_START: break; case api::enums::VOICE_ASSISTANT_WAKE_WORD_END: { ESP_LOGD(TAG, "Wake word detected"); - this->defer([this]() { this->wake_word_detected_trigger_->trigger(); }); + this->defer([this]() { this->wake_word_detected_trigger_.trigger(); }); break; } case api::enums::VOICE_ASSISTANT_STT_START: ESP_LOGD(TAG, "STT started"); - this->defer([this]() { this->listening_trigger_->trigger(); }); + this->defer([this]() { this->listening_trigger_.trigger(); }); break; case api::enums::VOICE_ASSISTANT_STT_END: { std::string text; @@ -665,12 +665,12 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { text += "..."; } ESP_LOGD(TAG, "Speech recognised as: \"%s\"", text.c_str()); - this->defer([this, text]() { this->stt_end_trigger_->trigger(text); }); + this->defer([this, text]() { this->stt_end_trigger_.trigger(text); }); break; } case api::enums::VOICE_ASSISTANT_INTENT_START: ESP_LOGD(TAG, "Intent started"); - this->defer([this]() { this->intent_start_trigger_->trigger(); }); + this->defer([this]() { this->intent_start_trigger_.trigger(); }); break; case api::enums::VOICE_ASSISTANT_INTENT_PROGRESS: { ESP_LOGD(TAG, "Intent progress"); @@ -693,7 +693,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } } #endif - this->defer([this, tts_url_for_trigger]() { this->intent_progress_trigger_->trigger(tts_url_for_trigger); }); + this->defer([this, tts_url_for_trigger]() { this->intent_progress_trigger_.trigger(tts_url_for_trigger); }); break; } case api::enums::VOICE_ASSISTANT_INTENT_END: { @@ -704,7 +704,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { this->continue_conversation_ = (arg.value == "1"); } } - this->defer([this]() { this->intent_end_trigger_->trigger(); }); + this->defer([this]() { this->intent_end_trigger_.trigger(); }); break; } case api::enums::VOICE_ASSISTANT_TTS_START: { @@ -724,7 +724,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } ESP_LOGD(TAG, "Response: \"%s\"", text.c_str()); this->defer([this, text]() { - this->tts_start_trigger_->trigger(text); + this->tts_start_trigger_.trigger(text); #ifdef USE_SPEAKER if (this->speaker_ != nullptr) { this->speaker_->start(); @@ -756,7 +756,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } this->started_streaming_tts_ = false; // Helps indicate reaching the TTS_END stage #endif - this->tts_end_trigger_->trigger(url); + this->tts_end_trigger_.trigger(url); }); State new_state = this->local_output_ ? State::STREAMING_RESPONSE : State::IDLE; if (new_state != this->state_) { @@ -776,7 +776,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { // No TTS start event ("nevermind") this->set_state_(State::IDLE, State::IDLE); } - this->defer([this]() { this->end_trigger_->trigger(); }); + this->defer([this]() { this->end_trigger_.trigger(); }); break; } case api::enums::VOICE_ASSISTANT_ERROR: { @@ -796,7 +796,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { // Wake word is not set up or not ready on Home Assistant so stop and do not retry until user starts again. this->defer([this, code, message]() { this->request_stop(); - this->error_trigger_->trigger(code, message); + this->error_trigger_.trigger(code, message); }); return; } @@ -805,7 +805,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { this->signal_stop_(); this->set_state_(State::STOP_MICROPHONE, State::IDLE); } - this->defer([this, code, message]() { this->error_trigger_->trigger(code, message); }); + this->defer([this, code, message]() { this->error_trigger_.trigger(code, message); }); break; } case api::enums::VOICE_ASSISTANT_TTS_STREAM_START: { @@ -813,7 +813,7 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { if (this->speaker_ != nullptr) { this->wait_for_stream_end_ = true; ESP_LOGD(TAG, "TTS stream start"); - this->defer([this] { this->tts_stream_start_trigger_->trigger(); }); + this->defer([this] { this->tts_stream_start_trigger_.trigger(); }); } #endif break; @@ -829,12 +829,12 @@ void VoiceAssistant::on_event(const api::VoiceAssistantEventResponse &msg) { } case api::enums::VOICE_ASSISTANT_STT_VAD_START: ESP_LOGD(TAG, "Starting STT by VAD"); - this->defer([this]() { this->stt_vad_start_trigger_->trigger(); }); + this->defer([this]() { this->stt_vad_start_trigger_.trigger(); }); break; case api::enums::VOICE_ASSISTANT_STT_VAD_END: ESP_LOGD(TAG, "STT by VAD end"); this->set_state_(State::STOP_MICROPHONE, State::AWAITING_RESPONSE); - this->defer([this]() { this->stt_vad_end_trigger_->trigger(); }); + this->defer([this]() { this->stt_vad_end_trigger_.trigger(); }); break; default: ESP_LOGD(TAG, "Unhandled event type: %" PRId32, msg.event_type); @@ -876,17 +876,17 @@ void VoiceAssistant::on_timer_event(const api::VoiceAssistantTimerEventResponse switch (msg.event_type) { case api::enums::VOICE_ASSISTANT_TIMER_STARTED: - this->timer_started_trigger_->trigger(timer); + this->timer_started_trigger_.trigger(timer); break; case api::enums::VOICE_ASSISTANT_TIMER_UPDATED: - this->timer_updated_trigger_->trigger(timer); + this->timer_updated_trigger_.trigger(timer); break; case api::enums::VOICE_ASSISTANT_TIMER_CANCELLED: - this->timer_cancelled_trigger_->trigger(timer); + this->timer_cancelled_trigger_.trigger(timer); this->timers_.erase(timer.id); break; case api::enums::VOICE_ASSISTANT_TIMER_FINISHED: - this->timer_finished_trigger_->trigger(timer); + this->timer_finished_trigger_.trigger(timer); this->timers_.erase(timer.id); break; } @@ -910,13 +910,13 @@ void VoiceAssistant::timer_tick_() { } res.push_back(timer); } - this->timer_tick_trigger_->trigger(res); + this->timer_tick_trigger_.trigger(res); } void VoiceAssistant::on_announce(const api::VoiceAssistantAnnounceRequest &msg) { #ifdef USE_MEDIA_PLAYER if (this->media_player_ != nullptr) { - this->tts_start_trigger_->trigger(msg.text); + this->tts_start_trigger_.trigger(msg.text); this->media_player_response_state_ = MediaPlayerResponseState::URL_SENT; @@ -939,8 +939,8 @@ void VoiceAssistant::on_announce(const api::VoiceAssistantAnnounceRequest &msg) this->set_state_(State::STREAMING_RESPONSE, State::STREAMING_RESPONSE); } - this->tts_end_trigger_->trigger(msg.media_id); - this->end_trigger_->trigger(); + this->tts_end_trigger_.trigger(msg.media_id); + this->end_trigger_.trigger(); } #endif } diff --git a/esphome/components/voice_assistant/voice_assistant.h b/esphome/components/voice_assistant/voice_assistant.h index d61a8fbbc1..2a5f3a55a7 100644 --- a/esphome/components/voice_assistant/voice_assistant.h +++ b/esphome/components/voice_assistant/voice_assistant.h @@ -195,38 +195,38 @@ class VoiceAssistant : public Component { void set_conversation_timeout(uint32_t conversation_timeout) { this->conversation_timeout_ = conversation_timeout; } void reset_conversation_id(); - Trigger<> *get_intent_end_trigger() const { return this->intent_end_trigger_; } - Trigger<> *get_intent_start_trigger() const { return this->intent_start_trigger_; } - Trigger *get_intent_progress_trigger() const { return this->intent_progress_trigger_; } - Trigger<> *get_listening_trigger() const { return this->listening_trigger_; } - Trigger<> *get_end_trigger() const { return this->end_trigger_; } - Trigger<> *get_start_trigger() const { return this->start_trigger_; } - Trigger<> *get_stt_vad_end_trigger() const { return this->stt_vad_end_trigger_; } - Trigger<> *get_stt_vad_start_trigger() const { return this->stt_vad_start_trigger_; } + Trigger<> *get_intent_end_trigger() { return &this->intent_end_trigger_; } + Trigger<> *get_intent_start_trigger() { return &this->intent_start_trigger_; } + Trigger *get_intent_progress_trigger() { return &this->intent_progress_trigger_; } + Trigger<> *get_listening_trigger() { return &this->listening_trigger_; } + Trigger<> *get_end_trigger() { return &this->end_trigger_; } + Trigger<> *get_start_trigger() { return &this->start_trigger_; } + Trigger<> *get_stt_vad_end_trigger() { return &this->stt_vad_end_trigger_; } + Trigger<> *get_stt_vad_start_trigger() { return &this->stt_vad_start_trigger_; } #ifdef USE_SPEAKER - Trigger<> *get_tts_stream_start_trigger() const { return this->tts_stream_start_trigger_; } - Trigger<> *get_tts_stream_end_trigger() const { return this->tts_stream_end_trigger_; } + Trigger<> *get_tts_stream_start_trigger() { return &this->tts_stream_start_trigger_; } + Trigger<> *get_tts_stream_end_trigger() { return &this->tts_stream_end_trigger_; } #endif - Trigger<> *get_wake_word_detected_trigger() const { return this->wake_word_detected_trigger_; } - Trigger *get_stt_end_trigger() const { return this->stt_end_trigger_; } - Trigger *get_tts_end_trigger() const { return this->tts_end_trigger_; } - Trigger *get_tts_start_trigger() const { return this->tts_start_trigger_; } - Trigger *get_error_trigger() const { return this->error_trigger_; } - Trigger<> *get_idle_trigger() const { return this->idle_trigger_; } + Trigger<> *get_wake_word_detected_trigger() { return &this->wake_word_detected_trigger_; } + Trigger *get_stt_end_trigger() { return &this->stt_end_trigger_; } + Trigger *get_tts_end_trigger() { return &this->tts_end_trigger_; } + Trigger *get_tts_start_trigger() { return &this->tts_start_trigger_; } + Trigger *get_error_trigger() { return &this->error_trigger_; } + Trigger<> *get_idle_trigger() { return &this->idle_trigger_; } - Trigger<> *get_client_connected_trigger() const { return this->client_connected_trigger_; } - Trigger<> *get_client_disconnected_trigger() const { return this->client_disconnected_trigger_; } + Trigger<> *get_client_connected_trigger() { return &this->client_connected_trigger_; } + Trigger<> *get_client_disconnected_trigger() { return &this->client_disconnected_trigger_; } void client_subscription(api::APIConnection *client, bool subscribe); api::APIConnection *get_api_connection() const { return this->api_client_; } void set_wake_word(const std::string &wake_word) { this->wake_word_ = wake_word; } - Trigger *get_timer_started_trigger() const { return this->timer_started_trigger_; } - Trigger *get_timer_updated_trigger() const { return this->timer_updated_trigger_; } - Trigger *get_timer_cancelled_trigger() const { return this->timer_cancelled_trigger_; } - Trigger *get_timer_finished_trigger() const { return this->timer_finished_trigger_; } - Trigger> *get_timer_tick_trigger() const { return this->timer_tick_trigger_; } + Trigger *get_timer_started_trigger() { return &this->timer_started_trigger_; } + Trigger *get_timer_updated_trigger() { return &this->timer_updated_trigger_; } + Trigger *get_timer_cancelled_trigger() { return &this->timer_cancelled_trigger_; } + Trigger *get_timer_finished_trigger() { return &this->timer_finished_trigger_; } + Trigger> *get_timer_tick_trigger() { return &this->timer_tick_trigger_; } void set_has_timers(bool has_timers) { this->has_timers_ = has_timers; } const std::unordered_map &get_timers() const { return this->timers_; } @@ -243,37 +243,37 @@ class VoiceAssistant : public Component { std::unique_ptr socket_ = nullptr; struct sockaddr_storage dest_addr_; - Trigger<> *intent_end_trigger_ = new Trigger<>(); - Trigger<> *intent_start_trigger_ = new Trigger<>(); - Trigger<> *listening_trigger_ = new Trigger<>(); - Trigger<> *end_trigger_ = new Trigger<>(); - Trigger<> *start_trigger_ = new Trigger<>(); - Trigger<> *stt_vad_start_trigger_ = new Trigger<>(); - Trigger<> *stt_vad_end_trigger_ = new Trigger<>(); + Trigger<> intent_end_trigger_; + Trigger<> intent_start_trigger_; + Trigger<> listening_trigger_; + Trigger<> end_trigger_; + Trigger<> start_trigger_; + Trigger<> stt_vad_start_trigger_; + Trigger<> stt_vad_end_trigger_; #ifdef USE_SPEAKER - Trigger<> *tts_stream_start_trigger_ = new Trigger<>(); - Trigger<> *tts_stream_end_trigger_ = new Trigger<>(); + Trigger<> tts_stream_start_trigger_; + Trigger<> tts_stream_end_trigger_; #endif - Trigger *intent_progress_trigger_ = new Trigger(); - Trigger<> *wake_word_detected_trigger_ = new Trigger<>(); - Trigger *stt_end_trigger_ = new Trigger(); - Trigger *tts_end_trigger_ = new Trigger(); - Trigger *tts_start_trigger_ = new Trigger(); - Trigger *error_trigger_ = new Trigger(); - Trigger<> *idle_trigger_ = new Trigger<>(); + Trigger intent_progress_trigger_; + Trigger<> wake_word_detected_trigger_; + Trigger stt_end_trigger_; + Trigger tts_end_trigger_; + Trigger tts_start_trigger_; + Trigger error_trigger_; + Trigger<> idle_trigger_; - Trigger<> *client_connected_trigger_ = new Trigger<>(); - Trigger<> *client_disconnected_trigger_ = new Trigger<>(); + Trigger<> client_connected_trigger_; + Trigger<> client_disconnected_trigger_; api::APIConnection *api_client_{nullptr}; std::unordered_map timers_; void timer_tick_(); - Trigger *timer_started_trigger_ = new Trigger(); - Trigger *timer_finished_trigger_ = new Trigger(); - Trigger *timer_updated_trigger_ = new Trigger(); - Trigger *timer_cancelled_trigger_ = new Trigger(); - Trigger> *timer_tick_trigger_ = new Trigger>(); + Trigger timer_started_trigger_; + Trigger timer_finished_trigger_; + Trigger timer_updated_trigger_; + Trigger timer_cancelled_trigger_; + Trigger> timer_tick_trigger_; bool has_timers_{false}; bool timer_tick_running_{false}; diff --git a/esphome/components/web_server_base/__init__.py b/esphome/components/web_server_base/__init__.py index 97cca5776b..11408ae260 100644 --- a/esphome/components/web_server_base/__init__.py +++ b/esphome/components/web_server_base/__init__.py @@ -50,4 +50,4 @@ async def to_code(config): "lib_ignore", ["ESPAsyncTCP", "AsyncTCP", "AsyncTCP_RP2040W"] ) # https://github.com/ESP32Async/ESPAsyncWebServer/blob/main/library.json - cg.add_library("ESP32Async/ESPAsyncWebServer", "3.9.5") + cg.add_library("ESP32Async/ESPAsyncWebServer", "3.9.6") diff --git a/esphome/core/__init__.py b/esphome/core/__init__.py index 6916f79ce3..484f679369 100644 --- a/esphome/core/__init__.py +++ b/esphome/core/__init__.py @@ -278,9 +278,13 @@ LAMBDA_PROG = re.compile(r"\bid\(\s*([a-zA-Z_][a-zA-Z0-9_]*)\s*\)(\.?)") class Lambda: def __init__(self, value): + from esphome.cpp_generator import Expression, statement + # pylint: disable=protected-access if isinstance(value, Lambda): self._value = value._value + elif isinstance(value, Expression): + self._value = str(statement(value)) else: self._value = value self._parts = None diff --git a/esphome/core/application.cpp b/esphome/core/application.cpp index 55eb25ce09..0e77be9ee4 100644 --- a/esphome/core/application.cpp +++ b/esphome/core/application.cpp @@ -210,7 +210,7 @@ void Application::loop() { #ifdef USE_ESP32 esp_chip_info_t chip_info; esp_chip_info(&chip_info); - ESP_LOGI(TAG, "ESP32 Chip: %s r%d.%d, %d core(s)", ESPHOME_VARIANT, chip_info.revision / 100, + ESP_LOGI(TAG, "ESP32 Chip: %s rev%d.%d, %d core(s)", ESPHOME_VARIANT, chip_info.revision / 100, chip_info.revision % 100, chip_info.cores); #if defined(USE_ESP32_VARIANT_ESP32) && !defined(USE_ESP32_MIN_CHIP_REVISION_SET) // Suggest optimization for chips that don't need the PSRAM cache workaround diff --git a/esphome/core/defines.h b/esphome/core/defines.h index e98cdd0ba0..1edc648084 100644 --- a/esphome/core/defines.h +++ b/esphome/core/defines.h @@ -240,6 +240,8 @@ #define USE_ETHERNET_KSZ8081 #define USE_ETHERNET_MANUAL_IP #define USE_ETHERNET_IP_STATE_LISTENERS +#define USE_ETHERNET_CONNECT_TRIGGER +#define USE_ETHERNET_DISCONNECT_TRIGGER #define ESPHOME_ETHERNET_IP_STATE_LISTENERS 2 #endif diff --git a/esphome/cpp_generator.py b/esphome/cpp_generator.py index cff0748c95..83f2d6cf81 100644 --- a/esphome/cpp_generator.py +++ b/esphome/cpp_generator.py @@ -462,6 +462,16 @@ def statement(expression: Expression | Statement) -> Statement: return ExpressionStatement(expression) +def literal(name: str) -> "MockObj": + """Create a literal name that will appear in the generated code + not surrounded by quotes. + + :param name: The name of the literal. + :return: The literal as a MockObj. + """ + return MockObj(name, "") + + def variable( id_: ID, rhs: SafeExpType, type_: "MockObj" = None, register=True ) -> "MockObj": @@ -665,7 +675,7 @@ async def get_variable_with_full_id(id_: ID) -> tuple[ID, "MockObj"]: async def process_lambda( - value: Lambda, + value: Lambda | Expression, parameters: TemplateArgsType, capture: str = "", return_type: SafeExpType = None, @@ -689,6 +699,14 @@ async def process_lambda( if value is None: return None + # Inadvertently passing a malformed parameters value will lead to the build process mysteriously hanging at the + # "Generating C++ source..." stage, so check here to save the developer's hair. + assert isinstance(parameters, list) and all( + isinstance(p, tuple) and len(p) == 2 for p in parameters + ) + if isinstance(value, Expression): + value = Lambda(value) + parts = value.parts[:] for i, id in enumerate(value.requires_ids): full_id, var = await get_variable_with_full_id(id) diff --git a/platformio.ini b/platformio.ini index 0f5bf2f8fb..bb0de3c2b1 100644 --- a/platformio.ini +++ b/platformio.ini @@ -114,7 +114,7 @@ lib_deps = ESP8266WiFi ; wifi (Arduino built-in) Update ; ota (Arduino built-in) ESP32Async/ESPAsyncTCP@2.0.0 ; async_tcp - ESP32Async/ESPAsyncWebServer@3.9.5 ; web_server_base + ESP32Async/ESPAsyncWebServer@3.9.6 ; web_server_base makuna/NeoPixelBus@2.7.3 ; neopixelbus ESP8266HTTPClient ; http_request (Arduino built-in) ESP8266mDNS ; mdns (Arduino built-in) @@ -202,7 +202,7 @@ lib_deps = ${common:arduino.lib_deps} ayushsharma82/RPAsyncTCP@1.3.2 ; async_tcp bblanchon/ArduinoJson@7.4.2 ; json - ESP32Async/ESPAsyncWebServer@3.9.5 ; web_server_base + ESP32Async/ESPAsyncWebServer@3.9.6 ; web_server_base build_flags = ${common:arduino.build_flags} -DUSE_RP2040 @@ -218,7 +218,7 @@ framework = arduino lib_compat_mode = soft lib_deps = bblanchon/ArduinoJson@7.4.2 ; json - ESP32Async/ESPAsyncWebServer@3.9.5 ; web_server_base + ESP32Async/ESPAsyncWebServer@3.9.6 ; web_server_base droscy/esp_wireguard@0.4.2 ; wireguard build_flags = ${common:arduino.build_flags} diff --git a/tests/components/dlms_meter/common-generic.yaml b/tests/components/dlms_meter/common-generic.yaml new file mode 100644 index 0000000000..edb1c66f0f --- /dev/null +++ b/tests/components/dlms_meter/common-generic.yaml @@ -0,0 +1,11 @@ +dlms_meter: + decryption_key: "36C66639E48A8CA4D6BC8B282A793BBB" # change this to your decryption key! + +sensor: + - platform: dlms_meter + reactive_energy_plus: + name: "Reactive energy taken from grid" + reactive_energy_minus: + name: "Reactive energy put into grid" + +<<: !include common.yaml diff --git a/tests/components/dlms_meter/common-netznoe.yaml b/tests/components/dlms_meter/common-netznoe.yaml new file mode 100644 index 0000000000..db064b64f9 --- /dev/null +++ b/tests/components/dlms_meter/common-netznoe.yaml @@ -0,0 +1,17 @@ +dlms_meter: + decryption_key: "36C66639E48A8CA4D6BC8B282A793BBB" # change this to your decryption key! + provider: netznoe # (optional) key - only set if using evn + +sensor: + - platform: dlms_meter + # EVN + power_factor: + name: "Power Factor" + +text_sensor: + - platform: dlms_meter + # EVN + meternumber: + name: "meterNumber" + +<<: !include common.yaml diff --git a/tests/components/dlms_meter/common.yaml b/tests/components/dlms_meter/common.yaml new file mode 100644 index 0000000000..6aa4e1b0ff --- /dev/null +++ b/tests/components/dlms_meter/common.yaml @@ -0,0 +1,27 @@ +sensor: + - platform: dlms_meter + voltage_l1: + name: "Voltage L1" + voltage_l2: + name: "Voltage L2" + voltage_l3: + name: "Voltage L3" + current_l1: + name: "Current L1" + current_l2: + name: "Current L2" + current_l3: + name: "Current L3" + active_power_plus: + name: "Active power taken from grid" + active_power_minus: + name: "Active power put into grid" + active_energy_plus: + name: "Active energy taken from grid" + active_energy_minus: + name: "Active energy put into grid" + +text_sensor: + - platform: dlms_meter + timestamp: + name: "timestamp" diff --git a/tests/components/dlms_meter/test.esp32-ard.yaml b/tests/components/dlms_meter/test.esp32-ard.yaml new file mode 100644 index 0000000000..4f8a06c31b --- /dev/null +++ b/tests/components/dlms_meter/test.esp32-ard.yaml @@ -0,0 +1,4 @@ +packages: + uart: !include ../../test_build_components/common/uart_2400/esp32-ard.yaml + +<<: !include common-generic.yaml diff --git a/tests/components/dlms_meter/test.esp32-idf.yaml b/tests/components/dlms_meter/test.esp32-idf.yaml new file mode 100644 index 0000000000..f993515fce --- /dev/null +++ b/tests/components/dlms_meter/test.esp32-idf.yaml @@ -0,0 +1,4 @@ +packages: + uart: !include ../../test_build_components/common/uart_2400/esp32-idf.yaml + +<<: !include common-netznoe.yaml diff --git a/tests/components/dlms_meter/test.esp8266-ard.yaml b/tests/components/dlms_meter/test.esp8266-ard.yaml new file mode 100644 index 0000000000..2ce7955c9f --- /dev/null +++ b/tests/components/dlms_meter/test.esp8266-ard.yaml @@ -0,0 +1,4 @@ +packages: + uart: !include ../../test_build_components/common/uart_2400/esp8266-ard.yaml + +<<: !include common-generic.yaml diff --git a/tests/components/ethernet/common-dm9051.yaml b/tests/components/ethernet/common-dm9051.yaml index 4526e7732d..bb8c74b820 100644 --- a/tests/components/ethernet/common-dm9051.yaml +++ b/tests/components/ethernet/common-dm9051.yaml @@ -13,3 +13,7 @@ ethernet: subnet: 255.255.255.0 domain: .local mac_address: "02:AA:BB:CC:DD:01" + on_connect: + - logger.log: "Ethernet connected!" + on_disconnect: + - logger.log: "Ethernet disconnected!" diff --git a/tests/components/ethernet/common-dp83848.yaml b/tests/components/ethernet/common-dp83848.yaml index f9069c5fb9..809613c79d 100644 --- a/tests/components/ethernet/common-dp83848.yaml +++ b/tests/components/ethernet/common-dp83848.yaml @@ -13,3 +13,7 @@ ethernet: subnet: 255.255.255.0 domain: .local mac_address: "02:AA:BB:CC:DD:01" + on_connect: + - logger.log: "Ethernet connected!" + on_disconnect: + - logger.log: "Ethernet disconnected!" diff --git a/tests/components/ethernet/common-ip101.yaml b/tests/components/ethernet/common-ip101.yaml index cea7a5cc35..41716a7850 100644 --- a/tests/components/ethernet/common-ip101.yaml +++ b/tests/components/ethernet/common-ip101.yaml @@ -13,3 +13,7 @@ ethernet: subnet: 255.255.255.0 domain: .local mac_address: "02:AA:BB:CC:DD:01" + on_connect: + - logger.log: "Ethernet connected!" + on_disconnect: + - logger.log: "Ethernet disconnected!" diff --git a/tests/components/ethernet/common-jl1101.yaml b/tests/components/ethernet/common-jl1101.yaml index 7b0a2dfdc4..d70a576c81 100644 --- a/tests/components/ethernet/common-jl1101.yaml +++ b/tests/components/ethernet/common-jl1101.yaml @@ -13,3 +13,7 @@ ethernet: subnet: 255.255.255.0 domain: .local mac_address: "02:AA:BB:CC:DD:01" + on_connect: + - logger.log: "Ethernet connected!" + on_disconnect: + - logger.log: "Ethernet disconnected!" diff --git a/tests/components/ethernet/common-ksz8081.yaml b/tests/components/ethernet/common-ksz8081.yaml index 65541832c2..e2add8d370 100644 --- a/tests/components/ethernet/common-ksz8081.yaml +++ b/tests/components/ethernet/common-ksz8081.yaml @@ -13,3 +13,7 @@ ethernet: subnet: 255.255.255.0 domain: .local mac_address: "02:AA:BB:CC:DD:01" + on_connect: + - logger.log: "Ethernet connected!" + on_disconnect: + - logger.log: "Ethernet disconnected!" diff --git a/tests/components/ethernet/common-ksz8081rna.yaml b/tests/components/ethernet/common-ksz8081rna.yaml index f04cba15b2..1bb404f720 100644 --- a/tests/components/ethernet/common-ksz8081rna.yaml +++ b/tests/components/ethernet/common-ksz8081rna.yaml @@ -13,3 +13,7 @@ ethernet: subnet: 255.255.255.0 domain: .local mac_address: "02:AA:BB:CC:DD:01" + on_connect: + - logger.log: "Ethernet connected!" + on_disconnect: + - logger.log: "Ethernet disconnected!" diff --git a/tests/components/ethernet/common-lan8670.yaml b/tests/components/ethernet/common-lan8670.yaml index fb751ebd23..ae4953974c 100644 --- a/tests/components/ethernet/common-lan8670.yaml +++ b/tests/components/ethernet/common-lan8670.yaml @@ -12,3 +12,7 @@ ethernet: gateway: 192.168.178.1 subnet: 255.255.255.0 domain: .local + on_connect: + - logger.log: "Ethernet connected!" + on_disconnect: + - logger.log: "Ethernet disconnected!" diff --git a/tests/components/ethernet/common-lan8720.yaml b/tests/components/ethernet/common-lan8720.yaml index 838d57df28..742800fdf4 100644 --- a/tests/components/ethernet/common-lan8720.yaml +++ b/tests/components/ethernet/common-lan8720.yaml @@ -13,3 +13,7 @@ ethernet: subnet: 255.255.255.0 domain: .local mac_address: "02:AA:BB:CC:DD:01" + on_connect: + - logger.log: "Ethernet connected!" + on_disconnect: + - logger.log: "Ethernet disconnected!" diff --git a/tests/components/ethernet/common-openeth.yaml b/tests/components/ethernet/common-openeth.yaml index fbb7579598..26595dbc52 100644 --- a/tests/components/ethernet/common-openeth.yaml +++ b/tests/components/ethernet/common-openeth.yaml @@ -1,2 +1,6 @@ ethernet: type: OPENETH + on_connect: + - logger.log: "Ethernet connected!" + on_disconnect: + - logger.log: "Ethernet disconnected!" diff --git a/tests/components/ethernet/common-rtl8201.yaml b/tests/components/ethernet/common-rtl8201.yaml index 0e7cbe73c6..d5a60f6e98 100644 --- a/tests/components/ethernet/common-rtl8201.yaml +++ b/tests/components/ethernet/common-rtl8201.yaml @@ -13,3 +13,7 @@ ethernet: subnet: 255.255.255.0 domain: .local mac_address: "02:AA:BB:CC:DD:01" + on_connect: + - logger.log: "Ethernet connected!" + on_disconnect: + - logger.log: "Ethernet disconnected!" diff --git a/tests/components/ethernet/common-w5500.yaml b/tests/components/ethernet/common-w5500.yaml index b3e96f000d..1f8b8650dd 100644 --- a/tests/components/ethernet/common-w5500.yaml +++ b/tests/components/ethernet/common-w5500.yaml @@ -13,3 +13,7 @@ ethernet: subnet: 255.255.255.0 domain: .local mac_address: "02:AA:BB:CC:DD:01" + on_connect: + - logger.log: "Ethernet connected!" + on_disconnect: + - logger.log: "Ethernet disconnected!" diff --git a/tests/components/mipi_spi/test.esp8266-ard.yaml b/tests/components/mipi_spi/test.esp8266-ard.yaml new file mode 100644 index 0000000000..ef6197d852 --- /dev/null +++ b/tests/components/mipi_spi/test.esp8266-ard.yaml @@ -0,0 +1,10 @@ +substitutions: + dc_pin: GPIO15 + cs_pin: GPIO5 + enable_pin: GPIO4 + reset_pin: GPIO16 + +packages: + spi: !include ../../test_build_components/common/spi/esp8266-ard.yaml + +<<: !include common.yaml diff --git a/tests/components/pmsx003/common.yaml b/tests/components/pmsx003/common.yaml index 3c60995804..eaa3cbc3e9 100644 --- a/tests/components/pmsx003/common.yaml +++ b/tests/components/pmsx003/common.yaml @@ -8,11 +8,11 @@ sensor: pm_10_0: name: PM 10.0 Concentration pm_1_0_std: - name: PM 1.0 Standard Atmospher Concentration + name: PM 1.0 Standard Atmospheric Concentration pm_2_5_std: - name: PM 2.5 Standard Atmospher Concentration + name: PM 2.5 Standard Atmospheric Concentration pm_10_0_std: - name: PM 10.0 Standard Atmospher Concentration + name: PM 10.0 Standard Atmospheric Concentration pm_0_3um: name: Particulate Count >0.3um pm_0_5um: diff --git a/tests/test_build_components/common/uart_2400/esp32-ard.yaml b/tests/test_build_components/common/uart_2400/esp32-ard.yaml new file mode 100644 index 0000000000..e0b6571104 --- /dev/null +++ b/tests/test_build_components/common/uart_2400/esp32-ard.yaml @@ -0,0 +1,11 @@ +# Common UART configuration for ESP32 Arduino tests - 2400 baud + +substitutions: + tx_pin: GPIO17 + rx_pin: GPIO16 + +uart: + - id: uart_bus + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + baud_rate: 2400 diff --git a/tests/test_build_components/common/uart_2400/esp32-idf.yaml b/tests/test_build_components/common/uart_2400/esp32-idf.yaml new file mode 100644 index 0000000000..7bded8c91d --- /dev/null +++ b/tests/test_build_components/common/uart_2400/esp32-idf.yaml @@ -0,0 +1,11 @@ +# Common UART configuration for ESP32 IDF tests - 2400 baud + +substitutions: + tx_pin: GPIO17 + rx_pin: GPIO16 + +uart: + - id: uart_bus + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + baud_rate: 2400 diff --git a/tests/test_build_components/common/uart_2400/esp8266-ard.yaml b/tests/test_build_components/common/uart_2400/esp8266-ard.yaml new file mode 100644 index 0000000000..6c9a4a558d --- /dev/null +++ b/tests/test_build_components/common/uart_2400/esp8266-ard.yaml @@ -0,0 +1,11 @@ +# Common UART configuration for ESP8266 Arduino tests - 2400 baud + +substitutions: + tx_pin: GPIO4 + rx_pin: GPIO5 + +uart: + - id: uart_bus + tx_pin: ${tx_pin} + rx_pin: ${rx_pin} + baud_rate: 2400 diff --git a/tests/unit_tests/test_cpp_generator.py b/tests/unit_tests/test_cpp_generator.py index 2c9f760c8e..8755e6e2a1 100644 --- a/tests/unit_tests/test_cpp_generator.py +++ b/tests/unit_tests/test_cpp_generator.py @@ -347,3 +347,280 @@ class TestMockObj: assert isinstance(actual, cg.MockObj) assert actual.base == "foo.eek" assert actual.op == "." + + +class TestStatementFunction: + """Tests for the statement() function.""" + + def test_statement__expression_converted_to_statement(self): + """Test that expressions are converted to ExpressionStatement.""" + expr = cg.RawExpression("foo()") + result = cg.statement(expr) + + assert isinstance(result, cg.ExpressionStatement) + assert str(result) == "foo();" + + def test_statement__statement_unchanged(self): + """Test that statements are returned unchanged.""" + stmt = cg.RawStatement("foo()") + result = cg.statement(stmt) + + assert result is stmt + assert str(result) == "foo()" + + def test_statement__expression_statement_unchanged(self): + """Test that ExpressionStatement is returned unchanged.""" + stmt = cg.ExpressionStatement(42) + result = cg.statement(stmt) + + assert result is stmt + assert str(result) == "42;" + + def test_statement__line_comment_unchanged(self): + """Test that LineComment is returned unchanged.""" + stmt = cg.LineComment("This is a comment") + result = cg.statement(stmt) + + assert result is stmt + assert str(result) == "// This is a comment" + + +class TestLiteralFunction: + """Tests for the literal() function.""" + + def test_literal__creates_mockobj(self): + """Test that literal() creates a MockObj.""" + result = cg.literal("MY_CONSTANT") + + assert isinstance(result, cg.MockObj) + assert result.base == "MY_CONSTANT" + assert result.op == "" + + def test_literal__string_representation(self): + """Test that literal names appear unquoted in generated code.""" + result = cg.literal("nullptr") + + assert str(result) == "nullptr" + + def test_literal__can_be_used_in_expressions(self): + """Test that literals can be used as part of larger expressions.""" + null_lit = cg.literal("nullptr") + expr = cg.CallExpression(cg.RawExpression("my_func"), null_lit) + + assert str(expr) == "my_func(nullptr)" + + def test_literal__common_cpp_literals(self): + """Test common C++ literal values.""" + test_cases = [ + ("nullptr", "nullptr"), + ("true", "true"), + ("false", "false"), + ("NULL", "NULL"), + ("NAN", "NAN"), + ] + + for name, expected in test_cases: + result = cg.literal(name) + assert str(result) == expected + + +class TestLambdaConstructor: + """Tests for the Lambda class constructor in core/__init__.py.""" + + def test_lambda__from_string(self): + """Test Lambda constructor with string argument.""" + from esphome.core import Lambda + + lambda_obj = Lambda("return x + 1;") + + assert lambda_obj.value == "return x + 1;" + assert str(lambda_obj) == "return x + 1;" + + def test_lambda__from_expression(self): + """Test Lambda constructor with Expression argument.""" + from esphome.core import Lambda + + expr = cg.RawExpression("x + 1") + lambda_obj = Lambda(expr) + + # Expression should be converted to statement (with semicolon) + assert lambda_obj.value == "x + 1;" + + def test_lambda__from_lambda(self): + """Test Lambda constructor with another Lambda argument.""" + from esphome.core import Lambda + + original = Lambda("return x + 1;") + copy = Lambda(original) + + assert copy.value == original.value + assert copy.value == "return x + 1;" + + def test_lambda__parts_parsing(self): + """Test that Lambda correctly parses parts with id() references.""" + from esphome.core import Lambda + + lambda_obj = Lambda("return id(my_sensor).state;") + parts = lambda_obj.parts + + # Parts should be split by LAMBDA_PROG regex: text, id, op, text + assert len(parts) == 4 + assert parts[0] == "return " + assert parts[1] == "my_sensor" + assert parts[2] == "." + assert parts[3] == "state;" + + def test_lambda__requires_ids(self): + """Test that Lambda correctly extracts required IDs.""" + from esphome.core import ID, Lambda + + lambda_obj = Lambda("return id(sensor1).state + id(sensor2).value;") + ids = lambda_obj.requires_ids + + assert len(ids) == 2 + assert all(isinstance(id_obj, ID) for id_obj in ids) + assert ids[0].id == "sensor1" + assert ids[1].id == "sensor2" + + def test_lambda__no_ids(self): + """Test Lambda with no id() references.""" + from esphome.core import Lambda + + lambda_obj = Lambda("return 42;") + ids = lambda_obj.requires_ids + + assert len(ids) == 0 + + def test_lambda__comment_removal(self): + """Test that comments are removed when parsing parts.""" + from esphome.core import Lambda + + lambda_obj = Lambda("return id(sensor).state; // Get sensor state") + parts = lambda_obj.parts + + # Comment should be replaced with space, not affect parsing + assert "my_sensor" not in str(parts) + + def test_lambda__multiline_string(self): + """Test Lambda with multiline string.""" + from esphome.core import Lambda + + code = """if (id(sensor).state > 0) { + return true; +} +return false;""" + lambda_obj = Lambda(code) + + assert lambda_obj.value == code + assert "sensor" in [id_obj.id for id_obj in lambda_obj.requires_ids] + + +@pytest.mark.asyncio +class TestProcessLambda: + """Tests for the process_lambda() async function.""" + + async def test_process_lambda__none_value(self): + """Test that None returns None.""" + result = await cg.process_lambda(None, []) + + assert result is None + + async def test_process_lambda__with_expression(self): + """Test process_lambda with Expression argument.""" + + expr = cg.RawExpression("return x + 1") + result = await cg.process_lambda(expr, [(int, "x")]) + + assert isinstance(result, cg.LambdaExpression) + assert "x + 1" in str(result) + + async def test_process_lambda__simple_lambda_no_ids(self): + """Test process_lambda with simple Lambda without id() references.""" + from esphome.core import Lambda + + lambda_obj = Lambda("return x + 1;") + result = await cg.process_lambda(lambda_obj, [(int, "x")]) + + assert isinstance(result, cg.LambdaExpression) + # Should have parameter + lambda_str = str(result) + assert "int32_t x" in lambda_str + assert "return x + 1;" in lambda_str + + async def test_process_lambda__with_return_type(self): + """Test process_lambda with return type specified.""" + from esphome.core import Lambda + + lambda_obj = Lambda("return x > 0;") + result = await cg.process_lambda(lambda_obj, [(int, "x")], return_type=bool) + + assert isinstance(result, cg.LambdaExpression) + lambda_str = str(result) + assert "-> bool" in lambda_str + + async def test_process_lambda__with_capture(self): + """Test process_lambda with capture specified.""" + from esphome.core import Lambda + + lambda_obj = Lambda("return captured + x;") + result = await cg.process_lambda(lambda_obj, [(int, "x")], capture="captured") + + assert isinstance(result, cg.LambdaExpression) + lambda_str = str(result) + assert "[captured]" in lambda_str + + async def test_process_lambda__empty_capture(self): + """Test process_lambda with empty capture (stateless lambda).""" + from esphome.core import Lambda + + lambda_obj = Lambda("return x + 1;") + result = await cg.process_lambda(lambda_obj, [(int, "x")], capture="") + + assert isinstance(result, cg.LambdaExpression) + lambda_str = str(result) + assert "[]" in lambda_str + + async def test_process_lambda__no_parameters(self): + """Test process_lambda with no parameters.""" + from esphome.core import Lambda + + lambda_obj = Lambda("return 42;") + result = await cg.process_lambda(lambda_obj, []) + + assert isinstance(result, cg.LambdaExpression) + lambda_str = str(result) + # Should have empty parameter list + assert "()" in lambda_str + + async def test_process_lambda__multiple_parameters(self): + """Test process_lambda with multiple parameters.""" + from esphome.core import Lambda + + lambda_obj = Lambda("return x + y + z;") + result = await cg.process_lambda( + lambda_obj, [(int, "x"), (float, "y"), (bool, "z")] + ) + + assert isinstance(result, cg.LambdaExpression) + lambda_str = str(result) + assert "int32_t x" in lambda_str + assert "float y" in lambda_str + assert "bool z" in lambda_str + + async def test_process_lambda__parameter_validation(self): + """Test that malformed parameters raise assertion error.""" + from esphome.core import Lambda + + lambda_obj = Lambda("return x;") + + # Test invalid parameter format (not list of tuples) + with pytest.raises(AssertionError): + await cg.process_lambda(lambda_obj, "invalid") + + # Test invalid tuple format (not 2-element tuples) + with pytest.raises(AssertionError): + await cg.process_lambda(lambda_obj, [(int, "x", "extra")]) + + # Test invalid tuple format (single element) + with pytest.raises(AssertionError): + await cg.process_lambda(lambda_obj, [(int,)])