diff --git a/esphome/components/api/api_server.cpp b/esphome/components/api/api_server.cpp index 0fd9c1a228..4dc6fe2390 100644 --- a/esphome/components/api/api_server.cpp +++ b/esphome/components/api/api_server.cpp @@ -24,6 +24,14 @@ static const char *const TAG = "api"; // APIServer APIServer *global_api_server = nullptr; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables) +#ifndef USE_API_YAML_SERVICES +// Global empty vector to avoid guard variables (saves 8 bytes) +// This is initialized at program startup before any threads +static const std::vector empty_user_services{}; + +const std::vector &get_empty_user_services_instance() { return empty_user_services; } +#endif + APIServer::APIServer() { global_api_server = this; // Pre-allocate shared write buffer diff --git a/esphome/components/api/api_server.h b/esphome/components/api/api_server.h index 9dc2b4b7d6..f34fd55974 100644 --- a/esphome/components/api/api_server.h +++ b/esphome/components/api/api_server.h @@ -25,6 +25,11 @@ struct SavedNoisePsk { } PACKED; // NOLINT #endif +#ifndef USE_API_YAML_SERVICES +// Forward declaration of helper function +const std::vector &get_empty_user_services_instance(); +#endif + class APIServer : public Component, public Controller { public: APIServer(); @@ -151,8 +156,11 @@ class APIServer : public Component, public Controller { #ifdef USE_API_YAML_SERVICES return this->user_services_; #else - static const std::vector EMPTY; - return this->user_services_ ? *this->user_services_ : EMPTY; + if (this->user_services_) { + return *this->user_services_; + } + // Return reference to global empty instance (no guard needed) + return get_empty_user_services_instance(); #endif } diff --git a/esphome/components/ld2410/button/__init__.py b/esphome/components/ld2410/button/__init__.py index 4cb50d707b..1cd56082c3 100644 --- a/esphome/components/ld2410/button/__init__.py +++ b/esphome/components/ld2410/button/__init__.py @@ -14,8 +14,8 @@ from esphome.const import ( from .. import CONF_LD2410_ID, LD2410Component, ld2410_ns +FactoryResetButton = ld2410_ns.class_("FactoryResetButton", button.Button) QueryButton = ld2410_ns.class_("QueryButton", button.Button) -ResetButton = ld2410_ns.class_("ResetButton", button.Button) RestartButton = ld2410_ns.class_("RestartButton", button.Button) CONF_QUERY_PARAMS = "query_params" @@ -23,7 +23,7 @@ CONF_QUERY_PARAMS = "query_params" CONFIG_SCHEMA = { cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), cv.Optional(CONF_FACTORY_RESET): button.button_schema( - ResetButton, + FactoryResetButton, device_class=DEVICE_CLASS_RESTART, entity_category=ENTITY_CATEGORY_CONFIG, icon=ICON_RESTART_ALERT, @@ -47,7 +47,7 @@ async def to_code(config): if factory_reset_config := config.get(CONF_FACTORY_RESET): b = await button.new_button(factory_reset_config) await cg.register_parented(b, config[CONF_LD2410_ID]) - cg.add(ld2410_component.set_reset_button(b)) + cg.add(ld2410_component.set_factory_reset_button(b)) if restart_config := config.get(CONF_RESTART): b = await button.new_button(restart_config) await cg.register_parented(b, config[CONF_LD2410_ID]) diff --git a/esphome/components/ld2410/button/factory_reset_button.cpp b/esphome/components/ld2410/button/factory_reset_button.cpp new file mode 100644 index 0000000000..a848b02a9d --- /dev/null +++ b/esphome/components/ld2410/button/factory_reset_button.cpp @@ -0,0 +1,9 @@ +#include "factory_reset_button.h" + +namespace esphome { +namespace ld2410 { + +void FactoryResetButton::press_action() { this->parent_->factory_reset(); } + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/button/reset_button.h b/esphome/components/ld2410/button/factory_reset_button.h similarity index 65% rename from esphome/components/ld2410/button/reset_button.h rename to esphome/components/ld2410/button/factory_reset_button.h index 78dd92c9f5..45bf979033 100644 --- a/esphome/components/ld2410/button/reset_button.h +++ b/esphome/components/ld2410/button/factory_reset_button.h @@ -6,9 +6,9 @@ namespace esphome { namespace ld2410 { -class ResetButton : public button::Button, public Parented { +class FactoryResetButton : public button::Button, public Parented { public: - ResetButton() = default; + FactoryResetButton() = default; protected: void press_action() override; diff --git a/esphome/components/ld2410/button/reset_button.cpp b/esphome/components/ld2410/button/reset_button.cpp deleted file mode 100644 index f16c5faa79..0000000000 --- a/esphome/components/ld2410/button/reset_button.cpp +++ /dev/null @@ -1,9 +0,0 @@ -#include "reset_button.h" - -namespace esphome { -namespace ld2410 { - -void ResetButton::press_action() { this->parent_->factory_reset(); } - -} // namespace ld2410 -} // namespace esphome diff --git a/esphome/components/ld2410/ld2410.cpp b/esphome/components/ld2410/ld2410.cpp index a34f99ee33..375d1088e8 100644 --- a/esphome/components/ld2410/ld2410.cpp +++ b/esphome/components/ld2410/ld2410.cpp @@ -18,11 +18,10 @@ namespace esphome { namespace ld2410 { static const char *const TAG = "ld2410"; -static const char *const NO_MAC = "08:05:04:03:02:01"; static const char *const UNKNOWN_MAC = "unknown"; static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X"; -enum BaudRateStructure : uint8_t { +enum BaudRate : uint8_t { BAUD_RATE_9600 = 1, BAUD_RATE_19200 = 2, BAUD_RATE_38400 = 3, @@ -33,23 +32,23 @@ enum BaudRateStructure : uint8_t { BAUD_RATE_460800 = 8, }; -enum DistanceResolutionStructure : uint8_t { +enum DistanceResolution : uint8_t { DISTANCE_RESOLUTION_0_2 = 0x01, DISTANCE_RESOLUTION_0_75 = 0x00, }; -enum LightFunctionStructure : uint8_t { +enum LightFunction : uint8_t { LIGHT_FUNCTION_OFF = 0x00, LIGHT_FUNCTION_BELOW = 0x01, LIGHT_FUNCTION_ABOVE = 0x02, }; -enum OutPinLevelStructure : uint8_t { +enum OutPinLevel : uint8_t { OUT_PIN_LEVEL_LOW = 0x00, OUT_PIN_LEVEL_HIGH = 0x01, }; -enum PeriodicDataStructure : uint8_t { +enum PeriodicData : uint8_t { DATA_TYPES = 6, TARGET_STATES = 8, MOVING_TARGET_LOW = 9, @@ -67,12 +66,12 @@ enum PeriodicDataStructure : uint8_t { }; enum PeriodicDataValue : uint8_t { - HEAD = 0xAA, - END = 0x55, + HEADER = 0xAA, + FOOTER = 0x55, CHECK = 0x00, }; -enum AckDataStructure : uint8_t { +enum AckData : uint8_t { COMMAND = 6, COMMAND_STATUS = 7, }; @@ -80,11 +79,11 @@ enum AckDataStructure : uint8_t { // Memory-efficient lookup tables struct StringToUint8 { const char *str; - uint8_t value; + const uint8_t value; }; struct Uint8ToString { - uint8_t value; + const uint8_t value; const char *str; }; @@ -144,96 +143,119 @@ template const char *find_str(const Uint8ToString (&arr)[N], uint8_t v } // Commands -static const uint8_t CMD_ENABLE_CONF = 0xFF; -static const uint8_t CMD_DISABLE_CONF = 0xFE; -static const uint8_t CMD_ENABLE_ENG = 0x62; -static const uint8_t CMD_DISABLE_ENG = 0x63; -static const uint8_t CMD_MAXDIST_DURATION = 0x60; -static const uint8_t CMD_QUERY = 0x61; -static const uint8_t CMD_GATE_SENS = 0x64; -static const uint8_t CMD_VERSION = 0xA0; -static const uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0xAB; -static const uint8_t CMD_SET_DISTANCE_RESOLUTION = 0xAA; -static const uint8_t CMD_QUERY_LIGHT_CONTROL = 0xAE; -static const uint8_t CMD_SET_LIGHT_CONTROL = 0xAD; -static const uint8_t CMD_SET_BAUD_RATE = 0xA1; -static const uint8_t CMD_BT_PASSWORD = 0xA9; -static const uint8_t CMD_MAC = 0xA5; -static const uint8_t CMD_RESET = 0xA2; -static const uint8_t CMD_RESTART = 0xA3; -static const uint8_t CMD_BLUETOOTH = 0xA4; +static constexpr uint8_t CMD_ENABLE_CONF = 0xFF; +static constexpr uint8_t CMD_DISABLE_CONF = 0xFE; +static constexpr uint8_t CMD_ENABLE_ENG = 0x62; +static constexpr uint8_t CMD_DISABLE_ENG = 0x63; +static constexpr uint8_t CMD_MAXDIST_DURATION = 0x60; +static constexpr uint8_t CMD_QUERY = 0x61; +static constexpr uint8_t CMD_GATE_SENS = 0x64; +static constexpr uint8_t CMD_QUERY_VERSION = 0xA0; +static constexpr uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0xAB; +static constexpr uint8_t CMD_SET_DISTANCE_RESOLUTION = 0xAA; +static constexpr uint8_t CMD_QUERY_LIGHT_CONTROL = 0xAE; +static constexpr uint8_t CMD_SET_LIGHT_CONTROL = 0xAD; +static constexpr uint8_t CMD_SET_BAUD_RATE = 0xA1; +static constexpr uint8_t CMD_BT_PASSWORD = 0xA9; +static constexpr uint8_t CMD_QUERY_MAC_ADDRESS = 0xA5; +static constexpr uint8_t CMD_RESET = 0xA2; +static constexpr uint8_t CMD_RESTART = 0xA3; +static constexpr uint8_t CMD_BLUETOOTH = 0xA4; // Commands values -static const uint8_t CMD_MAX_MOVE_VALUE = 0x00; -static const uint8_t CMD_MAX_STILL_VALUE = 0x01; -static const uint8_t CMD_DURATION_VALUE = 0x02; +static constexpr uint8_t CMD_MAX_MOVE_VALUE = 0x00; +static constexpr uint8_t CMD_MAX_STILL_VALUE = 0x01; +static constexpr uint8_t CMD_DURATION_VALUE = 0x02; +// Header & Footer size +static constexpr uint8_t HEADER_FOOTER_SIZE = 4; // Command Header & Footer -static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA}; -static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01}; +static constexpr uint8_t CMD_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xFD, 0xFC, 0xFB, 0xFA}; +static constexpr uint8_t CMD_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0x04, 0x03, 0x02, 0x01}; // Data Header & Footer -static const uint8_t DATA_FRAME_HEADER[4] = {0xF4, 0xF3, 0xF2, 0xF1}; -static const uint8_t DATA_FRAME_END[4] = {0xF8, 0xF7, 0xF6, 0xF5}; +static constexpr uint8_t DATA_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xF4, 0xF3, 0xF2, 0xF1}; +static constexpr uint8_t DATA_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0xF8, 0xF7, 0xF6, 0xF5}; +// MAC address the module uses when Bluetooth is disabled +static constexpr uint8_t NO_MAC[] = {0x08, 0x05, 0x04, 0x03, 0x02, 0x01}; static inline int two_byte_to_int(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; } +static bool validate_header_footer(const uint8_t *header_footer, const uint8_t *buffer) { + for (uint8_t i = 0; i < HEADER_FOOTER_SIZE; i++) { + if (header_footer[i] != buffer[i]) { + return false; // Mismatch in header/footer + } + } + return true; // Valid header/footer +} + void LD2410Component::dump_config() { - ESP_LOGCONFIG(TAG, "LD2410:"); + std::string mac_str = + mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC; + std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5], + this->version_[4], this->version_[3], this->version_[2]); + ESP_LOGCONFIG(TAG, + "LD2410:\n" + " Firmware version: %s\n" + " MAC address: %s\n" + " Throttle: %u ms", + version.c_str(), mac_str.c_str(), this->throttle_); #ifdef USE_BINARY_SENSOR - LOG_BINARY_SENSOR(" ", "TargetBinarySensor", this->target_binary_sensor_); - LOG_BINARY_SENSOR(" ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_); - LOG_BINARY_SENSOR(" ", "StillTargetBinarySensor", this->still_target_binary_sensor_); - LOG_BINARY_SENSOR(" ", "OutPinPresenceStatusBinarySensor", this->out_pin_presence_status_binary_sensor_); -#endif -#ifdef USE_SWITCH - LOG_SWITCH(" ", "EngineeringModeSwitch", this->engineering_mode_switch_); - LOG_SWITCH(" ", "BluetoothSwitch", this->bluetooth_switch_); -#endif -#ifdef USE_BUTTON - LOG_BUTTON(" ", "ResetButton", this->reset_button_); - LOG_BUTTON(" ", "RestartButton", this->restart_button_); - LOG_BUTTON(" ", "QueryButton", this->query_button_); + ESP_LOGCONFIG(TAG, "Binary Sensors:"); + LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_); + LOG_BINARY_SENSOR(" ", "MovingTarget", this->moving_target_binary_sensor_); + LOG_BINARY_SENSOR(" ", "StillTarget", this->still_target_binary_sensor_); + LOG_BINARY_SENSOR(" ", "OutPinPresenceStatus", this->out_pin_presence_status_binary_sensor_); #endif #ifdef USE_SENSOR - LOG_SENSOR(" ", "LightSensor", this->light_sensor_); - LOG_SENSOR(" ", "MovingTargetDistanceSensor", this->moving_target_distance_sensor_); - LOG_SENSOR(" ", "StillTargetDistanceSensor", this->still_target_distance_sensor_); - LOG_SENSOR(" ", "MovingTargetEnergySensor", this->moving_target_energy_sensor_); - LOG_SENSOR(" ", "StillTargetEnergySensor", this->still_target_energy_sensor_); - LOG_SENSOR(" ", "DetectionDistanceSensor", this->detection_distance_sensor_); - for (sensor::Sensor *s : this->gate_still_sensors_) { - LOG_SENSOR(" ", "NthGateStillSesnsor", s); - } + ESP_LOGCONFIG(TAG, "Sensors:"); + LOG_SENSOR(" ", "Light", this->light_sensor_); + LOG_SENSOR(" ", "DetectionDistance", this->detection_distance_sensor_); + LOG_SENSOR(" ", "MovingTargetDistance", this->moving_target_distance_sensor_); + LOG_SENSOR(" ", "MovingTargetEnergy", this->moving_target_energy_sensor_); + LOG_SENSOR(" ", "StillTargetDistance", this->still_target_distance_sensor_); + LOG_SENSOR(" ", "StillTargetEnergy", this->still_target_energy_sensor_); for (sensor::Sensor *s : this->gate_move_sensors_) { - LOG_SENSOR(" ", "NthGateMoveSesnsor", s); + LOG_SENSOR(" ", "GateMove", s); + } + for (sensor::Sensor *s : this->gate_still_sensors_) { + LOG_SENSOR(" ", "GateStill", s); } #endif #ifdef USE_TEXT_SENSOR - LOG_TEXT_SENSOR(" ", "VersionTextSensor", this->version_text_sensor_); - LOG_TEXT_SENSOR(" ", "MacTextSensor", this->mac_text_sensor_); -#endif -#ifdef USE_SELECT - LOG_SELECT(" ", "LightFunctionSelect", this->light_function_select_); - LOG_SELECT(" ", "OutPinLevelSelect", this->out_pin_level_select_); - LOG_SELECT(" ", "DistanceResolutionSelect", this->distance_resolution_select_); - LOG_SELECT(" ", "BaudRateSelect", this->baud_rate_select_); + ESP_LOGCONFIG(TAG, "Text Sensors:"); + LOG_TEXT_SENSOR(" ", "Mac", this->mac_text_sensor_); + LOG_TEXT_SENSOR(" ", "Version", this->version_text_sensor_); #endif #ifdef USE_NUMBER - LOG_NUMBER(" ", "LightThresholdNumber", this->light_threshold_number_); - LOG_NUMBER(" ", "MaxStillDistanceGateNumber", this->max_still_distance_gate_number_); - LOG_NUMBER(" ", "MaxMoveDistanceGateNumber", this->max_move_distance_gate_number_); - LOG_NUMBER(" ", "TimeoutNumber", this->timeout_number_); - for (number::Number *n : this->gate_still_threshold_numbers_) { - LOG_NUMBER(" ", "Still Thresholds Number", n); - } + ESP_LOGCONFIG(TAG, "Numbers:"); + LOG_NUMBER(" ", "LightThreshold", this->light_threshold_number_); + LOG_NUMBER(" ", "MaxMoveDistanceGate", this->max_move_distance_gate_number_); + LOG_NUMBER(" ", "MaxStillDistanceGate", this->max_still_distance_gate_number_); + LOG_NUMBER(" ", "Timeout", this->timeout_number_); for (number::Number *n : this->gate_move_threshold_numbers_) { - LOG_NUMBER(" ", "Move Thresholds Number", n); + LOG_NUMBER(" ", "MoveThreshold", n); + } + for (number::Number *n : this->gate_still_threshold_numbers_) { + LOG_NUMBER(" ", "StillThreshold", n); } #endif - this->read_all_info(); - ESP_LOGCONFIG(TAG, - " Throttle: %ums\n" - " MAC address: %s\n" - " Firmware version: %s", - this->throttle_, this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_.c_str(), this->version_.c_str()); +#ifdef USE_SELECT + ESP_LOGCONFIG(TAG, "Selects:"); + LOG_SELECT(" ", "BaudRate", this->baud_rate_select_); + LOG_SELECT(" ", "DistanceResolution", this->distance_resolution_select_); + LOG_SELECT(" ", "LightFunction", this->light_function_select_); + LOG_SELECT(" ", "OutPinLevel", this->out_pin_level_select_); +#endif +#ifdef USE_SWITCH + ESP_LOGCONFIG(TAG, "Switches:"); + LOG_SWITCH(" ", "Bluetooth", this->bluetooth_switch_); + LOG_SWITCH(" ", "EngineeringMode", this->engineering_mode_switch_); +#endif +#ifdef USE_BUTTON + ESP_LOGCONFIG(TAG, "Buttons:"); + LOG_BUTTON(" ", "FactoryReset", this->factory_reset_button_); + LOG_BUTTON(" ", "Query", this->query_button_); + LOG_BUTTON(" ", "Restart", this->restart_button_); +#endif } void LD2410Component::setup() { @@ -246,12 +268,12 @@ void LD2410Component::read_all_info() { this->get_version_(); this->get_mac_(); this->get_distance_resolution_(); - this->get_light_control_(); + this->query_light_control_(); this->query_parameters_(); this->set_config_mode_(false); #ifdef USE_SELECT const auto baud_rate = std::to_string(this->parent_->get_baud_rate()); - if (this->baud_rate_select_ != nullptr && this->baud_rate_select_->state != baud_rate) { + if (this->baud_rate_select_ != nullptr) { this->baud_rate_select_->publish_state(baud_rate); } #endif @@ -264,66 +286,59 @@ void LD2410Component::restart_and_read_all_info() { } void LD2410Component::loop() { - const int max_line_length = 80; - static uint8_t buffer[max_line_length]; - - while (available()) { - this->readline_(read(), buffer, max_line_length); + while (this->available()) { + this->readline_(this->read()); } } -void LD2410Component::send_command_(uint8_t command, const uint8_t *command_value, int command_value_len) { +void LD2410Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) { ESP_LOGV(TAG, "Sending COMMAND %02X", command); - // frame start bytes - this->write_array(CMD_FRAME_HEADER, 4); + // frame header bytes + this->write_array(CMD_FRAME_HEADER, sizeof(CMD_FRAME_HEADER)); // length bytes - int len = 2; - if (command_value != nullptr) + uint8_t len = 2; + if (command_value != nullptr) { len += command_value_len; - this->write_byte(lowbyte(len)); - this->write_byte(highbyte(len)); - - // command - this->write_byte(lowbyte(command)); - this->write_byte(highbyte(command)); + } + uint8_t len_cmd[] = {lowbyte(len), highbyte(len), command, 0x00}; + this->write_array(len_cmd, sizeof(len_cmd)); // command value bytes if (command_value != nullptr) { - for (int i = 0; i < command_value_len; i++) { + for (uint8_t i = 0; i < command_value_len; i++) { this->write_byte(command_value[i]); } } - // frame end bytes - this->write_array(CMD_FRAME_END, 4); + // frame footer bytes + this->write_array(CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)); // FIXME to remove delay(50); // NOLINT } -void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) { - if (len < 12) - return; // 4 frame start bytes + 2 length bytes + 1 data end byte + 1 crc byte + 4 frame end bytes - if (buffer[0] != 0xF4 || buffer[1] != 0xF3 || buffer[2] != 0xF2 || buffer[3] != 0xF1) // check 4 frame start bytes +void LD2410Component::handle_periodic_data_() { + // Reduce data update rate to reduce home assistant database growth + // Check this first to prevent unnecessary processing done in later checks/parsing + if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) { return; - if (buffer[7] != HEAD || buffer[len - 6] != END || buffer[len - 5] != CHECK) // Check constant values - return; // data head=0xAA, data end=0x55, crc=0x00 - - /* - Reduce data update rate to prevent home assistant database size grow fast - */ - int32_t current_millis = App.get_loop_component_start_time(); - if (current_millis - last_periodic_millis_ < this->throttle_) + } + // 4 frame header bytes + 2 length bytes + 1 data end byte + 1 crc byte + 4 frame footer bytes + // data header=0xAA, data footer=0x55, crc=0x00 + if (this->buffer_pos_ < 12 || !ld2410::validate_header_footer(DATA_FRAME_HEADER, this->buffer_data_) || + this->buffer_data_[7] != HEADER || this->buffer_data_[this->buffer_pos_ - 6] != FOOTER || + this->buffer_data_[this->buffer_pos_ - 5] != CHECK) { return; - last_periodic_millis_ = current_millis; + } + // Save the timestamp after validating the frame so, if invalid, we'll take the next frame immediately + this->last_periodic_millis_ = App.get_loop_component_start_time(); /* Data Type: 7th 0x01: Engineering mode 0x02: Normal mode */ - bool engineering_mode = buffer[DATA_TYPES] == 0x01; + bool engineering_mode = this->buffer_data_[DATA_TYPES] == 0x01; #ifdef USE_SWITCH - if (this->engineering_mode_switch_ != nullptr && - current_millis - last_engineering_mode_change_millis_ > this->throttle_) { + if (this->engineering_mode_switch_ != nullptr) { this->engineering_mode_switch_->publish_state(engineering_mode); } #endif @@ -335,7 +350,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) { 0x02 = Still targets 0x03 = Moving+Still targets */ - char target_state = buffer[TARGET_STATES]; + char target_state = this->buffer_data_[TARGET_STATES]; if (this->target_binary_sensor_ != nullptr) { this->target_binary_sensor_->publish_state(target_state != 0x00); } @@ -355,27 +370,30 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) { */ #ifdef USE_SENSOR if (this->moving_target_distance_sensor_ != nullptr) { - int new_moving_target_distance = ld2410::two_byte_to_int(buffer[MOVING_TARGET_LOW], buffer[MOVING_TARGET_HIGH]); + int new_moving_target_distance = + ld2410::two_byte_to_int(this->buffer_data_[MOVING_TARGET_LOW], this->buffer_data_[MOVING_TARGET_HIGH]); if (this->moving_target_distance_sensor_->get_state() != new_moving_target_distance) this->moving_target_distance_sensor_->publish_state(new_moving_target_distance); } if (this->moving_target_energy_sensor_ != nullptr) { - int new_moving_target_energy = buffer[MOVING_ENERGY]; + int new_moving_target_energy = this->buffer_data_[MOVING_ENERGY]; if (this->moving_target_energy_sensor_->get_state() != new_moving_target_energy) this->moving_target_energy_sensor_->publish_state(new_moving_target_energy); } if (this->still_target_distance_sensor_ != nullptr) { - int new_still_target_distance = ld2410::two_byte_to_int(buffer[STILL_TARGET_LOW], buffer[STILL_TARGET_HIGH]); + int new_still_target_distance = + ld2410::two_byte_to_int(this->buffer_data_[STILL_TARGET_LOW], this->buffer_data_[STILL_TARGET_HIGH]); if (this->still_target_distance_sensor_->get_state() != new_still_target_distance) this->still_target_distance_sensor_->publish_state(new_still_target_distance); } if (this->still_target_energy_sensor_ != nullptr) { - int new_still_target_energy = buffer[STILL_ENERGY]; + int new_still_target_energy = this->buffer_data_[STILL_ENERGY]; if (this->still_target_energy_sensor_->get_state() != new_still_target_energy) this->still_target_energy_sensor_->publish_state(new_still_target_energy); } if (this->detection_distance_sensor_ != nullptr) { - int new_detect_distance = ld2410::two_byte_to_int(buffer[DETECT_DISTANCE_LOW], buffer[DETECT_DISTANCE_HIGH]); + int new_detect_distance = + ld2410::two_byte_to_int(this->buffer_data_[DETECT_DISTANCE_LOW], this->buffer_data_[DETECT_DISTANCE_HIGH]); if (this->detection_distance_sensor_->get_state() != new_detect_distance) this->detection_distance_sensor_->publish_state(new_detect_distance); } @@ -388,7 +406,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) { for (std::vector::size_type i = 0; i != this->gate_move_sensors_.size(); i++) { sensor::Sensor *s = this->gate_move_sensors_[i]; if (s != nullptr) { - s->publish_state(buffer[MOVING_SENSOR_START + i]); + s->publish_state(this->buffer_data_[MOVING_SENSOR_START + i]); } } /* @@ -397,16 +415,17 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) { for (std::vector::size_type i = 0; i != this->gate_still_sensors_.size(); i++) { sensor::Sensor *s = this->gate_still_sensors_[i]; if (s != nullptr) { - s->publish_state(buffer[STILL_SENSOR_START + i]); + s->publish_state(this->buffer_data_[STILL_SENSOR_START + i]); } } /* Light sensor: 38th bytes */ if (this->light_sensor_ != nullptr) { - int new_light_sensor = buffer[LIGHT_SENSOR]; - if (this->light_sensor_->get_state() != new_light_sensor) + int new_light_sensor = this->buffer_data_[LIGHT_SENSOR]; + if (this->light_sensor_->get_state() != new_light_sensor) { this->light_sensor_->publish_state(new_light_sensor); + } } } else { for (auto *s : this->gate_move_sensors_) { @@ -427,7 +446,7 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) { #ifdef USE_BINARY_SENSOR if (engineering_mode) { if (this->out_pin_presence_status_binary_sensor_ != nullptr) { - this->out_pin_presence_status_binary_sensor_->publish_state(buffer[OUT_PIN_SENSOR] == 0x01); + this->out_pin_presence_status_binary_sensor_->publish_state(this->buffer_data_[OUT_PIN_SENSOR] == 0x01); } } else { if (this->out_pin_presence_status_binary_sensor_ != nullptr) { @@ -439,127 +458,149 @@ void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) { #ifdef USE_NUMBER std::function set_number_value(number::Number *n, float value) { - float normalized_value = value * 1.0; - if (n != nullptr && (!n->has_state() || n->state != normalized_value)) { - n->state = normalized_value; - return [n, normalized_value]() { n->publish_state(normalized_value); }; + if (n != nullptr && (!n->has_state() || n->state != value)) { + n->state = value; + return [n, value]() { n->publish_state(value); }; } return []() {}; } #endif -bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { - ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", buffer[COMMAND]); - if (len < 10) { +bool LD2410Component::handle_ack_data_() { + ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", this->buffer_data_[COMMAND]); + if (this->buffer_pos_ < 10) { 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, "Invalid header"); + if (!ld2410::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) { + ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str()); return true; } - if (buffer[COMMAND_STATUS] != 0x01) { + if (this->buffer_data_[COMMAND_STATUS] != 0x01) { ESP_LOGE(TAG, "Invalid status"); return true; } - if (ld2410::two_byte_to_int(buffer[8], buffer[9]) != 0x00) { - ESP_LOGE(TAG, "Invalid command: %u, %u", buffer[8], buffer[9]); + if (ld2410::two_byte_to_int(this->buffer_data_[8], this->buffer_data_[9]) != 0x00) { + ESP_LOGW(TAG, "Invalid command: %02X, %02X", this->buffer_data_[8], this->buffer_data_[9]); return true; } - switch (buffer[COMMAND]) { - case lowbyte(CMD_ENABLE_CONF): + switch (this->buffer_data_[COMMAND]) { + case CMD_ENABLE_CONF: ESP_LOGV(TAG, "Enable conf"); break; - case lowbyte(CMD_DISABLE_CONF): + + case CMD_DISABLE_CONF: ESP_LOGV(TAG, "Disabled conf"); break; - case lowbyte(CMD_SET_BAUD_RATE): + + case CMD_SET_BAUD_RATE: ESP_LOGV(TAG, "Baud rate change"); #ifdef USE_SELECT if (this->baud_rate_select_ != nullptr) { - ESP_LOGE(TAG, "Configure baud rate to %s and reinstall", this->baud_rate_select_->state.c_str()); + ESP_LOGE(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->state.c_str()); } #endif break; - case lowbyte(CMD_VERSION): - this->version_ = str_sprintf(VERSION_FMT, buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], buffer[14]); - ESP_LOGV(TAG, "Firmware version: %s", this->version_.c_str()); + + case CMD_QUERY_VERSION: { + std::memcpy(this->version_, &this->buffer_data_[12], sizeof(this->version_)); + std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5], + this->version_[4], this->version_[3], this->version_[2]); + ESP_LOGV(TAG, "Firmware version: %s", version.c_str()); #ifdef USE_TEXT_SENSOR if (this->version_text_sensor_ != nullptr) { - this->version_text_sensor_->publish_state(this->version_); + this->version_text_sensor_->publish_state(version); } #endif break; - case lowbyte(CMD_QUERY_DISTANCE_RESOLUTION): { - std::string distance_resolution = - find_str(DISTANCE_RESOLUTIONS_BY_UINT, ld2410::two_byte_to_int(buffer[10], buffer[11])); - ESP_LOGV(TAG, "Distance resolution: %s", distance_resolution.c_str()); + } + + case CMD_QUERY_DISTANCE_RESOLUTION: { + const auto *distance_resolution = find_str(DISTANCE_RESOLUTIONS_BY_UINT, this->buffer_data_[10]); + ESP_LOGV(TAG, "Distance resolution: %s", distance_resolution); #ifdef USE_SELECT - if (this->distance_resolution_select_ != nullptr && - this->distance_resolution_select_->state != distance_resolution) { + if (this->distance_resolution_select_ != nullptr) { this->distance_resolution_select_->publish_state(distance_resolution); } #endif - } break; - case lowbyte(CMD_QUERY_LIGHT_CONTROL): { - this->light_function_ = find_str(LIGHT_FUNCTIONS_BY_UINT, buffer[10]); - this->light_threshold_ = buffer[11] * 1.0; - this->out_pin_level_ = find_str(OUT_PIN_LEVELS_BY_UINT, buffer[12]); - 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())); + break; + } + + case CMD_QUERY_LIGHT_CONTROL: { + this->light_function_ = this->buffer_data_[10]; + this->light_threshold_ = this->buffer_data_[11]; + this->out_pin_level_ = this->buffer_data_[12]; + const auto *light_function_str = find_str(LIGHT_FUNCTIONS_BY_UINT, this->light_function_); + const auto *out_pin_level_str = find_str(OUT_PIN_LEVELS_BY_UINT, this->out_pin_level_); + ESP_LOGV(TAG, + "Light function is: %s\n" + "Light threshold is: %u\n" + "Out pin level: %s", + light_function_str, this->light_threshold_, out_pin_level_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_); + if (this->light_function_select_ != nullptr) { + this->light_function_select_->publish_state(light_function_str); } - if (this->out_pin_level_select_ != nullptr && this->out_pin_level_select_->state != this->out_pin_level_) { - this->out_pin_level_select_->publish_state(this->out_pin_level_); + if (this->out_pin_level_select_ != nullptr) { + this->out_pin_level_select_->publish_state(out_pin_level_str); } #endif #ifdef USE_NUMBER - if (this->light_threshold_number_ != nullptr && - (!this->light_threshold_number_->has_state() || - this->light_threshold_number_->state != this->light_threshold_)) { - this->light_threshold_number_->publish_state(this->light_threshold_); + if (this->light_threshold_number_ != nullptr) { + this->light_threshold_number_->publish_state(static_cast(this->light_threshold_)); } #endif - } break; - case lowbyte(CMD_MAC): - if (len < 20) { + break; + } + case CMD_QUERY_MAC_ADDRESS: { + if (this->buffer_pos_ < 20) { return false; } - this->mac_ = format_mac_address_pretty(&buffer[10]); - ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str()); + + this->bluetooth_on_ = std::memcmp(&this->buffer_data_[10], NO_MAC, sizeof(NO_MAC)) != 0; + if (this->bluetooth_on_) { + std::memcpy(this->mac_address_, &this->buffer_data_[10], sizeof(this->mac_address_)); + } + + std::string mac_str = + mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC; + ESP_LOGV(TAG, "MAC address: %s", mac_str.c_str()); #ifdef USE_TEXT_SENSOR if (this->mac_text_sensor_ != nullptr) { - this->mac_text_sensor_->publish_state(this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_); + this->mac_text_sensor_->publish_state(mac_str); } #endif #ifdef USE_SWITCH if (this->bluetooth_switch_ != nullptr) { - this->bluetooth_switch_->publish_state(this->mac_ != NO_MAC); + this->bluetooth_switch_->publish_state(this->bluetooth_on_); } #endif break; - case lowbyte(CMD_GATE_SENS): + } + + case CMD_GATE_SENS: ESP_LOGV(TAG, "Sensitivity"); break; - case lowbyte(CMD_BLUETOOTH): + + case CMD_BLUETOOTH: ESP_LOGV(TAG, "Bluetooth"); break; - case lowbyte(CMD_SET_DISTANCE_RESOLUTION): + + case CMD_SET_DISTANCE_RESOLUTION: ESP_LOGV(TAG, "Set distance resolution"); break; - case lowbyte(CMD_SET_LIGHT_CONTROL): + + case CMD_SET_LIGHT_CONTROL: ESP_LOGV(TAG, "Set light control"); break; - case lowbyte(CMD_BT_PASSWORD): + + case CMD_BT_PASSWORD: ESP_LOGV(TAG, "Set bluetooth password"); break; - case lowbyte(CMD_QUERY): // Query parameters response - { - if (buffer[10] != 0xAA) + + case CMD_QUERY: { // Query parameters response + if (this->buffer_data_[10] != 0xAA) return true; // value head=0xAA #ifdef USE_NUMBER /* @@ -567,29 +608,31 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { Still distance range: 14th byte */ std::vector> updates; - updates.push_back(set_number_value(this->max_move_distance_gate_number_, buffer[12])); - updates.push_back(set_number_value(this->max_still_distance_gate_number_, buffer[13])); + updates.push_back(set_number_value(this->max_move_distance_gate_number_, this->buffer_data_[12])); + updates.push_back(set_number_value(this->max_still_distance_gate_number_, this->buffer_data_[13])); /* Moving Sensitivities: 15~23th bytes */ for (std::vector::size_type i = 0; i != this->gate_move_threshold_numbers_.size(); i++) { - updates.push_back(set_number_value(this->gate_move_threshold_numbers_[i], buffer[14 + i])); + updates.push_back(set_number_value(this->gate_move_threshold_numbers_[i], this->buffer_data_[14 + i])); } /* Still Sensitivities: 24~32th bytes */ for (std::vector::size_type i = 0; i != this->gate_still_threshold_numbers_.size(); i++) { - updates.push_back(set_number_value(this->gate_still_threshold_numbers_[i], buffer[23 + i])); + updates.push_back(set_number_value(this->gate_still_threshold_numbers_[i], this->buffer_data_[23 + i])); } /* None Duration: 33~34th bytes */ - updates.push_back(set_number_value(this->timeout_number_, ld2410::two_byte_to_int(buffer[32], buffer[33]))); + updates.push_back(set_number_value(this->timeout_number_, + ld2410::two_byte_to_int(this->buffer_data_[32], this->buffer_data_[33]))); for (auto &update : updates) { update(); } #endif - } break; + break; + } default: break; } @@ -597,59 +640,66 @@ bool LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { return true; } -void LD2410Component::readline_(int readch, uint8_t *buffer, int len) { - static int pos = 0; +void LD2410Component::readline_(int readch) { + if (readch < 0) { + return; // No data available + } - if (readch >= 0) { - if (pos < len - 1) { - buffer[pos++] = readch; - buffer[pos] = 0; + if (this->buffer_pos_ < MAX_LINE_LENGTH - 1) { + this->buffer_data_[this->buffer_pos_++] = readch; + this->buffer_data_[this->buffer_pos_] = 0; + } else { + // We should never get here, but just in case... + ESP_LOGW(TAG, "Max command length exceeded; ignoring"); + this->buffer_pos_ = 0; + } + if (this->buffer_pos_ < 4) { + return; // Not enough data to process yet + } + if (this->buffer_data_[this->buffer_pos_ - 4] == DATA_FRAME_FOOTER[0] && + this->buffer_data_[this->buffer_pos_ - 3] == DATA_FRAME_FOOTER[1] && + this->buffer_data_[this->buffer_pos_ - 2] == DATA_FRAME_FOOTER[2] && + this->buffer_data_[this->buffer_pos_ - 1] == DATA_FRAME_FOOTER[3]) { + ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); + this->handle_periodic_data_(); + this->buffer_pos_ = 0; // Reset position index for next message + } else if (this->buffer_data_[this->buffer_pos_ - 4] == CMD_FRAME_FOOTER[0] && + this->buffer_data_[this->buffer_pos_ - 3] == CMD_FRAME_FOOTER[1] && + this->buffer_data_[this->buffer_pos_ - 2] == CMD_FRAME_FOOTER[2] && + this->buffer_data_[this->buffer_pos_ - 1] == CMD_FRAME_FOOTER[3]) { + ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str()); + if (this->handle_ack_data_()) { + this->buffer_pos_ = 0; // Reset position index for next message } else { - pos = 0; - } - if (pos >= 4) { - if (buffer[pos - 4] == 0xF8 && buffer[pos - 3] == 0xF7 && buffer[pos - 2] == 0xF6 && buffer[pos - 1] == 0xF5) { - ESP_LOGV(TAG, "Will handle Periodic Data"); - this->handle_periodic_data_(buffer, pos); - pos = 0; // Reset position index ready for next time - } else if (buffer[pos - 4] == 0x04 && buffer[pos - 3] == 0x03 && buffer[pos - 2] == 0x02 && - buffer[pos - 1] == 0x01) { - ESP_LOGV(TAG, "Will handle ACK Data"); - if (this->handle_ack_data_(buffer, pos)) { - pos = 0; // Reset position index ready for next time - } else { - ESP_LOGV(TAG, "ACK Data incomplete"); - } - } + ESP_LOGV(TAG, "Ack Data incomplete"); } } } void LD2410Component::set_config_mode_(bool enable) { - uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF; - uint8_t cmd_value[2] = {0x01, 0x00}; - this->send_command_(cmd, enable ? cmd_value : nullptr, 2); + const uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF; + const uint8_t cmd_value[2] = {0x01, 0x00}; + this->send_command_(cmd, enable ? cmd_value : nullptr, sizeof(cmd_value)); } void LD2410Component::set_bluetooth(bool enable) { this->set_config_mode_(true); - uint8_t enable_cmd_value[2] = {0x01, 0x00}; - uint8_t disable_cmd_value[2] = {0x00, 0x00}; - this->send_command_(CMD_BLUETOOTH, enable ? enable_cmd_value : disable_cmd_value, 2); + const uint8_t cmd_value[2] = {enable ? (uint8_t) 0x01 : (uint8_t) 0x00, 0x00}; + this->send_command_(CMD_BLUETOOTH, cmd_value, sizeof(cmd_value)); this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); } void LD2410Component::set_distance_resolution(const std::string &state) { this->set_config_mode_(true); - uint8_t cmd_value[2] = {find_uint8(DISTANCE_RESOLUTIONS_BY_STR, state), 0x00}; - this->send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value, 2); + const uint8_t cmd_value[2] = {find_uint8(DISTANCE_RESOLUTIONS_BY_STR, state), 0x00}; + this->send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value, sizeof(cmd_value)); this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); } void LD2410Component::set_baud_rate(const std::string &state) { this->set_config_mode_(true); - uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00}; - this->send_command_(CMD_SET_BAUD_RATE, cmd_value, 2); + const uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00}; + this->send_command_(CMD_SET_BAUD_RATE, cmd_value, sizeof(cmd_value)); this->set_timeout(200, [this]() { this->restart_(); }); } @@ -661,14 +711,13 @@ void LD2410Component::set_bluetooth_password(const std::string &password) { this->set_config_mode_(true); uint8_t cmd_value[6]; std::copy(password.begin(), password.end(), std::begin(cmd_value)); - this->send_command_(CMD_BT_PASSWORD, cmd_value, 6); + this->send_command_(CMD_BT_PASSWORD, cmd_value, sizeof(cmd_value)); this->set_config_mode_(false); } void LD2410Component::set_engineering_mode(bool enable) { + const uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG; this->set_config_mode_(true); - 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); } @@ -682,14 +731,17 @@ void LD2410Component::factory_reset() { void LD2410Component::restart_() { this->send_command_(CMD_RESTART, nullptr, 0); } void LD2410Component::query_parameters_() { this->send_command_(CMD_QUERY, nullptr, 0); } -void LD2410Component::get_version_() { this->send_command_(CMD_VERSION, nullptr, 0); } + +void LD2410Component::get_version_() { this->send_command_(CMD_QUERY_VERSION, nullptr, 0); } + void LD2410Component::get_mac_() { - uint8_t cmd_value[2] = {0x01, 0x00}; - this->send_command_(CMD_MAC, cmd_value, 2); + const uint8_t cmd_value[2] = {0x01, 0x00}; + this->send_command_(CMD_QUERY_MAC_ADDRESS, cmd_value, sizeof(cmd_value)); } + void LD2410Component::get_distance_resolution_() { this->send_command_(CMD_QUERY_DISTANCE_RESOLUTION, nullptr, 0); } -void LD2410Component::get_light_control_() { this->send_command_(CMD_QUERY_LIGHT_CONTROL, nullptr, 0); } +void LD2410Component::query_light_control_() { this->send_command_(CMD_QUERY_LIGHT_CONTROL, nullptr, 0); } #ifdef USE_NUMBER void LD2410Component::set_max_distances_timeout() { @@ -719,7 +771,7 @@ void LD2410Component::set_max_distances_timeout() { 0x00, 0x00}; this->set_config_mode_(true); - this->send_command_(CMD_MAXDIST_DURATION, value, 18); + this->send_command_(CMD_MAXDIST_DURATION, value, sizeof(value)); delay(50); // NOLINT this->query_parameters_(); this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); @@ -749,17 +801,17 @@ void LD2410Component::set_gate_threshold(uint8_t gate) { uint8_t value[18] = {0x00, 0x00, lowbyte(gate), highbyte(gate), 0x00, 0x00, 0x01, 0x00, lowbyte(motion), highbyte(motion), 0x00, 0x00, 0x02, 0x00, lowbyte(still), highbyte(still), 0x00, 0x00}; - this->send_command_(CMD_GATE_SENS, value, 18); + this->send_command_(CMD_GATE_SENS, value, sizeof(value)); delay(50); // NOLINT this->query_parameters_(); this->set_config_mode_(false); } -void LD2410Component::set_gate_still_threshold_number(int gate, number::Number *n) { +void LD2410Component::set_gate_still_threshold_number(uint8_t gate, number::Number *n) { this->gate_still_threshold_numbers_[gate] = n; } -void LD2410Component::set_gate_move_threshold_number(int gate, number::Number *n) { +void LD2410Component::set_gate_move_threshold_number(uint8_t gate, number::Number *n) { this->gate_move_threshold_numbers_[gate] = n; } #endif @@ -767,35 +819,29 @@ void LD2410Component::set_gate_move_threshold_number(int gate, number::Number *n void LD2410Component::set_light_out_control() { #ifdef USE_NUMBER if (this->light_threshold_number_ != nullptr && this->light_threshold_number_->has_state()) { - this->light_threshold_ = this->light_threshold_number_->state; + this->light_threshold_ = static_cast(this->light_threshold_number_->state); } #endif #ifdef USE_SELECT if (this->light_function_select_ != nullptr && this->light_function_select_->has_state()) { - this->light_function_ = this->light_function_select_->state; + this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->state); } if (this->out_pin_level_select_ != nullptr && this->out_pin_level_select_->has_state()) { - this->out_pin_level_ = this->out_pin_level_select_->state; + this->out_pin_level_ = find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->state); } #endif - if (this->light_function_.empty() || this->out_pin_level_.empty() || this->light_threshold_ < 0) { - return; - } this->set_config_mode_(true); - uint8_t light_function = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_); - uint8_t light_threshold = static_cast(this->light_threshold_); - uint8_t out_pin_level = find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_); - uint8_t value[4] = {light_function, light_threshold, out_pin_level, 0x00}; - this->send_command_(CMD_SET_LIGHT_CONTROL, value, 4); + uint8_t value[4] = {this->light_function_, this->light_threshold_, this->out_pin_level_, 0x00}; + this->send_command_(CMD_SET_LIGHT_CONTROL, value, sizeof(value)); delay(50); // NOLINT - this->get_light_control_(); + this->query_light_control_(); this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); this->set_config_mode_(false); } #ifdef USE_SENSOR -void LD2410Component::set_gate_move_sensor(int gate, sensor::Sensor *s) { this->gate_move_sensors_[gate] = s; } -void LD2410Component::set_gate_still_sensor(int gate, sensor::Sensor *s) { this->gate_still_sensors_[gate] = s; } +void LD2410Component::set_gate_move_sensor(uint8_t gate, sensor::Sensor *s) { this->gate_move_sensors_[gate] = s; } +void LD2410Component::set_gate_still_sensor(uint8_t gate, sensor::Sensor *s) { this->gate_still_sensors_[gate] = s; } #endif } // namespace ld2410 diff --git a/esphome/components/ld2410/ld2410.h b/esphome/components/ld2410/ld2410.h index 1b5f6e3057..8bd1dbcb5a 100644 --- a/esphome/components/ld2410/ld2410.h +++ b/esphome/components/ld2410/ld2410.h @@ -29,45 +29,48 @@ namespace esphome { namespace ld2410 { +static const uint8_t MAX_LINE_LENGTH = 46; // Max characters for serial buffer +static const uint8_t TOTAL_GATES = 9; // Total number of gates supported by the LD2410 + class LD2410Component : public Component, public uart::UARTDevice { -#ifdef USE_SENSOR - SUB_SENSOR(moving_target_distance) - SUB_SENSOR(still_target_distance) - SUB_SENSOR(moving_target_energy) - SUB_SENSOR(still_target_energy) - SUB_SENSOR(light) - SUB_SENSOR(detection_distance) -#endif #ifdef USE_BINARY_SENSOR - SUB_BINARY_SENSOR(target) + SUB_BINARY_SENSOR(out_pin_presence_status) SUB_BINARY_SENSOR(moving_target) SUB_BINARY_SENSOR(still_target) - SUB_BINARY_SENSOR(out_pin_presence_status) + SUB_BINARY_SENSOR(target) +#endif +#ifdef USE_SENSOR + SUB_SENSOR(light) + SUB_SENSOR(detection_distance) + SUB_SENSOR(moving_target_distance) + SUB_SENSOR(moving_target_energy) + SUB_SENSOR(still_target_distance) + SUB_SENSOR(still_target_energy) #endif #ifdef USE_TEXT_SENSOR SUB_TEXT_SENSOR(version) SUB_TEXT_SENSOR(mac) #endif +#ifdef USE_NUMBER + SUB_NUMBER(light_threshold) + SUB_NUMBER(max_move_distance_gate) + SUB_NUMBER(max_still_distance_gate) + SUB_NUMBER(timeout) +#endif #ifdef USE_SELECT - SUB_SELECT(distance_resolution) SUB_SELECT(baud_rate) + SUB_SELECT(distance_resolution) SUB_SELECT(light_function) SUB_SELECT(out_pin_level) #endif #ifdef USE_SWITCH - SUB_SWITCH(engineering_mode) SUB_SWITCH(bluetooth) + SUB_SWITCH(engineering_mode) #endif #ifdef USE_BUTTON - SUB_BUTTON(reset) - SUB_BUTTON(restart) + SUB_BUTTON(factory_reset) SUB_BUTTON(query) -#endif -#ifdef USE_NUMBER - SUB_NUMBER(max_still_distance_gate) - SUB_NUMBER(max_move_distance_gate) - SUB_NUMBER(timeout) - SUB_NUMBER(light_threshold) + SUB_BUTTON(restart) #endif public: @@ -76,14 +79,14 @@ class LD2410Component : public Component, public uart::UARTDevice { void loop() override; void set_light_out_control(); #ifdef USE_NUMBER - void set_gate_still_threshold_number(int gate, number::Number *n); - void set_gate_move_threshold_number(int gate, number::Number *n); + void set_gate_still_threshold_number(uint8_t gate, number::Number *n); + void set_gate_move_threshold_number(uint8_t gate, number::Number *n); void set_max_distances_timeout(); void set_gate_threshold(uint8_t gate); #endif #ifdef USE_SENSOR - void set_gate_move_sensor(int gate, sensor::Sensor *s); - void set_gate_still_sensor(int gate, sensor::Sensor *s); + void set_gate_move_sensor(uint8_t gate, sensor::Sensor *s); + void set_gate_still_sensor(uint8_t gate, sensor::Sensor *s); #endif void set_throttle(uint16_t value) { this->throttle_ = value; }; void set_bluetooth_password(const std::string &password); @@ -96,33 +99,35 @@ class LD2410Component : public Component, public uart::UARTDevice { void factory_reset(); protected: - void send_command_(uint8_t command_str, const uint8_t *command_value, int command_value_len); + void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len); void set_config_mode_(bool enable); - void handle_periodic_data_(uint8_t *buffer, int len); - bool handle_ack_data_(uint8_t *buffer, int len); - void readline_(int readch, uint8_t *buffer, int len); + void handle_periodic_data_(); + bool handle_ack_data_(); + void readline_(int readch); void query_parameters_(); void get_version_(); void get_mac_(); void get_distance_resolution_(); - void get_light_control_(); + void query_light_control_(); void restart_(); - int32_t last_periodic_millis_ = 0; - int32_t last_engineering_mode_change_millis_ = 0; - uint16_t throttle_; - float light_threshold_ = -1; - std::string version_; - std::string mac_; - std::string out_pin_level_; - std::string light_function_; + uint32_t last_periodic_millis_ = 0; + uint16_t throttle_ = 0; + uint8_t light_function_ = 0; + uint8_t light_threshold_ = 0; + uint8_t out_pin_level_ = 0; + uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer + uint8_t buffer_data_[MAX_LINE_LENGTH]; + uint8_t mac_address_[6] = {0, 0, 0, 0, 0, 0}; + uint8_t version_[6] = {0, 0, 0, 0, 0, 0}; + bool bluetooth_on_{false}; #ifdef USE_NUMBER - std::vector gate_still_threshold_numbers_ = std::vector(9); - std::vector gate_move_threshold_numbers_ = std::vector(9); + std::vector gate_move_threshold_numbers_ = std::vector(TOTAL_GATES); + std::vector gate_still_threshold_numbers_ = std::vector(TOTAL_GATES); #endif #ifdef USE_SENSOR - std::vector gate_still_sensors_ = std::vector(9); - std::vector gate_move_sensors_ = std::vector(9); + std::vector gate_move_sensors_ = std::vector(TOTAL_GATES); + std::vector gate_still_sensors_ = std::vector(TOTAL_GATES); #endif }; diff --git a/esphome/components/ld2450/ld2450.cpp b/esphome/components/ld2450/ld2450.cpp index 0e1123db1a..4b87f1cea4 100644 --- a/esphome/components/ld2450/ld2450.cpp +++ b/esphome/components/ld2450/ld2450.cpp @@ -1,5 +1,6 @@ #include "ld2450.h" #include +#include #ifdef USE_NUMBER #include "esphome/components/number/number.h" #endif @@ -123,16 +124,11 @@ static const uint8_t CMD_SET_ZONE = 0xC2; static inline uint16_t convert_seconds_to_ms(uint16_t value) { return value * 1000; }; -static inline std::string convert_signed_int_to_hex(int value) { - auto value_as_str = str_snprintf("%04x", 4, value & 0xFFFF); - return value_as_str; -} - static inline void convert_int_values_to_hex(const int *values, uint8_t *bytes) { for (int i = 0; i < 4; i++) { - std::string temp_hex = convert_signed_int_to_hex(values[i]); - bytes[i * 2] = std::stoi(temp_hex.substr(2, 2), nullptr, 16); // Store high byte - bytes[i * 2 + 1] = std::stoi(temp_hex.substr(0, 2), nullptr, 16); // Store low byte + uint16_t val = values[i] & 0xFFFF; + bytes[i * 2] = val & 0xFF; // Store low byte first (little-endian) + bytes[i * 2 + 1] = (val >> 8) & 0xFF; // Store high byte second } } @@ -428,6 +424,12 @@ void LD2450Component::send_command_(uint8_t command, const uint8_t *command_valu // [AA FF 03 00] [0E 03 B1 86 10 00 40 01] [00 00 00 00 00 00 00 00] [00 00 00 00 00 00 00 00] [55 CC] // Header Target 1 Target 2 Target 3 End void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { + // Early throttle check - moved before any processing to save CPU cycles + if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) { + ESP_LOGV(TAG, "Throttling: %d", this->throttle_); + return; + } + if (len < 29) { // header (4 bytes) + 8 x 3 target data + footer (2 bytes) ESP_LOGE(TAG, "Invalid message length"); return; @@ -441,11 +443,6 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { return; } - 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_ = App.get_loop_component_start_time(); int16_t target_count = 0; @@ -473,7 +470,10 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { if (sx != nullptr) { val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]); tx = val; - sx->publish_state(val); + if (this->cached_target_data_[index].x != val) { + sx->publish_state(val); + this->cached_target_data_[index].x = val; + } } // Y start = TARGET_Y + index * 8; @@ -481,14 +481,20 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { if (sy != nullptr) { val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]); ty = val; - sy->publish_state(val); + if (this->cached_target_data_[index].y != val) { + sy->publish_state(val); + this->cached_target_data_[index].y = val; + } } // RESOLUTION start = TARGET_RESOLUTION + index * 8; sensor::Sensor *sr = this->move_resolution_sensors_[index]; if (sr != nullptr) { val = (buffer[start + 1] << 8) | buffer[start]; - sr->publish_state(val); + if (this->cached_target_data_[index].resolution != val) { + sr->publish_state(val); + this->cached_target_data_[index].resolution = val; + } } #endif // SPEED @@ -502,13 +508,17 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { #ifdef USE_SENSOR sensor::Sensor *ss = this->move_speed_sensors_[index]; if (ss != nullptr) { - ss->publish_state(val); + if (this->cached_target_data_[index].speed != val) { + ss->publish_state(val); + this->cached_target_data_[index].speed = val; + } } #endif // DISTANCE - val = (uint16_t) sqrt( - pow(ld2450::decode_coordinate(buffer[TARGET_X + index * 8], buffer[(TARGET_X + index * 8) + 1]), 2) + - pow(ld2450::decode_coordinate(buffer[TARGET_Y + index * 8], buffer[(TARGET_Y + index * 8) + 1]), 2)); + // Optimized: use already decoded tx and ty values, replace pow() with multiplication + int32_t x_squared = (int32_t) tx * tx; + int32_t y_squared = (int32_t) ty * ty; + val = (uint16_t) sqrt(x_squared + y_squared); td = val; if (val > 0) { target_count++; @@ -516,7 +526,10 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { #ifdef USE_SENSOR sensor::Sensor *sd = this->move_distance_sensors_[index]; if (sd != nullptr) { - sd->publish_state(val); + if (this->cached_target_data_[index].distance != val) { + sd->publish_state(val); + this->cached_target_data_[index].distance = val; + } } // ANGLE angle = calculate_angle(static_cast(ty), static_cast(td)); @@ -525,7 +538,11 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { } sensor::Sensor *sa = this->move_angle_sensors_[index]; if (sa != nullptr) { - sa->publish_state(angle); + if (std::isnan(this->cached_target_data_[index].angle) || + std::abs(this->cached_target_data_[index].angle - angle) > 0.1f) { + sa->publish_state(angle); + this->cached_target_data_[index].angle = angle; + } } #endif #ifdef USE_TEXT_SENSOR @@ -536,7 +553,10 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { } text_sensor::TextSensor *tsd = this->direction_text_sensors_[index]; if (tsd != nullptr) { - tsd->publish_state(direction); + if (this->cached_target_data_[index].direction != direction) { + tsd->publish_state(direction); + this->cached_target_data_[index].direction = direction; + } } #endif @@ -563,32 +583,50 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { // Publish Still Target Count in Zones sensor::Sensor *szstc = this->zone_still_target_count_sensors_[index]; if (szstc != nullptr) { - szstc->publish_state(zone_still_targets); + if (this->cached_zone_data_[index].still_count != zone_still_targets) { + szstc->publish_state(zone_still_targets); + this->cached_zone_data_[index].still_count = zone_still_targets; + } } // Publish Moving Target Count in Zones sensor::Sensor *szmtc = this->zone_moving_target_count_sensors_[index]; if (szmtc != nullptr) { - szmtc->publish_state(zone_moving_targets); + if (this->cached_zone_data_[index].moving_count != zone_moving_targets) { + szmtc->publish_state(zone_moving_targets); + this->cached_zone_data_[index].moving_count = zone_moving_targets; + } } // Publish All Target Count in Zones sensor::Sensor *sztc = this->zone_target_count_sensors_[index]; if (sztc != nullptr) { - sztc->publish_state(zone_all_targets); + if (this->cached_zone_data_[index].total_count != zone_all_targets) { + sztc->publish_state(zone_all_targets); + this->cached_zone_data_[index].total_count = zone_all_targets; + } } } // End loop thru zones // Target Count if (this->target_count_sensor_ != nullptr) { - this->target_count_sensor_->publish_state(target_count); + if (this->cached_global_data_.target_count != target_count) { + this->target_count_sensor_->publish_state(target_count); + this->cached_global_data_.target_count = target_count; + } } // Still Target Count if (this->still_target_count_sensor_ != nullptr) { - this->still_target_count_sensor_->publish_state(still_target_count); + if (this->cached_global_data_.still_count != still_target_count) { + this->still_target_count_sensor_->publish_state(still_target_count); + this->cached_global_data_.still_count = still_target_count; + } } // Moving Target Count if (this->moving_target_count_sensor_ != nullptr) { - this->moving_target_count_sensor_->publish_state(moving_target_count); + if (this->cached_global_data_.moving_count != moving_target_count) { + this->moving_target_count_sensor_->publish_state(moving_target_count); + this->cached_global_data_.moving_count = moving_target_count; + } } #endif diff --git a/esphome/components/ld2450/ld2450.h b/esphome/components/ld2450/ld2450.h index b0c19dc96c..5ddccab638 100644 --- a/esphome/components/ld2450/ld2450.h +++ b/esphome/components/ld2450/ld2450.h @@ -5,6 +5,8 @@ #include "esphome/core/defines.h" #include "esphome/core/helpers.h" #include "esphome/core/preferences.h" +#include +#include #ifdef USE_SENSOR #include "esphome/components/sensor/sensor.h" #endif @@ -100,7 +102,7 @@ class LD2450Component : public Component, public uart::UARTDevice { void dump_config() override; void loop() override; void set_presence_timeout(); - void set_throttle(uint16_t value) { this->throttle_ = value; }; + void set_throttle(uint16_t value) { this->throttle_ = value; } void read_all_info(); void query_zone_info(); void restart_and_read_all_info(); @@ -164,6 +166,32 @@ class LD2450Component : public Component, public uart::UARTDevice { Zone zone_config_[MAX_ZONES]; std::string version_{}; std::string mac_{}; + + // Change detection - cache previous values to avoid redundant publishes + // All values are initialized to sentinel values that are outside the valid sensor ranges + // to ensure the first real measurement is always published + struct CachedTargetData { + int16_t x = std::numeric_limits::min(); // -32768, outside range of -4860 to 4860 + int16_t y = std::numeric_limits::min(); // -32768, outside range of 0 to 7560 + int16_t speed = std::numeric_limits::min(); // -32768, outside practical sensor range + uint16_t resolution = std::numeric_limits::max(); // 65535, unlikely resolution value + uint16_t distance = std::numeric_limits::max(); // 65535, outside range of 0 to ~8990 + float angle = NAN; // NAN, safe sentinel for floats + std::string direction = ""; // Empty string, will differ from any real direction + } cached_target_data_[MAX_TARGETS]; + + struct CachedZoneData { + uint8_t still_count = std::numeric_limits::max(); // 255, unlikely zone count + uint8_t moving_count = std::numeric_limits::max(); // 255, unlikely zone count + uint8_t total_count = std::numeric_limits::max(); // 255, unlikely zone count + } cached_zone_data_[MAX_ZONES]; + + struct CachedGlobalData { + uint8_t target_count = std::numeric_limits::max(); // 255, max 3 targets possible + uint8_t still_count = std::numeric_limits::max(); // 255, max 3 targets possible + uint8_t moving_count = std::numeric_limits::max(); // 255, max 3 targets possible + } cached_global_data_; + #ifdef USE_NUMBER ESPPreferenceObject pref_; // only used when numbers are in use ZoneOfNumbers zone_numbers_[MAX_ZONES]; diff --git a/esphome/components/nextion/display.py b/esphome/components/nextion/display.py index 0aa5efeba7..420f8f69c5 100644 --- a/esphome/components/nextion/display.py +++ b/esphome/components/nextion/display.py @@ -167,6 +167,7 @@ async def to_code(config): cg.add(var.set_wake_up_page(config[CONF_WAKE_UP_PAGE])) if CONF_START_UP_PAGE in config: + cg.add_define("USE_NEXTION_CONF_START_UP_PAGE") cg.add(var.set_start_up_page(config[CONF_START_UP_PAGE])) cg.add(var.set_auto_wake_on_touch(config[CONF_AUTO_WAKE_ON_TOUCH])) diff --git a/esphome/components/nextion/nextion.cpp b/esphome/components/nextion/nextion.cpp index bb75385d8c..bcb1aced9a 100644 --- a/esphome/components/nextion/nextion.cpp +++ b/esphome/components/nextion/nextion.cpp @@ -167,13 +167,15 @@ void Nextion::dump_config() { ESP_LOGCONFIG(TAG, " Touch Timeout: %" PRIu16, this->touch_sleep_timeout_); } - if (this->wake_up_page_ != -1) { - ESP_LOGCONFIG(TAG, " Wake Up Page: %d", this->wake_up_page_); + if (this->wake_up_page_ != 255) { + ESP_LOGCONFIG(TAG, " Wake Up Page: %u", this->wake_up_page_); } - if (this->start_up_page_ != -1) { - ESP_LOGCONFIG(TAG, " Start Up Page: %d", this->start_up_page_); +#ifdef USE_NEXTION_CONF_START_UP_PAGE + if (this->start_up_page_ != 255) { + ESP_LOGCONFIG(TAG, " Start Up Page: %u", this->start_up_page_); } +#endif // USE_NEXTION_CONF_START_UP_PAGE #ifdef USE_NEXTION_COMMAND_SPACING ESP_LOGCONFIG(TAG, " Cmd spacing: %u ms", this->command_pacer_.get_spacing()); @@ -301,12 +303,14 @@ void Nextion::loop() { this->set_backlight_brightness(this->brightness_.value()); } +#ifdef USE_NEXTION_CONF_START_UP_PAGE // Check if a startup page has been set and send the command - if (this->start_up_page_ >= 0) { + if (this->start_up_page_ != 255) { this->goto_page(this->start_up_page_); } +#endif // USE_NEXTION_CONF_START_UP_PAGE - if (this->wake_up_page_ >= 0) { + if (this->wake_up_page_ != 255) { this->set_wake_up_page(this->wake_up_page_); } diff --git a/esphome/components/nextion/nextion.h b/esphome/components/nextion/nextion.h index 0b77d234f5..f5fa26b98c 100644 --- a/esphome/components/nextion/nextion.h +++ b/esphome/components/nextion/nextion.h @@ -1194,7 +1194,7 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe /** * Sets which page Nextion loads when exiting sleep mode. Note this can be set even when Nextion is in sleep mode. - * @param wake_up_page The page id, from 0 to the last page in Nextion. Set -1 (not set to any existing page) to + * @param wake_up_page The page id, from 0 to the last page in Nextion. Set 255 (not set to any existing page) to * wakes up to current page. * * Example: @@ -1204,11 +1204,12 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * The display will wake up to page 2. */ - void set_wake_up_page(int16_t wake_up_page = -1); + void set_wake_up_page(uint8_t wake_up_page = 255); +#ifdef USE_NEXTION_CONF_START_UP_PAGE /** * Sets which page Nextion loads when connecting to ESPHome. - * @param start_up_page The page id, from 0 to the last page in Nextion. Set -1 (not set to any existing page) to + * @param start_up_page The page id, from 0 to the last page in Nextion. Set 255 (not set to any existing page) to * wakes up to current page. * * Example: @@ -1218,7 +1219,8 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe * * The display will go to page 2 when it establishes a connection to ESPHome. */ - void set_start_up_page(int16_t start_up_page = -1) { this->start_up_page_ = start_up_page; } + void set_start_up_page(uint8_t start_up_page = 255) { this->start_up_page_ = start_up_page; } +#endif // USE_NEXTION_CONF_START_UP_PAGE /** * Sets if Nextion should auto-wake from sleep when touch press occurs. @@ -1344,8 +1346,10 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe void process_serial_(); bool is_updating_ = false; uint16_t touch_sleep_timeout_ = 0; - int16_t wake_up_page_ = -1; - int16_t start_up_page_ = -1; + uint8_t wake_up_page_ = 255; +#ifdef USE_NEXTION_CONF_START_UP_PAGE + uint8_t start_up_page_ = 255; +#endif // USE_NEXTION_CONF_START_UP_PAGE bool auto_wake_on_touch_ = true; bool exit_reparse_on_start_ = false; bool skip_connection_handshake_ = false; diff --git a/esphome/components/nextion/nextion_commands.cpp b/esphome/components/nextion/nextion_commands.cpp index 84aacd1868..f8307c6c4b 100644 --- a/esphome/components/nextion/nextion_commands.cpp +++ b/esphome/components/nextion/nextion_commands.cpp @@ -10,7 +10,7 @@ static const char *const TAG = "nextion"; // Sleep safe commands void Nextion::soft_reset() { this->send_command_("rest"); } -void Nextion::set_wake_up_page(int16_t wake_up_page) { +void Nextion::set_wake_up_page(uint8_t wake_up_page) { this->wake_up_page_ = wake_up_page; this->add_no_result_to_queue_with_set_internal_("wake_up_page", "wup", wake_up_page, true); } diff --git a/esphome/components/scd4x/scd4x.cpp b/esphome/components/scd4x/scd4x.cpp index f617ffe276..4a700b70c2 100644 --- a/esphome/components/scd4x/scd4x.cpp +++ b/esphome/components/scd4x/scd4x.cpp @@ -7,6 +7,8 @@ namespace scd4x { static const char *const TAG = "scd4x"; +static const uint16_t SCD41_ID = 0x1408; +static const uint16_t SCD40_ID = 0x440; static const uint16_t SCD4X_CMD_GET_SERIAL_NUMBER = 0x3682; static const uint16_t SCD4X_CMD_TEMPERATURE_OFFSET = 0x241d; static const uint16_t SCD4X_CMD_ALTITUDE_COMPENSATION = 0x2427; @@ -23,8 +25,6 @@ static const uint16_t SCD4X_CMD_STOP_MEASUREMENTS = 0x3f86; static const uint16_t SCD4X_CMD_FACTORY_RESET = 0x3632; static const uint16_t SCD4X_CMD_GET_FEATURESET = 0x202f; static const float SCD4X_TEMPERATURE_OFFSET_MULTIPLIER = (1 << 16) / 175.0f; -static const uint16_t SCD41_ID = 0x1408; -static const uint16_t SCD40_ID = 0x440; void SCD4XComponent::setup() { ESP_LOGCONFIG(TAG, "Running setup"); @@ -51,47 +51,66 @@ void SCD4XComponent::setup() { if (!this->write_command(SCD4X_CMD_TEMPERATURE_OFFSET, (uint16_t) (temperature_offset_ * SCD4X_TEMPERATURE_OFFSET_MULTIPLIER))) { - ESP_LOGE(TAG, "Error setting temperature offset."); + ESP_LOGE(TAG, "Error setting temperature offset"); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); return; } - // If pressure compensation available use it - // else use altitude - if (ambient_pressure_compensation_) { - if (!this->update_ambient_pressure_compensation_(ambient_pressure_)) { - ESP_LOGE(TAG, "Error setting ambient pressure compensation."); + // If pressure compensation available use it, else use altitude + if (this->ambient_pressure_compensation_) { + if (!this->update_ambient_pressure_compensation_(this->ambient_pressure_)) { + ESP_LOGE(TAG, "Error setting ambient pressure compensation"); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); return; } } else { - if (!this->write_command(SCD4X_CMD_ALTITUDE_COMPENSATION, altitude_compensation_)) { - ESP_LOGE(TAG, "Error setting altitude compensation."); + if (!this->write_command(SCD4X_CMD_ALTITUDE_COMPENSATION, this->altitude_compensation_)) { + ESP_LOGE(TAG, "Error setting altitude compensation"); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); return; } } - if (!this->write_command(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, enable_asc_ ? 1 : 0)) { - ESP_LOGE(TAG, "Error setting automatic self calibration."); + if (!this->write_command(SCD4X_CMD_AUTOMATIC_SELF_CALIBRATION, this->enable_asc_ ? 1 : 0)) { + ESP_LOGE(TAG, "Error setting automatic self calibration"); this->error_code_ = MEASUREMENT_INIT_FAILED; this->mark_failed(); return; } - initialized_ = true; + this->initialized_ = true; // Finally start sensor measurements this->start_measurement_(); - ESP_LOGD(TAG, "Sensor initialized"); }); }); } void SCD4XComponent::dump_config() { - ESP_LOGCONFIG(TAG, "scd4x:"); + static const char *const MM_PERIODIC_STR = "Periodic (5s)"; + static const char *const MM_LOW_POWER_PERIODIC_STR = "Low power periodic (30s)"; + static const char *const MM_SINGLE_SHOT_STR = "Single shot"; + static const char *const MM_SINGLE_SHOT_RHT_ONLY_STR = "Single shot rht only"; + const char *measurement_mode_str = MM_PERIODIC_STR; + + switch (this->measurement_mode_) { + case PERIODIC: + // measurement_mode_str = MM_PERIODIC_STR; + break; + case LOW_POWER_PERIODIC: + measurement_mode_str = MM_LOW_POWER_PERIODIC_STR; + break; + case SINGLE_SHOT: + measurement_mode_str = MM_SINGLE_SHOT_STR; + break; + case SINGLE_SHOT_RHT_ONLY: + measurement_mode_str = MM_SINGLE_SHOT_RHT_ONLY_STR; + break; + } + + ESP_LOGCONFIG(TAG, "SCD4X:"); LOG_I2C_DEVICE(this); if (this->is_failed()) { switch (this->error_code_) { @@ -102,16 +121,20 @@ void SCD4XComponent::dump_config() { ESP_LOGW(TAG, "Measurement Initialization failed"); break; case SERIAL_NUMBER_IDENTIFICATION_FAILED: - ESP_LOGW(TAG, "Unable to read sensor firmware version"); + ESP_LOGW(TAG, "Unable to read firmware version"); break; default: ESP_LOGW(TAG, "Unknown setup error"); break; } } - ESP_LOGCONFIG(TAG, " Automatic self calibration: %s", ONOFF(this->enable_asc_)); + ESP_LOGCONFIG(TAG, + " Automatic self calibration: %s\n" + " Measurement mode: %s\n" + " Temperature offset: %.2f °C", + ONOFF(this->enable_asc_), measurement_mode_str, this->temperature_offset_); if (this->ambient_pressure_source_ != nullptr) { - ESP_LOGCONFIG(TAG, " Dynamic ambient pressure compensation using sensor '%s'", + ESP_LOGCONFIG(TAG, " Dynamic ambient pressure compensation using '%s'", this->ambient_pressure_source_->get_name().c_str()); } else { if (this->ambient_pressure_compensation_) { @@ -126,21 +149,6 @@ void SCD4XComponent::dump_config() { this->altitude_compensation_); } } - switch (this->measurement_mode_) { - case PERIODIC: - ESP_LOGCONFIG(TAG, " Measurement mode: periodic (5s)"); - break; - case LOW_POWER_PERIODIC: - ESP_LOGCONFIG(TAG, " Measurement mode: low power periodic (30s)"); - break; - case SINGLE_SHOT: - ESP_LOGCONFIG(TAG, " Measurement mode: single shot"); - break; - case SINGLE_SHOT_RHT_ONLY: - ESP_LOGCONFIG(TAG, " Measurement mode: single shot rht only"); - break; - } - ESP_LOGCONFIG(TAG, " Temperature offset: %.2f °C", this->temperature_offset_); LOG_UPDATE_INTERVAL(this); LOG_SENSOR(" ", "CO2", this->co2_sensor_); LOG_SENSOR(" ", "Temperature", this->temperature_sensor_); @@ -148,20 +156,20 @@ void SCD4XComponent::dump_config() { } void SCD4XComponent::update() { - if (!initialized_) { + if (!this->initialized_) { return; } if (this->ambient_pressure_source_ != nullptr) { float pressure = this->ambient_pressure_source_->state; if (!std::isnan(pressure)) { - set_ambient_pressure_compensation(pressure); + this->set_ambient_pressure_compensation(pressure); } } uint32_t wait_time = 0; if (this->measurement_mode_ == SINGLE_SHOT || this->measurement_mode_ == SINGLE_SHOT_RHT_ONLY) { - start_measurement_(); + this->start_measurement_(); wait_time = this->measurement_mode_ == SINGLE_SHOT ? 5000 : 50; // Single shot measurement takes 5 secs rht mode 50 ms } @@ -176,12 +184,12 @@ void SCD4XComponent::update() { if (!this->read_data(raw_read_status) || raw_read_status == 0x00) { this->status_set_warning(); - ESP_LOGW(TAG, "Data not ready yet!"); + ESP_LOGW(TAG, "Data not ready"); return; } if (!this->write_command(SCD4X_CMD_READ_MEASUREMENT)) { - ESP_LOGW(TAG, "Error reading measurement!"); + ESP_LOGW(TAG, "Error reading measurement"); this->status_set_warning(); return; // NO RETRY } @@ -218,7 +226,7 @@ bool SCD4XComponent::perform_forced_calibration(uint16_t current_co2_concentrati } this->set_timeout(500, [this, current_co2_concentration]() { if (this->write_command(SCD4X_CMD_PERFORM_FORCED_CALIBRATION, current_co2_concentration)) { - ESP_LOGD(TAG, "setting forced calibration Co2 level %d ppm", current_co2_concentration); + ESP_LOGD(TAG, "Setting forced calibration Co2 level %d ppm", current_co2_concentration); // frc takes 400 ms // because this method will be used very rarly // the simple approach with delay is ok @@ -226,11 +234,11 @@ bool SCD4XComponent::perform_forced_calibration(uint16_t current_co2_concentrati if (!this->start_measurement_()) { return false; } else { - ESP_LOGD(TAG, "forced calibration complete"); + ESP_LOGD(TAG, "Forced calibration complete"); } return true; } else { - ESP_LOGE(TAG, "force calibration failed"); + ESP_LOGE(TAG, "Force calibration failed"); this->error_code_ = FRC_FAILED; this->status_set_warning(); return false; @@ -261,25 +269,25 @@ bool SCD4XComponent::factory_reset() { void SCD4XComponent::set_ambient_pressure_compensation(float pressure_in_hpa) { ambient_pressure_compensation_ = true; uint16_t new_ambient_pressure = (uint16_t) pressure_in_hpa; - if (!initialized_) { - ambient_pressure_ = new_ambient_pressure; + if (!this->initialized_) { + this->ambient_pressure_ = new_ambient_pressure; return; } // Only send pressure value if it has changed since last update - if (new_ambient_pressure != ambient_pressure_) { - update_ambient_pressure_compensation_(new_ambient_pressure); - ambient_pressure_ = new_ambient_pressure; + if (new_ambient_pressure != this->ambient_pressure_) { + this->update_ambient_pressure_compensation_(new_ambient_pressure); + this->ambient_pressure_ = new_ambient_pressure; } else { - ESP_LOGD(TAG, "ambient pressure compensation skipped - no change required"); + ESP_LOGD(TAG, "Ambient pressure compensation skipped; no change required"); } } bool SCD4XComponent::update_ambient_pressure_compensation_(uint16_t pressure_in_hpa) { if (this->write_command(SCD4X_CMD_AMBIENT_PRESSURE_COMPENSATION, pressure_in_hpa)) { - ESP_LOGD(TAG, "setting ambient pressure compensation to %d hPa", pressure_in_hpa); + ESP_LOGD(TAG, "Setting ambient pressure compensation to %d hPa", pressure_in_hpa); return true; } else { - ESP_LOGE(TAG, "Error setting ambient pressure compensation."); + ESP_LOGE(TAG, "Error setting ambient pressure compensation"); return false; } } @@ -304,7 +312,7 @@ bool SCD4XComponent::start_measurement_() { static uint8_t remaining_retries = 3; while (remaining_retries) { if (!this->write_command(measurement_command)) { - ESP_LOGE(TAG, "Error starting measurements."); + ESP_LOGE(TAG, "Error starting measurements"); this->error_code_ = MEASUREMENT_INIT_FAILED; this->status_set_warning(); if (--remaining_retries == 0) diff --git a/esphome/components/scd4x/scd4x.h b/esphome/components/scd4x/scd4x.h index f2efb28ac1..237d226107 100644 --- a/esphome/components/scd4x/scd4x.h +++ b/esphome/components/scd4x/scd4x.h @@ -8,14 +8,20 @@ namespace esphome { namespace scd4x { -enum ERRORCODE { +enum ErrorCode : uint8_t { COMMUNICATION_FAILED, SERIAL_NUMBER_IDENTIFICATION_FAILED, MEASUREMENT_INIT_FAILED, FRC_FAILED, - UNKNOWN + UNKNOWN, +}; + +enum MeasurementMode : uint8_t { + PERIODIC, + LOW_POWER_PERIODIC, + SINGLE_SHOT, + SINGLE_SHOT_RHT_ONLY, }; -enum MeasurementMode { PERIODIC, LOW_POWER_PERIODIC, SINGLE_SHOT, SINGLE_SHOT_RHT_ONLY }; class SCD4XComponent : public PollingComponent, public sensirion_common::SensirionI2CDevice { public: @@ -39,15 +45,14 @@ class SCD4XComponent : public PollingComponent, public sensirion_common::Sensiri protected: bool update_ambient_pressure_compensation_(uint16_t pressure_in_hpa); bool start_measurement_(); - ERRORCODE error_code_; - bool initialized_{false}; - - float temperature_offset_; uint16_t altitude_compensation_; - bool ambient_pressure_compensation_; uint16_t ambient_pressure_; + bool initialized_{false}; + bool ambient_pressure_compensation_; bool enable_asc_; + float temperature_offset_; + ErrorCode error_code_; MeasurementMode measurement_mode_{PERIODIC}; sensor::Sensor *co2_sensor_{nullptr}; sensor::Sensor *temperature_sensor_{nullptr}; diff --git a/esphome/components/web_server_idf/web_server_idf.cpp b/esphome/components/web_server_idf/web_server_idf.cpp index 9478e4748c..d2447681f5 100644 --- a/esphome/components/web_server_idf/web_server_idf.cpp +++ b/esphome/components/web_server_idf/web_server_idf.cpp @@ -37,6 +37,15 @@ namespace web_server_idf { static const char *const TAG = "web_server_idf"; +// Global instance to avoid guard variable (saves 8 bytes) +// This is initialized at program startup before any threads +namespace { +// NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) +DefaultHeaders default_headers_instance; +} // namespace + +DefaultHeaders &DefaultHeaders::Instance() { return default_headers_instance; } + void AsyncWebServer::end() { if (this->server_) { httpd_stop(this->server_); diff --git a/esphome/components/web_server_idf/web_server_idf.h b/esphome/components/web_server_idf/web_server_idf.h index 8de25c8e96..e8e40ef9b0 100644 --- a/esphome/components/web_server_idf/web_server_idf.h +++ b/esphome/components/web_server_idf/web_server_idf.h @@ -328,10 +328,7 @@ class DefaultHeaders { void addHeader(const char *name, const char *value) { this->headers_.emplace_back(name, value); } // NOLINTNEXTLINE(readability-identifier-naming) - static DefaultHeaders &Instance() { - static DefaultHeaders instance; - return instance; - } + static DefaultHeaders &Instance(); protected: std::vector> headers_; diff --git a/esphome/core/color.cpp b/esphome/core/color.cpp index 58d995db2f..7e390b2354 100644 --- a/esphome/core/color.cpp +++ b/esphome/core/color.cpp @@ -2,10 +2,8 @@ namespace esphome { -const Color Color::BLACK(0, 0, 0, 0); -const Color Color::WHITE(255, 255, 255, 255); - -const Color COLOR_BLACK(0, 0, 0, 0); -const Color COLOR_WHITE(255, 255, 255, 255); +// C++20 constinit ensures compile-time initialization (stored in ROM) +constinit const Color Color::BLACK(0, 0, 0, 0); +constinit const Color Color::WHITE(255, 255, 255, 255); } // namespace esphome diff --git a/esphome/core/color.h b/esphome/core/color.h index 1c43fd9d3e..2b307bb438 100644 --- a/esphome/core/color.h +++ b/esphome/core/color.h @@ -5,7 +5,9 @@ namespace esphome { -inline static uint8_t esp_scale8(uint8_t i, uint8_t scale) { return (uint16_t(i) * (1 + uint16_t(scale))) / 256; } +inline static constexpr uint8_t esp_scale8(uint8_t i, uint8_t scale) { + return (uint16_t(i) * (1 + uint16_t(scale))) / 256; +} struct Color { union { @@ -31,17 +33,20 @@ struct Color { uint32_t raw_32; }; - inline Color() ESPHOME_ALWAYS_INLINE : r(0), g(0), b(0), w(0) {} // NOLINT - inline Color(uint8_t red, uint8_t green, uint8_t blue) ESPHOME_ALWAYS_INLINE : r(red), g(green), b(blue), w(0) {} + inline constexpr Color() ESPHOME_ALWAYS_INLINE : raw_32(0) {} // NOLINT + inline constexpr Color(uint8_t red, uint8_t green, uint8_t blue) ESPHOME_ALWAYS_INLINE : r(red), + g(green), + b(blue), + w(0) {} - inline Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) ESPHOME_ALWAYS_INLINE : r(red), - g(green), - b(blue), - w(white) {} - inline explicit Color(uint32_t colorcode) ESPHOME_ALWAYS_INLINE : r((colorcode >> 16) & 0xFF), - g((colorcode >> 8) & 0xFF), - b((colorcode >> 0) & 0xFF), - w((colorcode >> 24) & 0xFF) {} + inline constexpr Color(uint8_t red, uint8_t green, uint8_t blue, uint8_t white) ESPHOME_ALWAYS_INLINE : r(red), + g(green), + b(blue), + w(white) {} + inline explicit constexpr Color(uint32_t colorcode) ESPHOME_ALWAYS_INLINE : r((colorcode >> 16) & 0xFF), + g((colorcode >> 8) & 0xFF), + b((colorcode >> 0) & 0xFF), + w((colorcode >> 24) & 0xFF) {} inline bool is_on() ESPHOME_ALWAYS_INLINE { return this->raw_32 != 0; } @@ -169,9 +174,4 @@ struct Color { static const Color WHITE; }; -ESPDEPRECATED("Use Color::BLACK instead of COLOR_BLACK", "v1.21") -extern const Color COLOR_BLACK; -ESPDEPRECATED("Use Color::WHITE instead of COLOR_WHITE", "v1.21") -extern const Color COLOR_WHITE; - } // namespace esphome