mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-31 15:12:06 +00:00 
			
		
		
		
	[ld2450] Reduce CPU usage, eliminate redundant sensor updates (#9334)
This commit is contained in:
		| @@ -1,5 +1,6 @@ | |||||||
| #include "ld2450.h" | #include "ld2450.h" | ||||||
| #include <utility> | #include <utility> | ||||||
|  | #include <cmath> | ||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
| #include "esphome/components/number/number.h" | #include "esphome/components/number/number.h" | ||||||
| #endif | #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 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) { | static inline void convert_int_values_to_hex(const int *values, uint8_t *bytes) { | ||||||
|   for (int i = 0; i < 4; i++) { |   for (int i = 0; i < 4; i++) { | ||||||
|     std::string temp_hex = convert_signed_int_to_hex(values[i]); |     uint16_t val = values[i] & 0xFFFF; | ||||||
|     bytes[i * 2] = std::stoi(temp_hex.substr(2, 2), nullptr, 16);      // Store high byte |     bytes[i * 2] = val & 0xFF;             // Store low byte first (little-endian) | ||||||
|     bytes[i * 2 + 1] = std::stoi(temp_hex.substr(0, 2), nullptr, 16);  // Store low byte |     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] | //  [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 | //   Header       Target 1                  Target 2                  Target 3                  End | ||||||
| void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { | 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) |   if (len < 29) {  // header (4 bytes) + 8 x 3 target data + footer (2 bytes) | ||||||
|     ESP_LOGE(TAG, "Invalid message length"); |     ESP_LOGE(TAG, "Invalid message length"); | ||||||
|     return; |     return; | ||||||
| @@ -441,11 +443,6 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { | |||||||
|     return; |     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(); |   this->last_periodic_millis_ = App.get_loop_component_start_time(); | ||||||
|  |  | ||||||
|   int16_t target_count = 0; |   int16_t target_count = 0; | ||||||
| @@ -473,7 +470,10 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { | |||||||
|     if (sx != nullptr) { |     if (sx != nullptr) { | ||||||
|       val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]); |       val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]); | ||||||
|       tx = val; |       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 |     // Y | ||||||
|     start = TARGET_Y + index * 8; |     start = TARGET_Y + index * 8; | ||||||
| @@ -481,14 +481,20 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { | |||||||
|     if (sy != nullptr) { |     if (sy != nullptr) { | ||||||
|       val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]); |       val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]); | ||||||
|       ty = val; |       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 |     // RESOLUTION | ||||||
|     start = TARGET_RESOLUTION + index * 8; |     start = TARGET_RESOLUTION + index * 8; | ||||||
|     sensor::Sensor *sr = this->move_resolution_sensors_[index]; |     sensor::Sensor *sr = this->move_resolution_sensors_[index]; | ||||||
|     if (sr != nullptr) { |     if (sr != nullptr) { | ||||||
|       val = (buffer[start + 1] << 8) | buffer[start]; |       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 | #endif | ||||||
|     // SPEED |     // SPEED | ||||||
| @@ -502,13 +508,17 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { | |||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
|     sensor::Sensor *ss = this->move_speed_sensors_[index]; |     sensor::Sensor *ss = this->move_speed_sensors_[index]; | ||||||
|     if (ss != nullptr) { |     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 | #endif | ||||||
|     // DISTANCE |     // DISTANCE | ||||||
|     val = (uint16_t) sqrt( |     // Optimized: use already decoded tx and ty values, replace pow() with multiplication | ||||||
|         pow(ld2450::decode_coordinate(buffer[TARGET_X + index * 8], buffer[(TARGET_X + index * 8) + 1]), 2) + |     int32_t x_squared = (int32_t) tx * tx; | ||||||
|         pow(ld2450::decode_coordinate(buffer[TARGET_Y + index * 8], buffer[(TARGET_Y + index * 8) + 1]), 2)); |     int32_t y_squared = (int32_t) ty * ty; | ||||||
|  |     val = (uint16_t) sqrt(x_squared + y_squared); | ||||||
|     td = val; |     td = val; | ||||||
|     if (val > 0) { |     if (val > 0) { | ||||||
|       target_count++; |       target_count++; | ||||||
| @@ -516,7 +526,10 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { | |||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
|     sensor::Sensor *sd = this->move_distance_sensors_[index]; |     sensor::Sensor *sd = this->move_distance_sensors_[index]; | ||||||
|     if (sd != nullptr) { |     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 | ||||||
|     angle = calculate_angle(static_cast<float>(ty), static_cast<float>(td)); |     angle = calculate_angle(static_cast<float>(ty), static_cast<float>(td)); | ||||||
| @@ -525,7 +538,11 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { | |||||||
|     } |     } | ||||||
|     sensor::Sensor *sa = this->move_angle_sensors_[index]; |     sensor::Sensor *sa = this->move_angle_sensors_[index]; | ||||||
|     if (sa != nullptr) { |     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 | #endif | ||||||
| #ifdef USE_TEXT_SENSOR | #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]; |     text_sensor::TextSensor *tsd = this->direction_text_sensors_[index]; | ||||||
|     if (tsd != nullptr) { |     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 | #endif | ||||||
|  |  | ||||||
| @@ -563,32 +583,50 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { | |||||||
|     // Publish Still Target Count in Zones |     // Publish Still Target Count in Zones | ||||||
|     sensor::Sensor *szstc = this->zone_still_target_count_sensors_[index]; |     sensor::Sensor *szstc = this->zone_still_target_count_sensors_[index]; | ||||||
|     if (szstc != nullptr) { |     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 |     // Publish Moving Target Count in Zones | ||||||
|     sensor::Sensor *szmtc = this->zone_moving_target_count_sensors_[index]; |     sensor::Sensor *szmtc = this->zone_moving_target_count_sensors_[index]; | ||||||
|     if (szmtc != nullptr) { |     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 |     // Publish All Target Count in Zones | ||||||
|     sensor::Sensor *sztc = this->zone_target_count_sensors_[index]; |     sensor::Sensor *sztc = this->zone_target_count_sensors_[index]; | ||||||
|     if (sztc != nullptr) { |     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 |   }  // End loop thru zones | ||||||
|  |  | ||||||
|   // Target Count |   // Target Count | ||||||
|   if (this->target_count_sensor_ != nullptr) { |   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 |   // Still Target Count | ||||||
|   if (this->still_target_count_sensor_ != nullptr) { |   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 |   // Moving Target Count | ||||||
|   if (this->moving_target_count_sensor_ != nullptr) { |   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 | #endif | ||||||
|  |  | ||||||
|   | |||||||
| @@ -5,6 +5,8 @@ | |||||||
| #include "esphome/core/defines.h" | #include "esphome/core/defines.h" | ||||||
| #include "esphome/core/helpers.h" | #include "esphome/core/helpers.h" | ||||||
| #include "esphome/core/preferences.h" | #include "esphome/core/preferences.h" | ||||||
|  | #include <limits> | ||||||
|  | #include <cmath> | ||||||
| #ifdef USE_SENSOR | #ifdef USE_SENSOR | ||||||
| #include "esphome/components/sensor/sensor.h" | #include "esphome/components/sensor/sensor.h" | ||||||
| #endif | #endif | ||||||
| @@ -100,7 +102,7 @@ class LD2450Component : public Component, public uart::UARTDevice { | |||||||
|   void dump_config() override; |   void dump_config() override; | ||||||
|   void loop() override; |   void loop() override; | ||||||
|   void set_presence_timeout(); |   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 read_all_info(); | ||||||
|   void query_zone_info(); |   void query_zone_info(); | ||||||
|   void restart_and_read_all_info(); |   void restart_and_read_all_info(); | ||||||
| @@ -164,6 +166,32 @@ class LD2450Component : public Component, public uart::UARTDevice { | |||||||
|   Zone zone_config_[MAX_ZONES]; |   Zone zone_config_[MAX_ZONES]; | ||||||
|   std::string version_{}; |   std::string version_{}; | ||||||
|   std::string mac_{}; |   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<int16_t>::min();             // -32768, outside range of -4860 to 4860 | ||||||
|  |     int16_t y = std::numeric_limits<int16_t>::min();             // -32768, outside range of 0 to 7560 | ||||||
|  |     int16_t speed = std::numeric_limits<int16_t>::min();         // -32768, outside practical sensor range | ||||||
|  |     uint16_t resolution = std::numeric_limits<uint16_t>::max();  // 65535, unlikely resolution value | ||||||
|  |     uint16_t distance = std::numeric_limits<uint16_t>::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<uint8_t>::max();   // 255, unlikely zone count | ||||||
|  |     uint8_t moving_count = std::numeric_limits<uint8_t>::max();  // 255, unlikely zone count | ||||||
|  |     uint8_t total_count = std::numeric_limits<uint8_t>::max();   // 255, unlikely zone count | ||||||
|  |   } cached_zone_data_[MAX_ZONES]; | ||||||
|  |  | ||||||
|  |   struct CachedGlobalData { | ||||||
|  |     uint8_t target_count = std::numeric_limits<uint8_t>::max();  // 255, max 3 targets possible | ||||||
|  |     uint8_t still_count = std::numeric_limits<uint8_t>::max();   // 255, max 3 targets possible | ||||||
|  |     uint8_t moving_count = std::numeric_limits<uint8_t>::max();  // 255, max 3 targets possible | ||||||
|  |   } cached_global_data_; | ||||||
|  |  | ||||||
| #ifdef USE_NUMBER | #ifdef USE_NUMBER | ||||||
|   ESPPreferenceObject pref_;  // only used when numbers are in use |   ESPPreferenceObject pref_;  // only used when numbers are in use | ||||||
|   ZoneOfNumbers zone_numbers_[MAX_ZONES]; |   ZoneOfNumbers zone_numbers_[MAX_ZONES]; | ||||||
|   | |||||||
		Reference in New Issue
	
	Block a user