19static const char *
const TAG =
"ld2450";
20static const char *
const UNKNOWN_MAC =
"unknown";
21static const char *
const VERSION_FMT =
"%u.%02X.%02X%02X%02X%02X";
95template<
size_t N> uint8_t
find_uint8(
const StringToUint8 (&arr)[N],
const std::string &str) {
96 for (
const auto &entry : arr) {
103template<
size_t N>
const char *
find_str(
const Uint8ToString (&arr)[N], uint8_t value) {
104 for (
const auto &entry : arr) {
105 if (value == entry.value)
112static constexpr uint8_t CMD_ENABLE_CONF = 0xFF;
113static constexpr uint8_t CMD_DISABLE_CONF = 0xFE;
114static constexpr uint8_t CMD_QUERY_VERSION = 0xA0;
115static constexpr uint8_t CMD_QUERY_MAC_ADDRESS = 0xA5;
116static constexpr uint8_t CMD_RESET = 0xA2;
117static constexpr uint8_t CMD_RESTART = 0xA3;
118static constexpr uint8_t CMD_BLUETOOTH = 0xA4;
119static constexpr uint8_t CMD_SINGLE_TARGET_MODE = 0x80;
120static constexpr uint8_t CMD_MULTI_TARGET_MODE = 0x90;
121static constexpr uint8_t CMD_QUERY_TARGET_MODE = 0x91;
122static constexpr uint8_t CMD_SET_BAUD_RATE = 0xA1;
123static constexpr uint8_t CMD_QUERY_ZONE = 0xC1;
124static constexpr uint8_t CMD_SET_ZONE = 0xC2;
126static constexpr uint8_t HEADER_FOOTER_SIZE = 4;
128static constexpr uint8_t CMD_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xFD, 0xFC, 0xFB, 0xFA};
129static constexpr uint8_t CMD_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0x04, 0x03, 0x02, 0x01};
131static constexpr uint8_t DATA_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xAA, 0xFF, 0x03, 0x00};
132static constexpr uint8_t DATA_FRAME_FOOTER[2] = {0x55, 0xCC};
134static constexpr uint8_t NO_MAC[] = {0x08, 0x05, 0x04, 0x03, 0x02, 0x01};
136static inline uint16_t convert_seconds_to_ms(uint16_t value) {
return value * 1000; };
138static inline void convert_int_values_to_hex(
const int *values, uint8_t *bytes) {
139 for (uint8_t i = 0; i < 4; i++) {
140 uint16_t
val = values[i] & 0xFFFF;
142 bytes[i * 2 + 1] = (
val >> 8) & 0xFF;
146static inline int16_t decode_coordinate(uint8_t low_byte, uint8_t high_byte) {
147 int16_t coordinate = (high_byte & 0x7F) << 8 | low_byte;
148 if ((high_byte & 0x80) == 0) {
149 coordinate = -coordinate;
154static inline int16_t decode_speed(uint8_t low_byte, uint8_t high_byte) {
155 int16_t
speed = (high_byte & 0x7F) << 8 | low_byte;
156 if ((high_byte & 0x80) == 0) {
162static inline int16_t hex_to_signed_int(
const uint8_t *buffer, uint8_t offset) {
163 uint16_t hex_val = (buffer[offset + 1] << 8) | buffer[offset];
164 int16_t dec_val =
static_cast<int16_t
>(hex_val);
165 if (dec_val & 0x8000) {
171static inline float calculate_angle(
float base,
float hypotenuse) {
172 if (base < 0.0f || hypotenuse <= 0.0f) {
175 float angle_radians = acosf(base / hypotenuse);
176 float angle_degrees = angle_radians * (180.0f / std::numbers::pi_v<float>);
177 return angle_degrees;
180static inline bool validate_header_footer(
const uint8_t *header_footer,
const uint8_t *buffer) {
181 return std::memcmp(header_footer, buffer, HEADER_FOOTER_SIZE) == 0;
186 if (this->presence_timeout_number_ !=
nullptr) {
188 this->set_presence_timeout();
191 this->restart_and_read_all_info();
194void LD2450Component::dump_config() {
195 std::string mac_str =
201 " Firmware version: %s\n"
203 version.c_str(), mac_str.c_str());
204#ifdef USE_BINARY_SENSOR
205 ESP_LOGCONFIG(TAG,
"Binary Sensors:");
206 LOG_BINARY_SENSOR(
" ",
"MovingTarget", this->moving_target_binary_sensor_);
207 LOG_BINARY_SENSOR(
" ",
"StillTarget", this->still_target_binary_sensor_);
208 LOG_BINARY_SENSOR(
" ",
"Target", this->target_binary_sensor_);
211 ESP_LOGCONFIG(TAG,
"Sensors:");
212 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"MovingTargetCount", this->moving_target_count_sensor_);
213 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"StillTargetCount", this->still_target_count_sensor_);
214 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"TargetCount", this->target_count_sensor_);
216 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"TargetX", s);
219 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"TargetY", s);
222 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"TargetAngle", s);
225 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"TargetDistance", s);
228 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"TargetResolution", s);
231 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"TargetSpeed", s);
234 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"ZoneTargetCount", s);
237 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"ZoneMovingTargetCount", s);
240 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"ZoneStillTargetCount", s);
243#ifdef USE_TEXT_SENSOR
244 ESP_LOGCONFIG(TAG,
"Text Sensors:");
245 LOG_TEXT_SENSOR(
" ",
"Version", this->version_text_sensor_);
246 LOG_TEXT_SENSOR(
" ",
"MAC address", this->mac_text_sensor_);
248 LOG_TEXT_SENSOR(
" ",
"Direction", s);
252 ESP_LOGCONFIG(TAG,
"Numbers:");
253 LOG_NUMBER(
" ",
"PresenceTimeout", this->presence_timeout_number_);
255 LOG_NUMBER(
" ",
"ZoneX1", n.x1);
256 LOG_NUMBER(
" ",
"ZoneY1", n.y1);
257 LOG_NUMBER(
" ",
"ZoneX2", n.x2);
258 LOG_NUMBER(
" ",
"ZoneY2", n.y2);
262 ESP_LOGCONFIG(TAG,
"Selects:");
263 LOG_SELECT(
" ",
"BaudRate", this->baud_rate_select_);
264 LOG_SELECT(
" ",
"ZoneType", this->zone_type_select_);
267 ESP_LOGCONFIG(TAG,
"Switches:");
268 LOG_SWITCH(
" ",
"Bluetooth", this->bluetooth_switch_);
269 LOG_SWITCH(
" ",
"MultiTarget", this->multi_target_switch_);
272 ESP_LOGCONFIG(TAG,
"Buttons:");
273 LOG_BUTTON(
" ",
"FactoryReset", this->factory_reset_button_);
274 LOG_BUTTON(
" ",
"Restart", this->restart_button_);
278void LD2450Component::loop() {
288 if (index.x > zone.
x1 && index.x < zone.
x2 && index.y > zone.
y1 && index.y < zone.
y2 &&
289 index.is_moving == is_moving) {
297void LD2450Component::reset_radar_zone() {
308void LD2450Component::set_radar_zone(int32_t zone_type, int32_t zone1_x1, int32_t zone1_y1, int32_t zone1_x2,
309 int32_t zone1_y2, int32_t zone2_x1, int32_t zone2_y1, int32_t zone2_x2,
310 int32_t zone2_y2, int32_t zone3_x1, int32_t zone3_y1, int32_t zone3_x2,
313 int zone_parameters[12] = {zone1_x1, zone1_y1, zone1_x2, zone1_y2, zone2_x1, zone2_y1,
314 zone2_x2, zone2_y2, zone3_x1, zone3_y1, zone3_x2, zone3_y2};
315 for (uint8_t i = 0; i < MAX_ZONES; i++) {
326 uint8_t cmd_value[26] = {};
327 uint8_t zone_type_bytes[2] = {
static_cast<uint8_t
>(this->
zone_type_), 0x00};
328 uint8_t area_config[24] = {};
329 for (uint8_t i = 0; i < MAX_ZONES; i++) {
332 ld2450::convert_int_values_to_hex(values, area_config + (i * 8));
334 std::memcpy(cmd_value, zone_type_bytes,
sizeof(zone_type_bytes));
335 std::memcpy(cmd_value + 2, area_config,
sizeof(area_config));
337 this->
send_command_(CMD_SET_ZONE, cmd_value,
sizeof(cmd_value));
343 if (check_millis == 0) {
347 this->
timeout_ = ld2450::convert_seconds_to_ms(DEFAULT_PRESENCE_TIMEOUT);
354 uint8_t index, start;
355 for (index = 0; index < MAX_ZONES; index++) {
356 start = 12 + index * 8;
374void LD2450Component::read_all_info() {
383 if (this->baud_rate_select_ !=
nullptr && this->baud_rate_select_->state != baud_rate) {
384 this->baud_rate_select_->publish_state(baud_rate);
386 this->publish_zone_type();
391void LD2450Component::query_zone_info() {
398void LD2450Component::restart_and_read_all_info() {
401 this->
set_timeout(1500, [
this]() { this->read_all_info(); });
406 ESP_LOGV(TAG,
"Sending COMMAND %02X", command);
408 this->
write_array(CMD_FRAME_HEADER,
sizeof(CMD_FRAME_HEADER));
411 if (command_value !=
nullptr) {
412 len += command_value_len;
415 uint8_t len_cmd[] = {
len, 0x00, command, 0x00};
418 if (command_value !=
nullptr) {
419 this->
write_array(command_value, command_value_len);
422 this->
write_array(CMD_FRAME_FOOTER,
sizeof(CMD_FRAME_FOOTER));
424 if (command != CMD_ENABLE_CONF && command != CMD_DISABLE_CONF) {
434 ESP_LOGE(TAG,
"Invalid length");
437 if (!ld2450::validate_header_footer(DATA_FRAME_HEADER, this->
buffer_data_) ||
440 ESP_LOGE(TAG,
"Invalid header/footer");
444 int16_t target_count = 0;
445 int16_t still_target_count = 0;
446 int16_t moving_target_count = 0;
456 bool is_moving =
false;
458#if defined(USE_BINARY_SENSOR) || defined(USE_SENSOR) || defined(USE_TEXT_SENSOR)
460 for (index = 0; index < MAX_TARGETS; index++) {
467 SAFE_PUBLISH_SENSOR(this->move_x_sensors_[index], tx);
471 SAFE_PUBLISH_SENSOR(this->move_y_sensors_[index], ty);
475 SAFE_PUBLISH_SENSOR(this->move_resolution_sensors_[index], res);
482 moving_target_count++;
485 SAFE_PUBLISH_SENSOR(this->move_speed_sensors_[index], ts);
489 int32_t x_squared = (int32_t) tx * tx;
490 int32_t y_squared = (int32_t) ty * ty;
491 td = (uint16_t) sqrtf(x_squared + y_squared);
496 SAFE_PUBLISH_SENSOR(this->move_distance_sensors_[index], td);
498 angle = ld2450::calculate_angle(
static_cast<float>(ty),
static_cast<float>(td));
502 SAFE_PUBLISH_SENSOR(this->move_angle_sensors_[index], angle);
504#ifdef USE_TEXT_SENSOR
529 still_target_count = target_count - moving_target_count;
534 uint8_t zone_still_targets = 0;
535 uint8_t zone_moving_targets = 0;
536 uint8_t zone_all_targets = 0;
537 for (index = 0; index < MAX_ZONES; index++) {
540 zone_all_targets = zone_still_targets + zone_moving_targets;
543 SAFE_PUBLISH_SENSOR(this->zone_still_target_count_sensors_[index], zone_still_targets);
545 SAFE_PUBLISH_SENSOR(this->zone_moving_target_count_sensors_[index], zone_moving_targets);
547 SAFE_PUBLISH_SENSOR(this->zone_target_count_sensors_[index], zone_all_targets);
551 SAFE_PUBLISH_SENSOR(this->target_count_sensor_, target_count);
553 SAFE_PUBLISH_SENSOR(this->still_target_count_sensor_, still_target_count);
555 SAFE_PUBLISH_SENSOR(this->moving_target_count_sensor_, moving_target_count);
558#ifdef USE_BINARY_SENSOR
560 if (this->target_binary_sensor_ !=
nullptr) {
561 if (target_count > 0) {
562 this->target_binary_sensor_->publish_state(
true);
565 this->target_binary_sensor_->publish_state(
false);
567 ESP_LOGV(TAG,
"Clear presence waiting timeout: %d", this->
timeout_);
572 if (this->moving_target_binary_sensor_ !=
nullptr) {
573 if (moving_target_count > 0) {
574 this->moving_target_binary_sensor_->publish_state(
true);
577 this->moving_target_binary_sensor_->publish_state(
false);
582 if (this->still_target_binary_sensor_ !=
nullptr) {
583 if (still_target_count > 0) {
584 this->still_target_binary_sensor_->publish_state(
true);
587 this->still_target_binary_sensor_->publish_state(
false);
594 if (target_count > 0) {
597 if (moving_target_count > 0) {
600 if (still_target_count > 0) {
609 ESP_LOGE(TAG,
"Invalid length");
612 if (!ld2450::validate_header_footer(CMD_FRAME_HEADER, this->
buffer_data_)) {
617 ESP_LOGE(TAG,
"Invalid status");
626 case CMD_ENABLE_CONF:
627 ESP_LOGV(TAG,
"Enable conf");
630 case CMD_DISABLE_CONF:
631 ESP_LOGV(TAG,
"Disabled conf");
634 case CMD_SET_BAUD_RATE:
635 ESP_LOGV(TAG,
"Baud rate change");
637 if (this->baud_rate_select_ !=
nullptr) {
638 ESP_LOGE(TAG,
"Change baud rate to %s and reinstall", this->baud_rate_select_->state.c_str());
643 case CMD_QUERY_VERSION: {
647 ESP_LOGV(TAG,
"Firmware version: %s", version.c_str());
648#ifdef USE_TEXT_SENSOR
649 if (this->version_text_sensor_ !=
nullptr) {
650 this->version_text_sensor_->publish_state(version);
656 case CMD_QUERY_MAC_ADDRESS: {
666 std::string mac_str =
668 ESP_LOGV(TAG,
"MAC address: %s", mac_str.c_str());
669#ifdef USE_TEXT_SENSOR
670 if (this->mac_text_sensor_ !=
nullptr) {
671 this->mac_text_sensor_->publish_state(mac_str);
675 if (this->bluetooth_switch_ !=
nullptr) {
683 ESP_LOGV(TAG,
"Bluetooth");
686 case CMD_SINGLE_TARGET_MODE:
687 ESP_LOGV(TAG,
"Single target conf");
689 if (this->multi_target_switch_ !=
nullptr) {
690 this->multi_target_switch_->publish_state(
false);
695 case CMD_MULTI_TARGET_MODE:
696 ESP_LOGV(TAG,
"Multi target conf");
698 if (this->multi_target_switch_ !=
nullptr) {
699 this->multi_target_switch_->publish_state(
true);
704 case CMD_QUERY_TARGET_MODE:
705 ESP_LOGV(TAG,
"Query target tracking mode");
707 if (this->multi_target_switch_ !=
nullptr) {
708 this->multi_target_switch_->publish_state(this->
buffer_data_[10] == 0x02);
714 ESP_LOGV(TAG,
"Query zone conf");
716 this->publish_zone_type();
718 if (this->zone_type_select_ !=
nullptr) {
719 ESP_LOGV(TAG,
"Change zone type to: %s", this->zone_type_select_->state.c_str());
723 ESP_LOGV(TAG,
"Zone: Disabled");
726 ESP_LOGV(TAG,
"Zone: Area detection");
729 ESP_LOGV(TAG,
"Zone: Area filter");
735 ESP_LOGV(TAG,
"Set zone conf");
736 this->query_zone_info();
756 ESP_LOGW(TAG,
"Max command length exceeded; ignoring");
772 ESP_LOGV(TAG,
"Ack Data incomplete");
779 const uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
780 const uint8_t cmd_value[2] = {0x01, 0x00};
781 this->
send_command_(cmd, enable ? cmd_value :
nullptr,
sizeof(cmd_value));
785void LD2450Component::set_bluetooth(
bool enable) {
787 const uint8_t cmd_value[2] = {enable ? (uint8_t) 0x01 : (uint8_t) 0x00, 0x00};
788 this->
send_command_(CMD_BLUETOOTH, cmd_value,
sizeof(cmd_value));
789 this->
set_timeout(200, [
this]() { this->restart_and_read_all_info(); });
793void LD2450Component::set_baud_rate(
const std::string &
state) {
796 this->
send_command_(CMD_SET_BAUD_RATE, cmd_value,
sizeof(cmd_value));
801void LD2450Component::set_zone_type(
const std::string &
state) {
802 ESP_LOGV(TAG,
"Set zone type: %s",
state.c_str());
809void LD2450Component::publish_zone_type() {
812 if (this->zone_type_select_ !=
nullptr) {
813 this->zone_type_select_->publish_state(zone_type);
819void LD2450Component::set_multi_target(
bool enable) {
821 uint8_t cmd = enable ? CMD_MULTI_TARGET_MODE : CMD_SINGLE_TARGET_MODE;
827void LD2450Component::factory_reset() {
830 this->
set_timeout(200, [
this]() { this->restart_and_read_all_info(); });
841 uint8_t cmd_value[2] = {0x01, 0x00};
853void LD2450Component::set_move_x_sensor(uint8_t target,
sensor::Sensor *s) {
856void LD2450Component::set_move_y_sensor(uint8_t target,
sensor::Sensor *s) {
857 this->move_y_sensors_[target] =
new SensorWithDedup<int16_t>(s);
859void LD2450Component::set_move_speed_sensor(uint8_t target, sensor::Sensor *s) {
860 this->move_speed_sensors_[target] =
new SensorWithDedup<int16_t>(s);
862void LD2450Component::set_move_angle_sensor(uint8_t target, sensor::Sensor *s) {
863 this->move_angle_sensors_[target] =
new SensorWithDedup<float>(s);
865void LD2450Component::set_move_distance_sensor(uint8_t target, sensor::Sensor *s) {
866 this->move_distance_sensors_[target] =
new SensorWithDedup<uint16_t>(s);
868void LD2450Component::set_move_resolution_sensor(uint8_t target, sensor::Sensor *s) {
869 this->move_resolution_sensors_[target] =
new SensorWithDedup<uint16_t>(s);
871void LD2450Component::set_zone_target_count_sensor(uint8_t zone, sensor::Sensor *s) {
872 this->zone_target_count_sensors_[zone] =
new SensorWithDedup<uint8_t>(s);
874void LD2450Component::set_zone_still_target_count_sensor(uint8_t zone, sensor::Sensor *s) {
875 this->zone_still_target_count_sensors_[zone] =
new SensorWithDedup<uint8_t>(s);
877void LD2450Component::set_zone_moving_target_count_sensor(uint8_t zone, sensor::Sensor *s) {
878 this->zone_moving_target_count_sensors_[zone] =
new SensorWithDedup<uint8_t>(s);
881#ifdef USE_TEXT_SENSOR
882void LD2450Component::set_direction_text_sensor(uint8_t target, text_sensor::TextSensor *s) {
883 this->direction_text_sensors_[target] = s;
889void LD2450Component::set_zone_coordinate(uint8_t zone) {
894 if (!x1sens->has_state() || !y1sens->has_state() || !x2sens->has_state() || !y2sens->has_state()) {
904void LD2450Component::set_zone_numbers(uint8_t zone, number::Number *x1, number::Number *y1, number::Number *x2,
905 number::Number *y2) {
906 if (zone < MAX_ZONES) {
917void LD2450Component::set_presence_timeout() {
918 if (this->presence_timeout_number_ !=
nullptr) {
919 if (this->presence_timeout_number_->state == 0) {
921 this->presence_timeout_number_->publish_state(timeout);
922 this->
timeout_ = ld2450::convert_seconds_to_ms(timeout);
924 if (this->presence_timeout_number_->has_state()) {
926 this->
timeout_ = ld2450::convert_seconds_to_ms(this->presence_timeout_number_->state);
938 value = DEFAULT_PRESENCE_TIMEOUT;
uint32_t IRAM_ATTR HOT get_loop_component_start_time() const
Get the cached time in milliseconds from when the current component started its loop execution.
virtual void setup()
Where the component's initialization should happen.
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)=0
void save_to_flash_(float value)
uint8_t count_targets_in_zone_(const Zone &zone, bool is_moving)
std::array< SensorWithDedup< int16_t > *, MAX_TARGETS > move_speed_sensors_
std::array< SensorWithDedup< int16_t > *, MAX_TARGETS > move_x_sensors_
uint32_t still_presence_millis_
void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len)
void query_target_tracking_mode_()
void send_set_zone_command_()
std::array< text_sensor::TextSensor *, 3 > direction_text_sensors_
std::array< SensorWithDedup< float > *, MAX_TARGETS > move_angle_sensors_
void set_config_mode_(bool enable)
std::array< SensorWithDedup< uint8_t > *, MAX_ZONES > zone_still_target_count_sensors_
uint32_t moving_presence_millis_
std::array< SensorWithDedup< uint16_t > *, MAX_TARGETS > move_resolution_sensors_
std::array< SensorWithDedup< uint8_t > *, MAX_ZONES > zone_target_count_sensors_
void readline_(int readch)
uint8_t buffer_data_[MAX_LINE_LENGTH]
std::array< SensorWithDedup< uint8_t > *, MAX_ZONES > zone_moving_target_count_sensors_
Target target_info_[MAX_TARGETS]
uint32_t presence_millis_
Zone zone_config_[MAX_ZONES]
std::array< SensorWithDedup< uint16_t > *, MAX_TARGETS > move_distance_sensors_
ESPPreferenceObject pref_
std::array< SensorWithDedup< int16_t > *, MAX_TARGETS > move_y_sensors_
float restore_from_flash_()
ZoneOfNumbers zone_numbers_[MAX_ZONES]
void handle_periodic_data_()
bool get_timeout_status_(uint32_t check_millis)
void publish_state(float state)
Base-class for all sensors.
std::string get_state() const
Getter-syntax for .state.
void publish_state(const std::string &state)
uint32_t get_baud_rate() const
void write_array(const uint8_t *data, size_t len)
constexpr Uint8ToString DIRECTION_BY_UINT[]
constexpr StringToUint8 ZONE_TYPE_BY_STR[]
constexpr StringToUint8 BAUD_RATES_BY_STR[]
constexpr Uint8ToString ZONE_TYPE_BY_UINT[]
uint8_t find_uint8(const StringToUint8(&arr)[N], const std::string &str)
const char * find_str(const Uint8ToString(&arr)[N], uint8_t value)
std::vector< uint8_t > bytes
Providing packet encoding functions for exchanging data with a remote host.
bool mac_address_is_valid(const uint8_t *mac)
Check if the MAC address is not all zeros or all ones.
ESPPreferences * global_preferences
std::string format_hex_pretty(const uint8_t *data, size_t length, char separator, bool show_length)
Format a byte array in pretty-printed, human-readable hex format.
std::string format_mac_address_pretty(const uint8_t *mac)
std::string str_sprintf(const char *fmt,...)
void IRAM_ATTR HOT delay(uint32_t ms)
Application App
Global storage of Application pointer - only one Application can exist.