16static const char *
const TAG =
"ld2412";
17static const char *
const UNKNOWN_MAC =
"unknown";
18static const char *
const VERSION_FMT =
"%u.%02X.%02X%02X%02X%02X";
135template<
size_t N> uint8_t
find_uint8(
const StringToUint8 (&arr)[N],
const std::string &str) {
136 for (
const auto &entry : arr) {
137 if (str == entry.str) {
144template<
size_t N>
const char *
find_str(
const Uint8ToString (&arr)[N], uint8_t value) {
145 for (
const auto &entry : arr) {
146 if (value == entry.value) {
153static constexpr uint8_t DEFAULT_PRESENCE_TIMEOUT = 5;
155static constexpr uint8_t CMD_ENABLE_CONF = 0xFF;
156static constexpr uint8_t CMD_DISABLE_CONF = 0xFE;
157static constexpr uint8_t CMD_ENABLE_ENG = 0x62;
158static constexpr uint8_t CMD_DISABLE_ENG = 0x63;
159static constexpr uint8_t CMD_QUERY_BASIC_CONF = 0x12;
160static constexpr uint8_t CMD_BASIC_CONF = 0x02;
161static constexpr uint8_t CMD_QUERY_VERSION = 0xA0;
162static constexpr uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0x11;
163static constexpr uint8_t CMD_SET_DISTANCE_RESOLUTION = 0x01;
164static constexpr uint8_t CMD_QUERY_LIGHT_CONTROL = 0x1C;
165static constexpr uint8_t CMD_SET_LIGHT_CONTROL = 0x0C;
166static constexpr uint8_t CMD_SET_BAUD_RATE = 0xA1;
167static constexpr uint8_t CMD_QUERY_MAC_ADDRESS = 0xA5;
168static constexpr uint8_t CMD_FACTORY_RESET = 0xA2;
169static constexpr uint8_t CMD_RESTART = 0xA3;
170static constexpr uint8_t CMD_BLUETOOTH = 0xA4;
171static constexpr uint8_t CMD_DYNAMIC_BACKGROUND_CORRECTION = 0x0B;
172static constexpr uint8_t CMD_QUERY_DYNAMIC_BACKGROUND_CORRECTION = 0x1B;
173static constexpr uint8_t CMD_MOTION_GATE_SENS = 0x03;
174static constexpr uint8_t CMD_QUERY_MOTION_GATE_SENS = 0x13;
175static constexpr uint8_t CMD_STATIC_GATE_SENS = 0x04;
176static constexpr uint8_t CMD_QUERY_STATIC_GATE_SENS = 0x14;
177static constexpr uint8_t CMD_NONE = 0x00;
179static constexpr uint8_t CMD_MAX_MOVE_VALUE = 0x00;
180static constexpr uint8_t CMD_MAX_STILL_VALUE = 0x01;
181static constexpr uint8_t CMD_DURATION_VALUE = 0x02;
183static constexpr uint8_t MOVE_BITMASK = 0x01;
184static constexpr uint8_t STILL_BITMASK = 0x02;
186static constexpr uint8_t HEADER_FOOTER_SIZE = 4;
188static constexpr uint8_t CMD_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xFD, 0xFC, 0xFB, 0xFA};
189static constexpr uint8_t CMD_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0x04, 0x03, 0x02, 0x01};
191static constexpr uint8_t DATA_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xF4, 0xF3, 0xF2, 0xF1};
192static constexpr uint8_t DATA_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0xF8, 0xF7, 0xF6, 0xF5};
194static constexpr uint8_t NO_MAC[] = {0x08, 0x05, 0x04, 0x03, 0x02, 0x01};
196static inline int two_byte_to_int(
char firstbyte,
char secondbyte) {
return (int16_t) (secondbyte << 8) + firstbyte; }
198static inline bool validate_header_footer(
const uint8_t *header_footer,
const uint8_t *buffer) {
199 return std::memcmp(header_footer, buffer, HEADER_FOOTER_SIZE) == 0;
202void LD2412Component::dump_config() {
203 std::string mac_str =
209 " Firmware version: %s\n"
211 version.c_str(), mac_str.c_str());
212#ifdef USE_BINARY_SENSOR
213 ESP_LOGCONFIG(TAG,
"Binary Sensors:");
214 LOG_BINARY_SENSOR(
" ",
"DynamicBackgroundCorrectionStatus",
215 this->dynamic_background_correction_status_binary_sensor_);
216 LOG_BINARY_SENSOR(
" ",
"MovingTarget", this->moving_target_binary_sensor_);
217 LOG_BINARY_SENSOR(
" ",
"StillTarget", this->still_target_binary_sensor_);
218 LOG_BINARY_SENSOR(
" ",
"Target", this->target_binary_sensor_);
221 ESP_LOGCONFIG(TAG,
"Sensors:");
222 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"Light", this->light_sensor_);
223 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"DetectionDistance", this->detection_distance_sensor_);
224 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"MovingTargetDistance", this->moving_target_distance_sensor_);
225 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"MovingTargetEnergy", this->moving_target_energy_sensor_);
226 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"StillTargetDistance", this->still_target_distance_sensor_);
227 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"StillTargetEnergy", this->still_target_energy_sensor_);
229 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"GateStill", s);
232 LOG_SENSOR_WITH_DEDUP_SAFE(
" ",
"GateMove", s);
235#ifdef USE_TEXT_SENSOR
236 ESP_LOGCONFIG(TAG,
"Text Sensors:");
237 LOG_TEXT_SENSOR(
" ",
"MAC address", this->mac_text_sensor_);
238 LOG_TEXT_SENSOR(
" ",
"Version", this->version_text_sensor_);
241 ESP_LOGCONFIG(TAG,
"Numbers:");
242 LOG_NUMBER(
" ",
"LightThreshold", this->light_threshold_number_);
243 LOG_NUMBER(
" ",
"MaxDistanceGate", this->max_distance_gate_number_);
244 LOG_NUMBER(
" ",
"MinDistanceGate", this->min_distance_gate_number_);
245 LOG_NUMBER(
" ",
"Timeout", this->timeout_number_);
247 LOG_NUMBER(
" ",
"Move Thresholds", n);
250 LOG_NUMBER(
" ",
"Still Thresholds", n);
254 ESP_LOGCONFIG(TAG,
"Selects:");
255 LOG_SELECT(
" ",
"BaudRate", this->baud_rate_select_);
256 LOG_SELECT(
" ",
"DistanceResolution", this->distance_resolution_select_);
257 LOG_SELECT(
" ",
"LightFunction", this->light_function_select_);
258 LOG_SELECT(
" ",
"OutPinLevel", this->out_pin_level_select_);
261 ESP_LOGCONFIG(TAG,
"Switches:");
262 LOG_SWITCH(
" ",
"Bluetooth", this->bluetooth_switch_);
263 LOG_SWITCH(
" ",
"EngineeringMode", this->engineering_mode_switch_);
266 ESP_LOGCONFIG(TAG,
"Buttons:");
267 LOG_BUTTON(
" ",
"FactoryReset", this->factory_reset_button_);
268 LOG_BUTTON(
" ",
"Query", this->query_button_);
269 LOG_BUTTON(
" ",
"Restart", this->restart_button_);
270 LOG_BUTTON(
" ",
"StartDynamicBackgroundCorrection", this->start_dynamic_background_correction_button_);
275 ESP_LOGCONFIG(TAG,
"Running setup");
276 this->read_all_info();
279void LD2412Component::read_all_info() {
294 this->get_gate_threshold();
300 if (this->baud_rate_select_ !=
nullptr) {
301 this->baud_rate_select_->publish_state(baud_rate);
306void LD2412Component::restart_and_read_all_info() {
309 this->
set_timeout(1000, [
this]() { this->read_all_info(); });
312void LD2412Component::loop() {
319 ESP_LOGV(TAG,
"Sending COMMAND %02X", command);
321 this->
write_array(CMD_FRAME_HEADER, HEADER_FOOTER_SIZE);
324 if (command_value !=
nullptr) {
325 len += command_value_len;
328 uint8_t len_cmd[] = {
len, 0x00, command, 0x00};
332 if (command_value !=
nullptr) {
333 this->
write_array(command_value, command_value_len);
336 this->
write_array(CMD_FRAME_FOOTER, HEADER_FOOTER_SIZE);
338 if (command != CMD_ENABLE_CONF && command != CMD_DISABLE_CONF) {
358 if (this->engineering_mode_switch_ !=
nullptr) {
359 this->engineering_mode_switch_->publish_state(engineering_mode);
363#ifdef USE_BINARY_SENSOR
372 if (this->target_binary_sensor_ !=
nullptr) {
373 this->target_binary_sensor_->publish_state(target_state != 0x00);
375 if (this->moving_target_binary_sensor_ !=
nullptr) {
376 this->moving_target_binary_sensor_->publish_state(target_state & MOVE_BITMASK);
378 if (this->still_target_binary_sensor_ !=
nullptr) {
379 this->still_target_binary_sensor_->publish_state(target_state & STILL_BITMASK);
391 this->moving_target_distance_sensor_,
395 this->still_target_distance_sensor_,
398 if (this->detection_distance_sensor_ !=
nullptr) {
399 int new_detect_distance = 0;
400 if (target_state != 0x00 && (target_state & MOVE_BITMASK)) {
401 new_detect_distance =
403 }
else if (target_state != 0x00) {
404 new_detect_distance =
407 this->detection_distance_sensor_->publish_state_if_not_dup(new_detect_distance);
409 if (engineering_mode) {
415 for (uint8_t i = 0; i < TOTAL_GATES; i++) {
421 for (uint8_t i = 0; i < TOTAL_GATES; i++) {
429 for (
auto &gate_move_sensor : this->gate_move_sensors_) {
430 SAFE_PUBLISH_SENSOR_UNKNOWN(gate_move_sensor)
432 for (
auto &gate_still_sensor : this->gate_still_sensors_) {
433 SAFE_PUBLISH_SENSOR_UNKNOWN(gate_still_sensor)
435 SAFE_PUBLISH_SENSOR_UNKNOWN(this->light_sensor_)
459 ESP_LOGW(TAG,
"Invalid length");
462 if (!ld2412::validate_header_footer(CMD_FRAME_HEADER, this->
buffer_data_)) {
467 ESP_LOGW(TAG,
"Invalid status");
476 case CMD_ENABLE_CONF:
477 ESP_LOGV(TAG,
"Enable conf");
480 case CMD_DISABLE_CONF:
481 ESP_LOGV(TAG,
"Disabled conf");
484 case CMD_SET_BAUD_RATE:
485 ESP_LOGV(TAG,
"Baud rate change");
487 if (this->baud_rate_select_ !=
nullptr) {
488 ESP_LOGW(TAG,
"Change baud rate to %s and reinstall", this->baud_rate_select_->state.c_str());
493 case CMD_QUERY_VERSION: {
497 ESP_LOGV(TAG,
"Firmware version: %s", version.c_str());
498#ifdef USE_TEXT_SENSOR
499 if (this->version_text_sensor_ !=
nullptr) {
500 this->version_text_sensor_->publish_state(version);
505 case CMD_QUERY_DISTANCE_RESOLUTION: {
507 ESP_LOGV(TAG,
"Distance resolution: %s", distance_resolution);
509 if (this->distance_resolution_select_ !=
nullptr) {
510 this->distance_resolution_select_->publish_state(distance_resolution);
516 case CMD_QUERY_LIGHT_CONTROL: {
521 "Light function: %s\n"
522 "Light threshold: %u",
525 if (this->light_function_select_ !=
nullptr) {
526 this->light_function_select_->publish_state(light_function_str);
530 if (this->light_threshold_number_ !=
nullptr) {
531 this->light_threshold_number_->publish_state(
static_cast<float>(this->
light_threshold_));
537 case CMD_QUERY_MAC_ADDRESS: {
547 std::string mac_str =
549 ESP_LOGV(TAG,
"MAC address: %s", mac_str.c_str());
550#ifdef USE_TEXT_SENSOR
551 if (this->mac_text_sensor_ !=
nullptr) {
552 this->mac_text_sensor_->publish_state(mac_str);
556 if (this->bluetooth_switch_ !=
nullptr) {
563 case CMD_SET_DISTANCE_RESOLUTION:
564 ESP_LOGV(TAG,
"Handled set distance resolution command");
567 case CMD_QUERY_DYNAMIC_BACKGROUND_CORRECTION: {
568 ESP_LOGV(TAG,
"Handled query dynamic background correction");
569 bool dynamic_background_correction_active = (this->
buffer_data_[10] != 0x00);
570#ifdef USE_BINARY_SENSOR
571 if (this->dynamic_background_correction_status_binary_sensor_ !=
nullptr) {
572 this->dynamic_background_correction_status_binary_sensor_->publish_state(dynamic_background_correction_active);
580 ESP_LOGV(TAG,
"Handled bluetooth command");
583 case CMD_SET_LIGHT_CONTROL:
584 ESP_LOGV(TAG,
"Handled set light control command");
587 case CMD_QUERY_MOTION_GATE_SENS: {
589 std::vector<std::function<void(
void)>> updates;
594 for (
auto &update : updates) {
601 case CMD_QUERY_STATIC_GATE_SENS: {
603 std::vector<std::function<void(
void)>> updates;
608 for (
auto &update : updates) {
615 case CMD_QUERY_BASIC_CONF:
622 std::vector<std::function<void(
void)>> updates;
625 ESP_LOGV(TAG,
"min_distance_gate_number_: %u, max_distance_gate_number_ %u", this->
buffer_data_[10],
639 if (this->out_pin_level_select_ !=
nullptr) {
640 this->out_pin_level_select_->publish_state(out_pin_level_str);
643 for (
auto &update : updates) {
661 this->buffer_pos_ = 0;
664 if (this->buffer_pos_ < MAX_LINE_LENGTH - 1) {
669 ESP_LOGW(TAG,
"Max command length exceeded; ignoring");
670 this->buffer_pos_ = 0;
672 if (this->buffer_pos_ < 4) {
675 if (ld2412::validate_header_footer(DATA_FRAME_FOOTER, &this->
buffer_data_[this->buffer_pos_ - 4])) {
678 this->buffer_pos_ = 0;
679 }
else if (ld2412::validate_header_footer(CMD_FRAME_FOOTER, &this->
buffer_data_[this->buffer_pos_ - 4])) {
682 this->buffer_pos_ = 0;
684 ESP_LOGV(TAG,
"Ack Data incomplete");
690 const uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
691 const uint8_t cmd_value[2] = {0x01, 0x00};
692 this->
send_command_(cmd, enable ? cmd_value :
nullptr,
sizeof(cmd_value));
695void LD2412Component::set_bluetooth(
bool enable) {
697 const uint8_t cmd_value[2] = {enable ? (uint8_t) 0x01 : (uint8_t) 0x00, 0x00};
698 this->
send_command_(CMD_BLUETOOTH, cmd_value,
sizeof(cmd_value));
699 this->
set_timeout(200, [
this]() { this->restart_and_read_all_info(); });
702void LD2412Component::set_distance_resolution(
const std::string &
state) {
705 this->
send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value,
sizeof(cmd_value));
706 this->
set_timeout(200, [
this]() { this->restart_and_read_all_info(); });
709void LD2412Component::set_baud_rate(
const std::string &
state) {
712 this->
send_command_(CMD_SET_BAUD_RATE, cmd_value,
sizeof(cmd_value));
717 this->
send_command_(CMD_QUERY_DYNAMIC_BACKGROUND_CORRECTION,
nullptr, 0);
720void LD2412Component::start_dynamic_background_correction() {
724#ifdef USE_BINARY_SENSOR
725 if (this->dynamic_background_correction_status_binary_sensor_ !=
nullptr) {
726 this->dynamic_background_correction_status_binary_sensor_->publish_state(
true);
731 this->
send_command_(CMD_DYNAMIC_BACKGROUND_CORRECTION,
nullptr, 0);
735void LD2412Component::set_engineering_mode(
bool enable) {
736 const uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG;
742void LD2412Component::factory_reset() {
745 this->
set_timeout(2000, [
this]() { this->restart_and_read_all_info(); });
755 const uint8_t cmd_value[2] = {0x01, 0x00};
756 this->
send_command_(CMD_QUERY_MAC_ADDRESS, cmd_value,
sizeof(cmd_value));
763void LD2412Component::set_basic_config() {
765 if (!this->min_distance_gate_number_->has_state() || !this->max_distance_gate_number_->has_state() ||
766 !this->timeout_number_->has_state()) {
771 if (!this->out_pin_level_select_->has_state()) {
778 lowbyte(
static_cast<int>(this->min_distance_gate_number_->state)),
779 lowbyte(
static_cast<int>(this->max_distance_gate_number_->state) + 1),
780 lowbyte(
static_cast<int>(this->timeout_number_->state)),
781 highbyte(
static_cast<int>(this->timeout_number_->state)),
783 1, TOTAL_GATES, DEFAULT_PRESENCE_TIMEOUT, 0,
797void LD2412Component::set_gate_threshold() {
798 if (this->gate_move_threshold_numbers_.empty() && this->gate_still_threshold_numbers_.empty()) {
801 uint8_t value[TOTAL_GATES] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
803 if (!this->gate_move_threshold_numbers_.empty()) {
804 for (
size_t i = 0; i < this->gate_move_threshold_numbers_.size(); i++) {
805 value[i] = lowbyte(
static_cast<int>(this->gate_move_threshold_numbers_[i]->
state));
807 this->
send_command_(CMD_MOTION_GATE_SENS, value,
sizeof(value));
813 this->
send_command_(CMD_STATIC_GATE_SENS, value,
sizeof(value));
818void LD2412Component::get_gate_threshold() {
823void LD2412Component::set_gate_still_threshold_number(uint8_t gate, number::Number *n) {
827void LD2412Component::set_gate_move_threshold_number(uint8_t gate, number::Number *n) {
828 this->gate_move_threshold_numbers_[gate] = n;
832void LD2412Component::set_light_out_control() {
834 if (this->light_threshold_number_ !=
nullptr && this->light_threshold_number_->has_state()) {
835 this->
light_threshold_ =
static_cast<uint8_t
>(this->light_threshold_number_->state);
839 if (this->light_function_select_ !=
nullptr && this->light_function_select_->has_state()) {
845 this->
send_command_(CMD_SET_LIGHT_CONTROL, value,
sizeof(value));
847 this->
set_timeout(200, [
this]() { this->restart_and_read_all_info(); });
852void LD2412Component::set_gate_move_sensor(uint8_t gate, sensor::Sensor *s) {
853 this->gate_move_sensors_[gate] =
new SensorWithDedup<uint8_t>(s);
855void LD2412Component::set_gate_still_sensor(uint8_t gate, sensor::Sensor *s) {
856 this->gate_still_sensors_[gate] =
new SensorWithDedup<uint8_t>(s);
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.
void query_light_control_()
bool dynamic_background_correction_active_
void query_dynamic_background_correction_()
void set_config_mode_(bool enable)
std::array< number::Number *, TOTAL_GATES > gate_move_threshold_numbers_
std::array< SensorWithDedup< uint8_t > *, TOTAL_GATES > gate_move_sensors_
std::array< SensorWithDedup< uint8_t > *, TOTAL_GATES > gate_still_sensors_
std::array< number::Number *, TOTAL_GATES > gate_still_threshold_numbers_
void handle_periodic_data_()
void readline_(int readch)
uint8_t buffer_data_[MAX_LINE_LENGTH]
void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len)
void get_distance_resolution_()
Base-class for all numbers.
void publish_state(float state)
uint32_t get_baud_rate() const
void write_array(const uint8_t *data, size_t len)
constexpr Uint8ToString OUT_PIN_LEVELS_BY_UINT[]
uint8_t find_uint8(const StringToUint8(&arr)[N], const std::string &str)
std::function< void(void)> set_number_value(number::Number *n, float value)
constexpr Uint8ToString LIGHT_FUNCTIONS_BY_UINT[]
@ DISTANCE_RESOLUTION_0_5
@ DISTANCE_RESOLUTION_0_2
@ DISTANCE_RESOLUTION_0_75
const char * find_str(const Uint8ToString(&arr)[N], uint8_t value)
constexpr StringToUint8 BAUD_RATES_BY_STR[]
constexpr StringToUint8 LIGHT_FUNCTIONS_BY_STR[]
constexpr StringToUint8 DISTANCE_RESOLUTIONS_BY_STR[]
constexpr Uint8ToString DISTANCE_RESOLUTIONS_BY_UINT[]
constexpr StringToUint8 OUT_PIN_LEVELS_BY_STR[]
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.
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)