diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index fd9551b850..c97de6d5e5 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -148,6 +148,7 @@ BinarySensorCondition = binary_sensor_ns.class_("BinarySensorCondition", Conditi # Filters Filter = binary_sensor_ns.class_("Filter") +TimeoutFilter = binary_sensor_ns.class_("TimeoutFilter", Filter, cg.Component) DelayedOnOffFilter = binary_sensor_ns.class_("DelayedOnOffFilter", Filter, cg.Component) DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter, cg.Component) DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter, cg.Component) @@ -171,6 +172,19 @@ async def invert_filter_to_code(config, filter_id): return cg.new_Pvariable(filter_id) +@register_filter( + "timeout", + TimeoutFilter, + cv.templatable(cv.positive_time_period_milliseconds), +) +async def timeout_filter_to_code(config, filter_id): + var = cg.new_Pvariable(filter_id) + await cg.register_component(var, {}) + template_ = await cg.templatable(config, [], cg.uint32) + cg.add(var.set_timeout_value(template_)) + return var + + @register_filter( "delayed_on_off", DelayedOnOffFilter, diff --git a/esphome/components/binary_sensor/filter.cpp b/esphome/components/binary_sensor/filter.cpp index 41d0553b35..3567e9c72b 100644 --- a/esphome/components/binary_sensor/filter.cpp +++ b/esphome/components/binary_sensor/filter.cpp @@ -25,6 +25,12 @@ void Filter::input(bool value) { } } +void TimeoutFilter::input(bool value) { + this->set_timeout("timeout", this->timeout_delay_.value(), [this]() { this->parent_->invalidate_state(); }); + // we do not de-dup here otherwise changes from invalid to valid state will not be output + this->output(value); +} + optional DelayedOnOffFilter::new_value(bool value) { if (value) { this->set_timeout("ON_OFF", this->on_delay_.value(), [this]() { this->output(true); }); diff --git a/esphome/components/binary_sensor/filter.h b/esphome/components/binary_sensor/filter.h index 65838da49d..16f44aa5fe 100644 --- a/esphome/components/binary_sensor/filter.h +++ b/esphome/components/binary_sensor/filter.h @@ -16,7 +16,7 @@ class Filter { public: virtual optional new_value(bool value) = 0; - void input(bool value); + virtual void input(bool value); void output(bool value); @@ -28,6 +28,16 @@ class Filter { Deduplicator dedup_; }; +class TimeoutFilter : public Filter, public Component { + public: + optional new_value(bool value) override { return value; } + void input(bool value) override; + template void set_timeout_value(T timeout) { this->timeout_delay_ = timeout; } + + protected: + TemplatableValue timeout_delay_{}; +}; + class DelayedOnOffFilter : public Filter, public Component { public: optional new_value(bool value) override; diff --git a/esphome/components/esp32_ble/ble.cpp b/esphome/components/esp32_ble/ble.cpp index cf63ad34d7..b10d1fe10a 100644 --- a/esphome/components/esp32_ble/ble.cpp +++ b/esphome/components/esp32_ble/ble.cpp @@ -4,6 +4,7 @@ #include "ble_event_pool.h" #include "esphome/core/application.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #include @@ -516,13 +517,12 @@ void ESP32BLE::dump_config() { break; } ESP_LOGCONFIG(TAG, - "ESP32 BLE:\n" - " MAC address: %02X:%02X:%02X:%02X:%02X:%02X\n" + "BLE:\n" + " MAC address: %s\n" " IO Capability: %s", - mac_address[0], mac_address[1], mac_address[2], mac_address[3], mac_address[4], mac_address[5], - io_capability_s); + format_mac_address_pretty(mac_address).c_str(), io_capability_s); } else { - ESP_LOGCONFIG(TAG, "ESP32 BLE: bluetooth stack is not enabled"); + ESP_LOGCONFIG(TAG, "Bluetooth stack is not enabled"); } } diff --git a/esphome/components/esp32_hall/__init__.py b/esphome/components/esp32_hall/__init__.py new file mode 100644 index 0000000000..e69de29bb2 diff --git a/esphome/components/esp32_hall/sensor.py b/esphome/components/esp32_hall/sensor.py new file mode 100644 index 0000000000..b644389d3b --- /dev/null +++ b/esphome/components/esp32_hall/sensor.py @@ -0,0 +1,5 @@ +import esphome.config_validation as cv + +CONFIG_SCHEMA = cv.invalid( + "The esp32_hall component has been removed as of ESPHome 2025.7.0. See https://github.com/esphome/esphome/pull/9117 for details." +) diff --git a/esphome/components/ld2410/ld2410.cpp b/esphome/components/ld2410/ld2410.cpp index d7007ae0bd..b3c3649ceb 100644 --- a/esphome/components/ld2410/ld2410.cpp +++ b/esphome/components/ld2410/ld2410.cpp @@ -8,6 +8,8 @@ #include "esphome/components/sensor/sensor.h" #endif +#include "esphome/core/application.h" + #define highbyte(val) (uint8_t)((val) >> 8) #define lowbyte(val) (uint8_t)((val) &0xff) @@ -73,9 +75,9 @@ void LD2410Component::dump_config() { #endif this->read_all_info(); ESP_LOGCONFIG(TAG, - " Throttle_ : %ums\n" - " MAC Address : %s\n" - " Firmware Version : %s", + " Throttle: %ums\n" + " MAC address: %s\n" + " Firmware version: %s", this->throttle_, const_cast(this->mac_.c_str()), const_cast(this->version_.c_str())); } @@ -153,7 +155,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) { /* Reduce data update rate to prevent home assistant database size grow fast */ - int32_t current_millis = millis(); + int32_t current_millis = App.get_loop_component_start_time(); if (current_millis - last_periodic_millis_ < this->throttle_) return; last_periodic_millis_ = current_millis; @@ -299,21 +301,6 @@ const char MAC_FMT[] = "%02X:%02X:%02X:%02X:%02X:%02X"; const std::string UNKNOWN_MAC("unknown"); const std::string NO_MAC("08:05:04:03:02:01"); -std::string format_mac(uint8_t *buffer) { - std::string::size_type mac_size = 256; - std::string mac; - do { - mac.resize(mac_size + 1); - mac_size = std::snprintf(&mac[0], mac.size(), MAC_FMT, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], - buffer[15]); - } while (mac_size + 1 > mac.size()); - mac.resize(mac_size); - if (mac == NO_MAC) { - return UNKNOWN_MAC; - } - return mac; -} - #ifdef USE_NUMBER std::function set_number_value(number::Number *n, float value) { float normalized_value = value * 1.0; @@ -328,40 +315,40 @@ std::function set_number_value(number::Number *n, float value) { bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", buffer[COMMAND]); if (len < 10) { - ESP_LOGE(TAG, "Error with last command : incorrect length"); + ESP_LOGE(TAG, "Invalid length"); return true; } if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // check 4 frame start bytes - ESP_LOGE(TAG, "Error with last command : incorrect Header"); + ESP_LOGE(TAG, "Invalid header"); return true; } if (buffer[COMMAND_STATUS] != 0x01) { - ESP_LOGE(TAG, "Error with last command : status != 0x01"); + ESP_LOGE(TAG, "Invalid status"); return true; } if (this->two_byte_to_int_(buffer[8], buffer[9]) != 0x00) { - ESP_LOGE(TAG, "Error with last command , last buffer was: %u , %u", buffer[8], buffer[9]); + ESP_LOGE(TAG, "Invalid command: %u, %u", buffer[8], buffer[9]); return true; } switch (buffer[COMMAND]) { case lowbyte(CMD_ENABLE_CONF): - ESP_LOGV(TAG, "Handled Enable conf command"); + ESP_LOGV(TAG, "Enable conf"); break; case lowbyte(CMD_DISABLE_CONF): - ESP_LOGV(TAG, "Handled Disabled conf command"); + ESP_LOGV(TAG, "Disabled conf"); break; case lowbyte(CMD_SET_BAUD_RATE): - ESP_LOGV(TAG, "Handled baud rate change command"); + ESP_LOGV(TAG, "Baud rate change"); #ifdef USE_SELECT if (this->baud_rate_select_ != nullptr) { - ESP_LOGE(TAG, "Change baud rate component config to %s and reinstall", this->baud_rate_select_->state.c_str()); + ESP_LOGE(TAG, "Configure baud rate to %s and reinstall", this->baud_rate_select_->state.c_str()); } #endif break; case lowbyte(CMD_VERSION): this->version_ = format_version(buffer); - ESP_LOGV(TAG, "FW Version is: %s", const_cast(this->version_.c_str())); + ESP_LOGV(TAG, "Firmware version: %s", const_cast(this->version_.c_str())); #ifdef USE_TEXT_SENSOR if (this->version_text_sensor_ != nullptr) { this->version_text_sensor_->publish_state(this->version_); @@ -371,7 +358,7 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { case lowbyte(CMD_QUERY_DISTANCE_RESOLUTION): { std::string distance_resolution = DISTANCE_RESOLUTION_INT_TO_ENUM.at(this->two_byte_to_int_(buffer[10], buffer[11])); - ESP_LOGV(TAG, "Distance resolution is: %s", const_cast(distance_resolution.c_str())); + ESP_LOGV(TAG, "Distance resolution: %s", const_cast(distance_resolution.c_str())); #ifdef USE_SELECT if (this->distance_resolution_select_ != nullptr && this->distance_resolution_select_->state != distance_resolution) { @@ -383,9 +370,9 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { this->light_function_ = LIGHT_FUNCTION_INT_TO_ENUM.at(buffer[10]); this->light_threshold_ = buffer[11] * 1.0; this->out_pin_level_ = OUT_PIN_LEVEL_INT_TO_ENUM.at(buffer[12]); - ESP_LOGV(TAG, "Light function is: %s", const_cast(this->light_function_.c_str())); - ESP_LOGV(TAG, "Light threshold is: %f", this->light_threshold_); - ESP_LOGV(TAG, "Out pin level is: %s", const_cast(this->out_pin_level_.c_str())); + ESP_LOGV(TAG, "Light function: %s", const_cast(this->light_function_.c_str())); + ESP_LOGV(TAG, "Light threshold: %f", this->light_threshold_); + ESP_LOGV(TAG, "Out pin level: %s", const_cast(this->out_pin_level_.c_str())); #ifdef USE_SELECT if (this->light_function_select_ != nullptr && this->light_function_select_->state != this->light_function_) { this->light_function_select_->publish_state(this->light_function_); @@ -406,11 +393,11 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { if (len < 20) { return false; } - this->mac_ = format_mac(buffer); - ESP_LOGV(TAG, "MAC Address is: %s", const_cast(this->mac_.c_str())); + this->mac_ = format_mac_address_pretty(&buffer[10]); + ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str()); #ifdef USE_TEXT_SENSOR if (this->mac_text_sensor_ != nullptr) { - this->mac_text_sensor_->publish_state(this->mac_); + this->mac_text_sensor_->publish_state(this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_); } #endif #ifdef USE_SWITCH @@ -420,19 +407,19 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { #endif break; case lowbyte(CMD_GATE_SENS): - ESP_LOGV(TAG, "Handled sensitivity command"); + ESP_LOGV(TAG, "Sensitivity"); break; case lowbyte(CMD_BLUETOOTH): - ESP_LOGV(TAG, "Handled bluetooth command"); + ESP_LOGV(TAG, "Bluetooth"); break; case lowbyte(CMD_SET_DISTANCE_RESOLUTION): - ESP_LOGV(TAG, "Handled set distance resolution command"); + ESP_LOGV(TAG, "Set distance resolution"); break; case lowbyte(CMD_SET_LIGHT_CONTROL): - ESP_LOGV(TAG, "Handled set light control command"); + ESP_LOGV(TAG, "Set light control"); break; case lowbyte(CMD_BT_PASSWORD): - ESP_LOGV(TAG, "Handled set bluetooth password command"); + ESP_LOGV(TAG, "Set bluetooth password"); break; case lowbyte(CMD_QUERY): // Query parameters response { @@ -532,7 +519,7 @@ void LD2410Component::set_baud_rate(const std::string &state) { void LD2410Component::set_bluetooth_password(const std::string &password) { if (password.length() != 6) { - ESP_LOGE(TAG, "set_bluetooth_password(): invalid password length, must be exactly 6 chars '%s'", password.c_str()); + ESP_LOGE(TAG, "Password must be exactly 6 chars"); return; } this->set_config_mode_(true); @@ -544,7 +531,7 @@ void LD2410Component::set_bluetooth_password(const std::string &password) { void LD2410Component::set_engineering_mode(bool enable) { this->set_config_mode_(true); - last_engineering_mode_change_millis_ = millis(); + last_engineering_mode_change_millis_ = App.get_loop_component_start_time(); uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG; this->send_command_(cmd, nullptr, 0); this->set_config_mode_(false); diff --git a/esphome/components/ld2420/ld2420.cpp b/esphome/components/ld2420/ld2420.cpp index 5b3206bf12..62f1685598 100644 --- a/esphome/components/ld2420/ld2420.cpp +++ b/esphome/components/ld2420/ld2420.cpp @@ -1,4 +1,5 @@ #include "ld2420.h" +#include "esphome/core/application.h" #include "esphome/core/helpers.h" /* @@ -40,7 +41,7 @@ There are three documented parameters for modes: 00 04 = Energy output mode This mode outputs detailed signal energy values for each gate and the target distance. The data format consist of the following. - Header HH, Length LL, Persence PP, Distance DD, 16 Gate Energies EE, Footer FF + Header HH, Length LL, Presence PP, Distance DD, 16 Gate Energies EE, Footer FF HH HH HH HH LL LL PP DD DD EE EE .. 16x .. FF FF FF FF F4 F3 F2 F1 23 00 00 00 00 00 00 .. .. .. .. F8 F7 F6 F5 00 00 = debug output mode @@ -67,10 +68,10 @@ float LD2420Component::get_setup_priority() const { return setup_priority::BUS; void LD2420Component::dump_config() { ESP_LOGCONFIG(TAG, "LD2420:\n" - " Firmware Version : %7s\n" - "LD2420 Number:", + " Firmware version: %7s", this->ld2420_firmware_ver_); #ifdef USE_NUMBER + ESP_LOGCONFIG(TAG, "Number:"); LOG_NUMBER(TAG, " Gate Timeout:", this->gate_timeout_number_); LOG_NUMBER(TAG, " Gate Max Distance:", this->max_gate_distance_number_); LOG_NUMBER(TAG, " Gate Min Distance:", this->min_gate_distance_number_); @@ -86,10 +87,10 @@ void LD2420Component::dump_config() { LOG_BUTTON(TAG, " Factory Reset:", this->factory_reset_button_); LOG_BUTTON(TAG, " Restart Module:", this->restart_module_button_); #endif - ESP_LOGCONFIG(TAG, "LD2420 Select:"); + ESP_LOGCONFIG(TAG, "Select:"); LOG_SELECT(TAG, " Operating Mode", this->operating_selector_); - if (this->get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) { - ESP_LOGW(TAG, "LD2420 Firmware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_); + if (LD2420Component::get_firmware_int(this->ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) { + ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->ld2420_firmware_ver_); } } @@ -102,7 +103,7 @@ uint8_t LD2420Component::calc_checksum(void *data, size_t size) { return checksum; } -int LD2420Component::get_firmware_int_(const char *version_string) { +int LD2420Component::get_firmware_int(const char *version_string) { std::string version_str = version_string; if (version_str[0] == 'v') { version_str = version_str.substr(1); @@ -115,7 +116,7 @@ int LD2420Component::get_firmware_int_(const char *version_string) { void LD2420Component::setup() { ESP_LOGCONFIG(TAG, "Running setup"); if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { - ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections."); + ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); this->mark_failed(); return; } @@ -127,7 +128,7 @@ void LD2420Component::setup() { const char *pfw = this->ld2420_firmware_ver_; std::string fw_str(pfw); - for (auto &listener : listeners_) { + for (auto &listener : this->listeners_) { listener->on_fw_version(fw_str); } @@ -137,11 +138,11 @@ void LD2420Component::setup() { } memcpy(&this->new_config, &this->current_config, sizeof(this->current_config)); - if (get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) { + if (LD2420Component::get_firmware_int(this->ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) { this->set_operating_mode(OP_SIMPLE_MODE_STRING); this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING); this->set_mode_(CMD_SYSTEM_MODE_SIMPLE); - ESP_LOGW(TAG, "LD2420 Frimware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_); + ESP_LOGW(TAG, "Firmware version %s and older supports Simple Mode only", this->ld2420_firmware_ver_); } else { this->set_mode_(CMD_SYSTEM_MODE_ENERGY); this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING); @@ -151,18 +152,17 @@ void LD2420Component::setup() { #endif this->set_system_mode(this->system_mode_); this->set_config_mode(false); - ESP_LOGCONFIG(TAG, "LD2420 setup complete."); } void LD2420Component::apply_config_action() { const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config)); if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) { - ESP_LOGCONFIG(TAG, "No configuration change detected"); + ESP_LOGD(TAG, "No configuration change detected"); return; } - ESP_LOGCONFIG(TAG, "Reconfiguring LD2420"); + ESP_LOGD(TAG, "Reconfiguring"); if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { - ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections."); + ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); this->mark_failed(); return; } @@ -178,13 +178,12 @@ void LD2420Component::apply_config_action() { this->set_system_mode(this->system_mode_); this->set_config_mode(false); // Disable config mode to save new values in LD2420 nvm this->set_operating_mode(OP_NORMAL_MODE_STRING); - ESP_LOGCONFIG(TAG, "LD2420 reconfig complete."); } void LD2420Component::factory_reset_action() { - ESP_LOGCONFIG(TAG, "Setting factory defaults"); + ESP_LOGD(TAG, "Setting factory defaults"); if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) { - ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections."); + ESP_LOGE(TAG, ESP_LOG_MSG_COMM_FAIL); this->mark_failed(); return; } @@ -207,18 +206,16 @@ void LD2420Component::factory_reset_action() { this->init_gate_config_numbers(); this->refresh_gate_config_numbers(); #endif - ESP_LOGCONFIG(TAG, "LD2420 factory reset complete."); } void LD2420Component::restart_module_action() { - ESP_LOGCONFIG(TAG, "Restarting LD2420 module"); + ESP_LOGD(TAG, "Restarting"); this->send_module_restart(); this->set_timeout(250, [this]() { this->set_config_mode(true); - this->set_system_mode(system_mode_); + this->set_system_mode(this->system_mode_); this->set_config_mode(false); }); - ESP_LOGCONFIG(TAG, "LD2420 Restarted."); } void LD2420Component::revert_config_action() { @@ -226,18 +223,18 @@ void LD2420Component::revert_config_action() { #ifdef USE_NUMBER this->init_gate_config_numbers(); #endif - ESP_LOGCONFIG(TAG, "Reverted config number edits."); + ESP_LOGD(TAG, "Reverted config number edits"); } void LD2420Component::loop() { // If there is a active send command do not process it here, the send command call will handle it. - if (!get_cmd_active_()) { - if (!available()) + if (!this->get_cmd_active_()) { + if (!this->available()) return; static uint8_t buffer[2048]; static uint8_t rx_data; - while (available()) { - rx_data = read(); + while (this->available()) { + rx_data = this->read(); this->readline_(rx_data, buffer, sizeof(buffer)); } } @@ -292,7 +289,7 @@ void LD2420Component::report_gate_data() { void LD2420Component::set_operating_mode(const std::string &state) { // If unsupported firmware ignore mode select - if (get_firmware_int_(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) { + if (LD2420Component::get_firmware_int(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) { this->current_operating_mode = OP_MODE_TO_UINT.at(state); // Entering Auto Calibrate we need to clear the privoiuos data collection this->operating_selector_->publish_state(state); @@ -365,13 +362,13 @@ void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) { } // Resonable refresh rate for home assistant database size health - const int32_t current_millis = millis(); + const int32_t current_millis = App.get_loop_component_start_time(); if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS) return; this->last_periodic_millis = current_millis; for (auto &listener : this->listeners_) { - listener->on_distance(get_distance_()); - listener->on_presence(get_presence_()); + listener->on_distance(this->get_distance_()); + listener->on_presence(this->get_presence_()); listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0])); } @@ -392,9 +389,9 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) { char outbuf[bufsize]{0}; while (true) { if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') { - set_presence_(false); + this->set_presence_(false); } else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') { - set_presence_(true); + this->set_presence_(true); } if (inbuf[pos] >= '0' && inbuf[pos] <= '9') { if (index < bufsize - 1) { @@ -411,18 +408,18 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) { } outbuf[index] = '\0'; if (index > 1) - set_distance_(strtol(outbuf, &endptr, 10)); + this->set_distance_(strtol(outbuf, &endptr, 10)); - if (get_mode_() == CMD_SYSTEM_MODE_SIMPLE) { + if (this->get_mode_() == CMD_SYSTEM_MODE_SIMPLE) { // Resonable refresh rate for home assistant database size health - const int32_t current_millis = millis(); + const int32_t current_millis = App.get_loop_component_start_time(); if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS) return; this->last_normal_periodic_millis = current_millis; for (auto &listener : this->listeners_) - listener->on_distance(get_distance_()); + listener->on_distance(this->get_distance_()); for (auto &listener : this->listeners_) - listener->on_presence(get_presence_()); + listener->on_presence(this->get_presence_()); } } @@ -433,10 +430,10 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) { uint8_t data_element = 0; uint16_t data_pos = 0; if (this->cmd_reply_.length > CMD_MAX_BYTES) { - ESP_LOGW(TAG, "LD2420 reply - received command reply frame is corrupt, length exceeds %d bytes.", CMD_MAX_BYTES); + ESP_LOGW(TAG, "Reply frame too long"); return; } else if (this->cmd_reply_.length < 2) { - ESP_LOGW(TAG, "LD2420 reply - received command frame is corrupt, length is less than 2 bytes."); + ESP_LOGW(TAG, "Command frame too short"); return; } memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error)); @@ -447,13 +444,13 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) { this->cmd_reply_.ack = true; switch ((uint16_t) this->cmd_reply_.command) { case (CMD_ENABLE_CONF): - ESP_LOGD(TAG, "LD2420 reply - set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result); + ESP_LOGV(TAG, "Set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result); break; case (CMD_DISABLE_CONF): - ESP_LOGD(TAG, "LD2420 reply - set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result); + ESP_LOGV(TAG, "Set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result); break; case (CMD_READ_REGISTER): - ESP_LOGD(TAG, "LD2420 reply - read register: CMD = %2X %s", CMD_READ_REGISTER, result); + ESP_LOGV(TAG, "Read register: CMD = %2X %s", CMD_READ_REGISTER, result); // TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file data_pos = 0x0A; for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE * // NOLINT @@ -465,13 +462,13 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) { } break; case (CMD_WRITE_REGISTER): - ESP_LOGD(TAG, "LD2420 reply - write register: CMD = %2X %s", CMD_WRITE_REGISTER, result); + ESP_LOGV(TAG, "Write register: CMD = %2X %s", CMD_WRITE_REGISTER, result); break; case (CMD_WRITE_ABD_PARAM): - ESP_LOGD(TAG, "LD2420 reply - write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result); + ESP_LOGV(TAG, "Write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result); break; case (CMD_READ_ABD_PARAM): - ESP_LOGD(TAG, "LD2420 reply - read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result); + ESP_LOGV(TAG, "Read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result); data_pos = CMD_ABD_DATA_REPLY_START; for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE * // NOLINT ((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE)); @@ -483,11 +480,11 @@ void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) { } break; case (CMD_WRITE_SYS_PARAM): - ESP_LOGD(TAG, "LD2420 reply - set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result); + ESP_LOGV(TAG, "Set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result); break; case (CMD_READ_VERSION): memcpy(this->ld2420_firmware_ver_, &buffer[12], buffer[10]); - ESP_LOGD(TAG, "LD2420 reply - module firmware version: %7s %s", this->ld2420_firmware_ver_, result); + ESP_LOGV(TAG, "Firmware version: %7s %s", this->ld2420_firmware_ver_, result); break; default: break; @@ -533,7 +530,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) { } while (!this->cmd_reply_.ack) { - while (available()) { + while (this->available()) { this->readline_(read(), ack_buffer, sizeof(ack_buffer)); } delay_microseconds_safe(1450); @@ -548,7 +545,7 @@ int LD2420Component::send_cmd_from_array(CmdFrameT frame) { if (this->cmd_reply_.ack) retry = 0; if (this->cmd_reply_.error > 0) - handle_cmd_error(error); + this->handle_cmd_error(error); } return error; } @@ -563,7 +560,7 @@ uint8_t LD2420Component::set_config_mode(bool enable) { cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER); } cmd_frame.footer = CMD_FRAME_FOOTER; - ESP_LOGD(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command); + ESP_LOGV(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command); return this->send_cmd_from_array(cmd_frame); } @@ -576,7 +573,7 @@ void LD2420Component::ld2420_restart() { cmd_frame.header = CMD_FRAME_HEADER; cmd_frame.command = CMD_RESTART; cmd_frame.footer = CMD_FRAME_FOOTER; - ESP_LOGD(TAG, "Sending restart command: %2X", cmd_frame.command); + ESP_LOGV(TAG, "Sending restart command: %2X", cmd_frame.command); this->send_cmd_from_array(cmd_frame); } @@ -588,7 +585,7 @@ void LD2420Component::get_reg_value_(uint16_t reg) { cmd_frame.data[1] = reg; cmd_frame.data_length += 2; cmd_frame.footer = CMD_FRAME_FOOTER; - ESP_LOGD(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command); + ESP_LOGV(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command); this->send_cmd_from_array(cmd_frame); } @@ -602,11 +599,11 @@ void LD2420Component::set_reg_value(uint16_t reg, uint16_t value) { memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE)); cmd_frame.data_length += 2; cmd_frame.footer = CMD_FRAME_FOOTER; - ESP_LOGD(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value); + ESP_LOGV(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value); this->send_cmd_from_array(cmd_frame); } -void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGI(TAG, "Command failed: %s", ERR_MESSAGE[error]); } +void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGE(TAG, "Command failed: %s", ERR_MESSAGE[error]); } int LD2420Component::get_gate_threshold_(uint8_t gate) { uint8_t error; @@ -619,7 +616,7 @@ int LD2420Component::get_gate_threshold_(uint8_t gate) { memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate])); cmd_frame.data_length += 2; cmd_frame.footer = CMD_FRAME_FOOTER; - ESP_LOGD(TAG, "Sending read gate %d high/low theshold command: %2X", gate, cmd_frame.command); + ESP_LOGV(TAG, "Sending read gate %d high/low threshold command: %2X", gate, cmd_frame.command); error = this->send_cmd_from_array(cmd_frame); if (error == 0) { this->current_config.move_thresh[gate] = cmd_reply_.data[0]; @@ -644,7 +641,7 @@ int LD2420Component::get_min_max_distances_timeout_() { sizeof(CMD_TIMEOUT_REG)); // Register: global delay time cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG); cmd_frame.footer = CMD_FRAME_FOOTER; - ESP_LOGD(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command); + ESP_LOGV(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command); error = this->send_cmd_from_array(cmd_frame); if (error == 0) { this->current_config.min_gate = (uint16_t) cmd_reply_.data[0]; @@ -667,9 +664,9 @@ void LD2420Component::set_system_mode(uint16_t mode) { memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm)); cmd_frame.data_length += sizeof(unknown_parm); cmd_frame.footer = CMD_FRAME_FOOTER; - ESP_LOGD(TAG, "Sending write system mode command: %2X", cmd_frame.command); + ESP_LOGV(TAG, "Sending write system mode command: %2X", cmd_frame.command); if (this->send_cmd_from_array(cmd_frame) == 0) - set_mode_(mode); + this->set_mode_(mode); } void LD2420Component::get_firmware_version_() { @@ -679,7 +676,7 @@ void LD2420Component::get_firmware_version_() { cmd_frame.command = CMD_READ_VERSION; cmd_frame.footer = CMD_FRAME_FOOTER; - ESP_LOGD(TAG, "Sending read firmware version command: %2X", cmd_frame.command); + ESP_LOGV(TAG, "Sending read firmware version command: %2X", cmd_frame.command); this->send_cmd_from_array(cmd_frame); } @@ -712,7 +709,7 @@ void LD2420Component::set_min_max_distances_timeout(uint32_t max_gate_distance, cmd_frame.data_length += sizeof(timeout); cmd_frame.footer = CMD_FRAME_FOOTER; - ESP_LOGD(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command); + ESP_LOGV(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command); this->send_cmd_from_array(cmd_frame); } @@ -738,7 +735,7 @@ void LD2420Component::set_gate_threshold(uint8_t gate) { sizeof(this->new_config.still_thresh[gate])); cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]); cmd_frame.footer = CMD_FRAME_FOOTER; - ESP_LOGD(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command); + ESP_LOGV(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command); this->send_cmd_from_array(cmd_frame); } diff --git a/esphome/components/ld2420/ld2420.h b/esphome/components/ld2420/ld2420.h index 2b50c7a1d4..5e011100e6 100644 --- a/esphome/components/ld2420/ld2420.h +++ b/esphome/components/ld2420/ld2420.h @@ -179,7 +179,7 @@ class LD2420Component : public Component, public uart::UARTDevice { void set_operating_mode(const std::string &state); void auto_calibrate_sensitivity(); void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number); - uint8_t calc_checksum(void *data, size_t size); + static uint8_t calc_checksum(void *data, size_t size); RegConfigT current_config; RegConfigT new_config; @@ -222,7 +222,7 @@ class LD2420Component : public Component, public uart::UARTDevice { volatile bool ack; }; - int get_firmware_int_(const char *version_string); + static int get_firmware_int(const char *version_string); void get_firmware_version_(); int get_gate_threshold_(uint8_t gate); void get_reg_value_(uint16_t reg); diff --git a/esphome/components/ld2450/ld2450.cpp b/esphome/components/ld2450/ld2450.cpp index 519e4d89a3..718c853d22 100644 --- a/esphome/components/ld2450/ld2450.cpp +++ b/esphome/components/ld2450/ld2450.cpp @@ -6,7 +6,9 @@ #ifdef USE_SENSOR #include "esphome/components/sensor/sensor.h" #endif +#include "esphome/core/application.h" #include "esphome/core/component.h" +#include "esphome/core/helpers.h" #define highbyte(val) (uint8_t)((val) >> 8) #define lowbyte(val) (uint8_t)((val) &0xff) @@ -96,11 +98,6 @@ static inline std::string get_direction(int16_t speed) { return STATIONARY; } -static inline std::string format_mac(uint8_t *buffer) { - return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], - buffer[15]); -} - static inline std::string format_version(uint8_t *buffer) { return str_sprintf("%u.%02X.%02X%02X%02X%02X", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], buffer[14]); @@ -120,7 +117,7 @@ void LD2450Component::setup() { } void LD2450Component::dump_config() { - ESP_LOGCONFIG(TAG, "HLK-LD2450 Human motion tracking radar module:"); + ESP_LOGCONFIG(TAG, "LD2450:"); #ifdef USE_BINARY_SENSOR LOG_BINARY_SENSOR(" ", "TargetBinarySensor", this->target_binary_sensor_); LOG_BINARY_SENSOR(" ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_); @@ -189,9 +186,9 @@ void LD2450Component::dump_config() { LOG_NUMBER(" ", "PresenceTimeoutNumber", this->presence_timeout_number_); #endif ESP_LOGCONFIG(TAG, - " Throttle : %ums\n" - " MAC Address : %s\n" - " Firmware version : %s", + " Throttle: %ums\n" + " MAC Address: %s\n" + " Firmware version: %s", this->throttle_, const_cast(this->mac_.c_str()), const_cast(this->version_.c_str())); } @@ -266,8 +263,7 @@ bool LD2450Component::get_timeout_status_(uint32_t check_millis) { if (this->timeout_ == 0) { this->timeout_ = ld2450::convert_seconds_to_ms(DEFAULT_PRESENCE_TIMEOUT); } - auto current_millis = millis(); - return current_millis - check_millis >= this->timeout_; + return App.get_loop_component_start_time() - check_millis >= this->timeout_; } // Extract, store and publish zone details LD2450 buffer @@ -354,25 +350,24 @@ void LD2450Component::send_command_(uint8_t command, const uint8_t *command_valu // Header Target 1 Target 2 Target 3 End void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { if (len < 29) { // header (4 bytes) + 8 x 3 target data + footer (2 bytes) - ESP_LOGE(TAG, "Periodic data: invalid message length"); + ESP_LOGE(TAG, "Invalid message length"); return; } if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) { // header - ESP_LOGE(TAG, "Periodic data: invalid message header"); + ESP_LOGE(TAG, "Invalid message header"); return; } if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) { // footer - ESP_LOGE(TAG, "Periodic data: invalid message footer"); + ESP_LOGE(TAG, "Invalid message footer"); return; } - auto current_millis = millis(); - if (current_millis - this->last_periodic_millis_ < this->throttle_) { + if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) { ESP_LOGV(TAG, "Throttling: %d", this->throttle_); return; } - this->last_periodic_millis_ = current_millis; + this->last_periodic_millis_ = App.get_loop_component_start_time(); int16_t target_count = 0; int16_t still_target_count = 0; @@ -555,13 +550,13 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { #ifdef USE_SENSOR // For presence timeout check if (target_count > 0) { - this->presence_millis_ = millis(); + this->presence_millis_ = App.get_loop_component_start_time(); } if (moving_target_count > 0) { - this->moving_presence_millis_ = millis(); + this->moving_presence_millis_ = App.get_loop_component_start_time(); } if (still_target_count > 0) { - this->still_presence_millis_ = millis(); + this->still_presence_millis_ = App.get_loop_component_start_time(); } #endif } @@ -569,31 +564,31 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { ESP_LOGV(TAG, "Handling ack data for command %02X", buffer[COMMAND]); if (len < 10) { - ESP_LOGE(TAG, "Ack data: invalid length"); + ESP_LOGE(TAG, "Invalid ack length"); return true; } if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // frame header - ESP_LOGE(TAG, "Ack data: invalid header (command %02X)", buffer[COMMAND]); + ESP_LOGE(TAG, "Invalid ack header (command %02X)", buffer[COMMAND]); return true; } if (buffer[COMMAND_STATUS] != 0x01) { - ESP_LOGE(TAG, "Ack data: invalid status"); + ESP_LOGE(TAG, "Invalid ack status"); return true; } if (buffer[8] || buffer[9]) { - ESP_LOGE(TAG, "Ack data: last buffer was %u, %u", buffer[8], buffer[9]); + ESP_LOGE(TAG, "Last buffer was %u, %u", buffer[8], buffer[9]); return true; } switch (buffer[COMMAND]) { case lowbyte(CMD_ENABLE_CONF): - ESP_LOGV(TAG, "Got enable conf command"); + ESP_LOGV(TAG, "Enable conf command"); break; case lowbyte(CMD_DISABLE_CONF): - ESP_LOGV(TAG, "Got disable conf command"); + ESP_LOGV(TAG, "Disable conf command"); break; case lowbyte(CMD_SET_BAUD_RATE): - ESP_LOGV(TAG, "Got baud rate change command"); + ESP_LOGV(TAG, "Baud rate change command"); #ifdef USE_SELECT if (this->baud_rate_select_ != nullptr) { ESP_LOGV(TAG, "Change baud rate to %s", this->baud_rate_select_->state.c_str()); @@ -613,7 +608,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { if (len < 20) { return false; } - this->mac_ = ld2450::format_mac(buffer); + this->mac_ = format_mac_address_pretty(&buffer[10]); ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str()); #ifdef USE_TEXT_SENSOR if (this->mac_text_sensor_ != nullptr) { @@ -622,15 +617,15 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { #endif #ifdef USE_SWITCH if (this->bluetooth_switch_ != nullptr) { - this->bluetooth_switch_->publish_state(this->mac_ != NO_MAC); + this->bluetooth_switch_->publish_state(this->mac_ != UNKNOWN_MAC); } #endif break; case lowbyte(CMD_BLUETOOTH): - ESP_LOGV(TAG, "Got Bluetooth command"); + ESP_LOGV(TAG, "Bluetooth command"); break; case lowbyte(CMD_SINGLE_TARGET_MODE): - ESP_LOGV(TAG, "Got single target conf command"); + ESP_LOGV(TAG, "Single target conf command"); #ifdef USE_SWITCH if (this->multi_target_switch_ != nullptr) { this->multi_target_switch_->publish_state(false); @@ -638,7 +633,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { #endif break; case lowbyte(CMD_MULTI_TARGET_MODE): - ESP_LOGV(TAG, "Got multi target conf command"); + ESP_LOGV(TAG, "Multi target conf command"); #ifdef USE_SWITCH if (this->multi_target_switch_ != nullptr) { this->multi_target_switch_->publish_state(true); @@ -646,7 +641,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { #endif break; case lowbyte(CMD_QUERY_TARGET_MODE): - ESP_LOGV(TAG, "Got query target tracking mode command"); + ESP_LOGV(TAG, "Query target tracking mode command"); #ifdef USE_SWITCH if (this->multi_target_switch_ != nullptr) { this->multi_target_switch_->publish_state(buffer[10] == 0x02); @@ -654,7 +649,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { #endif break; case lowbyte(CMD_QUERY_ZONE): - ESP_LOGV(TAG, "Got query zone conf command"); + ESP_LOGV(TAG, "Query zone conf command"); this->zone_type_ = std::stoi(std::to_string(buffer[10]), nullptr, 16); this->publish_zone_type(); #ifdef USE_SELECT @@ -674,7 +669,7 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { this->process_zone_(buffer); break; case lowbyte(CMD_SET_ZONE): - ESP_LOGV(TAG, "Got set zone conf command"); + ESP_LOGV(TAG, "Set zone conf command"); this->query_zone_info(); break; default: diff --git a/esphome/components/modbus/modbus.h b/esphome/components/modbus/modbus.h index 4a78ed4aab..aebdbccc78 100644 --- a/esphome/components/modbus/modbus.h +++ b/esphome/components/modbus/modbus.h @@ -64,6 +64,14 @@ class ModbusDevice { this->parent_->send(this->address_, function, start_address, number_of_entities, payload_len, payload); } void send_raw(const std::vector &payload) { this->parent_->send_raw(payload); } + void send_error(uint8_t function_code, uint8_t exception_code) { + std::vector error_response; + error_response.reserve(3); + error_response.push_back(this->address_); + error_response.push_back(function_code | 0x80); + error_response.push_back(exception_code); + this->send_raw(error_response); + } // If more than one device is connected block sending a new command before a response is received bool waiting_for_response() { return parent_->waiting_for_response != 0; } diff --git a/esphome/components/modbus_controller/__init__.py b/esphome/components/modbus_controller/__init__.py index 61b60498d0..8079b824b0 100644 --- a/esphome/components/modbus_controller/__init__.py +++ b/esphome/components/modbus_controller/__init__.py @@ -112,6 +112,22 @@ TYPE_REGISTER_MAP = { "FP32_R": 2, } +CPP_TYPE_REGISTER_MAP = { + "RAW": cg.uint16, + "U_WORD": cg.uint16, + "S_WORD": cg.int16, + "U_DWORD": cg.uint32, + "U_DWORD_R": cg.uint32, + "S_DWORD": cg.int32, + "S_DWORD_R": cg.int32, + "U_QWORD": cg.uint64, + "U_QWORD_R": cg.uint64, + "S_QWORD": cg.int64, + "S_QWORD_R": cg.int64, + "FP32": cg.float_, + "FP32_R": cg.float_, +} + ModbusCommandSentTrigger = modbus_controller_ns.class_( "ModbusCommandSentTrigger", automation.Trigger.template(cg.int_, cg.int_) ) @@ -285,21 +301,24 @@ async def to_code(config): cg.add(var.set_offline_skip_updates(config[CONF_OFFLINE_SKIP_UPDATES])) if CONF_SERVER_REGISTERS in config: for server_register in config[CONF_SERVER_REGISTERS]: + server_register_var = cg.new_Pvariable( + server_register[CONF_ID], + server_register[CONF_ADDRESS], + server_register[CONF_VALUE_TYPE], + TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]], + ) + cpp_type = CPP_TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]] cg.add( - var.add_server_register( - cg.new_Pvariable( - server_register[CONF_ID], - server_register[CONF_ADDRESS], - server_register[CONF_VALUE_TYPE], - TYPE_REGISTER_MAP[server_register[CONF_VALUE_TYPE]], - await cg.process_lambda( - server_register[CONF_READ_LAMBDA], - [], - return_type=cg.float_, - ), - ) + server_register_var.set_read_lambda( + cg.TemplateArguments(cpp_type), + await cg.process_lambda( + server_register[CONF_READ_LAMBDA], + [(cg.uint16, "address")], + return_type=cpp_type, + ), ) ) + cg.add(var.add_server_register(server_register_var)) await register_modbus_device(var, config) for conf in config.get(CONF_ON_COMMAND_SENT, []): trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var) diff --git a/esphome/components/modbus_controller/modbus_controller.cpp b/esphome/components/modbus_controller/modbus_controller.cpp index 48ff868087..81e9ccf0a6 100644 --- a/esphome/components/modbus_controller/modbus_controller.cpp +++ b/esphome/components/modbus_controller/modbus_controller.cpp @@ -117,12 +117,17 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t bool found = false; for (auto *server_register : this->server_registers_) { if (server_register->address == current_address) { - float value = server_register->read_lambda(); + if (!server_register->read_lambda) { + break; + } + int64_t value = server_register->read_lambda(); + ESP_LOGD(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %s.", + server_register->address, static_cast(server_register->value_type), + server_register->register_count, server_register->format_value(value).c_str()); - ESP_LOGD(TAG, "Matched register. Address: 0x%02X. Value type: %zu. Register count: %u. Value: %0.1f.", - server_register->address, static_cast(server_register->value_type), - server_register->register_count, value); - std::vector payload = float_to_payload(value, server_register->value_type); + std::vector payload; + payload.reserve(server_register->register_count * 2); + number_to_payload(payload, value, server_register->value_type); sixteen_bit_response.insert(sixteen_bit_response.end(), payload.cbegin(), payload.cend()); current_address += server_register->register_count; found = true; @@ -132,11 +137,7 @@ void ModbusController::on_modbus_read_registers(uint8_t function_code, uint16_t if (!found) { ESP_LOGW(TAG, "Could not match any register to address %02X. Sending exception response.", current_address); - std::vector error_response; - error_response.push_back(this->address_); - error_response.push_back(0x81); - error_response.push_back(0x02); - this->send_raw(error_response); + send_error(function_code, 0x02); return; } } diff --git a/esphome/components/modbus_controller/modbus_controller.h b/esphome/components/modbus_controller/modbus_controller.h index dfd52e44bc..11d27c4025 100644 --- a/esphome/components/modbus_controller/modbus_controller.h +++ b/esphome/components/modbus_controller/modbus_controller.h @@ -63,6 +63,10 @@ enum class SensorValueType : uint8_t { FP32_R = 0xD }; +inline bool value_type_is_float(SensorValueType v) { + return v == SensorValueType::FP32 || v == SensorValueType::FP32_R; +} + inline ModbusFunctionCode modbus_register_read_function(ModbusRegisterType reg_type) { switch (reg_type) { case ModbusRegisterType::COIL: @@ -253,18 +257,53 @@ class SensorItem { }; class ServerRegister { + using ReadLambda = std::function; + public: - ServerRegister(uint16_t address, SensorValueType value_type, uint8_t register_count, - std::function read_lambda) { + ServerRegister(uint16_t address, SensorValueType value_type, uint8_t register_count) { this->address = address; this->value_type = value_type; this->register_count = register_count; - this->read_lambda = std::move(read_lambda); } + + template void set_read_lambda(const std::function &&user_read_lambda) { + this->read_lambda = [this, user_read_lambda]() -> int64_t { + T user_value = user_read_lambda(this->address); + if constexpr (std::is_same_v) { + return bit_cast(user_value); + } else { + return static_cast(user_value); + } + }; + } + + // Formats a raw value into a string representation based on the value type for debugging + std::string format_value(int64_t value) const { + switch (this->value_type) { + case SensorValueType::U_WORD: + case SensorValueType::U_DWORD: + case SensorValueType::U_DWORD_R: + case SensorValueType::U_QWORD: + case SensorValueType::U_QWORD_R: + return std::to_string(static_cast(value)); + case SensorValueType::S_WORD: + case SensorValueType::S_DWORD: + case SensorValueType::S_DWORD_R: + case SensorValueType::S_QWORD: + case SensorValueType::S_QWORD_R: + return std::to_string(value); + case SensorValueType::FP32_R: + case SensorValueType::FP32: + return str_sprintf("%.1f", bit_cast(static_cast(value))); + default: + return std::to_string(value); + } + } + uint16_t address{0}; SensorValueType value_type{SensorValueType::RAW}; uint8_t register_count{0}; - std::function read_lambda; + ReadLambda read_lambda; }; // ModbusController::create_register_ranges_ tries to optimize register range @@ -444,7 +483,7 @@ class ModbusController : public PollingComponent, public modbus::ModbusDevice { void on_modbus_data(const std::vector &data) override; /// called when a modbus error response was received void on_modbus_error(uint8_t function_code, uint8_t exception_code) override; - /// called when a modbus request (function code 3 or 4) was parsed without errors + /// called when a modbus request (function code 0x03 or 0x04) was parsed without errors void on_modbus_read_registers(uint8_t function_code, uint16_t start_address, uint16_t number_of_registers) final; /// default delegate called by process_modbus_data when a response has retrieved from the incoming queue void on_register_data(ModbusRegisterType register_type, uint16_t start_address, const std::vector &data); @@ -529,7 +568,7 @@ inline float payload_to_float(const std::vector &data, const SensorItem int64_t number = payload_to_number(data, item.sensor_value_type, item.offset, item.bitmask); float float_value; - if (item.sensor_value_type == SensorValueType::FP32 || item.sensor_value_type == SensorValueType::FP32_R) { + if (value_type_is_float(item.sensor_value_type)) { float_value = bit_cast(static_cast(number)); } else { float_value = static_cast(number); @@ -541,7 +580,7 @@ inline float payload_to_float(const std::vector &data, const SensorItem inline std::vector float_to_payload(float value, SensorValueType value_type) { int64_t val; - if (value_type == SensorValueType::FP32 || value_type == SensorValueType::FP32_R) { + if (value_type_is_float(value_type)) { val = bit_cast(value); } else { val = llroundf(value); diff --git a/esphome/components/mqtt/mqtt_backend.h b/esphome/components/mqtt/mqtt_backend.h index 3962c40a42..0c1720ec34 100644 --- a/esphome/components/mqtt/mqtt_backend.h +++ b/esphome/components/mqtt/mqtt_backend.h @@ -17,7 +17,8 @@ enum class MQTTClientDisconnectReason : int8_t { MQTT_MALFORMED_CREDENTIALS = 4, MQTT_NOT_AUTHORIZED = 5, ESP8266_NOT_ENOUGH_SPACE = 6, - TLS_BAD_FINGERPRINT = 7 + TLS_BAD_FINGERPRINT = 7, + DNS_RESOLVE_ERROR = 8 }; /// internal struct for MQTT messages. diff --git a/esphome/components/mqtt/mqtt_client.cpp b/esphome/components/mqtt/mqtt_client.cpp index 3ba1ac6077..20e0b4a499 100644 --- a/esphome/components/mqtt/mqtt_client.cpp +++ b/esphome/components/mqtt/mqtt_client.cpp @@ -229,6 +229,8 @@ void MQTTClientComponent::check_dnslookup_() { if (this->dns_resolve_error_) { ESP_LOGW(TAG, "Couldn't resolve IP address for '%s'", this->credentials_.address.c_str()); this->state_ = MQTT_CLIENT_DISCONNECTED; + this->disconnect_reason_ = MQTTClientDisconnectReason::DNS_RESOLVE_ERROR; + this->on_disconnect_.call(MQTTClientDisconnectReason::DNS_RESOLVE_ERROR); return; } @@ -698,7 +700,9 @@ void MQTTClientComponent::set_on_connect(mqtt_on_connect_callback_t &&callback) } void MQTTClientComponent::set_on_disconnect(mqtt_on_disconnect_callback_t &&callback) { + auto callback_copy = callback; this->mqtt_backend_.set_on_disconnect(std::forward(callback)); + this->on_disconnect_.add(std::move(callback_copy)); } #if ASYNC_TCP_SSL_ENABLED diff --git a/esphome/components/mqtt/mqtt_client.h b/esphome/components/mqtt/mqtt_client.h index a95b122383..325ca56f4b 100644 --- a/esphome/components/mqtt/mqtt_client.h +++ b/esphome/components/mqtt/mqtt_client.h @@ -8,6 +8,7 @@ #include "esphome/components/network/ip_address.h" #include "esphome/core/automation.h" #include "esphome/core/component.h" +#include "esphome/core/helpers.h" #include "esphome/core/log.h" #if defined(USE_ESP32) #include "mqtt_backend_esp32.h" @@ -334,6 +335,7 @@ class MQTTClientComponent : public Component { uint32_t connect_begin_; uint32_t last_connected_{0}; optional disconnect_reason_{}; + CallbackManager on_disconnect_; bool publish_nan_as_none_{false}; bool wait_for_connection_{false}; diff --git a/esphome/components/sensor/sensor.cpp b/esphome/components/sensor/sensor.cpp index 6d6cff0400..7dab63b026 100644 --- a/esphome/components/sensor/sensor.cpp +++ b/esphome/components/sensor/sensor.cpp @@ -23,16 +23,22 @@ std::string state_class_to_string(StateClass state_class) { Sensor::Sensor() : state(NAN), raw_state(NAN) {} int8_t Sensor::get_accuracy_decimals() { - if (this->accuracy_decimals_.has_value()) - return *this->accuracy_decimals_; + if (this->sensor_flags_.has_accuracy_override) + return this->accuracy_decimals_; return 0; } -void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { this->accuracy_decimals_ = accuracy_decimals; } +void Sensor::set_accuracy_decimals(int8_t accuracy_decimals) { + this->accuracy_decimals_ = accuracy_decimals; + this->sensor_flags_.has_accuracy_override = true; +} -void Sensor::set_state_class(StateClass state_class) { this->state_class_ = state_class; } +void Sensor::set_state_class(StateClass state_class) { + this->state_class_ = state_class; + this->sensor_flags_.has_state_class_override = true; +} StateClass Sensor::get_state_class() { - if (this->state_class_.has_value()) - return *this->state_class_; + if (this->sensor_flags_.has_state_class_override) + return this->state_class_; return StateClass::STATE_CLASS_NONE; } diff --git a/esphome/components/sensor/sensor.h b/esphome/components/sensor/sensor.h index 456e876497..3fb6e5522b 100644 --- a/esphome/components/sensor/sensor.h +++ b/esphome/components/sensor/sensor.h @@ -80,9 +80,9 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa * state changes to the database when they are published, even if the state is the * same as before. */ - bool get_force_update() const { return force_update_; } + bool get_force_update() const { return sensor_flags_.force_update; } /// Set force update mode. - void set_force_update(bool force_update) { force_update_ = force_update; } + void set_force_update(bool force_update) { sensor_flags_.force_update = force_update; } /// Add a filter to the filter chain. Will be appended to the back. void add_filter(Filter *filter); @@ -155,9 +155,17 @@ class Sensor : public EntityBase, public EntityBase_DeviceClass, public EntityBa Filter *filter_list_{nullptr}; ///< Store all active filters. - optional accuracy_decimals_; ///< Accuracy in decimals override - optional state_class_{STATE_CLASS_NONE}; ///< State class override - bool force_update_{false}; ///< Force update mode + // Group small members together to avoid padding + int8_t accuracy_decimals_{-1}; ///< Accuracy in decimals (-1 = not set) + StateClass state_class_{STATE_CLASS_NONE}; ///< State class (STATE_CLASS_NONE = not set) + + // Bit-packed flags for sensor-specific settings + struct SensorFlags { + uint8_t has_accuracy_override : 1; + uint8_t has_state_class_override : 1; + uint8_t force_update : 1; + uint8_t reserved : 5; // Reserved for future use + } sensor_flags_{}; }; } // namespace sensor diff --git a/esphome/components/wifi/wifi_component.cpp b/esphome/components/wifi/wifi_component.cpp index 51ae1c9f8e..d717b68340 100644 --- a/esphome/components/wifi/wifi_component.cpp +++ b/esphome/components/wifi/wifi_component.cpp @@ -741,11 +741,6 @@ void WiFiComponent::set_power_save_mode(WiFiPowerSaveMode power_save) { this->po void WiFiComponent::set_passive_scan(bool passive) { this->passive_scan_ = passive; } -std::string WiFiComponent::format_mac_addr(const uint8_t *mac) { - char buf[20]; - sprintf(buf, "%02X:%02X:%02X:%02X:%02X:%02X", mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); - return buf; -} bool WiFiComponent::is_captive_portal_active_() { #ifdef USE_CAPTIVE_PORTAL return captive_portal::global_captive_portal != nullptr && captive_portal::global_captive_portal->is_active(); diff --git a/esphome/components/wifi/wifi_component.h b/esphome/components/wifi/wifi_component.h index 982007e47f..efd43077d1 100644 --- a/esphome/components/wifi/wifi_component.h +++ b/esphome/components/wifi/wifi_component.h @@ -321,8 +321,6 @@ class WiFiComponent : public Component { int32_t get_wifi_channel(); protected: - static std::string format_mac_addr(const uint8_t mac[6]); - #ifdef USE_WIFI_AP void setup_ap_config_(); #endif // USE_WIFI_AP diff --git a/esphome/components/wifi/wifi_component_esp32_arduino.cpp b/esphome/components/wifi/wifi_component_esp32_arduino.cpp index 3fc2c009db..a7877eb90b 100644 --- a/esphome/components/wifi/wifi_component_esp32_arduino.cpp +++ b/esphome/components/wifi/wifi_component_esp32_arduino.cpp @@ -550,7 +550,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ memcpy(buf, it.ssid, it.ssid_len); buf[it.ssid_len] = '\0'; ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, - format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); + format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); #if USE_NETWORK_IPV6 this->set_timeout(100, [] { WiFi.enableIPv6(); }); #endif /* USE_NETWORK_IPV6 */ @@ -566,7 +566,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); } else { ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, - format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); + format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); } uint8_t reason = it.reason; @@ -636,13 +636,13 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: { auto it = info.wifi_sta_connected; auto &mac = it.bssid; - ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(mac).c_str()); + ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(mac).c_str()); break; } case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: { auto it = info.wifi_sta_disconnected; auto &mac = it.bssid; - ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(mac).c_str()); + ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(mac).c_str()); break; } case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: { @@ -651,7 +651,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: { auto it = info.wifi_ap_probereqrecved; - ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi); + ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi); break; } default: diff --git a/esphome/components/wifi/wifi_component_esp8266.cpp b/esphome/components/wifi/wifi_component_esp8266.cpp index 594bc79e54..ae1daed8b5 100644 --- a/esphome/components/wifi/wifi_component_esp8266.cpp +++ b/esphome/components/wifi/wifi_component_esp8266.cpp @@ -496,7 +496,8 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { char buf[33]; memcpy(buf, it.ssid, it.ssid_len); buf[it.ssid_len] = '\0'; - ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_addr(it.bssid).c_str(), it.channel); + ESP_LOGV(TAG, "Connected ssid='%s' bssid=%s channel=%u", buf, format_mac_address_pretty(it.bssid).c_str(), + it.channel); s_sta_connected = true; break; } @@ -510,7 +511,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { s_sta_connect_not_found = true; } else { ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, - format_mac_addr(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason))); + format_mac_address_pretty(it.bssid).c_str(), LOG_STR_ARG(get_disconnect_reason_str(it.reason))); s_sta_connect_error = true; } s_sta_connected = false; @@ -545,17 +546,17 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { } case EVENT_SOFTAPMODE_STACONNECTED: { auto it = event->event_info.sta_connected; - ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", format_mac_addr(it.mac).c_str(), it.aid); + ESP_LOGV(TAG, "AP client connected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid); break; } case EVENT_SOFTAPMODE_STADISCONNECTED: { auto it = event->event_info.sta_disconnected; - ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", format_mac_addr(it.mac).c_str(), it.aid); + ESP_LOGV(TAG, "AP client disconnected MAC=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), it.aid); break; } case EVENT_SOFTAPMODE_PROBEREQRECVED: { auto it = event->event_info.ap_probereqrecved; - ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi); + ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi); break; } #if USE_ARDUINO_VERSION_CODE >= VERSION_CODE(2, 4, 0) @@ -567,7 +568,7 @@ void WiFiComponent::wifi_event_callback(System_Event_t *event) { } case EVENT_SOFTAPMODE_DISTRIBUTE_STA_IP: { auto it = event->event_info.distribute_sta_ip; - ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", format_mac_addr(it.mac).c_str(), + ESP_LOGV(TAG, "AP Distribute Station IP MAC=%s IP=%s aid=%u", format_mac_address_pretty(it.mac).c_str(), format_ip_addr(it.ip).c_str(), it.aid); break; } diff --git a/esphome/components/wifi/wifi_component_esp_idf.cpp b/esphome/components/wifi/wifi_component_esp_idf.cpp index e767e7ffc1..f0655a6d1d 100644 --- a/esphome/components/wifi/wifi_component_esp_idf.cpp +++ b/esphome/components/wifi/wifi_component_esp_idf.cpp @@ -691,7 +691,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { memcpy(buf, it.ssid, it.ssid_len); buf[it.ssid_len] = '\0'; ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, - format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); + format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); s_sta_connected = true; } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_STA_DISCONNECTED) { @@ -708,7 +708,7 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { return; } else { ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, - format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); + format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); s_sta_connect_error = true; } s_sta_connected = false; @@ -780,15 +780,15 @@ void WiFiComponent::wifi_process_event_(IDFWiFiEvent *data) { } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_PROBEREQRECVED) { const auto &it = data->data.ap_probe_req_rx; - ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi); + ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi); } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STACONNECTED) { const auto &it = data->data.ap_staconnected; - ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(it.mac).c_str()); + ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(it.mac).c_str()); } else if (data->event_base == WIFI_EVENT && data->event_id == WIFI_EVENT_AP_STADISCONNECTED) { const auto &it = data->data.ap_stadisconnected; - ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(it.mac).c_str()); + ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(it.mac).c_str()); } else if (data->event_base == IP_EVENT && data->event_id == IP_EVENT_AP_STAIPASSIGNED) { const auto &it = data->data.ip_ap_staipassigned; diff --git a/esphome/components/wifi/wifi_component_libretiny.cpp b/esphome/components/wifi/wifi_component_libretiny.cpp index 0f7b688290..b15f710150 100644 --- a/esphome/components/wifi/wifi_component_libretiny.cpp +++ b/esphome/components/wifi/wifi_component_libretiny.cpp @@ -281,7 +281,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ memcpy(buf, it.ssid, it.ssid_len); buf[it.ssid_len] = '\0'; ESP_LOGV(TAG, "Connected ssid='%s' bssid=" LOG_SECRET("%s") " channel=%u, authmode=%s", buf, - format_mac_addr(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); + format_mac_address_pretty(it.bssid).c_str(), it.channel, get_auth_mode_str(it.authmode)); break; } @@ -294,7 +294,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ ESP_LOGW(TAG, "Disconnected ssid='%s' reason='Probe Request Unsuccessful'", buf); } else { ESP_LOGW(TAG, "Disconnected ssid='%s' bssid=" LOG_SECRET("%s") " reason='%s'", buf, - format_mac_addr(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); + format_mac_address_pretty(it.bssid).c_str(), get_disconnect_reason_str(it.reason)); } uint8_t reason = it.reason; @@ -349,13 +349,13 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ case ESPHOME_EVENT_ID_WIFI_AP_STACONNECTED: { auto it = info.wifi_sta_connected; auto &mac = it.bssid; - ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_addr(mac).c_str()); + ESP_LOGV(TAG, "AP client connected MAC=%s", format_mac_address_pretty(mac).c_str()); break; } case ESPHOME_EVENT_ID_WIFI_AP_STADISCONNECTED: { auto it = info.wifi_sta_disconnected; auto &mac = it.bssid; - ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_addr(mac).c_str()); + ESP_LOGV(TAG, "AP client disconnected MAC=%s", format_mac_address_pretty(mac).c_str()); break; } case ESPHOME_EVENT_ID_WIFI_AP_STAIPASSIGNED: { @@ -364,7 +364,7 @@ void WiFiComponent::wifi_event_callback_(esphome_wifi_event_id_t event, esphome_ } case ESPHOME_EVENT_ID_WIFI_AP_PROBEREQRECVED: { auto it = info.wifi_ap_probereqrecved; - ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_addr(it.mac).c_str(), it.rssi); + ESP_LOGVV(TAG, "AP receive Probe Request MAC=%s RSSI=%d", format_mac_address_pretty(it.mac).c_str(), it.rssi); break; } default: diff --git a/esphome/components/xiaomi_ble/xiaomi_ble.cpp b/esphome/components/xiaomi_ble/xiaomi_ble.cpp index a80daa0b80..564870d74e 100644 --- a/esphome/components/xiaomi_ble/xiaomi_ble.cpp +++ b/esphome/components/xiaomi_ble/xiaomi_ble.cpp @@ -320,7 +320,7 @@ bool decrypt_xiaomi_payload(std::vector &raw, const uint8_t *bindkey, c memcpy(mac_address + 4, mac_reverse + 1, 1); memcpy(mac_address + 5, mac_reverse, 1); ESP_LOGVV(TAG, "decrypt_xiaomi_payload(): authenticated decryption failed."); - ESP_LOGVV(TAG, " MAC address : %s", format_hex_pretty(mac_address, 6).c_str()); + ESP_LOGVV(TAG, " MAC address : %s", format_mac_address_pretty(mac_address).c_str()); ESP_LOGVV(TAG, " Packet : %s", format_hex_pretty(raw.data(), raw.size()).c_str()); ESP_LOGVV(TAG, " Key : %s", format_hex_pretty(vector.key, vector.keysize).c_str()); ESP_LOGVV(TAG, " Iv : %s", format_hex_pretty(vector.iv, vector.ivsize).c_str()); diff --git a/esphome/const.py b/esphome/const.py index 6d7d9c0c1b..ed6390d8c3 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -1099,7 +1099,7 @@ UNIT_KILOMETER_PER_HOUR = "km/h" UNIT_KILOVOLT_AMPS = "kVA" UNIT_KILOVOLT_AMPS_HOURS = "kVAh" UNIT_KILOVOLT_AMPS_REACTIVE = "kVAR" -UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kVARh" +UNIT_KILOVOLT_AMPS_REACTIVE_HOURS = "kvarh" UNIT_KILOWATT = "kW" UNIT_KILOWATT_HOURS = "kWh" UNIT_LITRE = "L" @@ -1135,7 +1135,7 @@ UNIT_VOLT = "V" UNIT_VOLT_AMPS = "VA" UNIT_VOLT_AMPS_HOURS = "VAh" UNIT_VOLT_AMPS_REACTIVE = "var" -UNIT_VOLT_AMPS_REACTIVE_HOURS = "VARh" +UNIT_VOLT_AMPS_REACTIVE_HOURS = "varh" UNIT_WATT = "W" UNIT_WATT_HOURS = "Wh" diff --git a/esphome/core/helpers.cpp b/esphome/core/helpers.cpp index ec79cb8bbb..79dbb314c8 100644 --- a/esphome/core/helpers.cpp +++ b/esphome/core/helpers.cpp @@ -356,6 +356,10 @@ size_t parse_hex(const char *str, size_t length, uint8_t *data, size_t count) { return chars; } +std::string format_mac_address_pretty(const uint8_t *mac) { + return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); +} + static char format_hex_char(uint8_t v) { return v >= 10 ? 'a' + (v - 10) : '0' + v; } std::string format_hex(const uint8_t *data, size_t length) { std::string ret; @@ -732,7 +736,7 @@ std::string get_mac_address() { std::string get_mac_address_pretty() { uint8_t mac[6]; get_mac_address_raw(mac); - return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, mac[0], mac[1], mac[2], mac[3], mac[4], mac[5]); + return format_mac_address_pretty(mac); } #ifdef USE_ESP32 diff --git a/esphome/core/helpers.h b/esphome/core/helpers.h index 477f260bf0..8bd5b813c7 100644 --- a/esphome/core/helpers.h +++ b/esphome/core/helpers.h @@ -402,6 +402,8 @@ template::value, int> = 0> optional< return parse_hex(str.c_str(), str.length()); } +/// Format the six-byte array \p mac into a MAC address. +std::string format_mac_address_pretty(const uint8_t mac[6]); /// Format the byte array \p data of length \p len in lowercased hex. std::string format_hex(const uint8_t *data, size_t length); /// Format the vector \p data in lowercased hex. diff --git a/tests/components/binary_sensor/common.yaml b/tests/components/binary_sensor/common.yaml index 148b7d2405..2b4a006352 100644 --- a/tests/components/binary_sensor/common.yaml +++ b/tests/components/binary_sensor/common.yaml @@ -4,6 +4,31 @@ binary_sensor: id: some_binary_sensor name: "Random binary" lambda: return (random_uint32() & 1) == 0; + filters: + - invert: + - delayed_on: 100ms + - delayed_off: 100ms + # Templated, delays for 1s (1000ms) only if a reed switch is active + - delayed_on_off: !lambda "return 1000;" + - delayed_on_off: + time_on: 10s + time_off: !lambda "return 1000;" + - autorepeat: + - delay: 1s + time_off: 100ms + time_on: 900ms + - delay: 5s + time_off: 100ms + time_on: 400ms + - lambda: |- + if (id(some_binary_sensor).state) { + return x; + } else { + return {}; + } + - settle: 100ms + - timeout: 10s + on_state_change: then: - logger.log: diff --git a/tests/integration/fixtures/host_mode_with_sensor.yaml b/tests/integration/fixtures/host_mode_with_sensor.yaml index fecd0b435b..0ac495f3b1 100644 --- a/tests/integration/fixtures/host_mode_with_sensor.yaml +++ b/tests/integration/fixtures/host_mode_with_sensor.yaml @@ -8,5 +8,8 @@ sensor: name: Test Sensor id: test_sensor unit_of_measurement: °C + accuracy_decimals: 2 + state_class: measurement + force_update: true lambda: return 42.0; update_interval: 0.1s diff --git a/tests/integration/test_host_mode_sensor.py b/tests/integration/test_host_mode_sensor.py index f0c938da1c..f12e53b244 100644 --- a/tests/integration/test_host_mode_sensor.py +++ b/tests/integration/test_host_mode_sensor.py @@ -4,6 +4,7 @@ from __future__ import annotations import asyncio +import aioesphomeapi from aioesphomeapi import EntityState import pytest @@ -47,3 +48,23 @@ async def test_host_mode_with_sensor( # Verify the sensor state assert test_sensor_state.state == 42.0 assert len(states) > 0, "No states received" + + # Verify the optimized fields are working correctly + # Get entity info to check accuracy_decimals, state_class, etc. + entities, _ = await client.list_entities_services() + sensor_info: aioesphomeapi.SensorInfo | None = None + for entity in entities: + if isinstance(entity, aioesphomeapi.SensorInfo): + sensor_info = entity + break + + assert sensor_info is not None, "Sensor entity info not found" + assert sensor_info.accuracy_decimals == 2, ( + f"Expected accuracy_decimals=2, got {sensor_info.accuracy_decimals}" + ) + assert sensor_info.state_class == aioesphomeapi.StateClass.MEASUREMENT, ( + f"Expected state_class=StateClass.MEASUREMENT, got {sensor_info.state_class}" + ) + assert sensor_info.force_update is True, ( + f"Expected force_update=True, got {sensor_info.force_update}" + )