139template<
size_t N> uint8_t
find_uint8(
const StringToUint8 (&arr)[N],
const char *str) {
140 for (
const auto &entry : arr) {
141 if (strcmp(str, entry.str) == 0) {
148template<
size_t N>
const char *
find_str(
const Uint8ToString (&arr)[N], uint8_t value) {
149 for (
const auto &entry : arr) {
150 if (value == entry.value) {
157static constexpr uint8_t DEFAULT_PRESENCE_TIMEOUT = 5;
159static constexpr uint8_t CMD_ENABLE_CONF = 0xFF;
160static constexpr uint8_t CMD_DISABLE_CONF = 0xFE;
161static constexpr uint8_t CMD_ENABLE_ENG = 0x62;
162static constexpr uint8_t CMD_DISABLE_ENG = 0x63;
163static constexpr uint8_t CMD_QUERY_BASIC_CONF = 0x12;
164static constexpr uint8_t CMD_BASIC_CONF = 0x02;
165static constexpr uint8_t CMD_QUERY_VERSION = 0xA0;
166static constexpr uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0x11;
167static constexpr uint8_t CMD_SET_DISTANCE_RESOLUTION = 0x01;
168static constexpr uint8_t CMD_QUERY_LIGHT_CONTROL = 0x1C;
169static constexpr uint8_t CMD_SET_LIGHT_CONTROL = 0x0C;
170static constexpr uint8_t CMD_SET_BAUD_RATE = 0xA1;
171static constexpr uint8_t CMD_QUERY_MAC_ADDRESS = 0xA5;
172static constexpr uint8_t CMD_FACTORY_RESET = 0xA2;
173static constexpr uint8_t CMD_RESTART = 0xA3;
174static constexpr uint8_t CMD_BLUETOOTH = 0xA4;
175static constexpr uint8_t CMD_DYNAMIC_BACKGROUND_CORRECTION = 0x0B;
176static constexpr uint8_t CMD_QUERY_DYNAMIC_BACKGROUND_CORRECTION = 0x1B;
177static constexpr uint8_t CMD_MOTION_GATE_SENS = 0x03;
178static constexpr uint8_t CMD_QUERY_MOTION_GATE_SENS = 0x13;
179static constexpr uint8_t CMD_STATIC_GATE_SENS = 0x04;
180static constexpr uint8_t CMD_QUERY_STATIC_GATE_SENS = 0x14;
181static constexpr uint8_t CMD_NONE = 0x00;
183static constexpr uint8_t CMD_MAX_MOVE_VALUE = 0x00;
184static constexpr uint8_t CMD_MAX_STILL_VALUE = 0x01;
185static constexpr uint8_t CMD_DURATION_VALUE = 0x02;
187static constexpr uint8_t MOVE_BITMASK = 0x01;
188static constexpr uint8_t STILL_BITMASK = 0x02;
190static constexpr uint8_t HEADER_FOOTER_SIZE = 4;
192static constexpr uint8_t CMD_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xFD, 0xFC, 0xFB, 0xFA};
193static constexpr uint8_t CMD_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0x04, 0x03, 0x02, 0x01};
195static constexpr uint8_t DATA_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xF4, 0xF3, 0xF2, 0xF1};
196static constexpr uint8_t DATA_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0xF8, 0xF7, 0xF6, 0xF5};
198static constexpr uint8_t NO_MAC[] = {0x08, 0x05, 0x04, 0x03, 0x02, 0x01};
199static constexpr uint8_t GATE_SENS_ACK_SIZE =
ACK_PAYLOAD + TOTAL_GATES + HEADER_FOOTER_SIZE;
201static constexpr uint16_t GATE_SENS_ACK_LENGTH = GATE_SENS_ACK_SIZE -
COMMAND - HEADER_FOOTER_SIZE;
203static inline bool validate_header_footer(
const uint8_t *header_footer,
const uint8_t *
buffer) {
204 return std::memcmp(header_footer,
buffer, HEADER_FOOTER_SIZE) == 0;
207void LD2412Component::dump_config() {
208 char mac_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
214 " Firmware version: %s\n"
217#ifdef USE_BINARY_SENSOR
218 ESP_LOGCONFIG(TAG,
"Binary Sensors:");
219 LOG_BINARY_SENSOR(
" ",
"DynamicBackgroundCorrectionStatus",
220 this->dynamic_background_correction_status_binary_sensor_);
221 LOG_BINARY_SENSOR(
" ",
"MovingTarget", this->moving_target_binary_sensor_);
222 LOG_BINARY_SENSOR(
" ",
"StillTarget", this->still_target_binary_sensor_);
223 LOG_BINARY_SENSOR(
" ",
"Target", this->target_binary_sensor_);
226 ESP_LOGCONFIG(TAG,
"Sensors:");
227 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"Light", this->light_sensor_);
228 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"DetectionDistance", this->detection_distance_sensor_);
229 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"MovingTargetDistance", this->moving_target_distance_sensor_);
230 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"MovingTargetEnergy", this->moving_target_energy_sensor_);
231 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"StillTargetDistance", this->still_target_distance_sensor_);
232 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"StillTargetEnergy", this->still_target_energy_sensor_);
234 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"GateStill", s);
237 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"GateMove", s);
240#ifdef USE_TEXT_SENSOR
241 ESP_LOGCONFIG(TAG,
"Text Sensors:");
242 LOG_TEXT_SENSOR(
" ",
"MAC address", this->mac_text_sensor_);
243 LOG_TEXT_SENSOR(
" ",
"Version", this->version_text_sensor_);
246 ESP_LOGCONFIG(TAG,
"Numbers:");
247 LOG_NUMBER(
" ",
"LightThreshold", this->light_threshold_number_);
248 LOG_NUMBER(
" ",
"MaxDistanceGate", this->max_distance_gate_number_);
249 LOG_NUMBER(
" ",
"MinDistanceGate", this->min_distance_gate_number_);
250 LOG_NUMBER(
" ",
"Timeout", this->timeout_number_);
252 LOG_NUMBER(
" ",
"Move Thresholds", n);
255 LOG_NUMBER(
" ",
"Still Thresholds", n);
259 ESP_LOGCONFIG(TAG,
"Selects:");
260 LOG_SELECT(
" ",
"BaudRate", this->baud_rate_select_);
261 LOG_SELECT(
" ",
"DistanceResolution", this->distance_resolution_select_);
262 LOG_SELECT(
" ",
"LightFunction", this->light_function_select_);
263 LOG_SELECT(
" ",
"OutPinLevel", this->out_pin_level_select_);
266 ESP_LOGCONFIG(TAG,
"Switches:");
267 LOG_SWITCH(
" ",
"Bluetooth", this->bluetooth_switch_);
268 LOG_SWITCH(
" ",
"EngineeringMode", this->engineering_mode_switch_);
271 ESP_LOGCONFIG(TAG,
"Buttons:");
272 LOG_BUTTON(
" ",
"FactoryReset", this->factory_reset_button_);
273 LOG_BUTTON(
" ",
"Query", this->query_button_);
274 LOG_BUTTON(
" ",
"Restart", this->restart_button_);
275 LOG_BUTTON(
" ",
"StartDynamicBackgroundCorrection", this->start_dynamic_background_correction_button_);
280 ESP_LOGCONFIG(TAG,
"Running setup");
281 this->read_all_info();
284void LD2412Component::read_all_info() {
299 this->get_gate_threshold();
304 if (this->baud_rate_select_ !=
nullptr) {
306 this->baud_rate_select_->publish_state(*index);
312void LD2412Component::restart_and_read_all_info() {
315 this->
set_timeout(1000, [
this]() { this->read_all_info(); });
318void LD2412Component::loop() {
321 uint8_t buf[MAX_LINE_LENGTH];
323 size_t to_read = std::min(avail,
sizeof(buf));
329 for (
size_t i = 0; i < to_read; i++) {
336 ESP_LOGV(TAG,
"Sending COMMAND %02X", command);
338 this->
write_array(CMD_FRAME_HEADER, HEADER_FOOTER_SIZE);
341 if (command_value !=
nullptr) {
342 len += command_value_len;
345 uint8_t len_cmd[] = {
len, 0x00, command, 0x00};
349 if (command_value !=
nullptr) {
350 this->
write_array(command_value, command_value_len);
353 this->
write_array(CMD_FRAME_FOOTER, HEADER_FOOTER_SIZE);
355 if (command != CMD_ENABLE_CONF && command != CMD_DISABLE_CONF) {
375 if (this->engineering_mode_switch_ !=
nullptr) {
376 this->engineering_mode_switch_->publish_state(engineering_mode);
380#ifdef USE_BINARY_SENSOR
389 if (this->target_binary_sensor_ !=
nullptr) {
390 this->target_binary_sensor_->publish_state(target_state != 0x00);
392 if (this->moving_target_binary_sensor_ !=
nullptr) {
393 this->moving_target_binary_sensor_->publish_state(target_state & MOVE_BITMASK);
395 if (this->still_target_binary_sensor_ !=
nullptr) {
396 this->still_target_binary_sensor_->publish_state(target_state & STILL_BITMASK);
407 SAFE_PUBLISH_SENSOR(this->moving_target_distance_sensor_,
410 SAFE_PUBLISH_SENSOR(this->still_target_distance_sensor_,
413 if (this->detection_distance_sensor_.has_sensor()) {
414 int new_detect_distance = 0;
415 if (target_state != 0x00 && (target_state & MOVE_BITMASK)) {
416 new_detect_distance =
418 }
else if (target_state != 0x00) {
421 this->detection_distance_sensor_.publish_state_if_not_dup(new_detect_distance);
423 if (engineering_mode) {
426 ESP_LOGW(TAG,
"Engineering mode packet too short: %u", this->
buffer_pos_);
433 for (uint8_t i = 0; i < TOTAL_GATES; i++) {
439 for (uint8_t i = 0; i < TOTAL_GATES; i++) {
448 for (
auto &gate_move_sensor : this->gate_move_sensors_) {
449 SAFE_PUBLISH_SENSOR_UNKNOWN(gate_move_sensor);
451 for (
auto &gate_still_sensor : this->gate_still_sensors_) {
452 SAFE_PUBLISH_SENSOR_UNKNOWN(gate_still_sensor);
454 SAFE_PUBLISH_SENSOR_UNKNOWN(this->light_sensor_);
476static constexpr uint32_t GATE_QUERY_TIMEOUT_ID = 0;
477static constexpr uint32_t GATE_QUERY_TIMEOUT_MS = 2000;
483 ESP_LOGW(TAG,
"Invalid length");
486 if (!ld2412::validate_header_footer(CMD_FRAME_HEADER, this->
buffer_data_)) {
492 ESP_LOGW(TAG,
"Invalid status");
501 case CMD_ENABLE_CONF:
502 ESP_LOGV(TAG,
"Enable conf");
505 case CMD_DISABLE_CONF:
506 ESP_LOGV(TAG,
"Disabled conf");
509 case CMD_SET_BAUD_RATE:
510 ESP_LOGV(TAG,
"Baud rate change");
512 if (this->baud_rate_select_ !=
nullptr) {
513 auto baud = this->baud_rate_select_->current_option();
514 ESP_LOGW(TAG,
"Change baud rate to %.*s and reinstall", (
int) baud.size(), baud.c_str());
519 case CMD_QUERY_VERSION: {
526 ESP_LOGV(TAG,
"Firmware version: %s", version_s);
527#ifdef USE_TEXT_SENSOR
528 if (this->version_text_sensor_ !=
nullptr) {
529 this->version_text_sensor_->publish_state(version_s);
534 case CMD_QUERY_DISTANCE_RESOLUTION: {
536 ESP_LOGV(TAG,
"Distance resolution: %s", distance_resolution);
538 if (this->distance_resolution_select_ !=
nullptr) {
539 this->distance_resolution_select_->publish_state(distance_resolution);
545 case CMD_QUERY_LIGHT_CONTROL: {
549 ESP_LOGV(TAG,
"Light function: %s, threshold: %u", light_function_str, this->
light_threshold_);
551 if (this->light_function_select_ !=
nullptr) {
552 this->light_function_select_->publish_state(light_function_str);
556 if (this->light_threshold_number_ !=
nullptr) {
557 this->light_threshold_number_->publish_state(
static_cast<float>(this->
light_threshold_));
563 case CMD_QUERY_MAC_ADDRESS: {
573 char mac_s[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
575 ESP_LOGV(TAG,
"MAC address: %s", mac_str);
576#ifdef USE_TEXT_SENSOR
577 if (this->mac_text_sensor_ !=
nullptr) {
578 this->mac_text_sensor_->publish_state(mac_str);
582 if (this->bluetooth_switch_ !=
nullptr) {
589 case CMD_SET_DISTANCE_RESOLUTION:
590 ESP_LOGV(TAG,
"Handled set distance resolution command");
593 case CMD_QUERY_DYNAMIC_BACKGROUND_CORRECTION: {
594 ESP_LOGV(TAG,
"Handled query dynamic background correction");
595 bool dynamic_background_correction_active = (this->
buffer_data_[10] != 0x00);
596#ifdef USE_BINARY_SENSOR
597 if (this->dynamic_background_correction_status_binary_sensor_ !=
nullptr) {
598 this->dynamic_background_correction_status_binary_sensor_->publish_state(dynamic_background_correction_active);
606 ESP_LOGV(TAG,
"Handled bluetooth command");
609 case CMD_SET_LIGHT_CONTROL:
610 ESP_LOGV(TAG,
"Handled set light control command");
613 case CMD_QUERY_MOTION_GATE_SENS:
614 case CMD_QUERY_STATIC_GATE_SENS: {
617 if (declared != GATE_SENS_ACK_LENGTH || this->
buffer_pos_ > GATE_SENS_ACK_SIZE) {
619 ESP_LOGW(TAG,
"Dropping gate threshold answer of %u bytes", this->
buffer_pos_);
628 const bool pending = this->
cancel_timeout(GATE_QUERY_TIMEOUT_ID + motion);
629 for (
size_t i = 0; i < TOTAL_GATES; i++) {
632 if (!pending || !has_value(group.
numbers[i])) {
646 case CMD_QUERY_BASIC_CONF:
658 ESP_LOGV(TAG,
"min_distance_gate_number_: %u, max_distance_gate_number_ %u", this->
buffer_data_[10],
671 if (this->out_pin_level_select_ !=
nullptr) {
672 this->out_pin_level_select_->publish_state(out_pin_level_str);
690 this->buffer_pos_ = 0;
693 if (this->buffer_pos_ < MAX_LINE_LENGTH - 1) {
698 ESP_LOGW(TAG,
"Max command length exceeded; ignoring");
699 this->buffer_pos_ = 0;
701 if (this->buffer_pos_ < 4) {
704 if (ld2412::validate_header_footer(DATA_FRAME_FOOTER, &this->
buffer_data_[this->buffer_pos_ - 4])) {
705 ESP_LOGV_HEX(TAG, this->
buffer_data_, this->buffer_pos_, MAX_LINE_LENGTH,
"Handling Periodic Data: %s");
707 this->buffer_pos_ = 0;
708 }
else if (ld2412::validate_header_footer(CMD_FRAME_FOOTER, &this->
buffer_data_[this->buffer_pos_ - 4])) {
709 ESP_LOGV_HEX(TAG, this->
buffer_data_, this->buffer_pos_, MAX_LINE_LENGTH,
"Handling Ack Data: %s");
711 this->buffer_pos_ = 0;
713 ESP_LOGV(TAG,
"Ack Data incomplete");
719 const uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
720 const uint8_t cmd_value[2] = {0x01, 0x00};
721 this->
send_command_(cmd, enable ? cmd_value :
nullptr,
sizeof(cmd_value));
724void LD2412Component::set_bluetooth(
bool enable) {
726 const uint8_t cmd_value[2] = {enable ? (uint8_t) 0x01 : (uint8_t) 0x00, 0x00};
727 this->
send_command_(CMD_BLUETOOTH, cmd_value,
sizeof(cmd_value));
728 this->
set_timeout(200, [
this]() { this->restart_and_read_all_info(); });
731void LD2412Component::set_distance_resolution(
const char *
state) {
734 this->
send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value,
sizeof(cmd_value));
735 this->
set_timeout(200, [
this]() { this->restart_and_read_all_info(); });
738void LD2412Component::set_baud_rate(
const char *
state) {
741 this->
send_command_(CMD_SET_BAUD_RATE, cmd_value,
sizeof(cmd_value));
746 this->
send_command_(CMD_QUERY_DYNAMIC_BACKGROUND_CORRECTION,
nullptr, 0);
749void LD2412Component::start_dynamic_background_correction() {
753#ifdef USE_BINARY_SENSOR
754 if (this->dynamic_background_correction_status_binary_sensor_ !=
nullptr) {
755 this->dynamic_background_correction_status_binary_sensor_->publish_state(
true);
760 this->
send_command_(CMD_DYNAMIC_BACKGROUND_CORRECTION,
nullptr, 0);
764void LD2412Component::set_engineering_mode(
bool enable) {
765 const uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG;
771void LD2412Component::factory_reset() {
779 this->
set_timeout(2000, [
this]() { this->restart_and_read_all_info(); });
789 const uint8_t cmd_value[2] = {0x01, 0x00};
790 this->
send_command_(CMD_QUERY_MAC_ADDRESS, cmd_value,
sizeof(cmd_value));
797void LD2412Component::set_basic_config() {
798 uint8_t min_gate = 1;
799 uint8_t max_gate = TOTAL_GATES;
800 uint16_t timeout = DEFAULT_PRESENCE_TIMEOUT;
801 uint8_t out_pin_level = 0x01;
804 if (this->min_distance_gate_number_ !=
nullptr) {
805 if (!this->min_distance_gate_number_->has_state())
807 min_gate =
static_cast<int>(this->min_distance_gate_number_->state);
809 if (this->max_distance_gate_number_ !=
nullptr) {
810 if (!this->max_distance_gate_number_->has_state())
812 max_gate =
static_cast<int>(this->max_distance_gate_number_->state) + 1;
814 if (this->timeout_number_ !=
nullptr) {
815 if (!this->timeout_number_->has_state())
817 timeout =
static_cast<int>(this->timeout_number_->state);
821 if (this->out_pin_level_select_ !=
nullptr) {
822 if (!this->out_pin_level_select_->has_state())
829 lowbyte(min_gate), lowbyte(max_gate), lowbyte(timeout), highbyte(timeout), out_pin_level,
839 const uint8_t command = motion ? CMD_MOTION_GATE_SENS : CMD_STATIC_GATE_SENS;
840 uint8_t value[TOTAL_GATES];
841 uint8_t configured = 0;
842 for (uint8_t i = 0; i < TOTAL_GATES; i++) {
845 value[i] =
static_cast<uint8_t
>(n->
state);
851 if (configured < TOTAL_GATES && !group.
read) {
853 ESP_LOGW(TAG,
"Command %02X held back until the module reports its gate thresholds", command);
854 this->
send_command_(motion ? CMD_QUERY_MOTION_GATE_SENS : CMD_QUERY_STATIC_GATE_SENS,
nullptr, 0);
855 this->
set_timeout(GATE_QUERY_TIMEOUT_ID + motion, GATE_QUERY_TIMEOUT_MS,
863 ESP_LOGE(TAG,
"Command %02X not sent, the module did not report its gate thresholds",
864 motion ? CMD_MOTION_GATE_SENS : CMD_STATIC_GATE_SENS);
876 const bool motion = std::find(motion_numbers.begin(), motion_numbers.end(), n) != motion_numbers.end();
882void LD2412Component::get_gate_threshold() {
889void LD2412Component::set_light_out_control() {
891 if (this->light_threshold_number_ !=
nullptr && this->light_threshold_number_->has_state()) {
892 this->
light_threshold_ =
static_cast<uint8_t
>(this->light_threshold_number_->state);
896 if (this->light_function_select_ !=
nullptr && this->light_function_select_->has_state()) {
902 this->
send_command_(CMD_SET_LIGHT_CONTROL, value,
sizeof(value));
904 this->
set_timeout(200, [
this]() { this->restart_and_read_all_info(); });
908void LD2412Component::set_gate_move_sensor(uint8_t gate, sensor::Sensor *s) {
909 this->gate_move_sensors_[gate].set_sensor(s);
911void LD2412Component::set_gate_still_sensor(uint8_t gate, sensor::Sensor *s) {
912 this->gate_still_sensors_[gate].set_sensor(s);
virtual void setup()
Where the component's initialization should happen.
bool cancel_timeout(const char *name)
Cancel a timeout function.
void set_timeout(const char *name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a const char* name.
void query_light_control_()
bool dynamic_background_correction_active_
void query_dynamic_background_correction_()
void set_config_mode_(bool enable)
void send_gate_thresholds_(bool motion)
void handle_periodic_data_()
std::array< SensorWithDedup< uint8_t >, TOTAL_GATES > gate_move_sensors_
GateThresholds & gate_thresholds_(bool motion)
void readline_(int readch)
void gate_query_timeout_(bool motion)
GateThresholds gate_still_thresholds_
std::array< SensorWithDedup< uint8_t >, TOTAL_GATES > gate_still_sensors_
uint8_t buffer_data_[MAX_LINE_LENGTH]
void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len)
uint8_t mac_address_[MAC_ADDRESS_SIZE]
GateThresholds gate_move_thresholds_
void get_distance_resolution_()
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)
uint8_t find_uint8(const StringToUint8(&arr)[N], const char *str)
constexpr Uint8ToString OUT_PIN_LEVELS_BY_UINT[]
constexpr Uint8ToString LIGHT_FUNCTIONS_BY_UINT[]
@ DISTANCE_RESOLUTION_0_5
@ DISTANCE_RESOLUTION_0_2
@ DISTANCE_RESOLUTION_0_75
ESPHOME_LOG_TAG(TAG, "ld2412")
const char * find_str(const Uint8ToString(&arr)[N], uint8_t value)
constexpr uint32_t BAUD_RATES[]
constexpr StringToUint8 BAUD_RATES_BY_STR[]
constexpr StringToUint8 LIGHT_FUNCTIONS_BY_STR[]
constexpr StringToUint8 DISTANCE_RESOLUTIONS_BY_STR[]
void set_number_value(number::Number *n, float value)
constexpr Uint8ToString DISTANCE_RESOLUTIONS_BY_UINT[]
constexpr StringToUint8 OUT_PIN_LEVELS_BY_STR[]
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)
value[LAYOUT.day_index()]
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)
std::array< uint8_t, TOTAL_GATES > last_read
std::array< number::Number *, TOTAL_GATES > numbers