mirror of
				https://github.com/esphome/esphome.git
				synced 2025-10-30 22:53:59 +00:00 
			
		
		
		
	[ld2450] Add new component (#5674)
Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> Co-authored-by: Marcus Better <marcus@better.se> Co-authored-by: Trevor Schirmer <24777085+TrevorSchirmer@users.noreply.github.com> Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
This commit is contained in:
		| @@ -234,6 +234,7 @@ esphome/components/kuntze/* @ssieb | ||||
| esphome/components/lcd_menu/* @numo68 | ||||
| esphome/components/ld2410/* @regevbr @sebcaps | ||||
| esphome/components/ld2420/* @descipher | ||||
| esphome/components/ld2450/* @hareeshmu | ||||
| esphome/components/ledc/* @OttoWinter | ||||
| esphome/components/libretiny/* @kuba2k2 | ||||
| esphome/components/libretiny_pwm/* @kuba2k2 | ||||
|   | ||||
							
								
								
									
										51
									
								
								esphome/components/ld2450/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										51
									
								
								esphome/components/ld2450/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,51 @@ | ||||
| import esphome.codegen as cg | ||||
| import esphome.config_validation as cv | ||||
| from esphome.components import uart | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     CONF_THROTTLE, | ||||
| ) | ||||
|  | ||||
| DEPENDENCIES = ["uart"] | ||||
| CODEOWNERS = ["@hareeshmu"] | ||||
| MULTI_CONF = True | ||||
|  | ||||
| ld2450_ns = cg.esphome_ns.namespace("ld2450") | ||||
| LD2450Component = ld2450_ns.class_("LD2450Component", cg.Component, uart.UARTDevice) | ||||
|  | ||||
| CONF_LD2450_ID = "ld2450_id" | ||||
|  | ||||
| CONFIG_SCHEMA = cv.All( | ||||
|     cv.Schema( | ||||
|         { | ||||
|             cv.GenerateID(): cv.declare_id(LD2450Component), | ||||
|             cv.Optional(CONF_THROTTLE, default="1000ms"): cv.All( | ||||
|                 cv.positive_time_period_milliseconds, | ||||
|                 cv.Range(min=cv.TimePeriod(milliseconds=1)), | ||||
|             ), | ||||
|         } | ||||
|     ) | ||||
|     .extend(uart.UART_DEVICE_SCHEMA) | ||||
|     .extend(cv.COMPONENT_SCHEMA) | ||||
| ) | ||||
|  | ||||
| LD2450BaseSchema = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), | ||||
|     }, | ||||
| ) | ||||
|  | ||||
| FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( | ||||
|     "ld2450", | ||||
|     require_tx=True, | ||||
|     require_rx=True, | ||||
|     parity="NONE", | ||||
|     stop_bits=1, | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     var = cg.new_Pvariable(config[CONF_ID]) | ||||
|     await cg.register_component(var, config) | ||||
|     await uart.register_uart_device(var, config) | ||||
|     cg.add(var.set_throttle(config[CONF_THROTTLE])) | ||||
							
								
								
									
										47
									
								
								esphome/components/ld2450/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										47
									
								
								esphome/components/ld2450/binary_sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,47 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import binary_sensor | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_HAS_MOVING_TARGET, | ||||
|     CONF_HAS_STILL_TARGET, | ||||
|     CONF_HAS_TARGET, | ||||
|     DEVICE_CLASS_MOTION, | ||||
|     DEVICE_CLASS_OCCUPANCY, | ||||
| ) | ||||
|  | ||||
| from . import CONF_LD2450_ID, LD2450Component | ||||
|  | ||||
| DEPENDENCIES = ["ld2450"] | ||||
|  | ||||
| ICON_MEDITATION = "mdi:meditation" | ||||
| ICON_SHIELD_ACCOUNT = "mdi:shield-account" | ||||
| ICON_TARGET_ACCOUNT = "mdi:target-account" | ||||
|  | ||||
| CONFIG_SCHEMA = { | ||||
|     cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), | ||||
|     cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema( | ||||
|         device_class=DEVICE_CLASS_OCCUPANCY, | ||||
|         icon=ICON_SHIELD_ACCOUNT, | ||||
|     ), | ||||
|     cv.Optional(CONF_HAS_MOVING_TARGET): binary_sensor.binary_sensor_schema( | ||||
|         device_class=DEVICE_CLASS_MOTION, | ||||
|         icon=ICON_TARGET_ACCOUNT, | ||||
|     ), | ||||
|     cv.Optional(CONF_HAS_STILL_TARGET): binary_sensor.binary_sensor_schema( | ||||
|         device_class=DEVICE_CLASS_OCCUPANCY, | ||||
|         icon=ICON_MEDITATION, | ||||
|     ), | ||||
| } | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     ld2450_component = await cg.get_variable(config[CONF_LD2450_ID]) | ||||
|     if has_target_config := config.get(CONF_HAS_TARGET): | ||||
|         sens = await binary_sensor.new_binary_sensor(has_target_config) | ||||
|         cg.add(ld2450_component.set_target_binary_sensor(sens)) | ||||
|     if has_moving_target_config := config.get(CONF_HAS_MOVING_TARGET): | ||||
|         sens = await binary_sensor.new_binary_sensor(has_moving_target_config) | ||||
|         cg.add(ld2450_component.set_moving_target_binary_sensor(sens)) | ||||
|     if has_still_target_config := config.get(CONF_HAS_STILL_TARGET): | ||||
|         sens = await binary_sensor.new_binary_sensor(has_still_target_config) | ||||
|         cg.add(ld2450_component.set_still_target_binary_sensor(sens)) | ||||
							
								
								
									
										45
									
								
								esphome/components/ld2450/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								esphome/components/ld2450/button/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import button | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_FACTORY_RESET, | ||||
|     CONF_RESTART, | ||||
|     DEVICE_CLASS_RESTART, | ||||
|     ENTITY_CATEGORY_CONFIG, | ||||
|     ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|     ICON_RESTART, | ||||
|     ICON_RESTART_ALERT, | ||||
| ) | ||||
|  | ||||
| from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns | ||||
|  | ||||
| ResetButton = ld2450_ns.class_("ResetButton", button.Button) | ||||
| RestartButton = ld2450_ns.class_("RestartButton", button.Button) | ||||
|  | ||||
| CONFIG_SCHEMA = { | ||||
|     cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), | ||||
|     cv.Optional(CONF_FACTORY_RESET): button.button_schema( | ||||
|         ResetButton, | ||||
|         device_class=DEVICE_CLASS_RESTART, | ||||
|         entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|         icon=ICON_RESTART_ALERT, | ||||
|     ), | ||||
|     cv.Optional(CONF_RESTART): button.button_schema( | ||||
|         RestartButton, | ||||
|         device_class=DEVICE_CLASS_RESTART, | ||||
|         entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|         icon=ICON_RESTART, | ||||
|     ), | ||||
| } | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     ld2450_component = await cg.get_variable(config[CONF_LD2450_ID]) | ||||
|     if factory_reset_config := config.get(CONF_FACTORY_RESET): | ||||
|         b = await button.new_button(factory_reset_config) | ||||
|         await cg.register_parented(b, config[CONF_LD2450_ID]) | ||||
|         cg.add(ld2450_component.set_reset_button(b)) | ||||
|     if restart_config := config.get(CONF_RESTART): | ||||
|         b = await button.new_button(restart_config) | ||||
|         await cg.register_parented(b, config[CONF_LD2450_ID]) | ||||
|         cg.add(ld2450_component.set_restart_button(b)) | ||||
							
								
								
									
										9
									
								
								esphome/components/ld2450/button/reset_button.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								esphome/components/ld2450/button/reset_button.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| #include "reset_button.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| void ResetButton::press_action() { this->parent_->factory_reset(); } | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										18
									
								
								esphome/components/ld2450/button/reset_button.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/ld2450/button/reset_button.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/button/button.h" | ||||
| #include "../ld2450.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| class ResetButton : public button::Button, public Parented<LD2450Component> { | ||||
|  public: | ||||
|   ResetButton() = default; | ||||
|  | ||||
|  protected: | ||||
|   void press_action() override; | ||||
| }; | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										9
									
								
								esphome/components/ld2450/button/restart_button.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										9
									
								
								esphome/components/ld2450/button/restart_button.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,9 @@ | ||||
| #include "restart_button.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| void RestartButton::press_action() { this->parent_->restart_and_read_all_info(); } | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										18
									
								
								esphome/components/ld2450/button/restart_button.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/ld2450/button/restart_button.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/button/button.h" | ||||
| #include "../ld2450.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| class RestartButton : public button::Button, public Parented<LD2450Component> { | ||||
|  public: | ||||
|   RestartButton() = default; | ||||
|  | ||||
|  protected: | ||||
|   void press_action() override; | ||||
| }; | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										867
									
								
								esphome/components/ld2450/ld2450.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										867
									
								
								esphome/components/ld2450/ld2450.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,867 @@ | ||||
| #include "ld2450.h" | ||||
| #include <utility> | ||||
| #ifdef USE_NUMBER | ||||
| #include "esphome/components/number/number.h" | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #endif | ||||
| #include "esphome/core/component.h" | ||||
|  | ||||
| #define highbyte(val) (uint8_t)((val) >> 8) | ||||
| #define lowbyte(val) (uint8_t)((val) &0xff) | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| static const char *const TAG = "ld2450"; | ||||
| static const char *const UNKNOWN_MAC("unknown"); | ||||
|  | ||||
| // LD2450 UART Serial Commands | ||||
| static const uint8_t CMD_ENABLE_CONF = 0x00FF; | ||||
| static const uint8_t CMD_DISABLE_CONF = 0x00FE; | ||||
| static const uint8_t CMD_VERSION = 0x00A0; | ||||
| static const uint8_t CMD_MAC = 0x00A5; | ||||
| static const uint8_t CMD_RESET = 0x00A2; | ||||
| static const uint8_t CMD_RESTART = 0x00A3; | ||||
| static const uint8_t CMD_BLUETOOTH = 0x00A4; | ||||
| static const uint8_t CMD_SINGLE_TARGET_MODE = 0x0080; | ||||
| static const uint8_t CMD_MULTI_TARGET_MODE = 0x0090; | ||||
| static const uint8_t CMD_QUERY_TARGET_MODE = 0x0091; | ||||
| static const uint8_t CMD_SET_BAUD_RATE = 0x00A1; | ||||
| static const uint8_t CMD_QUERY_ZONE = 0x00C1; | ||||
| static const uint8_t CMD_SET_ZONE = 0x00C2; | ||||
|  | ||||
| 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 | ||||
|   } | ||||
| } | ||||
|  | ||||
| static inline int16_t decode_coordinate(uint8_t low_byte, uint8_t high_byte) { | ||||
|   int16_t coordinate = (high_byte & 0x7F) << 8 | low_byte; | ||||
|   if ((high_byte & 0x80) == 0) { | ||||
|     coordinate = -coordinate; | ||||
|   } | ||||
|   return coordinate;  // mm | ||||
| } | ||||
|  | ||||
| static inline int16_t decode_speed(uint8_t low_byte, uint8_t high_byte) { | ||||
|   int16_t speed = (high_byte & 0x7F) << 8 | low_byte; | ||||
|   if ((high_byte & 0x80) == 0) { | ||||
|     speed = -speed; | ||||
|   } | ||||
|   return speed * 10;  // mm/s | ||||
| } | ||||
|  | ||||
| static inline int16_t hex_to_signed_int(const uint8_t *buffer, uint8_t offset) { | ||||
|   uint16_t hex_val = (buffer[offset + 1] << 8) | buffer[offset]; | ||||
|   int16_t dec_val = static_cast<int16_t>(hex_val); | ||||
|   if (dec_val & 0x8000) { | ||||
|     dec_val -= 65536; | ||||
|   } | ||||
|   return dec_val; | ||||
| } | ||||
|  | ||||
| static inline float calculate_angle(float base, float hypotenuse) { | ||||
|   if (base < 0.0 || hypotenuse <= 0.0) { | ||||
|     return 0.0; | ||||
|   } | ||||
|   float angle_radians = std::acos(base / hypotenuse); | ||||
|   float angle_degrees = angle_radians * (180.0 / M_PI); | ||||
|   return angle_degrees; | ||||
| } | ||||
|  | ||||
| static inline std::string get_direction(int16_t speed) { | ||||
|   static const char *const APPROACHING = "Approaching"; | ||||
|   static const char *const MOVING_AWAY = "Moving away"; | ||||
|   static const char *const STATIONARY = "Stationary"; | ||||
|  | ||||
|   if (speed > 0) { | ||||
|     return MOVING_AWAY; | ||||
|   } | ||||
|   if (speed < 0) { | ||||
|     return APPROACHING; | ||||
|   } | ||||
|   return STATIONARY; | ||||
| } | ||||
|  | ||||
| static inline std::string format_mac(uint8_t *buffer) { | ||||
|   return str_snprintf("%02X:%02X:%02X:%02X:%02X:%02X", 17, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14], | ||||
|                       buffer[15]); | ||||
| } | ||||
|  | ||||
| static inline std::string format_version(uint8_t *buffer) { | ||||
|   return str_sprintf("%u.%02X.%02X%02X%02X%02X", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], | ||||
|                      buffer[14]); | ||||
| } | ||||
|  | ||||
| LD2450Component::LD2450Component() {} | ||||
|  | ||||
| void LD2450Component::setup() { | ||||
|   ESP_LOGCONFIG(TAG, "Setting up HLK-LD2450..."); | ||||
| #ifdef USE_NUMBER | ||||
|   this->pref_ = global_preferences->make_preference<float>(this->presence_timeout_number_->get_object_id_hash()); | ||||
|   this->set_presence_timeout(); | ||||
| #endif | ||||
|   this->read_all_info(); | ||||
| } | ||||
|  | ||||
| void LD2450Component::dump_config() { | ||||
|   ESP_LOGCONFIG(TAG, "HLK-LD2450 Human motion tracking radar module:"); | ||||
| #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_); | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   LOG_SWITCH("  ", "BluetoothSwitch", this->bluetooth_switch_); | ||||
|   LOG_SWITCH("  ", "MultiTargetSwitch", this->multi_target_switch_); | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|   LOG_BUTTON("  ", "ResetButton", this->reset_button_); | ||||
|   LOG_BUTTON("  ", "RestartButton", this->restart_button_); | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   LOG_SENSOR("  ", "TargetCountSensor", this->target_count_sensor_); | ||||
|   LOG_SENSOR("  ", "StillTargetCountSensor", this->still_target_count_sensor_); | ||||
|   LOG_SENSOR("  ", "MovingTargetCountSensor", this->moving_target_count_sensor_); | ||||
|   for (sensor::Sensor *s : this->move_x_sensors_) { | ||||
|     LOG_SENSOR("  ", "NthTargetXSensor", s); | ||||
|   } | ||||
|   for (sensor::Sensor *s : this->move_y_sensors_) { | ||||
|     LOG_SENSOR("  ", "NthTargetYSensor", s); | ||||
|   } | ||||
|   for (sensor::Sensor *s : this->move_speed_sensors_) { | ||||
|     LOG_SENSOR("  ", "NthTargetSpeedSensor", s); | ||||
|   } | ||||
|   for (sensor::Sensor *s : this->move_angle_sensors_) { | ||||
|     LOG_SENSOR("  ", "NthTargetAngleSensor", s); | ||||
|   } | ||||
|   for (sensor::Sensor *s : this->move_distance_sensors_) { | ||||
|     LOG_SENSOR("  ", "NthTargetDistanceSensor", s); | ||||
|   } | ||||
|   for (sensor::Sensor *s : this->move_resolution_sensors_) { | ||||
|     LOG_SENSOR("  ", "NthTargetResolutionSensor", s); | ||||
|   } | ||||
|   for (sensor::Sensor *s : this->zone_target_count_sensors_) { | ||||
|     LOG_SENSOR("  ", "NthZoneTargetCountSensor", s); | ||||
|   } | ||||
|   for (sensor::Sensor *s : this->zone_still_target_count_sensors_) { | ||||
|     LOG_SENSOR("  ", "NthZoneStillTargetCountSensor", s); | ||||
|   } | ||||
|   for (sensor::Sensor *s : this->zone_moving_target_count_sensors_) { | ||||
|     LOG_SENSOR("  ", "NthZoneMovingTargetCountSensor", s); | ||||
|   } | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   LOG_TEXT_SENSOR("  ", "VersionTextSensor", this->version_text_sensor_); | ||||
|   LOG_TEXT_SENSOR("  ", "MacTextSensor", this->mac_text_sensor_); | ||||
|   for (text_sensor::TextSensor *s : this->direction_text_sensors_) { | ||||
|     LOG_TEXT_SENSOR("  ", "NthDirectionTextSensor", s); | ||||
|   } | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
|   for (number::Number *n : this->zone_x1_numbers_) { | ||||
|     LOG_NUMBER("  ", "ZoneX1Number", n); | ||||
|   } | ||||
|   for (number::Number *n : this->zone_y1_numbers_) { | ||||
|     LOG_NUMBER("  ", "ZoneY1Number", n); | ||||
|   } | ||||
|   for (number::Number *n : this->zone_x2_numbers_) { | ||||
|     LOG_NUMBER("  ", "ZoneX2Number", n); | ||||
|   } | ||||
|   for (number::Number *n : this->zone_y2_numbers_) { | ||||
|     LOG_NUMBER("  ", "ZoneY2Number", n); | ||||
|   } | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
|   LOG_SELECT("  ", "BaudRateSelect", this->baud_rate_select_); | ||||
|   LOG_SELECT("  ", "ZoneTypeSelect", this->zone_type_select_); | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
|   LOG_NUMBER("  ", "PresenceTimeoutNumber", this->presence_timeout_number_); | ||||
| #endif | ||||
|   ESP_LOGCONFIG(TAG, "  Throttle : %ums", this->throttle_); | ||||
|   ESP_LOGCONFIG(TAG, "  MAC Address : %s", const_cast<char *>(this->mac_.c_str())); | ||||
|   ESP_LOGCONFIG(TAG, "  Firmware version : %s", const_cast<char *>(this->version_.c_str())); | ||||
| } | ||||
|  | ||||
| void LD2450Component::loop() { | ||||
|   while (this->available()) { | ||||
|     this->readline_(read(), this->buffer_data_, MAX_LINE_LENGTH); | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Count targets in zone | ||||
| uint8_t LD2450Component::count_targets_in_zone_(const Zone &zone, bool is_moving) { | ||||
|   uint8_t count = 0; | ||||
|   for (auto &index : this->target_info_) { | ||||
|     if (index.x > zone.x1 && index.x < zone.x2 && index.y > zone.y1 && index.y < zone.y2 && | ||||
|         index.is_moving == is_moving) { | ||||
|       count++; | ||||
|     } | ||||
|   } | ||||
|   return count; | ||||
| } | ||||
|  | ||||
| // Service reset_radar_zone | ||||
| void LD2450Component::reset_radar_zone() { | ||||
|   this->zone_type_ = 0; | ||||
|   for (auto &i : this->zone_config_) { | ||||
|     i.x1 = 0; | ||||
|     i.y1 = 0; | ||||
|     i.x2 = 0; | ||||
|     i.y2 = 0; | ||||
|   } | ||||
|   this->send_set_zone_command_(); | ||||
| } | ||||
|  | ||||
| void LD2450Component::set_radar_zone(int32_t zone_type, int32_t zone1_x1, int32_t zone1_y1, int32_t zone1_x2, | ||||
|                                      int32_t zone1_y2, int32_t zone2_x1, int32_t zone2_y1, int32_t zone2_x2, | ||||
|                                      int32_t zone2_y2, int32_t zone3_x1, int32_t zone3_y1, int32_t zone3_x2, | ||||
|                                      int32_t zone3_y2) { | ||||
|   this->zone_type_ = zone_type; | ||||
|   int zone_parameters[12] = {zone1_x1, zone1_y1, zone1_x2, zone1_y2, zone2_x1, zone2_y1, | ||||
|                              zone2_x2, zone2_y2, zone3_x1, zone3_y1, zone3_x2, zone3_y2}; | ||||
|   for (int i = 0; i < MAX_ZONES; i++) { | ||||
|     this->zone_config_[i].x1 = zone_parameters[i * 4]; | ||||
|     this->zone_config_[i].y1 = zone_parameters[i * 4 + 1]; | ||||
|     this->zone_config_[i].x2 = zone_parameters[i * 4 + 2]; | ||||
|     this->zone_config_[i].y2 = zone_parameters[i * 4 + 3]; | ||||
|   } | ||||
|   this->send_set_zone_command_(); | ||||
| } | ||||
|  | ||||
| // Set Zone on LD2450 Sensor | ||||
| void LD2450Component::send_set_zone_command_() { | ||||
|   uint8_t cmd_value[26] = {}; | ||||
|   uint8_t zone_type_bytes[2] = {static_cast<uint8_t>(this->zone_type_), 0x00}; | ||||
|   uint8_t area_config[24] = {}; | ||||
|   for (int i = 0; i < MAX_ZONES; i++) { | ||||
|     int values[4] = {this->zone_config_[i].x1, this->zone_config_[i].y1, this->zone_config_[i].x2, | ||||
|                      this->zone_config_[i].y2}; | ||||
|     ld2450::convert_int_values_to_hex(values, area_config + (i * 8)); | ||||
|   } | ||||
|   std::memcpy(cmd_value, zone_type_bytes, 2); | ||||
|   std::memcpy(cmd_value + 2, area_config, 24); | ||||
|   this->set_config_mode_(true); | ||||
|   this->send_command_(CMD_SET_ZONE, cmd_value, 26); | ||||
|   this->set_config_mode_(false); | ||||
| } | ||||
|  | ||||
| // Check presense timeout to reset presence status | ||||
| bool LD2450Component::get_timeout_status_(uint32_t check_millis) { | ||||
|   if (check_millis == 0) { | ||||
|     return true; | ||||
|   } | ||||
|   if (this->timeout_ == 0) { | ||||
|     this->timeout_ = ld2450::convert_seconds_to_ms(DEFAULT_PRESENCE_TIMEOUT); | ||||
|   } | ||||
|   auto current_millis = millis(); | ||||
|   return current_millis - check_millis >= this->timeout_; | ||||
| } | ||||
|  | ||||
| // Extract, store and publish zone details LD2450 buffer | ||||
| void LD2450Component::process_zone_(uint8_t *buffer) { | ||||
|   uint8_t index, start; | ||||
|   for (index = 0; index < MAX_ZONES; index++) { | ||||
|     start = 12 + index * 8; | ||||
|     this->zone_config_[index].x1 = ld2450::hex_to_signed_int(buffer, start); | ||||
|     this->zone_config_[index].y1 = ld2450::hex_to_signed_int(buffer, start + 2); | ||||
|     this->zone_config_[index].x2 = ld2450::hex_to_signed_int(buffer, start + 4); | ||||
|     this->zone_config_[index].y2 = ld2450::hex_to_signed_int(buffer, start + 6); | ||||
| #ifdef USE_NUMBER | ||||
|     this->zone_x1_numbers_[index]->publish_state(this->zone_config_[index].x1); | ||||
|     this->zone_y1_numbers_[index]->publish_state(this->zone_config_[index].y1); | ||||
|     this->zone_x2_numbers_[index]->publish_state(this->zone_config_[index].x2); | ||||
|     this->zone_y2_numbers_[index]->publish_state(this->zone_config_[index].y2); | ||||
| #endif | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Read all info from LD2450 buffer | ||||
| void LD2450Component::read_all_info() { | ||||
|   this->set_config_mode_(true); | ||||
|   this->get_version_(); | ||||
|   this->get_mac_(); | ||||
|   this->query_target_tracking_mode_(); | ||||
|   this->query_zone_(); | ||||
|   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) { | ||||
|     this->baud_rate_select_->publish_state(baud_rate); | ||||
|   } | ||||
|   this->publish_zone_type(); | ||||
| #endif | ||||
| } | ||||
|  | ||||
| // Read zone info from LD2450 buffer | ||||
| void LD2450Component::query_zone_info() { | ||||
|   this->set_config_mode_(true); | ||||
|   this->query_zone_(); | ||||
|   this->set_config_mode_(false); | ||||
| } | ||||
|  | ||||
| // Restart LD2450 and read all info from buffer | ||||
| void LD2450Component::restart_and_read_all_info() { | ||||
|   this->set_config_mode_(true); | ||||
|   this->restart_(); | ||||
|   this->set_timeout(1000, [this]() { this->read_all_info(); }); | ||||
| } | ||||
|  | ||||
| // Send command with values to LD2450 | ||||
| void LD2450Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) { | ||||
|   ESP_LOGV(TAG, "Sending command %02X", command); | ||||
|   // frame header | ||||
|   this->write_array(CMD_FRAME_HEADER, 4); | ||||
|   // length bytes | ||||
|   int 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)); | ||||
|   // command value bytes | ||||
|   if (command_value != nullptr) { | ||||
|     for (int i = 0; i < command_value_len; i++) { | ||||
|       this->write_byte(command_value[i]); | ||||
|     } | ||||
|   } | ||||
|   // footer | ||||
|   this->write_array(CMD_FRAME_END, 4); | ||||
|   // FIXME to remove | ||||
|   delay(50);  // NOLINT | ||||
| } | ||||
|  | ||||
| // LD2450 Radar data message: | ||||
| //  [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) { | ||||
|   if (len < 29) {  // header (4 bytes) + 8 x 3 target data + footer (2 bytes) | ||||
|     ESP_LOGE(TAG, "Periodic data: invalid message length"); | ||||
|     return; | ||||
|   } | ||||
|   if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) {  // header | ||||
|     ESP_LOGE(TAG, "Periodic data: invalid message header"); | ||||
|     return; | ||||
|   } | ||||
|   if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) {  // footer | ||||
|     ESP_LOGE(TAG, "Periodic data: invalid message footer"); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   auto current_millis = millis(); | ||||
|   if (current_millis - this->last_periodic_millis_ < this->throttle_) { | ||||
|     ESP_LOGV(TAG, "Throttling: %d", this->throttle_); | ||||
|     return; | ||||
|   } | ||||
|  | ||||
|   this->last_periodic_millis_ = current_millis; | ||||
|  | ||||
|   int16_t target_count = 0; | ||||
|   int16_t still_target_count = 0; | ||||
|   int16_t moving_target_count = 0; | ||||
|   int16_t start = 0; | ||||
|   int16_t val = 0; | ||||
|   uint8_t index = 0; | ||||
|   int16_t tx = 0; | ||||
|   int16_t ty = 0; | ||||
|   int16_t td = 0; | ||||
|   int16_t ts = 0; | ||||
|   int16_t angle = 0; | ||||
|   std::string direction{}; | ||||
|   bool is_moving = false; | ||||
|  | ||||
| #ifdef USE_SENSOR | ||||
|   // Loop thru targets | ||||
|   // X | ||||
|   for (index = 0; index < MAX_TARGETS; index++) { | ||||
|     start = TARGET_X + index * 8; | ||||
|     is_moving = false; | ||||
|     sensor::Sensor *sx = this->move_x_sensors_[index]; | ||||
|     if (sx != nullptr) { | ||||
|       val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]); | ||||
|       tx = val; | ||||
|       sx->publish_state(val); | ||||
|     } | ||||
|     // Y | ||||
|     start = TARGET_Y + index * 8; | ||||
|     sensor::Sensor *sy = this->move_y_sensors_[index]; | ||||
|     if (sy != nullptr) { | ||||
|       val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]); | ||||
|       ty = val; | ||||
|       sy->publish_state(val); | ||||
|     } | ||||
|     // SPEED | ||||
|     start = TARGET_SPEED + index * 8; | ||||
|     sensor::Sensor *ss = this->move_speed_sensors_[index]; | ||||
|     if (ss != nullptr) { | ||||
|       val = ld2450::decode_speed(buffer[start], buffer[start + 1]); | ||||
|       ts = val; | ||||
|       if (val) { | ||||
|         is_moving = true; | ||||
|         moving_target_count++; | ||||
|       } | ||||
|       ss->publish_state(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); | ||||
|     } | ||||
|     // DISTANCE | ||||
|     sensor::Sensor *sd = this->move_distance_sensors_[index]; | ||||
|     if (sd != nullptr) { | ||||
|       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)); | ||||
|       td = val; | ||||
|       if (val > 0) { | ||||
|         target_count++; | ||||
|       } | ||||
|  | ||||
|       sd->publish_state(val); | ||||
|     } | ||||
|     // ANGLE | ||||
|     angle = calculate_angle(static_cast<float>(ty), static_cast<float>(td)); | ||||
|     if (tx > 0) { | ||||
|       angle = angle * -1; | ||||
|     } | ||||
|     sensor::Sensor *sa = this->move_angle_sensors_[index]; | ||||
|     if (sa != nullptr) { | ||||
|       sa->publish_state(angle); | ||||
|     } | ||||
| #endif | ||||
|     // DIRECTION | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|     direction = get_direction(ts); | ||||
|     if (td == 0) { | ||||
|       direction = "NA"; | ||||
|     } | ||||
|     text_sensor::TextSensor *tsd = this->direction_text_sensors_[index]; | ||||
|     if (tsd != nullptr) { | ||||
|       tsd->publish_state(direction); | ||||
|     } | ||||
| #endif | ||||
|  | ||||
|     // Store target info for zone target count | ||||
|     this->target_info_[index].x = tx; | ||||
|     this->target_info_[index].y = ty; | ||||
|     this->target_info_[index].is_moving = is_moving; | ||||
|  | ||||
|   }  // End loop thru targets | ||||
|  | ||||
| #ifdef USE_SENSOR | ||||
|   // Loop thru zones | ||||
|   uint8_t zone_still_targets = 0; | ||||
|   uint8_t zone_moving_targets = 0; | ||||
|   uint8_t zone_all_targets = 0; | ||||
|   for (index = 0; index < MAX_ZONES; index++) { | ||||
|     // Publish Still Target Count in Zones | ||||
|     sensor::Sensor *szstc = this->zone_still_target_count_sensors_[index]; | ||||
|     if (szstc != nullptr) { | ||||
|       zone_still_targets = this->count_targets_in_zone_(this->zone_config_[index], false); | ||||
|       szstc->publish_state(zone_still_targets); | ||||
|     } | ||||
|     // Publish Moving Target Count in Zones | ||||
|     sensor::Sensor *szmtc = this->zone_moving_target_count_sensors_[index]; | ||||
|     if (szmtc != nullptr) { | ||||
|       zone_moving_targets = this->count_targets_in_zone_(this->zone_config_[index], true); | ||||
|       szmtc->publish_state(zone_moving_targets); | ||||
|     } | ||||
|  | ||||
|     zone_all_targets = zone_still_targets + 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); | ||||
|     } | ||||
|  | ||||
|   }  // End loop thru zones | ||||
|  | ||||
|   still_target_count = target_count - moving_target_count; | ||||
|   // Target Count | ||||
|   if (this->target_count_sensor_ != nullptr) { | ||||
|     this->target_count_sensor_->publish_state(target_count); | ||||
|   } | ||||
|   // Still Target Count | ||||
|   if (this->still_target_count_sensor_ != nullptr) { | ||||
|     this->still_target_count_sensor_->publish_state(still_target_count); | ||||
|   } | ||||
|   // Moving Target Count | ||||
|   if (this->moving_target_count_sensor_ != nullptr) { | ||||
|     this->moving_target_count_sensor_->publish_state(moving_target_count); | ||||
|   } | ||||
| #endif | ||||
|  | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   // Target Presence | ||||
|   if (this->target_binary_sensor_ != nullptr) { | ||||
|     if (target_count > 0) { | ||||
|       this->target_binary_sensor_->publish_state(true); | ||||
|     } else { | ||||
|       if (this->get_timeout_status_(this->presence_millis_)) { | ||||
|         this->target_binary_sensor_->publish_state(false); | ||||
|       } else { | ||||
|         ESP_LOGV(TAG, "Clear presence waiting timeout: %d", this->timeout_); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   // Moving Target Presence | ||||
|   if (this->moving_target_binary_sensor_ != nullptr) { | ||||
|     if (moving_target_count > 0) { | ||||
|       this->moving_target_binary_sensor_->publish_state(true); | ||||
|     } else { | ||||
|       if (this->get_timeout_status_(this->moving_presence_millis_)) { | ||||
|         this->moving_target_binary_sensor_->publish_state(false); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
|   // Still Target Presence | ||||
|   if (this->still_target_binary_sensor_ != nullptr) { | ||||
|     if (still_target_count > 0) { | ||||
|       this->still_target_binary_sensor_->publish_state(true); | ||||
|     } else { | ||||
|       if (this->get_timeout_status_(this->still_presence_millis_)) { | ||||
|         this->still_target_binary_sensor_->publish_state(false); | ||||
|       } | ||||
|     } | ||||
|   } | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   // For presence timeout check | ||||
|   if (target_count > 0) { | ||||
|     this->presence_millis_ = millis(); | ||||
|   } | ||||
|   if (moving_target_count > 0) { | ||||
|     this->moving_presence_millis_ = millis(); | ||||
|   } | ||||
|   if (still_target_count > 0) { | ||||
|     this->still_presence_millis_ = millis(); | ||||
|   } | ||||
| #endif | ||||
| } | ||||
|  | ||||
| bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { | ||||
|   ESP_LOGV(TAG, "Handling ack data for command %02X", buffer[COMMAND]); | ||||
|   if (len < 10) { | ||||
|     ESP_LOGE(TAG, "Ack data: invalid length"); | ||||
|     return true; | ||||
|   } | ||||
|   if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) {  // frame header | ||||
|     ESP_LOGE(TAG, "Ack data: invalid header (command %02X)", buffer[COMMAND]); | ||||
|     return true; | ||||
|   } | ||||
|   if (buffer[COMMAND_STATUS] != 0x01) { | ||||
|     ESP_LOGE(TAG, "Ack data: invalid status"); | ||||
|     return true; | ||||
|   } | ||||
|   if (buffer[8] || buffer[9]) { | ||||
|     ESP_LOGE(TAG, "Ack data: last buffer was %u, %u", buffer[8], buffer[9]); | ||||
|     return true; | ||||
|   } | ||||
|  | ||||
|   switch (buffer[COMMAND]) { | ||||
|     case lowbyte(CMD_ENABLE_CONF): | ||||
|       ESP_LOGV(TAG, "Got enable conf command"); | ||||
|       break; | ||||
|     case lowbyte(CMD_DISABLE_CONF): | ||||
|       ESP_LOGV(TAG, "Got disable conf command"); | ||||
|       break; | ||||
|     case lowbyte(CMD_SET_BAUD_RATE): | ||||
|       ESP_LOGV(TAG, "Got baud rate change command"); | ||||
| #ifdef USE_SELECT | ||||
|       if (this->baud_rate_select_ != nullptr) { | ||||
|         ESP_LOGV(TAG, "Change baud rate to %s", this->baud_rate_select_->state.c_str()); | ||||
|       } | ||||
| #endif | ||||
|       break; | ||||
|     case lowbyte(CMD_VERSION): | ||||
|       this->version_ = ld2450::format_version(buffer); | ||||
|       ESP_LOGV(TAG, "Firmware version: %s", this->version_.c_str()); | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|       if (this->version_text_sensor_ != nullptr) { | ||||
|         this->version_text_sensor_->publish_state(this->version_); | ||||
|       } | ||||
| #endif | ||||
|       break; | ||||
|     case lowbyte(CMD_MAC): | ||||
|       if (len < 20) { | ||||
|         return false; | ||||
|       } | ||||
|       this->mac_ = ld2450::format_mac(buffer); | ||||
|       ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str()); | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|       if (this->mac_text_sensor_ != nullptr) { | ||||
|         this->mac_text_sensor_->publish_state(this->mac_); | ||||
|       } | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|       if (this->bluetooth_switch_ != nullptr) { | ||||
|         this->bluetooth_switch_->publish_state(this->mac_ != UNKNOWN_MAC); | ||||
|       } | ||||
| #endif | ||||
|       break; | ||||
|     case lowbyte(CMD_BLUETOOTH): | ||||
|       ESP_LOGV(TAG, "Got Bluetooth command"); | ||||
|       break; | ||||
|     case lowbyte(CMD_SINGLE_TARGET_MODE): | ||||
|       ESP_LOGV(TAG, "Got single target conf command"); | ||||
| #ifdef USE_SWITCH | ||||
|       if (this->multi_target_switch_ != nullptr) { | ||||
|         this->multi_target_switch_->publish_state(false); | ||||
|       } | ||||
| #endif | ||||
|       break; | ||||
|     case lowbyte(CMD_MULTI_TARGET_MODE): | ||||
|       ESP_LOGV(TAG, "Got multi target conf command"); | ||||
| #ifdef USE_SWITCH | ||||
|       if (this->multi_target_switch_ != nullptr) { | ||||
|         this->multi_target_switch_->publish_state(true); | ||||
|       } | ||||
| #endif | ||||
|       break; | ||||
|     case lowbyte(CMD_QUERY_TARGET_MODE): | ||||
|       ESP_LOGV(TAG, "Got query target tracking mode command"); | ||||
| #ifdef USE_SWITCH | ||||
|       if (this->multi_target_switch_ != nullptr) { | ||||
|         this->multi_target_switch_->publish_state(buffer[10] == 0x02); | ||||
|       } | ||||
| #endif | ||||
|       break; | ||||
|     case lowbyte(CMD_QUERY_ZONE): | ||||
|       ESP_LOGV(TAG, "Got query zone conf command"); | ||||
|       this->zone_type_ = std::stoi(std::to_string(buffer[10]), nullptr, 16); | ||||
|       this->publish_zone_type(); | ||||
| #ifdef USE_SELECT | ||||
|       if (this->zone_type_select_ != nullptr) { | ||||
|         ESP_LOGV(TAG, "Change zone type to: %s", this->zone_type_select_->state.c_str()); | ||||
|       } | ||||
| #endif | ||||
|       if (buffer[10] == 0x00) { | ||||
|         ESP_LOGV(TAG, "Zone: Disabled"); | ||||
|       } | ||||
|       if (buffer[10] == 0x01) { | ||||
|         ESP_LOGV(TAG, "Zone: Area detection"); | ||||
|       } | ||||
|       if (buffer[10] == 0x02) { | ||||
|         ESP_LOGV(TAG, "Zone: Area filter"); | ||||
|       } | ||||
|       this->process_zone_(buffer); | ||||
|       break; | ||||
|     case lowbyte(CMD_SET_ZONE): | ||||
|       ESP_LOGV(TAG, "Got set zone conf command"); | ||||
|       this->query_zone_info(); | ||||
|       break; | ||||
|     default: | ||||
|       break; | ||||
|   } | ||||
|   return true; | ||||
| } | ||||
|  | ||||
| // Read LD2450 buffer data | ||||
| void LD2450Component::readline_(int readch, uint8_t *buffer, uint8_t len) { | ||||
|   if (readch < 0) { | ||||
|     return; | ||||
|   } | ||||
|   if (this->buffer_pos_ < len - 1) { | ||||
|     buffer[this->buffer_pos_++] = readch; | ||||
|     buffer[this->buffer_pos_] = 0; | ||||
|   } else { | ||||
|     this->buffer_pos_ = 0; | ||||
|   } | ||||
|   if (this->buffer_pos_ < 4) { | ||||
|     return; | ||||
|   } | ||||
|   if (buffer[this->buffer_pos_ - 2] == 0x55 && buffer[this->buffer_pos_ - 1] == 0xCC) { | ||||
|     ESP_LOGV(TAG, "Handle periodic radar data"); | ||||
|     this->handle_periodic_data_(buffer, this->buffer_pos_); | ||||
|     this->buffer_pos_ = 0;  // Reset position index for next frame | ||||
|   } else if (buffer[this->buffer_pos_ - 4] == 0x04 && buffer[this->buffer_pos_ - 3] == 0x03 && | ||||
|              buffer[this->buffer_pos_ - 2] == 0x02 && buffer[this->buffer_pos_ - 1] == 0x01) { | ||||
|     ESP_LOGV(TAG, "Handle command ack data"); | ||||
|     if (this->handle_ack_data_(buffer, this->buffer_pos_)) { | ||||
|       this->buffer_pos_ = 0;  // Reset position index for next frame | ||||
|     } else { | ||||
|       ESP_LOGV(TAG, "Command ack data invalid"); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Set Config Mode - Pre-requisite sending commands | ||||
| void LD2450Component::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); | ||||
| } | ||||
|  | ||||
| // Set Bluetooth Enable/Disable | ||||
| void LD2450Component::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); | ||||
|   this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); | ||||
| } | ||||
|  | ||||
| // Set Baud rate | ||||
| void LD2450Component::set_baud_rate(const std::string &state) { | ||||
|   this->set_config_mode_(true); | ||||
|   uint8_t cmd_value[2] = {BAUD_RATE_ENUM_TO_INT.at(state), 0x00}; | ||||
|   this->send_command_(CMD_SET_BAUD_RATE, cmd_value, 2); | ||||
|   this->set_timeout(200, [this]() { this->restart_(); }); | ||||
| } | ||||
|  | ||||
| // Set Zone Type - one of: Disabled, Detection, Filter | ||||
| void LD2450Component::set_zone_type(const std::string &state) { | ||||
|   ESP_LOGV(TAG, "Set zone type: %s", state.c_str()); | ||||
|   uint8_t zone_type = ZONE_TYPE_ENUM_TO_INT.at(state); | ||||
|   this->zone_type_ = zone_type; | ||||
|   this->send_set_zone_command_(); | ||||
| } | ||||
|  | ||||
| // Publish Zone Type to Select component | ||||
| void LD2450Component::publish_zone_type() { | ||||
| #ifdef USE_SELECT | ||||
|   std::string zone_type = ZONE_TYPE_INT_TO_ENUM.at(static_cast<ZoneTypeStructure>(this->zone_type_)); | ||||
|   if (this->zone_type_select_ != nullptr) { | ||||
|     this->zone_type_select_->publish_state(zone_type); | ||||
|   } | ||||
| #endif | ||||
| } | ||||
|  | ||||
| // Set Single/Multiplayer target detection | ||||
| void LD2450Component::set_multi_target(bool enable) { | ||||
|   this->set_config_mode_(true); | ||||
|   uint8_t cmd = enable ? CMD_MULTI_TARGET_MODE : CMD_SINGLE_TARGET_MODE; | ||||
|   this->send_command_(cmd, nullptr, 0); | ||||
|   this->set_config_mode_(false); | ||||
| } | ||||
|  | ||||
| // LD2450 factory reset | ||||
| void LD2450Component::factory_reset() { | ||||
|   this->set_config_mode_(true); | ||||
|   this->send_command_(CMD_RESET, nullptr, 0); | ||||
|   this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); | ||||
| } | ||||
|  | ||||
| // Restart LD2450 module | ||||
| void LD2450Component::restart_() { this->send_command_(CMD_RESTART, nullptr, 0); } | ||||
|  | ||||
| // Get LD2450 firmware version | ||||
| void LD2450Component::get_version_() { this->send_command_(CMD_VERSION, nullptr, 0); } | ||||
|  | ||||
| // Get LD2450 mac address | ||||
| void LD2450Component::get_mac_() { | ||||
|   uint8_t cmd_value[2] = {0x01, 0x00}; | ||||
|   this->send_command_(CMD_MAC, cmd_value, 2); | ||||
| } | ||||
|  | ||||
| // Query for target tracking mode | ||||
| void LD2450Component::query_target_tracking_mode_() { this->send_command_(CMD_QUERY_TARGET_MODE, nullptr, 0); } | ||||
|  | ||||
| // Query for zone info | ||||
| void LD2450Component::query_zone_() { this->send_command_(CMD_QUERY_ZONE, nullptr, 0); } | ||||
|  | ||||
| #ifdef USE_SENSOR | ||||
| void LD2450Component::set_move_x_sensor(uint8_t target, sensor::Sensor *s) { this->move_x_sensors_[target] = s; } | ||||
| void LD2450Component::set_move_y_sensor(uint8_t target, sensor::Sensor *s) { this->move_y_sensors_[target] = s; } | ||||
| void LD2450Component::set_move_speed_sensor(uint8_t target, sensor::Sensor *s) { | ||||
|   this->move_speed_sensors_[target] = s; | ||||
| } | ||||
| void LD2450Component::set_move_angle_sensor(uint8_t target, sensor::Sensor *s) { | ||||
|   this->move_angle_sensors_[target] = s; | ||||
| } | ||||
| void LD2450Component::set_move_distance_sensor(uint8_t target, sensor::Sensor *s) { | ||||
|   this->move_distance_sensors_[target] = s; | ||||
| } | ||||
| void LD2450Component::set_move_resolution_sensor(uint8_t target, sensor::Sensor *s) { | ||||
|   this->move_resolution_sensors_[target] = s; | ||||
| } | ||||
| void LD2450Component::set_zone_target_count_sensor(uint8_t zone, sensor::Sensor *s) { | ||||
|   this->zone_target_count_sensors_[zone] = s; | ||||
| } | ||||
| void LD2450Component::set_zone_still_target_count_sensor(uint8_t zone, sensor::Sensor *s) { | ||||
|   this->zone_still_target_count_sensors_[zone] = s; | ||||
| } | ||||
| void LD2450Component::set_zone_moving_target_count_sensor(uint8_t zone, sensor::Sensor *s) { | ||||
|   this->zone_moving_target_count_sensors_[zone] = s; | ||||
| } | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
| void LD2450Component::set_direction_text_sensor(uint8_t target, text_sensor::TextSensor *s) { | ||||
|   this->direction_text_sensors_[target] = s; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| // Send Zone coordinates data to LD2450 | ||||
| #ifdef USE_NUMBER | ||||
| void LD2450Component::set_zone_coordinate(uint8_t zone) { | ||||
|   number::Number *x1sens = this->zone_x1_numbers_[zone]; | ||||
|   number::Number *y1sens = this->zone_y1_numbers_[zone]; | ||||
|   number::Number *x2sens = this->zone_x2_numbers_[zone]; | ||||
|   number::Number *y2sens = this->zone_y2_numbers_[zone]; | ||||
|   if (!x1sens->has_state() || !y1sens->has_state() || !x2sens->has_state() || !y2sens->has_state()) { | ||||
|     return; | ||||
|   } | ||||
|   this->zone_config_[zone].x1 = static_cast<int>(x1sens->state); | ||||
|   this->zone_config_[zone].y1 = static_cast<int>(y1sens->state); | ||||
|   this->zone_config_[zone].x2 = static_cast<int>(x2sens->state); | ||||
|   this->zone_config_[zone].y2 = static_cast<int>(y2sens->state); | ||||
|   this->send_set_zone_command_(); | ||||
| } | ||||
|  | ||||
| void LD2450Component::set_zone_x1_number(uint8_t zone, number::Number *n) { this->zone_x1_numbers_[zone] = n; } | ||||
| void LD2450Component::set_zone_y1_number(uint8_t zone, number::Number *n) { this->zone_y1_numbers_[zone] = n; } | ||||
| void LD2450Component::set_zone_x2_number(uint8_t zone, number::Number *n) { this->zone_x2_numbers_[zone] = n; } | ||||
| void LD2450Component::set_zone_y2_number(uint8_t zone, number::Number *n) { this->zone_y2_numbers_[zone] = n; } | ||||
| #endif | ||||
|  | ||||
| // Set Presence Timeout load and save from flash | ||||
| #ifdef USE_NUMBER | ||||
| void LD2450Component::set_presence_timeout() { | ||||
|   if (this->presence_timeout_number_ != nullptr) { | ||||
|     if (this->presence_timeout_number_->state == 0) { | ||||
|       float timeout = this->restore_from_flash_(); | ||||
|       this->presence_timeout_number_->publish_state(timeout); | ||||
|       this->timeout_ = ld2450::convert_seconds_to_ms(timeout); | ||||
|     } | ||||
|     if (this->presence_timeout_number_->has_state()) { | ||||
|       this->save_to_flash_(this->presence_timeout_number_->state); | ||||
|       this->timeout_ = ld2450::convert_seconds_to_ms(this->presence_timeout_number_->state); | ||||
|     } | ||||
|   } | ||||
| } | ||||
|  | ||||
| // Save Presence Timeout to flash | ||||
| void LD2450Component::save_to_flash_(float value) { this->pref_.save(&value); } | ||||
|  | ||||
| // Load Presence Timeout from flash | ||||
| float LD2450Component::restore_from_flash_() { | ||||
|   float value; | ||||
|   if (!this->pref_.load(&value)) { | ||||
|     value = DEFAULT_PRESENCE_TIMEOUT; | ||||
|   } | ||||
|   return value; | ||||
| } | ||||
| #endif | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										231
									
								
								esphome/components/ld2450/ld2450.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										231
									
								
								esphome/components/ld2450/ld2450.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,231 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include <iomanip> | ||||
| #include <map> | ||||
| #include "esphome/components/uart/uart.h" | ||||
| #include "esphome/core/component.h" | ||||
| #include "esphome/core/defines.h" | ||||
| #include "esphome/core/helpers.h" | ||||
| #include "esphome/core/preferences.h" | ||||
| #ifdef USE_SENSOR | ||||
| #include "esphome/components/sensor/sensor.h" | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
| #include "esphome/components/number/number.h" | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
| #include "esphome/components/switch/switch.h" | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
| #include "esphome/components/button/button.h" | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
| #include "esphome/components/select/select.h" | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
| #include "esphome/components/text_sensor/text_sensor.h" | ||||
| #endif | ||||
| #ifdef USE_BINARY_SENSOR | ||||
| #include "esphome/components/binary_sensor/binary_sensor.h" | ||||
| #endif | ||||
|  | ||||
| #ifndef M_PI | ||||
| #define M_PI 3.14 | ||||
| #endif | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| // Constants | ||||
| static const uint8_t DEFAULT_PRESENCE_TIMEOUT = 5;  // Timeout to reset presense status 5 sec. | ||||
| static const uint8_t MAX_LINE_LENGTH = 60;          // Max characters for serial buffer | ||||
| static const uint8_t MAX_TARGETS = 3;               // Max 3 Targets in LD2450 | ||||
| static const uint8_t MAX_ZONES = 3;                 // Max 3 Zones in LD2450 | ||||
|  | ||||
| // Target coordinate struct | ||||
| struct Target { | ||||
|   int16_t x; | ||||
|   int16_t y; | ||||
|   bool is_moving; | ||||
| }; | ||||
|  | ||||
| // Zone coordinate struct | ||||
| struct Zone { | ||||
|   int16_t x1 = 0; | ||||
|   int16_t y1 = 0; | ||||
|   int16_t x2 = 0; | ||||
|   int16_t y2 = 0; | ||||
| }; | ||||
|  | ||||
| enum BaudRateStructure : uint8_t { | ||||
|   BAUD_RATE_9600 = 1, | ||||
|   BAUD_RATE_19200 = 2, | ||||
|   BAUD_RATE_38400 = 3, | ||||
|   BAUD_RATE_57600 = 4, | ||||
|   BAUD_RATE_115200 = 5, | ||||
|   BAUD_RATE_230400 = 6, | ||||
|   BAUD_RATE_256000 = 7, | ||||
|   BAUD_RATE_460800 = 8 | ||||
| }; | ||||
|  | ||||
| // Convert baud rate enum to int | ||||
| static const std::map<std::string, uint8_t> BAUD_RATE_ENUM_TO_INT{ | ||||
|     {"9600", BAUD_RATE_9600},     {"19200", BAUD_RATE_19200},   {"38400", BAUD_RATE_38400}, | ||||
|     {"57600", BAUD_RATE_57600},   {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400}, | ||||
|     {"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800}}; | ||||
|  | ||||
| // Zone type struct | ||||
| enum ZoneTypeStructure : uint8_t { ZONE_DISABLED = 0, ZONE_DETECTION = 1, ZONE_FILTER = 2 }; | ||||
|  | ||||
| // Convert zone type int to enum | ||||
| static const std::map<ZoneTypeStructure, std::string> ZONE_TYPE_INT_TO_ENUM{ | ||||
|     {ZONE_DISABLED, "Disabled"}, {ZONE_DETECTION, "Detection"}, {ZONE_FILTER, "Filter"}}; | ||||
|  | ||||
| // Convert zone type enum to int | ||||
| static const std::map<std::string, uint8_t> ZONE_TYPE_ENUM_TO_INT{ | ||||
|     {"Disabled", ZONE_DISABLED}, {"Detection", ZONE_DETECTION}, {"Filter", ZONE_FILTER}}; | ||||
|  | ||||
| // LD2450 serial 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}; | ||||
|  | ||||
| enum PeriodicDataStructure : uint8_t { | ||||
|   TARGET_X = 4, | ||||
|   TARGET_Y = 6, | ||||
|   TARGET_SPEED = 8, | ||||
|   TARGET_RESOLUTION = 10, | ||||
| }; | ||||
|  | ||||
| enum PeriodicDataValue : uint8_t { HEAD = 0XAA, END = 0x55, CHECK = 0x00 }; | ||||
|  | ||||
| enum AckDataStructure : uint8_t { COMMAND = 6, COMMAND_STATUS = 7 }; | ||||
|  | ||||
| class LD2450Component : public Component, public uart::UARTDevice { | ||||
| #ifdef USE_SENSOR | ||||
|   SUB_SENSOR(target_count) | ||||
|   SUB_SENSOR(still_target_count) | ||||
|   SUB_SENSOR(moving_target_count) | ||||
| #endif | ||||
| #ifdef USE_BINARY_SENSOR | ||||
|   SUB_BINARY_SENSOR(target) | ||||
|   SUB_BINARY_SENSOR(moving_target) | ||||
|   SUB_BINARY_SENSOR(still_target) | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   SUB_TEXT_SENSOR(version) | ||||
|   SUB_TEXT_SENSOR(mac) | ||||
| #endif | ||||
| #ifdef USE_SELECT | ||||
|   SUB_SELECT(baud_rate) | ||||
|   SUB_SELECT(zone_type) | ||||
| #endif | ||||
| #ifdef USE_SWITCH | ||||
|   SUB_SWITCH(bluetooth) | ||||
|   SUB_SWITCH(multi_target) | ||||
| #endif | ||||
| #ifdef USE_BUTTON | ||||
|   SUB_BUTTON(reset) | ||||
|   SUB_BUTTON(restart) | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
|   SUB_NUMBER(presence_timeout) | ||||
| #endif | ||||
|  | ||||
|  public: | ||||
|   LD2450Component(); | ||||
|   void setup() override; | ||||
|   void dump_config() override; | ||||
|   void loop() override; | ||||
|   void set_presence_timeout(); | ||||
|   void set_throttle(uint16_t value) { this->throttle_ = value; }; | ||||
|   void read_all_info(); | ||||
|   void query_zone_info(); | ||||
|   void restart_and_read_all_info(); | ||||
|   void set_bluetooth(bool enable); | ||||
|   void set_multi_target(bool enable); | ||||
|   void set_baud_rate(const std::string &state); | ||||
|   void set_zone_type(const std::string &state); | ||||
|   void publish_zone_type(); | ||||
|   void factory_reset(); | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   void set_direction_text_sensor(uint8_t target, text_sensor::TextSensor *s); | ||||
| #endif | ||||
| #ifdef USE_NUMBER | ||||
|   void set_zone_coordinate(uint8_t zone); | ||||
|   void set_zone_x1_number(uint8_t zone, number::Number *n); | ||||
|   void set_zone_y1_number(uint8_t zone, number::Number *n); | ||||
|   void set_zone_x2_number(uint8_t zone, number::Number *n); | ||||
|   void set_zone_y2_number(uint8_t zone, number::Number *n); | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   void set_move_x_sensor(uint8_t target, sensor::Sensor *s); | ||||
|   void set_move_y_sensor(uint8_t target, sensor::Sensor *s); | ||||
|   void set_move_speed_sensor(uint8_t target, sensor::Sensor *s); | ||||
|   void set_move_angle_sensor(uint8_t target, sensor::Sensor *s); | ||||
|   void set_move_distance_sensor(uint8_t target, sensor::Sensor *s); | ||||
|   void set_move_resolution_sensor(uint8_t target, sensor::Sensor *s); | ||||
|   void set_zone_target_count_sensor(uint8_t zone, sensor::Sensor *s); | ||||
|   void set_zone_still_target_count_sensor(uint8_t zone, sensor::Sensor *s); | ||||
|   void set_zone_moving_target_count_sensor(uint8_t zone, sensor::Sensor *s); | ||||
| #endif | ||||
|   void reset_radar_zone(); | ||||
|   void set_radar_zone(int32_t zone_type, int32_t zone1_x1, int32_t zone1_y1, int32_t zone1_x2, int32_t zone1_y2, | ||||
|                       int32_t zone2_x1, int32_t zone2_y1, int32_t zone2_x2, int32_t zone2_y2, int32_t zone3_x1, | ||||
|                       int32_t zone3_y1, int32_t zone3_x2, int32_t zone3_y2); | ||||
|  | ||||
|  protected: | ||||
|   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, uint8_t len); | ||||
|   bool handle_ack_data_(uint8_t *buffer, uint8_t len); | ||||
|   void process_zone_(uint8_t *buffer); | ||||
|   void readline_(int readch, uint8_t *buffer, uint8_t len); | ||||
|   void get_version_(); | ||||
|   void get_mac_(); | ||||
|   void query_target_tracking_mode_(); | ||||
|   void query_zone_(); | ||||
|   void restart_(); | ||||
|   void send_set_zone_command_(); | ||||
|   void save_to_flash_(float value); | ||||
|   float restore_from_flash_(); | ||||
|   bool get_timeout_status_(uint32_t check_millis); | ||||
|   uint8_t count_targets_in_zone_(const Zone &zone, bool is_moving); | ||||
|  | ||||
|   Target target_info_[MAX_TARGETS]; | ||||
|   Zone zone_config_[MAX_ZONES]; | ||||
|   uint8_t buffer_pos_ = 0;  // where to resume processing/populating buffer | ||||
|   uint8_t buffer_data_[MAX_LINE_LENGTH]; | ||||
|   uint32_t last_periodic_millis_ = 0; | ||||
|   uint32_t presence_millis_ = 0; | ||||
|   uint32_t still_presence_millis_ = 0; | ||||
|   uint32_t moving_presence_millis_ = 0; | ||||
|   uint16_t throttle_ = 0; | ||||
|   uint16_t timeout_ = 5; | ||||
|   uint8_t zone_type_ = 0; | ||||
|   std::string version_{}; | ||||
|   std::string mac_{}; | ||||
| #ifdef USE_NUMBER | ||||
|   ESPPreferenceObject pref_;  // only used when numbers are in use | ||||
|   std::vector<number::Number *> zone_x1_numbers_ = std::vector<number::Number *>(MAX_ZONES); | ||||
|   std::vector<number::Number *> zone_y1_numbers_ = std::vector<number::Number *>(MAX_ZONES); | ||||
|   std::vector<number::Number *> zone_x2_numbers_ = std::vector<number::Number *>(MAX_ZONES); | ||||
|   std::vector<number::Number *> zone_y2_numbers_ = std::vector<number::Number *>(MAX_ZONES); | ||||
| #endif | ||||
| #ifdef USE_SENSOR | ||||
|   std::vector<sensor::Sensor *> move_x_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS); | ||||
|   std::vector<sensor::Sensor *> move_y_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS); | ||||
|   std::vector<sensor::Sensor *> move_speed_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS); | ||||
|   std::vector<sensor::Sensor *> move_angle_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS); | ||||
|   std::vector<sensor::Sensor *> move_distance_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS); | ||||
|   std::vector<sensor::Sensor *> move_resolution_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS); | ||||
|   std::vector<sensor::Sensor *> zone_target_count_sensors_ = std::vector<sensor::Sensor *>(MAX_ZONES); | ||||
|   std::vector<sensor::Sensor *> zone_still_target_count_sensors_ = std::vector<sensor::Sensor *>(MAX_ZONES); | ||||
|   std::vector<sensor::Sensor *> zone_moving_target_count_sensors_ = std::vector<sensor::Sensor *>(MAX_ZONES); | ||||
| #endif | ||||
| #ifdef USE_TEXT_SENSOR | ||||
|   std::vector<text_sensor::TextSensor *> direction_text_sensors_ = std::vector<text_sensor::TextSensor *>(3); | ||||
| #endif | ||||
| }; | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										120
									
								
								esphome/components/ld2450/number/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										120
									
								
								esphome/components/ld2450/number/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,120 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import number | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ID, | ||||
|     DEVICE_CLASS_DISTANCE, | ||||
|     ENTITY_CATEGORY_CONFIG, | ||||
|     ICON_TIMELAPSE, | ||||
|     UNIT_MILLIMETER, | ||||
|     UNIT_SECOND, | ||||
| ) | ||||
|  | ||||
| from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns | ||||
|  | ||||
| CONF_PRESENCE_TIMEOUT = "presence_timeout" | ||||
| CONF_X1 = "x1" | ||||
| CONF_X2 = "x2" | ||||
| CONF_Y1 = "y1" | ||||
| CONF_Y2 = "y2" | ||||
| ICON_ARROW_BOTTOM_RIGHT = "mdi:arrow-bottom-right" | ||||
| ICON_ARROW_BOTTOM_RIGHT_BOLD_BOX_OUTLINE = "mdi:arrow-bottom-right-bold-box-outline" | ||||
| ICON_ARROW_TOP_LEFT = "mdi:arrow-top-left" | ||||
| ICON_ARROW_TOP_LEFT_BOLD_BOX_OUTLINE = "mdi:arrow-top-left-bold-box-outline" | ||||
| MAX_ZONES = 3 | ||||
|  | ||||
| PresenceTimeoutNumber = ld2450_ns.class_("PresenceTimeoutNumber", number.Number) | ||||
| ZoneCoordinateNumber = ld2450_ns.class_("ZoneCoordinateNumber", number.Number) | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), | ||||
|         cv.Required(CONF_PRESENCE_TIMEOUT): number.number_schema( | ||||
|             PresenceTimeoutNumber, | ||||
|             unit_of_measurement=UNIT_SECOND, | ||||
|             entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|             icon=ICON_TIMELAPSE, | ||||
|         ), | ||||
|     } | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = CONFIG_SCHEMA.extend( | ||||
|     { | ||||
|         cv.Optional(f"zone_{n + 1}"): cv.Schema( | ||||
|             { | ||||
|                 cv.Required(CONF_X1): number.number_schema( | ||||
|                     ZoneCoordinateNumber, | ||||
|                     device_class=DEVICE_CLASS_DISTANCE, | ||||
|                     unit_of_measurement=UNIT_MILLIMETER, | ||||
|                     entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|                     icon=ICON_ARROW_TOP_LEFT_BOLD_BOX_OUTLINE, | ||||
|                 ), | ||||
|                 cv.Required(CONF_Y1): number.number_schema( | ||||
|                     ZoneCoordinateNumber, | ||||
|                     device_class=DEVICE_CLASS_DISTANCE, | ||||
|                     unit_of_measurement=UNIT_MILLIMETER, | ||||
|                     entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|                     icon=ICON_ARROW_TOP_LEFT, | ||||
|                 ), | ||||
|                 cv.Required(CONF_X2): number.number_schema( | ||||
|                     ZoneCoordinateNumber, | ||||
|                     device_class=DEVICE_CLASS_DISTANCE, | ||||
|                     unit_of_measurement=UNIT_MILLIMETER, | ||||
|                     entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|                     icon=ICON_ARROW_BOTTOM_RIGHT_BOLD_BOX_OUTLINE, | ||||
|                 ), | ||||
|                 cv.Required(CONF_Y2): number.number_schema( | ||||
|                     ZoneCoordinateNumber, | ||||
|                     device_class=DEVICE_CLASS_DISTANCE, | ||||
|                     unit_of_measurement=UNIT_MILLIMETER, | ||||
|                     entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|                     icon=ICON_ARROW_BOTTOM_RIGHT, | ||||
|                 ), | ||||
|             } | ||||
|         ) | ||||
|         for n in range(MAX_ZONES) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     ld2450_component = await cg.get_variable(config[CONF_LD2450_ID]) | ||||
|     if presence_timeout_config := config.get(CONF_PRESENCE_TIMEOUT): | ||||
|         n = await number.new_number( | ||||
|             presence_timeout_config, | ||||
|             min_value=0, | ||||
|             max_value=3600, | ||||
|             step=1, | ||||
|         ) | ||||
|         await cg.register_parented(n, config[CONF_LD2450_ID]) | ||||
|         cg.add(ld2450_component.set_presence_timeout_number(n)) | ||||
|     for x in range(MAX_ZONES): | ||||
|         if zone_conf := config.get(f"zone_{x + 1}"): | ||||
|             if zone_x1_config := zone_conf.get(CONF_X1): | ||||
|                 n = cg.new_Pvariable(zone_x1_config[CONF_ID], x) | ||||
|                 await number.register_number( | ||||
|                     n, zone_x1_config, min_value=-4860, max_value=4860, step=1 | ||||
|                 ) | ||||
|                 await cg.register_parented(n, config[CONF_LD2450_ID]) | ||||
|                 cg.add(ld2450_component.set_zone_x1_number(x, n)) | ||||
|             if zone_y1_config := zone_conf.get(CONF_Y1): | ||||
|                 n = cg.new_Pvariable(zone_y1_config[CONF_ID], x) | ||||
|                 await number.register_number( | ||||
|                     n, zone_y1_config, min_value=0, max_value=7560, step=1 | ||||
|                 ) | ||||
|                 await cg.register_parented(n, config[CONF_LD2450_ID]) | ||||
|                 cg.add(ld2450_component.set_zone_y1_number(x, n)) | ||||
|             if zone_x2_config := zone_conf.get(CONF_X2): | ||||
|                 n = cg.new_Pvariable(zone_x2_config[CONF_ID], x) | ||||
|                 await number.register_number( | ||||
|                     n, zone_x2_config, min_value=-4860, max_value=4860, step=1 | ||||
|                 ) | ||||
|                 await cg.register_parented(n, config[CONF_LD2450_ID]) | ||||
|                 cg.add(ld2450_component.set_zone_x2_number(x, n)) | ||||
|             if zone_y2_config := zone_conf.get(CONF_Y2): | ||||
|                 n = cg.new_Pvariable(zone_y2_config[CONF_ID], x) | ||||
|                 await number.register_number( | ||||
|                     n, zone_y2_config, min_value=0, max_value=7560, step=1 | ||||
|                 ) | ||||
|                 await cg.register_parented(n, config[CONF_LD2450_ID]) | ||||
|                 cg.add(ld2450_component.set_zone_y2_number(x, n)) | ||||
							
								
								
									
										12
									
								
								esphome/components/ld2450/number/presence_timeout_number.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								esphome/components/ld2450/number/presence_timeout_number.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| #include "presence_timeout_number.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| void PresenceTimeoutNumber::control(float value) { | ||||
|   this->publish_state(value); | ||||
|   this->parent_->set_presence_timeout(); | ||||
| } | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										18
									
								
								esphome/components/ld2450/number/presence_timeout_number.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/ld2450/number/presence_timeout_number.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/number/number.h" | ||||
| #include "../ld2450.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| class PresenceTimeoutNumber : public number::Number, public Parented<LD2450Component> { | ||||
|  public: | ||||
|   PresenceTimeoutNumber() = default; | ||||
|  | ||||
|  protected: | ||||
|   void control(float value) override; | ||||
| }; | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										14
									
								
								esphome/components/ld2450/number/zone_coordinate_number.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										14
									
								
								esphome/components/ld2450/number/zone_coordinate_number.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,14 @@ | ||||
| #include "zone_coordinate_number.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| ZoneCoordinateNumber::ZoneCoordinateNumber(uint8_t zone) : zone_(zone) {} | ||||
|  | ||||
| void ZoneCoordinateNumber::control(float value) { | ||||
|   this->publish_state(value); | ||||
|   this->parent_->set_zone_coordinate(this->zone_); | ||||
| } | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										19
									
								
								esphome/components/ld2450/number/zone_coordinate_number.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										19
									
								
								esphome/components/ld2450/number/zone_coordinate_number.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,19 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/number/number.h" | ||||
| #include "../ld2450.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| class ZoneCoordinateNumber : public number::Number, public Parented<LD2450Component> { | ||||
|  public: | ||||
|   ZoneCoordinateNumber(uint8_t zone); | ||||
|  | ||||
|  protected: | ||||
|   uint8_t zone_; | ||||
|   void control(float value) override; | ||||
| }; | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										56
									
								
								esphome/components/ld2450/select/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										56
									
								
								esphome/components/ld2450/select/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,56 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import select | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import CONF_BAUD_RATE, ENTITY_CATEGORY_CONFIG, ICON_THERMOMETER | ||||
|  | ||||
| from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns | ||||
|  | ||||
| CONF_ZONE_TYPE = "zone_type" | ||||
|  | ||||
| BaudRateSelect = ld2450_ns.class_("BaudRateSelect", select.Select) | ||||
| ZoneTypeSelect = ld2450_ns.class_("ZoneTypeSelect", select.Select) | ||||
|  | ||||
| CONFIG_SCHEMA = { | ||||
|     cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), | ||||
|     cv.Optional(CONF_BAUD_RATE): select.select_schema( | ||||
|         BaudRateSelect, | ||||
|         entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|         icon=ICON_THERMOMETER, | ||||
|     ), | ||||
|     cv.Optional(CONF_ZONE_TYPE): select.select_schema( | ||||
|         ZoneTypeSelect, | ||||
|         entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|         icon=ICON_THERMOMETER, | ||||
|     ), | ||||
| } | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     ld2450_component = await cg.get_variable(config[CONF_LD2450_ID]) | ||||
|     if baud_rate_config := config.get(CONF_BAUD_RATE): | ||||
|         s = await select.new_select( | ||||
|             baud_rate_config, | ||||
|             options=[ | ||||
|                 "9600", | ||||
|                 "19200", | ||||
|                 "38400", | ||||
|                 "57600", | ||||
|                 "115200", | ||||
|                 "230400", | ||||
|                 "256000", | ||||
|                 "460800", | ||||
|             ], | ||||
|         ) | ||||
|         await cg.register_parented(s, config[CONF_LD2450_ID]) | ||||
|         cg.add(ld2450_component.set_baud_rate_select(s)) | ||||
|     if zone_type_config := config.get(CONF_ZONE_TYPE): | ||||
|         s = await select.new_select( | ||||
|             zone_type_config, | ||||
|             options=[ | ||||
|                 "Disabled", | ||||
|                 "Detection", | ||||
|                 "Filter", | ||||
|             ], | ||||
|         ) | ||||
|         await cg.register_parented(s, config[CONF_LD2450_ID]) | ||||
|         cg.add(ld2450_component.set_zone_type_select(s)) | ||||
							
								
								
									
										12
									
								
								esphome/components/ld2450/select/baud_rate_select.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								esphome/components/ld2450/select/baud_rate_select.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| #include "baud_rate_select.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| void BaudRateSelect::control(const std::string &value) { | ||||
|   this->publish_state(value); | ||||
|   this->parent_->set_baud_rate(state); | ||||
| } | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										18
									
								
								esphome/components/ld2450/select/baud_rate_select.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/ld2450/select/baud_rate_select.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/select/select.h" | ||||
| #include "../ld2450.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| class BaudRateSelect : public select::Select, public Parented<LD2450Component> { | ||||
|  public: | ||||
|   BaudRateSelect() = default; | ||||
|  | ||||
|  protected: | ||||
|   void control(const std::string &value) override; | ||||
| }; | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										12
									
								
								esphome/components/ld2450/select/zone_type_select.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								esphome/components/ld2450/select/zone_type_select.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| #include "zone_type_select.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| void ZoneTypeSelect::control(const std::string &value) { | ||||
|   this->publish_state(value); | ||||
|   this->parent_->set_zone_type(state); | ||||
| } | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										18
									
								
								esphome/components/ld2450/select/zone_type_select.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/ld2450/select/zone_type_select.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/select/select.h" | ||||
| #include "../ld2450.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| class ZoneTypeSelect : public select::Select, public Parented<LD2450Component> { | ||||
|  public: | ||||
|   ZoneTypeSelect() = default; | ||||
|  | ||||
|  protected: | ||||
|   void control(const std::string &value) override; | ||||
| }; | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										156
									
								
								esphome/components/ld2450/sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										156
									
								
								esphome/components/ld2450/sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,156 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import sensor | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_ANGLE, | ||||
|     CONF_DISTANCE, | ||||
|     CONF_RESOLUTION, | ||||
|     CONF_SPEED, | ||||
|     DEVICE_CLASS_DISTANCE, | ||||
|     DEVICE_CLASS_SPEED, | ||||
|     UNIT_DEGREES, | ||||
|     UNIT_MILLIMETER, | ||||
| ) | ||||
|  | ||||
| from . import CONF_LD2450_ID, LD2450Component | ||||
|  | ||||
| DEPENDENCIES = ["ld2450"] | ||||
|  | ||||
| CONF_MOVING_TARGET_COUNT = "moving_target_count" | ||||
| CONF_STILL_TARGET_COUNT = "still_target_count" | ||||
| CONF_TARGET_COUNT = "target_count" | ||||
| CONF_X = "x" | ||||
| CONF_Y = "y" | ||||
|  | ||||
| ICON_ACCOUNT_GROUP = "mdi:account-group" | ||||
| ICON_ACCOUNT_SWITCH = "mdi:account-switch" | ||||
| ICON_ALPHA_X_BOX_OUTLINE = "mdi:alpha-x-box-outline" | ||||
| ICON_ALPHA_Y_BOX_OUTLINE = "mdi:alpha-y-box-outline" | ||||
| ICON_FORMAT_TEXT_ROTATION_ANGLE_UP = "mdi:format-text-rotation-angle-up" | ||||
| ICON_HUMAN_GREETING_PROXIMITY = "mdi:human-greeting-proximity" | ||||
| ICON_MAP_MARKER_ACCOUNT = "mdi:map-marker-account" | ||||
| ICON_MAP_MARKER_DISTANCE = "mdi:map-marker-distance" | ||||
| ICON_RELATION_ZERO_OR_ONE_TO_ZERO_OR_ONE = "mdi:relation-zero-or-one-to-zero-or-one" | ||||
| ICON_SPEEDOMETER_SLOW = "mdi:speedometer-slow" | ||||
|  | ||||
| MAX_TARGETS = 3 | ||||
| MAX_ZONES = 3 | ||||
|  | ||||
| UNIT_MILLIMETER_PER_SECOND = "mm/s" | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), | ||||
|         cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema( | ||||
|             icon=ICON_ACCOUNT_GROUP, | ||||
|         ), | ||||
|         cv.Optional(CONF_STILL_TARGET_COUNT): sensor.sensor_schema( | ||||
|             icon=ICON_HUMAN_GREETING_PROXIMITY, | ||||
|         ), | ||||
|         cv.Optional(CONF_MOVING_TARGET_COUNT): sensor.sensor_schema( | ||||
|             icon=ICON_ACCOUNT_SWITCH, | ||||
|         ), | ||||
|     } | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = CONFIG_SCHEMA.extend( | ||||
|     { | ||||
|         cv.Optional(f"target_{n + 1}"): cv.Schema( | ||||
|             { | ||||
|                 cv.Optional(CONF_X): sensor.sensor_schema( | ||||
|                     device_class=DEVICE_CLASS_DISTANCE, | ||||
|                     unit_of_measurement=UNIT_MILLIMETER, | ||||
|                     icon=ICON_ALPHA_X_BOX_OUTLINE, | ||||
|                 ), | ||||
|                 cv.Optional(CONF_Y): sensor.sensor_schema( | ||||
|                     device_class=DEVICE_CLASS_DISTANCE, | ||||
|                     unit_of_measurement=UNIT_MILLIMETER, | ||||
|                     icon=ICON_ALPHA_Y_BOX_OUTLINE, | ||||
|                 ), | ||||
|                 cv.Optional(CONF_SPEED): sensor.sensor_schema( | ||||
|                     device_class=DEVICE_CLASS_SPEED, | ||||
|                     unit_of_measurement=UNIT_MILLIMETER_PER_SECOND, | ||||
|                     icon=ICON_SPEEDOMETER_SLOW, | ||||
|                 ), | ||||
|                 cv.Optional(CONF_ANGLE): sensor.sensor_schema( | ||||
|                     unit_of_measurement=UNIT_DEGREES, | ||||
|                     icon=ICON_FORMAT_TEXT_ROTATION_ANGLE_UP, | ||||
|                 ), | ||||
|                 cv.Optional(CONF_DISTANCE): sensor.sensor_schema( | ||||
|                     device_class=DEVICE_CLASS_DISTANCE, | ||||
|                     unit_of_measurement=UNIT_MILLIMETER, | ||||
|                     icon=ICON_MAP_MARKER_DISTANCE, | ||||
|                 ), | ||||
|                 cv.Optional(CONF_RESOLUTION): sensor.sensor_schema( | ||||
|                     device_class=DEVICE_CLASS_DISTANCE, | ||||
|                     unit_of_measurement=UNIT_MILLIMETER, | ||||
|                     icon=ICON_RELATION_ZERO_OR_ONE_TO_ZERO_OR_ONE, | ||||
|                 ), | ||||
|             } | ||||
|         ) | ||||
|         for n in range(MAX_TARGETS) | ||||
|     }, | ||||
|     { | ||||
|         cv.Optional(f"zone_{n + 1}"): cv.Schema( | ||||
|             { | ||||
|                 cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema( | ||||
|                     icon=ICON_MAP_MARKER_ACCOUNT, | ||||
|                 ), | ||||
|                 cv.Optional(CONF_STILL_TARGET_COUNT): sensor.sensor_schema( | ||||
|                     icon=ICON_MAP_MARKER_ACCOUNT, | ||||
|                 ), | ||||
|                 cv.Optional(CONF_MOVING_TARGET_COUNT): sensor.sensor_schema( | ||||
|                     icon=ICON_MAP_MARKER_ACCOUNT, | ||||
|                 ), | ||||
|             } | ||||
|         ) | ||||
|         for n in range(MAX_ZONES) | ||||
|     }, | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     ld2450_component = await cg.get_variable(config[CONF_LD2450_ID]) | ||||
|  | ||||
|     if target_count_config := config.get(CONF_TARGET_COUNT): | ||||
|         sens = await sensor.new_sensor(target_count_config) | ||||
|         cg.add(ld2450_component.set_target_count_sensor(sens)) | ||||
|  | ||||
|     if still_target_count_config := config.get(CONF_STILL_TARGET_COUNT): | ||||
|         sens = await sensor.new_sensor(still_target_count_config) | ||||
|         cg.add(ld2450_component.set_still_target_count_sensor(sens)) | ||||
|  | ||||
|     if moving_target_count_config := config.get(CONF_MOVING_TARGET_COUNT): | ||||
|         sens = await sensor.new_sensor(moving_target_count_config) | ||||
|         cg.add(ld2450_component.set_moving_target_count_sensor(sens)) | ||||
|     for n in range(MAX_TARGETS): | ||||
|         if target_conf := config.get(f"target_{n + 1}"): | ||||
|             if x_config := target_conf.get(CONF_X): | ||||
|                 sens = await sensor.new_sensor(x_config) | ||||
|                 cg.add(ld2450_component.set_move_x_sensor(n, sens)) | ||||
|             if y_config := target_conf.get(CONF_Y): | ||||
|                 sens = await sensor.new_sensor(y_config) | ||||
|                 cg.add(ld2450_component.set_move_y_sensor(n, sens)) | ||||
|             if speed_config := target_conf.get(CONF_SPEED): | ||||
|                 sens = await sensor.new_sensor(speed_config) | ||||
|                 cg.add(ld2450_component.set_move_speed_sensor(n, sens)) | ||||
|             if angle_config := target_conf.get(CONF_ANGLE): | ||||
|                 sens = await sensor.new_sensor(angle_config) | ||||
|                 cg.add(ld2450_component.set_move_angle_sensor(n, sens)) | ||||
|             if distance_config := target_conf.get(CONF_DISTANCE): | ||||
|                 sens = await sensor.new_sensor(distance_config) | ||||
|                 cg.add(ld2450_component.set_move_distance_sensor(n, sens)) | ||||
|             if resolution_config := target_conf.get(CONF_RESOLUTION): | ||||
|                 sens = await sensor.new_sensor(resolution_config) | ||||
|                 cg.add(ld2450_component.set_move_resolution_sensor(n, sens)) | ||||
|     for n in range(MAX_ZONES): | ||||
|         if zone_config := config.get(f"zone_{n + 1}"): | ||||
|             if target_count_config := zone_config.get(CONF_TARGET_COUNT): | ||||
|                 sens = await sensor.new_sensor(target_count_config) | ||||
|                 cg.add(ld2450_component.set_zone_target_count_sensor(n, sens)) | ||||
|             if still_target_count_config := zone_config.get(CONF_STILL_TARGET_COUNT): | ||||
|                 sens = await sensor.new_sensor(still_target_count_config) | ||||
|                 cg.add(ld2450_component.set_zone_still_target_count_sensor(n, sens)) | ||||
|             if moving_target_count_config := zone_config.get(CONF_MOVING_TARGET_COUNT): | ||||
|                 sens = await sensor.new_sensor(moving_target_count_config) | ||||
|                 cg.add(ld2450_component.set_zone_moving_target_count_sensor(n, sens)) | ||||
							
								
								
									
										45
									
								
								esphome/components/ld2450/switch/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										45
									
								
								esphome/components/ld2450/switch/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,45 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import switch | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     DEVICE_CLASS_SWITCH, | ||||
|     ENTITY_CATEGORY_CONFIG, | ||||
|     ICON_BLUETOOTH, | ||||
|     ICON_PULSE, | ||||
| ) | ||||
|  | ||||
| from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns | ||||
|  | ||||
| BluetoothSwitch = ld2450_ns.class_("BluetoothSwitch", switch.Switch) | ||||
| MultiTargetSwitch = ld2450_ns.class_("MultiTargetSwitch", switch.Switch) | ||||
|  | ||||
| CONF_BLUETOOTH = "bluetooth" | ||||
| CONF_MULTI_TARGET = "multi_target" | ||||
|  | ||||
| CONFIG_SCHEMA = { | ||||
|     cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), | ||||
|     cv.Optional(CONF_BLUETOOTH): switch.switch_schema( | ||||
|         BluetoothSwitch, | ||||
|         device_class=DEVICE_CLASS_SWITCH, | ||||
|         entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|         icon=ICON_BLUETOOTH, | ||||
|     ), | ||||
|     cv.Optional(CONF_MULTI_TARGET): switch.switch_schema( | ||||
|         MultiTargetSwitch, | ||||
|         device_class=DEVICE_CLASS_SWITCH, | ||||
|         entity_category=ENTITY_CATEGORY_CONFIG, | ||||
|         icon=ICON_PULSE, | ||||
|     ), | ||||
| } | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     ld2450_component = await cg.get_variable(config[CONF_LD2450_ID]) | ||||
|     if bluetooth_config := config.get(CONF_BLUETOOTH): | ||||
|         s = await switch.new_switch(bluetooth_config) | ||||
|         await cg.register_parented(s, config[CONF_LD2450_ID]) | ||||
|         cg.add(ld2450_component.set_bluetooth_switch(s)) | ||||
|     if multi_target_config := config.get(CONF_MULTI_TARGET): | ||||
|         s = await switch.new_switch(multi_target_config) | ||||
|         await cg.register_parented(s, config[CONF_LD2450_ID]) | ||||
|         cg.add(ld2450_component.set_multi_target_switch(s)) | ||||
							
								
								
									
										12
									
								
								esphome/components/ld2450/switch/bluetooth_switch.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								esphome/components/ld2450/switch/bluetooth_switch.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| #include "bluetooth_switch.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| void BluetoothSwitch::write_state(bool state) { | ||||
|   this->publish_state(state); | ||||
|   this->parent_->set_bluetooth(state); | ||||
| } | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										18
									
								
								esphome/components/ld2450/switch/bluetooth_switch.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/ld2450/switch/bluetooth_switch.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/switch/switch.h" | ||||
| #include "../ld2450.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| class BluetoothSwitch : public switch_::Switch, public Parented<LD2450Component> { | ||||
|  public: | ||||
|   BluetoothSwitch() = default; | ||||
|  | ||||
|  protected: | ||||
|   void write_state(bool state) override; | ||||
| }; | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										12
									
								
								esphome/components/ld2450/switch/multi_target_switch.cpp
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								esphome/components/ld2450/switch/multi_target_switch.cpp
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,12 @@ | ||||
| #include "multi_target_switch.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| void MultiTargetSwitch::write_state(bool state) { | ||||
|   this->publish_state(state); | ||||
|   this->parent_->set_multi_target(state); | ||||
| } | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										18
									
								
								esphome/components/ld2450/switch/multi_target_switch.h
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										18
									
								
								esphome/components/ld2450/switch/multi_target_switch.h
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,18 @@ | ||||
| #pragma once | ||||
|  | ||||
| #include "esphome/components/switch/switch.h" | ||||
| #include "../ld2450.h" | ||||
|  | ||||
| namespace esphome { | ||||
| namespace ld2450 { | ||||
|  | ||||
| class MultiTargetSwitch : public switch_::Switch, public Parented<LD2450Component> { | ||||
|  public: | ||||
|   MultiTargetSwitch() = default; | ||||
|  | ||||
|  protected: | ||||
|   void write_state(bool state) override; | ||||
| }; | ||||
|  | ||||
| }  // namespace ld2450 | ||||
| }  // namespace esphome | ||||
							
								
								
									
										62
									
								
								esphome/components/ld2450/text_sensor.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										62
									
								
								esphome/components/ld2450/text_sensor.py
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,62 @@ | ||||
| import esphome.codegen as cg | ||||
| from esphome.components import text_sensor | ||||
| import esphome.config_validation as cv | ||||
| from esphome.const import ( | ||||
|     CONF_DIRECTION, | ||||
|     CONF_MAC_ADDRESS, | ||||
|     CONF_VERSION, | ||||
|     ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|     ENTITY_CATEGORY_NONE, | ||||
|     ICON_BLUETOOTH, | ||||
|     ICON_CHIP, | ||||
|     ICON_SIGN_DIRECTION, | ||||
| ) | ||||
|  | ||||
| from . import CONF_LD2450_ID, LD2450Component | ||||
|  | ||||
| DEPENDENCIES = ["ld2450"] | ||||
|  | ||||
| MAX_TARGETS = 3 | ||||
|  | ||||
| CONFIG_SCHEMA = cv.Schema( | ||||
|     { | ||||
|         cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), | ||||
|         cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema( | ||||
|             entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|             icon=ICON_CHIP, | ||||
|         ), | ||||
|         cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema( | ||||
|             entity_category=ENTITY_CATEGORY_DIAGNOSTIC, | ||||
|             icon=ICON_BLUETOOTH, | ||||
|         ), | ||||
|     } | ||||
| ) | ||||
|  | ||||
| CONFIG_SCHEMA = CONFIG_SCHEMA.extend( | ||||
|     { | ||||
|         cv.Optional(f"target_{n + 1}"): cv.Schema( | ||||
|             { | ||||
|                 cv.Optional(CONF_DIRECTION): text_sensor.text_sensor_schema( | ||||
|                     entity_category=ENTITY_CATEGORY_NONE, | ||||
|                     icon=ICON_SIGN_DIRECTION, | ||||
|                 ), | ||||
|             } | ||||
|         ) | ||||
|         for n in range(MAX_TARGETS) | ||||
|     } | ||||
| ) | ||||
|  | ||||
|  | ||||
| async def to_code(config): | ||||
|     ld2450_component = await cg.get_variable(config[CONF_LD2450_ID]) | ||||
|     if version_config := config.get(CONF_VERSION): | ||||
|         sens = await text_sensor.new_text_sensor(version_config) | ||||
|         cg.add(ld2450_component.set_version_text_sensor(sens)) | ||||
|     if mac_address_config := config.get(CONF_MAC_ADDRESS): | ||||
|         sens = await text_sensor.new_text_sensor(mac_address_config) | ||||
|         cg.add(ld2450_component.set_mac_text_sensor(sens)) | ||||
|     for n in range(MAX_TARGETS): | ||||
|         if direction_conf := config.get(f"target_{n + 1}"): | ||||
|             if direction_config := direction_conf.get(CONF_DIRECTION): | ||||
|                 sens = await text_sensor.new_text_sensor(direction_config) | ||||
|                 cg.add(ld2450_component.set_direction_text_sensor(n, sens)) | ||||
							
								
								
									
										168
									
								
								tests/components/ld2450/common.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										168
									
								
								tests/components/ld2450/common.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,168 @@ | ||||
| uart: | ||||
|   - id: ld2450_uart | ||||
|     tx_pin: ${tx_pin} | ||||
|     rx_pin: ${rx_pin} | ||||
|     baud_rate: 256000 | ||||
|     parity: NONE | ||||
|     stop_bits: 1 | ||||
|  | ||||
| ld2450: | ||||
|   - id: ld2450_radar | ||||
|     uart_id: ld2450_uart | ||||
|     throttle: 1000ms | ||||
|  | ||||
| button: | ||||
|   - platform: ld2450 | ||||
|     ld2450_id: ld2450_radar | ||||
|     factory_reset: | ||||
|       name: LD2450 Factory Reset | ||||
|       entity_category: config | ||||
|     restart: | ||||
|       name: LD2450 Restart | ||||
|       entity_category: config | ||||
|  | ||||
| sensor: | ||||
|   - platform: ld2450 | ||||
|     ld2450_id: ld2450_radar | ||||
|     target_count: | ||||
|       name: Presence Target Count | ||||
|     still_target_count: | ||||
|       name: Still Target Count | ||||
|     moving_target_count: | ||||
|       name: Moving Target Count | ||||
|     target_1: | ||||
|       x: | ||||
|         name: Target-1 X | ||||
|       y: | ||||
|         name: Target-1 Y | ||||
|       speed: | ||||
|         name: Target-1 Speed | ||||
|       angle: | ||||
|         name: Target-1 Angle | ||||
|       distance: | ||||
|         name: Target-1 Distance | ||||
|       resolution: | ||||
|         name: Target-1 Resolution | ||||
|     target_2: | ||||
|       x: | ||||
|         name: Target-2 X | ||||
|       y: | ||||
|         name: Target-2 Y | ||||
|       speed: | ||||
|         name: Target-2 Speed | ||||
|       angle: | ||||
|         name: Target-2 Angle | ||||
|       distance: | ||||
|         name: Target-2 Distance | ||||
|       resolution: | ||||
|         name: Target-2 Resolution | ||||
|     target_3: | ||||
|       x: | ||||
|         name: Target-3 X | ||||
|       y: | ||||
|         name: Target-3 Y | ||||
|       speed: | ||||
|         name: Target-3 Speed | ||||
|       angle: | ||||
|         name: Target-3 Angle | ||||
|       distance: | ||||
|         name: Target-3 Distance | ||||
|       resolution: | ||||
|         name: Target-3 Resolution | ||||
|     zone_1: | ||||
|       target_count: | ||||
|         name: Zone-1 All Target Count | ||||
|       still_target_count: | ||||
|         name: Zone-1 Still Target Count | ||||
|       moving_target_count: | ||||
|         name: Zone-1 Moving Target Count | ||||
|     zone_2: | ||||
|       target_count: | ||||
|         name: Zone-2 All Target Count | ||||
|       still_target_count: | ||||
|         name: Zone-2 Still Target Count | ||||
|       moving_target_count: | ||||
|         name: Zone-2 Moving Target Count | ||||
|     zone_3: | ||||
|       target_count: | ||||
|         name: Zone-3 All Target Count | ||||
|       still_target_count: | ||||
|         name: Zone-3 Still Target Count | ||||
|       moving_target_count: | ||||
|         name: Zone-3 Moving Target Count | ||||
|  | ||||
| binary_sensor: | ||||
|   - platform: ld2450 | ||||
|     ld2450_id: ld2450_radar | ||||
|     has_target: | ||||
|       name: Presence | ||||
|     has_moving_target: | ||||
|       name: Moving Target | ||||
|     has_still_target: | ||||
|       name: Still Target | ||||
|  | ||||
| switch: | ||||
|   - platform: ld2450 | ||||
|     ld2450_id: ld2450_radar | ||||
|     bluetooth: | ||||
|       name: Bluetooth | ||||
|     multi_target: | ||||
|       name: Multi Target Tracking | ||||
|  | ||||
| text_sensor: | ||||
|   - platform: ld2450 | ||||
|     ld2450_id: ld2450_radar | ||||
|     version: | ||||
|       name: LD2450 Firmware | ||||
|     mac_address: | ||||
|       name: LD2450 BT MAC | ||||
|     target_1: | ||||
|       direction: | ||||
|         name: Target-1 Direction | ||||
|     target_2: | ||||
|       direction: | ||||
|         name: Target-2 Direction | ||||
|     target_3: | ||||
|       direction: | ||||
|         name: Target-3 Direction | ||||
|  | ||||
| number: | ||||
|   - platform: ld2450 | ||||
|     ld2450_id: ld2450_radar | ||||
|     presence_timeout: | ||||
|       name: Timeout | ||||
|     zone_1: | ||||
|       x1: | ||||
|         name: Zone-1 X1 | ||||
|       y1: | ||||
|         name: Zone-1 Y1 | ||||
|       x2: | ||||
|         name: Zone-1 X2 | ||||
|       y2: | ||||
|         name: Zone-1 Y2 | ||||
|     zone_2: | ||||
|       x1: | ||||
|         name: Zone-2 X1 | ||||
|       y1: | ||||
|         name: Zone-2 Y1 | ||||
|       x2: | ||||
|         name: Zone-2 X2 | ||||
|       y2: | ||||
|         name: Zone-2 Y2 | ||||
|     zone_3: | ||||
|       x1: | ||||
|         name: Zone-3 X1 | ||||
|       y1: | ||||
|         name: Zone-3 Y1 | ||||
|       x2: | ||||
|         name: Zone-3 X2 | ||||
|       y2: | ||||
|         name: Zone-3 Y2 | ||||
|  | ||||
| select: | ||||
|   - platform: ld2450 | ||||
|     ld2450_id: ld2450_radar | ||||
|     baud_rate: | ||||
|       name: Baud Rate | ||||
|     zone_type: | ||||
|       name: Zone Type | ||||
							
								
								
									
										5
									
								
								tests/components/ld2450/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/ld2450/test.esp32-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| substitutions: | ||||
|   tx_pin: GPIO17 | ||||
|   rx_pin: GPIO16 | ||||
|  | ||||
| <<: !include common.yaml | ||||
							
								
								
									
										5
									
								
								tests/components/ld2450/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/ld2450/test.esp32-c3-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| substitutions: | ||||
|   tx_pin: GPIO4 | ||||
|   rx_pin: GPIO5 | ||||
|  | ||||
| <<: !include common.yaml | ||||
							
								
								
									
										5
									
								
								tests/components/ld2450/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/ld2450/test.esp32-c3-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| substitutions: | ||||
|   tx_pin: GPIO4 | ||||
|   rx_pin: GPIO5 | ||||
|  | ||||
| <<: !include common.yaml | ||||
							
								
								
									
										5
									
								
								tests/components/ld2450/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/ld2450/test.esp32-idf.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| substitutions: | ||||
|   tx_pin: GPIO17 | ||||
|   rx_pin: GPIO16 | ||||
|  | ||||
| <<: !include common.yaml | ||||
							
								
								
									
										5
									
								
								tests/components/ld2450/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/ld2450/test.esp8266-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| substitutions: | ||||
|   tx_pin: GPIO4 | ||||
|   rx_pin: GPIO5 | ||||
|  | ||||
| <<: !include common.yaml | ||||
							
								
								
									
										5
									
								
								tests/components/ld2450/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										5
									
								
								tests/components/ld2450/test.rp2040-ard.yaml
									
									
									
									
									
										Normal file
									
								
							| @@ -0,0 +1,5 @@ | ||||
| substitutions: | ||||
|   tx_pin: GPIO4 | ||||
|   rx_pin: GPIO5 | ||||
|  | ||||
| <<: !include common.yaml | ||||
		Reference in New Issue
	
	Block a user