11 #define highbyte(val) (uint8_t)((val) >> 8) 12 #define lowbyte(val) (uint8_t)((val) &0xff) 17 static const char *
const TAG =
"ld2450";
18 static const char *
const NO_MAC(
"08:05:04:03:02:01");
22 static const uint8_t CMD_ENABLE_CONF = 0x00FF;
23 static const uint8_t CMD_DISABLE_CONF = 0x00FE;
24 static const uint8_t CMD_VERSION = 0x00A0;
25 static const uint8_t CMD_MAC = 0x00A5;
26 static const uint8_t CMD_RESET = 0x00A2;
27 static const uint8_t CMD_RESTART = 0x00A3;
28 static const uint8_t CMD_BLUETOOTH = 0x00A4;
29 static const uint8_t CMD_SINGLE_TARGET_MODE = 0x0080;
30 static const uint8_t CMD_MULTI_TARGET_MODE = 0x0090;
31 static const uint8_t CMD_QUERY_TARGET_MODE = 0x0091;
32 static const uint8_t CMD_SET_BAUD_RATE = 0x00A1;
33 static const uint8_t CMD_QUERY_ZONE = 0x00C1;
34 static const uint8_t CMD_SET_ZONE = 0x00C2;
36 static inline uint16_t convert_seconds_to_ms(uint16_t value) {
return value * 1000; };
38 static inline std::string convert_signed_int_to_hex(
int value) {
39 auto value_as_str =
str_snprintf(
"%04x", 4, value & 0xFFFF);
43 static inline void convert_int_values_to_hex(
const int *values, uint8_t *
bytes) {
44 for (
int i = 0; i < 4; i++) {
45 std::string temp_hex = convert_signed_int_to_hex(values[i]);
46 bytes[i * 2] = std::stoi(temp_hex.substr(2, 2),
nullptr, 16);
47 bytes[i * 2 + 1] = std::stoi(temp_hex.substr(0, 2),
nullptr, 16);
51 static inline int16_t decode_coordinate(uint8_t low_byte, uint8_t high_byte) {
52 int16_t coordinate = (high_byte & 0x7F) << 8 | low_byte;
53 if ((high_byte & 0x80) == 0) {
54 coordinate = -coordinate;
59 static inline int16_t decode_speed(uint8_t low_byte, uint8_t high_byte) {
60 int16_t
speed = (high_byte & 0x7F) << 8 | low_byte;
61 if ((high_byte & 0x80) == 0) {
67 static inline int16_t hex_to_signed_int(
const uint8_t *buffer, uint8_t offset) {
68 uint16_t hex_val = (buffer[offset + 1] << 8) | buffer[offset];
69 int16_t dec_val =
static_cast<int16_t
>(hex_val);
70 if (dec_val & 0x8000) {
76 static inline float calculate_angle(
float base,
float hypotenuse) {
77 if (base < 0.0 || hypotenuse <= 0.0) {
80 float angle_radians = std::acos(base / hypotenuse);
81 float angle_degrees = angle_radians * (180.0 / M_PI);
85 static inline std::string get_direction(int16_t speed) {
86 static const char *
const APPROACHING =
"Approaching";
87 static const char *
const MOVING_AWAY =
"Moving away";
88 static const char *
const STATIONARY =
"Stationary";
99 static inline std::string
format_mac(uint8_t *buffer) {
100 return str_snprintf(
"%02X:%02X:%02X:%02X:%02X:%02X", 17, buffer[10], buffer[11], buffer[12], buffer[13], buffer[14],
105 return str_sprintf(
"%u.%02X.%02X%02X%02X%02X", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15],
109 LD2450Component::LD2450Component() {}
111 void LD2450Component::setup() {
112 ESP_LOGCONFIG(TAG,
"Setting up HLK-LD2450...");
114 if (this->presence_timeout_number_ !=
nullptr) {
116 this->set_presence_timeout();
119 this->restart_and_read_all_info();
122 void LD2450Component::dump_config() {
123 ESP_LOGCONFIG(TAG,
"HLK-LD2450 Human motion tracking radar module:");
124 #ifdef USE_BINARY_SENSOR 125 LOG_BINARY_SENSOR(
" ",
"TargetBinarySensor", this->target_binary_sensor_);
126 LOG_BINARY_SENSOR(
" ",
"MovingTargetBinarySensor", this->moving_target_binary_sensor_);
127 LOG_BINARY_SENSOR(
" ",
"StillTargetBinarySensor", this->still_target_binary_sensor_);
130 LOG_SWITCH(
" ",
"BluetoothSwitch", this->bluetooth_switch_);
131 LOG_SWITCH(
" ",
"MultiTargetSwitch", this->multi_target_switch_);
134 LOG_BUTTON(
" ",
"ResetButton", this->reset_button_);
135 LOG_BUTTON(
" ",
"RestartButton", this->restart_button_);
138 LOG_SENSOR(
" ",
"TargetCountSensor", this->target_count_sensor_);
139 LOG_SENSOR(
" ",
"StillTargetCountSensor", this->still_target_count_sensor_);
140 LOG_SENSOR(
" ",
"MovingTargetCountSensor", this->moving_target_count_sensor_);
142 LOG_SENSOR(
" ",
"NthTargetXSensor", s);
145 LOG_SENSOR(
" ",
"NthTargetYSensor", s);
148 LOG_SENSOR(
" ",
"NthTargetSpeedSensor", s);
151 LOG_SENSOR(
" ",
"NthTargetAngleSensor", s);
154 LOG_SENSOR(
" ",
"NthTargetDistanceSensor", s);
157 LOG_SENSOR(
" ",
"NthTargetResolutionSensor", s);
160 LOG_SENSOR(
" ",
"NthZoneTargetCountSensor", s);
163 LOG_SENSOR(
" ",
"NthZoneStillTargetCountSensor", s);
166 LOG_SENSOR(
" ",
"NthZoneMovingTargetCountSensor", s);
169 #ifdef USE_TEXT_SENSOR 170 LOG_TEXT_SENSOR(
" ",
"VersionTextSensor", this->version_text_sensor_);
171 LOG_TEXT_SENSOR(
" ",
"MacTextSensor", this->mac_text_sensor_);
173 LOG_TEXT_SENSOR(
" ",
"NthDirectionTextSensor", s);
178 LOG_NUMBER(
" ",
"ZoneX1Number", n.x1);
179 LOG_NUMBER(
" ",
"ZoneY1Number", n.y1);
180 LOG_NUMBER(
" ",
"ZoneX2Number", n.x2);
181 LOG_NUMBER(
" ",
"ZoneY2Number", n.y2);
185 LOG_SELECT(
" ",
"BaudRateSelect", this->baud_rate_select_);
186 LOG_SELECT(
" ",
"ZoneTypeSelect", this->zone_type_select_);
189 LOG_NUMBER(
" ",
"PresenceTimeoutNumber", this->presence_timeout_number_);
191 ESP_LOGCONFIG(TAG,
" Throttle : %ums", this->
throttle_);
192 ESP_LOGCONFIG(TAG,
" MAC Address : %s", const_cast<char *>(this->
mac_.c_str()));
193 ESP_LOGCONFIG(TAG,
" Firmware version : %s", const_cast<char *>(this->
version_.c_str()));
196 void LD2450Component::loop() {
206 if (index.x > zone.
x1 && index.x < zone.
x2 && index.y > zone.
y1 && index.y < zone.
y2 &&
207 index.is_moving == is_moving) {
215 void LD2450Component::reset_radar_zone() {
226 void LD2450Component::set_radar_zone(int32_t zone_type, int32_t zone1_x1, int32_t zone1_y1, int32_t zone1_x2,
227 int32_t zone1_y2, int32_t zone2_x1, int32_t zone2_y1, int32_t zone2_x2,
228 int32_t zone2_y2, int32_t zone3_x1, int32_t zone3_y1, int32_t zone3_x2,
231 int zone_parameters[12] = {zone1_x1, zone1_y1, zone1_x2, zone1_y2, zone2_x1, zone2_y1,
232 zone2_x2, zone2_y2, zone3_x1, zone3_y1, zone3_x2, zone3_y2};
233 for (
int i = 0; i < MAX_ZONES; i++) {
244 uint8_t cmd_value[26] = {};
245 uint8_t zone_type_bytes[2] = {
static_cast<uint8_t
>(this->
zone_type_), 0x00};
246 uint8_t area_config[24] = {};
247 for (
int i = 0; i < MAX_ZONES; i++) {
250 ld2450::convert_int_values_to_hex(values, area_config + (i * 8));
252 std::memcpy(cmd_value, zone_type_bytes, 2);
253 std::memcpy(cmd_value + 2, area_config, 24);
261 if (check_millis == 0) {
265 this->
timeout_ = ld2450::convert_seconds_to_ms(DEFAULT_PRESENCE_TIMEOUT);
267 auto current_millis =
millis();
268 return current_millis - check_millis >= this->
timeout_;
273 uint8_t index, start;
274 for (index = 0; index < MAX_ZONES; index++) {
275 start = 12 + index * 8;
276 this->
zone_config_[index].
x1 = ld2450::hex_to_signed_int(buffer, start);
277 this->
zone_config_[index].
y1 = ld2450::hex_to_signed_int(buffer, start + 2);
278 this->
zone_config_[index].
x2 = ld2450::hex_to_signed_int(buffer, start + 4);
279 this->
zone_config_[index].
y2 = ld2450::hex_to_signed_int(buffer, start + 6);
282 if (this->zone_numbers_[index].x1 !=
nullptr) {
283 this->zone_numbers_[index].x1->publish_state(this->
zone_config_[index].x1);
284 this->zone_numbers_[index].y1->publish_state(this->
zone_config_[index].y1);
285 this->zone_numbers_[index].x2->publish_state(this->
zone_config_[index].x2);
286 this->zone_numbers_[index].y2->publish_state(this->
zone_config_[index].y2);
293 void LD2450Component::read_all_info() {
302 if (this->baud_rate_select_ !=
nullptr && this->baud_rate_select_->state != baud_rate) {
303 this->baud_rate_select_->publish_state(baud_rate);
305 this->publish_zone_type();
310 void LD2450Component::query_zone_info() {
317 void LD2450Component::restart_and_read_all_info() {
320 this->
set_timeout(1500, [
this]() { this->read_all_info(); });
325 ESP_LOGV(TAG,
"Sending command %02X", command);
330 if (command_value !=
nullptr) {
331 len += command_value_len;
339 if (command_value !=
nullptr) {
340 for (
int i = 0; i < command_value_len; i++) {
355 ESP_LOGE(TAG,
"Periodic data: invalid message length");
358 if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) {
359 ESP_LOGE(TAG,
"Periodic data: invalid message header");
362 if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) {
363 ESP_LOGE(TAG,
"Periodic data: invalid message footer");
367 auto current_millis =
millis();
368 if (current_millis - this->last_periodic_millis_ < this->
throttle_) {
369 ESP_LOGV(TAG,
"Throttling: %d", this->throttle_);
375 int16_t target_count = 0;
376 int16_t still_target_count = 0;
377 int16_t moving_target_count = 0;
387 bool is_moving =
false;
389 #if defined(USE_BINARY_SENSOR) || defined(USE_SENSOR) || defined(USE_TEXT_SENSOR) 391 for (index = 0; index < MAX_TARGETS; index++) {
398 val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]);
406 val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]);
414 val = (buffer[start + 1] << 8) | buffer[start];
420 val = ld2450::decode_speed(buffer[start], buffer[start + 1]);
424 moving_target_count++;
433 val = (uint16_t) sqrt(
434 pow(ld2450::decode_coordinate(buffer[
TARGET_X + index * 8], buffer[(
TARGET_X + index * 8) + 1]), 2) +
435 pow(ld2450::decode_coordinate(buffer[
TARGET_Y + index * 8], buffer[(
TARGET_Y + index * 8) + 1]), 2));
446 angle = calculate_angle(static_cast<float>(ty), static_cast<float>(td));
455 #ifdef USE_TEXT_SENSOR 462 if (tsd !=
nullptr) {
474 still_target_count = target_count - moving_target_count;
479 uint8_t zone_still_targets = 0;
480 uint8_t zone_moving_targets = 0;
481 uint8_t zone_all_targets = 0;
482 for (index = 0; index < MAX_ZONES; index++) {
485 zone_all_targets = zone_still_targets + zone_moving_targets;
488 sensor::Sensor *szstc = this->zone_still_target_count_sensors_[index];
489 if (szstc !=
nullptr) {
493 sensor::Sensor *szmtc = this->zone_moving_target_count_sensors_[index];
494 if (szmtc !=
nullptr) {
499 if (sztc !=
nullptr) {
506 if (this->target_count_sensor_ !=
nullptr) {
507 this->target_count_sensor_->publish_state(target_count);
510 if (this->still_target_count_sensor_ !=
nullptr) {
511 this->still_target_count_sensor_->publish_state(still_target_count);
514 if (this->moving_target_count_sensor_ !=
nullptr) {
515 this->moving_target_count_sensor_->publish_state(moving_target_count);
519 #ifdef USE_BINARY_SENSOR 521 if (this->target_binary_sensor_ !=
nullptr) {
522 if (target_count > 0) {
523 this->target_binary_sensor_->publish_state(
true);
526 this->target_binary_sensor_->publish_state(
false);
528 ESP_LOGV(TAG,
"Clear presence waiting timeout: %d", this->
timeout_);
533 if (this->moving_target_binary_sensor_ !=
nullptr) {
534 if (moving_target_count > 0) {
535 this->moving_target_binary_sensor_->publish_state(
true);
538 this->moving_target_binary_sensor_->publish_state(
false);
543 if (this->still_target_binary_sensor_ !=
nullptr) {
544 if (still_target_count > 0) {
545 this->still_target_binary_sensor_->publish_state(
true);
548 this->still_target_binary_sensor_->publish_state(
false);
555 if (target_count > 0) {
558 if (moving_target_count > 0) {
561 if (still_target_count > 0) {
568 ESP_LOGV(TAG,
"Handling ack data for command %02X", buffer[
COMMAND]);
570 ESP_LOGE(TAG,
"Ack data: invalid length");
573 if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) {
574 ESP_LOGE(TAG,
"Ack data: invalid header (command %02X)", buffer[COMMAND]);
578 ESP_LOGE(TAG,
"Ack data: invalid status");
581 if (buffer[8] || buffer[9]) {
582 ESP_LOGE(TAG,
"Ack data: last buffer was %u, %u", buffer[8], buffer[9]);
586 switch (buffer[COMMAND]) {
587 case lowbyte(CMD_ENABLE_CONF):
588 ESP_LOGV(TAG,
"Got enable conf command");
590 case lowbyte(CMD_DISABLE_CONF):
591 ESP_LOGV(TAG,
"Got disable conf command");
593 case lowbyte(CMD_SET_BAUD_RATE):
594 ESP_LOGV(TAG,
"Got baud rate change command");
596 if (this->baud_rate_select_ !=
nullptr) {
597 ESP_LOGV(TAG,
"Change baud rate to %s", this->baud_rate_select_->state.c_str());
601 case lowbyte(CMD_VERSION):
602 this->
version_ = ld2450::format_version(buffer);
603 ESP_LOGV(TAG,
"Firmware version: %s", this->
version_.c_str());
604 #ifdef USE_TEXT_SENSOR 605 if (this->version_text_sensor_ !=
nullptr) {
606 this->version_text_sensor_->publish_state(this->
version_);
610 case lowbyte(CMD_MAC):
614 this->
mac_ = ld2450::format_mac(buffer);
615 ESP_LOGV(TAG,
"MAC address: %s", this->
mac_.c_str());
616 #ifdef USE_TEXT_SENSOR 617 if (this->mac_text_sensor_ !=
nullptr) {
618 this->mac_text_sensor_->publish_state(this->
mac_ == NO_MAC ? UNKNOWN_MAC : this->
mac_);
622 if (this->bluetooth_switch_ !=
nullptr) {
623 this->bluetooth_switch_->publish_state(this->
mac_ != NO_MAC);
627 case lowbyte(CMD_BLUETOOTH):
628 ESP_LOGV(TAG,
"Got Bluetooth command");
630 case lowbyte(CMD_SINGLE_TARGET_MODE):
631 ESP_LOGV(TAG,
"Got single target conf command");
633 if (this->multi_target_switch_ !=
nullptr) {
634 this->multi_target_switch_->publish_state(
false);
638 case lowbyte(CMD_MULTI_TARGET_MODE):
639 ESP_LOGV(TAG,
"Got multi target conf command");
641 if (this->multi_target_switch_ !=
nullptr) {
642 this->multi_target_switch_->publish_state(
true);
646 case lowbyte(CMD_QUERY_TARGET_MODE):
647 ESP_LOGV(TAG,
"Got query target tracking mode command");
649 if (this->multi_target_switch_ !=
nullptr) {
650 this->multi_target_switch_->publish_state(buffer[10] == 0x02);
654 case lowbyte(CMD_QUERY_ZONE):
655 ESP_LOGV(TAG,
"Got query zone conf command");
657 this->publish_zone_type();
659 if (this->zone_type_select_ !=
nullptr) {
660 ESP_LOGV(TAG,
"Change zone type to: %s", this->zone_type_select_->state.c_str());
663 if (buffer[10] == 0x00) {
664 ESP_LOGV(TAG,
"Zone: Disabled");
666 if (buffer[10] == 0x01) {
667 ESP_LOGV(TAG,
"Zone: Area detection");
669 if (buffer[10] == 0x02) {
670 ESP_LOGV(TAG,
"Zone: Area filter");
674 case lowbyte(CMD_SET_ZONE):
675 ESP_LOGV(TAG,
"Got set zone conf command");
676 this->query_zone_info();
699 ESP_LOGV(TAG,
"Handle periodic radar data");
704 ESP_LOGV(TAG,
"Handle command ack data");
708 ESP_LOGV(TAG,
"Command ack data invalid");
715 uint8_t
cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
716 uint8_t cmd_value[2] = {0x01, 0x00};
721 void LD2450Component::set_bluetooth(
bool enable) {
723 uint8_t enable_cmd_value[2] = {0x01, 0x00};
724 uint8_t disable_cmd_value[2] = {0x00, 0x00};
725 this->
send_command_(CMD_BLUETOOTH, enable ? enable_cmd_value : disable_cmd_value, 2);
726 this->
set_timeout(200, [
this]() { this->restart_and_read_all_info(); });
730 void LD2450Component::set_baud_rate(
const std::string &
state) {
732 uint8_t cmd_value[2] = {BAUD_RATE_ENUM_TO_INT.at(state), 0x00};
738 void LD2450Component::set_zone_type(
const std::string &state) {
739 ESP_LOGV(TAG,
"Set zone type: %s", state.c_str());
740 uint8_t zone_type = ZONE_TYPE_ENUM_TO_INT.at(state);
746 void LD2450Component::publish_zone_type() {
748 std::string zone_type = ZONE_TYPE_INT_TO_ENUM.at(static_cast<ZoneTypeStructure>(this->
zone_type_));
749 if (this->zone_type_select_ !=
nullptr) {
750 this->zone_type_select_->publish_state(zone_type);
756 void LD2450Component::set_multi_target(
bool enable) {
758 uint8_t
cmd = enable ? CMD_MULTI_TARGET_MODE : CMD_SINGLE_TARGET_MODE;
764 void LD2450Component::factory_reset() {
767 this->
set_timeout(200, [
this]() { this->restart_and_read_all_info(); });
778 uint8_t cmd_value[2] = {0x01, 0x00};
789 void LD2450Component::set_move_x_sensor(uint8_t target,
sensor::Sensor *s) { this->move_x_sensors_[target] = s; }
790 void LD2450Component::set_move_y_sensor(uint8_t target,
sensor::Sensor *s) { this->move_y_sensors_[target] = s; }
791 void LD2450Component::set_move_speed_sensor(uint8_t target,
sensor::Sensor *s) {
792 this->move_speed_sensors_[target] = s;
794 void LD2450Component::set_move_angle_sensor(uint8_t target,
sensor::Sensor *s) {
795 this->move_angle_sensors_[target] = s;
797 void LD2450Component::set_move_distance_sensor(uint8_t target,
sensor::Sensor *s) {
798 this->move_distance_sensors_[target] = s;
800 void LD2450Component::set_move_resolution_sensor(uint8_t target,
sensor::Sensor *s) {
801 this->move_resolution_sensors_[target] = s;
803 void LD2450Component::set_zone_target_count_sensor(uint8_t zone,
sensor::Sensor *s) {
804 this->zone_target_count_sensors_[zone] = s;
806 void LD2450Component::set_zone_still_target_count_sensor(uint8_t zone,
sensor::Sensor *s) {
807 this->zone_still_target_count_sensors_[zone] = s;
809 void LD2450Component::set_zone_moving_target_count_sensor(uint8_t zone,
sensor::Sensor *s) {
810 this->zone_moving_target_count_sensors_[zone] = s;
813 #ifdef USE_TEXT_SENSOR 815 this->direction_text_sensors_[target] = s;
821 void LD2450Component::set_zone_coordinate(uint8_t zone) {
838 if (zone < MAX_ZONES) {
839 this->zone_numbers_[zone].x1 = x1;
840 this->zone_numbers_[zone].y1 = y1;
841 this->zone_numbers_[zone].x2 = x2;
842 this->zone_numbers_[zone].y2 = y2;
849 void LD2450Component::set_presence_timeout() {
850 if (this->presence_timeout_number_ !=
nullptr) {
851 if (this->presence_timeout_number_->state == 0) {
853 this->presence_timeout_number_->publish_state(timeout);
854 this->
timeout_ = ld2450::convert_seconds_to_ms(timeout);
856 if (this->presence_timeout_number_->has_state()) {
858 this->
timeout_ = ld2450::convert_seconds_to_ms(this->presence_timeout_number_->state);
870 value = DEFAULT_PRESENCE_TIMEOUT;
void set_config_mode_(bool enable)
bool has_state() const
Return whether this number has gotten a full state yet.
uint32_t moving_presence_millis_
void handle_periodic_data_(uint8_t *buffer, uint8_t len)
uint32_t get_baud_rate() const
void write_array(const uint8_t *data, size_t len)
std::vector< sensor::Sensor * > move_speed_sensors_
std::vector< sensor::Sensor * > move_resolution_sensors_
const char * to_string(SHTCXType type)
bool handle_ack_data_(uint8_t *buffer, uint8_t len)
void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len)
void write_byte(uint8_t data)
uint32_t last_periodic_millis_
std::vector< sensor::Sensor * > move_angle_sensors_
void set_timeout(const std::string &name, uint32_t timeout, std::function< void()> &&f)
Set a timeout function with a unique name.
uint8_t count_targets_in_zone_(const Zone &zone, bool is_moving)
void readline_(int readch, uint8_t *buffer, uint8_t len)
float restore_from_flash_()
Zone zone_config_[MAX_ZONES]
std::string format_mac(uint8_t *buffer)
void publish_state(const std::string &state)
uint8_t buffer_data_[MAX_LINE_LENGTH]
uint32_t IRAM_ATTR HOT millis()
std::vector< sensor::Sensor * > move_distance_sensors_
std::vector< sensor::Sensor * > move_y_sensors_
void query_target_tracking_mode_()
uint32_t presence_millis_
void process_zone_(uint8_t *buffer)
ZoneOfNumbers zone_numbers_[MAX_ZONES]
ESPPreferences * global_preferences
Base-class for all numbers.
std::string str_sprintf(const char *fmt,...)
void publish_state(float state)
Publish a new state to the front-end.
Target target_info_[MAX_TARGETS]
std::vector< sensor::Sensor * > zone_moving_target_count_sensors_
uint32_t still_presence_millis_
std::vector< sensor::Sensor * > move_x_sensors_
virtual ESPPreferenceObject make_preference(size_t length, uint32_t type, bool in_flash)=0
const std::string NO_MAC("08:05:04:03:02:01")
std::vector< text_sensor::TextSensor * > direction_text_sensors_
Implementation of SPI Controller mode.
bool get_timeout_status_(uint32_t check_millis)
void save_to_flash_(float value)
std::vector< uint8_t > bytes
Base-class for all sensors.
std::string str_snprintf(const char *fmt, size_t len,...)
void send_set_zone_command_()
std::vector< sensor::Sensor * > zone_target_count_sensors_
const std::string UNKNOWN_MAC("unknown")
std::string format_version(uint8_t *buffer)
void IRAM_ATTR HOT delay(uint32_t ms)
std::vector< sensor::Sensor * > zone_still_target_count_sensors_
ESPPreferenceObject pref_