15static const char *
const TAG =
"ld2410";
124template<
size_t N> uint8_t
find_uint8(
const StringToUint8 (&arr)[N],
const char *str) {
125 for (
const auto &entry : arr) {
126 if (strcmp(str, entry.str) == 0)
132template<
size_t N>
const char *
find_str(
const Uint8ToString (&arr)[N], uint8_t value) {
133 for (
const auto &entry : arr) {
134 if (value == entry.value)
141static constexpr uint8_t CMD_ENABLE_CONF = 0xFF;
142static constexpr uint8_t CMD_DISABLE_CONF = 0xFE;
143static constexpr uint8_t CMD_ENABLE_ENG = 0x62;
144static constexpr uint8_t CMD_DISABLE_ENG = 0x63;
145static constexpr uint8_t CMD_MAXDIST_DURATION = 0x60;
146static constexpr uint8_t CMD_QUERY = 0x61;
147static constexpr uint8_t CMD_GATE_SENS = 0x64;
148static constexpr uint8_t CMD_QUERY_VERSION = 0xA0;
149static constexpr uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0xAB;
150static constexpr uint8_t CMD_SET_DISTANCE_RESOLUTION = 0xAA;
151static constexpr uint8_t CMD_QUERY_LIGHT_CONTROL = 0xAE;
152static constexpr uint8_t CMD_SET_LIGHT_CONTROL = 0xAD;
153static constexpr uint8_t CMD_SET_BAUD_RATE = 0xA1;
154static constexpr uint8_t CMD_BT_PASSWORD = 0xA9;
155static constexpr uint8_t CMD_QUERY_MAC_ADDRESS = 0xA5;
156static constexpr uint8_t CMD_RESET = 0xA2;
157static constexpr uint8_t CMD_RESTART = 0xA3;
158static constexpr uint8_t CMD_BLUETOOTH = 0xA4;
160static constexpr uint8_t CMD_MAX_MOVE_VALUE = 0x00;
161static constexpr uint8_t CMD_MAX_STILL_VALUE = 0x01;
162static constexpr uint8_t CMD_DURATION_VALUE = 0x02;
164static constexpr uint8_t MOVE_BITMASK = 0x01;
165static constexpr uint8_t STILL_BITMASK = 0x02;
167static constexpr uint8_t HEADER_FOOTER_SIZE = 4;
169static constexpr uint8_t CMD_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xFD, 0xFC, 0xFB, 0xFA};
170static constexpr uint8_t CMD_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0x04, 0x03, 0x02, 0x01};
172static constexpr uint8_t DATA_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xF4, 0xF3, 0xF2, 0xF1};
173static constexpr uint8_t DATA_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0xF8, 0xF7, 0xF6, 0xF5};
175static constexpr uint8_t NO_MAC[] = {0x08, 0x05, 0x04, 0x03, 0x02, 0x01};
177static inline bool validate_header_footer(
const uint8_t *header_footer,
const uint8_t *buffer) {
178 return std::memcmp(header_footer, buffer, HEADER_FOOTER_SIZE) == 0;
181void LD2410Component::dump_config() {
182 char mac_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
188 " Firmware version: %s\n"
191#ifdef USE_BINARY_SENSOR
192 ESP_LOGCONFIG(TAG,
"Binary Sensors:");
193 LOG_BINARY_SENSOR(
" ",
"Target", this->target_binary_sensor_);
194 LOG_BINARY_SENSOR(
" ",
"MovingTarget", this->moving_target_binary_sensor_);
195 LOG_BINARY_SENSOR(
" ",
"StillTarget", this->still_target_binary_sensor_);
196 LOG_BINARY_SENSOR(
" ",
"OutPinPresenceStatus", this->out_pin_presence_status_binary_sensor_);
199 ESP_LOGCONFIG(TAG,
"Sensors:");
200 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"Light", this->light_sensor_);
201 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"DetectionDistance", this->detection_distance_sensor_);
202 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"MovingTargetDistance", this->moving_target_distance_sensor_);
203 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"MovingTargetEnergy", this->moving_target_energy_sensor_);
204 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"StillTargetDistance", this->still_target_distance_sensor_);
205 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"StillTargetEnergy", this->still_target_energy_sensor_);
207 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"GateMove", s);
210 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"GateStill", s);
213#ifdef USE_TEXT_SENSOR
214 ESP_LOGCONFIG(TAG,
"Text Sensors:");
215 LOG_TEXT_SENSOR(
" ",
"Mac", this->mac_text_sensor_);
216 LOG_TEXT_SENSOR(
" ",
"Version", this->version_text_sensor_);
219 ESP_LOGCONFIG(TAG,
"Numbers:");
220 LOG_NUMBER(
" ",
"LightThreshold", this->light_threshold_number_);
221 LOG_NUMBER(
" ",
"MaxMoveDistanceGate", this->max_move_distance_gate_number_);
222 LOG_NUMBER(
" ",
"MaxStillDistanceGate", this->max_still_distance_gate_number_);
223 LOG_NUMBER(
" ",
"Timeout", this->timeout_number_);
225 LOG_NUMBER(
" ",
"MoveThreshold", n);
228 LOG_NUMBER(
" ",
"StillThreshold", n);
232 ESP_LOGCONFIG(TAG,
"Selects:");
233 LOG_SELECT(
" ",
"BaudRate", this->baud_rate_select_);
234 LOG_SELECT(
" ",
"DistanceResolution", this->distance_resolution_select_);
235 LOG_SELECT(
" ",
"LightFunction", this->light_function_select_);
236 LOG_SELECT(
" ",
"OutPinLevel", this->out_pin_level_select_);
239 ESP_LOGCONFIG(TAG,
"Switches:");
240 LOG_SWITCH(
" ",
"Bluetooth", this->bluetooth_switch_);
241 LOG_SWITCH(
" ",
"EngineeringMode", this->engineering_mode_switch_);
244 ESP_LOGCONFIG(TAG,
"Buttons:");
245 LOG_BUTTON(
" ",
"FactoryReset", this->factory_reset_button_);
246 LOG_BUTTON(
" ",
"Query", this->query_button_);
247 LOG_BUTTON(
" ",
"Restart", this->restart_button_);
253void LD2410Component::read_all_info() {
262 if (this->baud_rate_select_ !=
nullptr) {
264 this->baud_rate_select_->publish_state(*index);
270void LD2410Component::restart_and_read_all_info() {
273 this->
set_timeout(1000, [
this]() { this->read_all_info(); });
276void LD2410Component::loop() {
279 uint8_t buf[MAX_LINE_LENGTH];
281 size_t to_read = std::min(avail,
sizeof(buf));
287 for (
size_t i = 0; i < to_read; i++) {
294 ESP_LOGV(TAG,
"Sending COMMAND %02X", command);
296 this->
write_array(CMD_FRAME_HEADER,
sizeof(CMD_FRAME_HEADER));
299 if (command_value !=
nullptr) {
300 len += command_value_len;
303 uint8_t len_cmd[] = {
len, 0x00, command, 0x00};
306 if (command_value !=
nullptr) {
307 this->
write_array(command_value, command_value_len);
310 this->
write_array(CMD_FRAME_FOOTER,
sizeof(CMD_FRAME_FOOTER));
312 if (command != CMD_ENABLE_CONF && command != CMD_DISABLE_CONF) {
332 if (this->engineering_mode_switch_ !=
nullptr) {
333 this->engineering_mode_switch_->publish_state(engineering_mode);
336#ifdef USE_BINARY_SENSOR
345 if (this->target_binary_sensor_ !=
nullptr) {
346 this->target_binary_sensor_->publish_state(target_state != 0x00);
348 if (this->moving_target_binary_sensor_ !=
nullptr) {
349 this->moving_target_binary_sensor_->publish_state(target_state & MOVE_BITMASK);
351 if (this->still_target_binary_sensor_ !=
nullptr) {
352 this->still_target_binary_sensor_->publish_state(target_state & STILL_BITMASK);
363 SAFE_PUBLISH_SENSOR(this->moving_target_distance_sensor_,
366 SAFE_PUBLISH_SENSOR(this->still_target_distance_sensor_,
369 SAFE_PUBLISH_SENSOR(this->detection_distance_sensor_,
372 if (engineering_mode) {
378 for (uint8_t i = 0; i < TOTAL_GATES; i++) {
384 for (uint8_t i = 0; i < TOTAL_GATES; i++) {
392 for (
auto &gate_move_sensor : this->gate_move_sensors_) {
393 SAFE_PUBLISH_SENSOR_UNKNOWN(gate_move_sensor);
395 for (
auto &gate_still_sensor : this->gate_still_sensors_) {
396 SAFE_PUBLISH_SENSOR_UNKNOWN(gate_still_sensor);
398 SAFE_PUBLISH_SENSOR_UNKNOWN(this->light_sensor_);
401#ifdef USE_BINARY_SENSOR
402 if (this->out_pin_presence_status_binary_sensor_ !=
nullptr) {
403 this->out_pin_presence_status_binary_sensor_->publish_state(
422 ESP_LOGE(TAG,
"Invalid length");
425 if (!ld2410::validate_header_footer(CMD_FRAME_HEADER, this->
buffer_data_)) {
431 ESP_LOGE(TAG,
"Invalid status");
440 case CMD_ENABLE_CONF:
441 ESP_LOGV(TAG,
"Enable conf");
444 case CMD_DISABLE_CONF:
445 ESP_LOGV(TAG,
"Disabled conf");
448 case CMD_SET_BAUD_RATE:
449 ESP_LOGV(TAG,
"Baud rate change");
451 if (this->baud_rate_select_ !=
nullptr) {
452 auto baud = this->baud_rate_select_->current_option();
453 ESP_LOGE(TAG,
"Change baud rate to %.*s and reinstall", (
int) baud.size(), baud.c_str());
458 case CMD_QUERY_VERSION: {
462 ESP_LOGV(TAG,
"Firmware version: %s", version_s);
463#ifdef USE_TEXT_SENSOR
464 if (this->version_text_sensor_ !=
nullptr) {
465 this->version_text_sensor_->publish_state(version_s);
471 case CMD_QUERY_DISTANCE_RESOLUTION: {
473 ESP_LOGV(TAG,
"Distance resolution: %s", distance_resolution);
475 if (this->distance_resolution_select_ !=
nullptr) {
476 this->distance_resolution_select_->publish_state(distance_resolution);
482 case CMD_QUERY_LIGHT_CONTROL: {
488 ESP_LOGV(TAG,
"Light function: %s, threshold: %u, out pin level: %s", light_function_str, this->
light_threshold_,
491 if (this->light_function_select_ !=
nullptr) {
492 this->light_function_select_->publish_state(light_function_str);
494 if (this->out_pin_level_select_ !=
nullptr) {
495 this->out_pin_level_select_->publish_state(out_pin_level_str);
499 if (this->light_threshold_number_ !=
nullptr) {
500 this->light_threshold_number_->publish_state(
static_cast<float>(this->
light_threshold_));
505 case CMD_QUERY_MAC_ADDRESS: {
515 char mac_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
517 ESP_LOGV(TAG,
"MAC address: %s", mac_str);
518#ifdef USE_TEXT_SENSOR
519 if (this->mac_text_sensor_ !=
nullptr) {
520 this->mac_text_sensor_->publish_state(mac_str);
524 if (this->bluetooth_switch_ !=
nullptr) {
532 ESP_LOGV(TAG,
"Sensitivity");
536 ESP_LOGV(TAG,
"Bluetooth");
539 case CMD_SET_DISTANCE_RESOLUTION:
540 ESP_LOGV(TAG,
"Set distance resolution");
543 case CMD_SET_LIGHT_CONTROL:
544 ESP_LOGV(TAG,
"Set light control");
547 case CMD_BT_PASSWORD:
548 ESP_LOGV(TAG,
"Set bluetooth password");
559 std::vector<std::function<void(
void)>> updates;
565 for (std::vector<number::Number *>::size_type i = 0; i != this->gate_move_threshold_numbers_.size(); i++) {
579 for (
auto &update : updates) {
602 ESP_LOGW(TAG,
"Max command length exceeded; ignoring");
610#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
617#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
624 ESP_LOGV(TAG,
"Ack Data incomplete");
630 const uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
631 const uint8_t cmd_value[2] = {0x01, 0x00};
632 this->
send_command_(cmd, enable ? cmd_value :
nullptr,
sizeof(cmd_value));
635void LD2410Component::set_bluetooth(
bool enable) {
637 const uint8_t cmd_value[2] = {enable ? (uint8_t) 0x01 : (uint8_t) 0x00, 0x00};
638 this->
send_command_(CMD_BLUETOOTH, cmd_value,
sizeof(cmd_value));
639 this->
set_timeout(200, [
this]() { this->restart_and_read_all_info(); });
642void LD2410Component::set_distance_resolution(
const char *
state) {
645 this->
send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value,
sizeof(cmd_value));
646 this->
set_timeout(200, [
this]() { this->restart_and_read_all_info(); });
649void LD2410Component::set_baud_rate(
const char *
state) {
652 this->
send_command_(CMD_SET_BAUD_RATE, cmd_value,
sizeof(cmd_value));
656void LD2410Component::set_bluetooth_password(
const std::string &password) {
657 if (password.length() != 6) {
658 ESP_LOGE(TAG,
"Password must be exactly 6 chars");
662 uint8_t cmd_value[6];
663 std::copy(password.begin(), password.end(), std::begin(cmd_value));
664 this->
send_command_(CMD_BT_PASSWORD, cmd_value,
sizeof(cmd_value));
668void LD2410Component::set_engineering_mode(
bool enable) {
669 const uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG;
675void LD2410Component::factory_reset() {
678 this->
set_timeout(200, [
this]() { this->restart_and_read_all_info(); });
688 const uint8_t cmd_value[2] = {0x01, 0x00};
689 this->
send_command_(CMD_QUERY_MAC_ADDRESS, cmd_value,
sizeof(cmd_value));
697void LD2410Component::set_max_distances_timeout() {
698 if (!this->max_move_distance_gate_number_->has_state() || !this->max_still_distance_gate_number_->has_state() ||
699 !this->timeout_number_->has_state()) {
702 int max_moving_distance_gate_range =
static_cast<int>(this->max_move_distance_gate_number_->state);
703 int max_still_distance_gate_range =
static_cast<int>(this->max_still_distance_gate_number_->state);
704 int timeout =
static_cast<int>(this->timeout_number_->state);
705 uint8_t value[18] = {0x00,
707 lowbyte(max_moving_distance_gate_range),
708 highbyte(max_moving_distance_gate_range),
713 lowbyte(max_still_distance_gate_range),
714 highbyte(max_still_distance_gate_range),
724 this->
send_command_(CMD_MAXDIST_DURATION, value,
sizeof(value));
726 this->
set_timeout(200, [
this]() { this->restart_and_read_all_info(); });
730void LD2410Component::set_gate_threshold(uint8_t gate) {
731 number::Number *motionsens = this->gate_move_threshold_numbers_[gate];
734 if (!motionsens->has_state() || !stillsens->has_state()) {
737 int motion =
static_cast<int>(motionsens->state);
738 int still =
static_cast<int>(stillsens->state);
750 uint8_t value[18] = {0x00, 0x00, lowbyte(gate), highbyte(gate), 0x00, 0x00,
751 0x01, 0x00, lowbyte(motion), highbyte(motion), 0x00, 0x00,
752 0x02, 0x00, lowbyte(still), highbyte(still), 0x00, 0x00};
758void LD2410Component::set_gate_still_threshold_number(uint8_t gate, number::Number *n) {
762void LD2410Component::set_gate_move_threshold_number(uint8_t gate, number::Number *n) {
763 this->gate_move_threshold_numbers_[gate] = n;
767void LD2410Component::set_light_out_control() {
769 if (this->light_threshold_number_ !=
nullptr && this->light_threshold_number_->has_state()) {
770 this->
light_threshold_ =
static_cast<uint8_t
>(this->light_threshold_number_->state);
774 if (this->light_function_select_ !=
nullptr && this->light_function_select_->has_state()) {
777 if (this->out_pin_level_select_ !=
nullptr && this->out_pin_level_select_->has_state()) {
783 this->
send_command_(CMD_SET_LIGHT_CONTROL, value,
sizeof(value));
785 this->
set_timeout(200, [
this]() { this->restart_and_read_all_info(); });
790void LD2410Component::set_gate_move_sensor(uint8_t gate, sensor::Sensor *s) {
791 this->gate_move_sensors_[gate].set_sensor(s);
794void LD2410Component::set_gate_still_sensor(uint8_t gate, sensor::Sensor *s) {
795 this->gate_still_sensors_[gate].set_sensor(s);
virtual void setup()
Where the component's initialization should happen.
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
std::array< number::Number *, TOTAL_GATES > gate_still_threshold_numbers_
void get_distance_resolution_()
void set_config_mode_(bool enable)
std::array< number::Number *, TOTAL_GATES > gate_move_threshold_numbers_
void readline_(int readch)
std::array< SensorWithDedup< uint8_t >, TOTAL_GATES > gate_still_sensors_
uint8_t mac_address_[MAC_ADDRESS_SIZE]
void handle_periodic_data_()
void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len)
uint8_t buffer_data_[MAX_LINE_LENGTH]
std::array< SensorWithDedup< uint8_t >, TOTAL_GATES > gate_move_sensors_
void query_light_control_()
Base-class for all numbers.
void publish_state(float state)
uint32_t get_baud_rate() const
optional< std::array< uint8_t, N > > read_array()
void write_array(const uint8_t *data, size_t len)
constexpr Uint8ToString DISTANCE_RESOLUTIONS_BY_UINT[]
constexpr StringToUint8 DISTANCE_RESOLUTIONS_BY_STR[]
constexpr StringToUint8 BAUD_RATES_BY_STR[]
uint8_t find_uint8(const StringToUint8(&arr)[N], const char *str)
constexpr StringToUint8 OUT_PIN_LEVELS_BY_STR[]
const char * find_str(const Uint8ToString(&arr)[N], uint8_t value)
std::function< void(void)> set_number_value(number::Number *n, float value)
@ DISTANCE_RESOLUTION_0_2
@ DISTANCE_RESOLUTION_0_75
constexpr uint32_t BAUD_RATES[]
constexpr Uint8ToString OUT_PIN_LEVELS_BY_UINT[]
constexpr StringToUint8 LIGHT_FUNCTIONS_BY_STR[]
constexpr Uint8ToString LIGHT_FUNCTIONS_BY_UINT[]
void format_version_str(const uint8_t *version, std::span< char, 20 > buffer)
const char * format_mac_str(const uint8_t *mac_address, std::span< char, MAC_ADDRESS_PRETTY_BUFFER_SIZE > buffer)
optional< size_t > find_index(const uint32_t(&arr)[N], uint32_t value)
char * format_hex_pretty_to(char *buffer, size_t buffer_size, const uint8_t *data, size_t length, char separator)
Format byte array as uppercase hex to buffer (base implementation).
constexpr size_t format_hex_pretty_size(size_t byte_count)
Calculate buffer size needed for format_hex_pretty_to with separator: "XX:XX:...:XX\0".
constexpr uint16_t encode_uint16(uint8_t msb, uint8_t lsb)
Encode a 16-bit value given the most and least significant byte.
void HOT delay(uint32_t ms)