[ld2450] Clean-up for consistency, reduce CPU usage when idle (#9363)

This commit is contained in:
Keith Burzinski 2025-07-07 07:22:49 -05:00 committed by GitHub
parent 4e25b6da7b
commit db877e688a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
6 changed files with 280 additions and 231 deletions

View File

@ -13,13 +13,13 @@ from esphome.const import (
from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns
ResetButton = ld2450_ns.class_("ResetButton", button.Button) FactoryResetButton = ld2450_ns.class_("FactoryResetButton", button.Button)
RestartButton = ld2450_ns.class_("RestartButton", button.Button) RestartButton = ld2450_ns.class_("RestartButton", button.Button)
CONFIG_SCHEMA = { CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component), cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
cv.Optional(CONF_FACTORY_RESET): button.button_schema( cv.Optional(CONF_FACTORY_RESET): button.button_schema(
ResetButton, FactoryResetButton,
device_class=DEVICE_CLASS_RESTART, device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG, entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART_ALERT, icon=ICON_RESTART_ALERT,
@ -38,7 +38,7 @@ async def to_code(config):
if factory_reset_config := config.get(CONF_FACTORY_RESET): if factory_reset_config := config.get(CONF_FACTORY_RESET):
b = await button.new_button(factory_reset_config) b = await button.new_button(factory_reset_config)
await cg.register_parented(b, config[CONF_LD2450_ID]) await cg.register_parented(b, config[CONF_LD2450_ID])
cg.add(ld2450_component.set_reset_button(b)) cg.add(ld2450_component.set_factory_reset_button(b))
if restart_config := config.get(CONF_RESTART): if restart_config := config.get(CONF_RESTART):
b = await button.new_button(restart_config) b = await button.new_button(restart_config)
await cg.register_parented(b, config[CONF_LD2450_ID]) await cg.register_parented(b, config[CONF_LD2450_ID])

View File

@ -0,0 +1,9 @@
#include "factory_reset_button.h"
namespace esphome {
namespace ld2450 {
void FactoryResetButton::press_action() { this->parent_->factory_reset(); }
} // namespace ld2450
} // namespace esphome

View File

@ -6,9 +6,9 @@
namespace esphome { namespace esphome {
namespace ld2450 { namespace ld2450 {
class ResetButton : public button::Button, public Parented<LD2450Component> { class FactoryResetButton : public button::Button, public Parented<LD2450Component> {
public: public:
ResetButton() = default; FactoryResetButton() = default;
protected: protected:
void press_action() override; void press_action() override;

View File

@ -1,9 +0,0 @@
#include "reset_button.h"
namespace esphome {
namespace ld2450 {
void ResetButton::press_action() { this->parent_->factory_reset(); }
} // namespace ld2450
} // namespace esphome

View File

@ -18,11 +18,10 @@ namespace esphome {
namespace ld2450 { namespace ld2450 {
static const char *const TAG = "ld2450"; static const char *const TAG = "ld2450";
static const char *const NO_MAC = "08:05:04:03:02:01";
static const char *const UNKNOWN_MAC = "unknown"; static const char *const UNKNOWN_MAC = "unknown";
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X"; static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
enum BaudRateStructure : uint8_t { enum BaudRate : uint8_t {
BAUD_RATE_9600 = 1, BAUD_RATE_9600 = 1,
BAUD_RATE_19200 = 2, BAUD_RATE_19200 = 2,
BAUD_RATE_38400 = 3, BAUD_RATE_38400 = 3,
@ -33,14 +32,13 @@ enum BaudRateStructure : uint8_t {
BAUD_RATE_460800 = 8 BAUD_RATE_460800 = 8
}; };
// Zone type struct enum ZoneType : uint8_t {
enum ZoneTypeStructure : uint8_t {
ZONE_DISABLED = 0, ZONE_DISABLED = 0,
ZONE_DETECTION = 1, ZONE_DETECTION = 1,
ZONE_FILTER = 2, ZONE_FILTER = 2,
}; };
enum PeriodicDataStructure : uint8_t { enum PeriodicData : uint8_t {
TARGET_X = 4, TARGET_X = 4,
TARGET_Y = 6, TARGET_Y = 6,
TARGET_SPEED = 8, TARGET_SPEED = 8,
@ -48,12 +46,12 @@ enum PeriodicDataStructure : uint8_t {
}; };
enum PeriodicDataValue : uint8_t { enum PeriodicDataValue : uint8_t {
HEAD = 0xAA, HEADER = 0xAA,
END = 0x55, FOOTER = 0x55,
CHECK = 0x00, CHECK = 0x00,
}; };
enum AckDataStructure : uint8_t { enum AckData : uint8_t {
COMMAND = 6, COMMAND = 6,
COMMAND_STATUS = 7, COMMAND_STATUS = 7,
}; };
@ -61,11 +59,11 @@ enum AckDataStructure : uint8_t {
// Memory-efficient lookup tables // Memory-efficient lookup tables
struct StringToUint8 { struct StringToUint8 {
const char *str; const char *str;
uint8_t value; const uint8_t value;
}; };
struct Uint8ToString { struct Uint8ToString {
uint8_t value; const uint8_t value;
const char *str; const char *str;
}; };
@ -75,6 +73,13 @@ constexpr StringToUint8 BAUD_RATES_BY_STR[] = {
{"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800}, {"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800},
}; };
constexpr Uint8ToString DIRECTION_BY_UINT[] = {
{DIRECTION_APPROACHING, "Approaching"},
{DIRECTION_MOVING_AWAY, "Moving away"},
{DIRECTION_STATIONARY, "Stationary"},
{DIRECTION_NA, "NA"},
};
constexpr Uint8ToString ZONE_TYPE_BY_UINT[] = { constexpr Uint8ToString ZONE_TYPE_BY_UINT[] = {
{ZONE_DISABLED, "Disabled"}, {ZONE_DISABLED, "Disabled"},
{ZONE_DETECTION, "Detection"}, {ZONE_DETECTION, "Detection"},
@ -104,28 +109,35 @@ template<size_t N> const char *find_str(const Uint8ToString (&arr)[N], uint8_t v
return ""; // Not found return ""; // Not found
} }
// LD2450 serial command header & footer
static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA};
static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01};
// LD2450 UART Serial Commands // LD2450 UART Serial Commands
static const uint8_t CMD_ENABLE_CONF = 0xFF; static constexpr uint8_t CMD_ENABLE_CONF = 0xFF;
static const uint8_t CMD_DISABLE_CONF = 0xFE; static constexpr uint8_t CMD_DISABLE_CONF = 0xFE;
static const uint8_t CMD_VERSION = 0xA0; static constexpr uint8_t CMD_QUERY_VERSION = 0xA0;
static const uint8_t CMD_MAC = 0xA5; static constexpr uint8_t CMD_QUERY_MAC_ADDRESS = 0xA5;
static const uint8_t CMD_RESET = 0xA2; static constexpr uint8_t CMD_RESET = 0xA2;
static const uint8_t CMD_RESTART = 0xA3; static constexpr uint8_t CMD_RESTART = 0xA3;
static const uint8_t CMD_BLUETOOTH = 0xA4; static constexpr uint8_t CMD_BLUETOOTH = 0xA4;
static const uint8_t CMD_SINGLE_TARGET_MODE = 0x80; static constexpr uint8_t CMD_SINGLE_TARGET_MODE = 0x80;
static const uint8_t CMD_MULTI_TARGET_MODE = 0x90; static constexpr uint8_t CMD_MULTI_TARGET_MODE = 0x90;
static const uint8_t CMD_QUERY_TARGET_MODE = 0x91; static constexpr uint8_t CMD_QUERY_TARGET_MODE = 0x91;
static const uint8_t CMD_SET_BAUD_RATE = 0xA1; static constexpr uint8_t CMD_SET_BAUD_RATE = 0xA1;
static const uint8_t CMD_QUERY_ZONE = 0xC1; static constexpr uint8_t CMD_QUERY_ZONE = 0xC1;
static const uint8_t CMD_SET_ZONE = 0xC2; static constexpr uint8_t CMD_SET_ZONE = 0xC2;
// Header & Footer size
static constexpr uint8_t HEADER_FOOTER_SIZE = 4;
// Command Header & Footer
static constexpr uint8_t CMD_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xFD, 0xFC, 0xFB, 0xFA};
static constexpr uint8_t CMD_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0x04, 0x03, 0x02, 0x01};
// Data Header & Footer
static constexpr uint8_t DATA_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xAA, 0xFF, 0x03, 0x00};
static constexpr uint8_t DATA_FRAME_FOOTER[2] = {0x55, 0xCC};
// MAC address the module uses when Bluetooth is disabled
static constexpr uint8_t NO_MAC[] = {0x08, 0x05, 0x04, 0x03, 0x02, 0x01};
static inline uint16_t convert_seconds_to_ms(uint16_t value) { return value * 1000; }; static inline uint16_t convert_seconds_to_ms(uint16_t value) { return value * 1000; };
static inline void convert_int_values_to_hex(const int *values, uint8_t *bytes) { static inline void convert_int_values_to_hex(const int *values, uint8_t *bytes) {
for (int i = 0; i < 4; i++) { for (uint8_t i = 0; i < 4; i++) {
uint16_t val = values[i] & 0xFFFF; uint16_t val = values[i] & 0xFFFF;
bytes[i * 2] = val & 0xFF; // Store low byte first (little-endian) bytes[i * 2] = val & 0xFF; // Store low byte first (little-endian)
bytes[i * 2 + 1] = (val >> 8) & 0xFF; // Store high byte second bytes[i * 2 + 1] = (val >> 8) & 0xFF; // Store high byte second
@ -166,18 +178,13 @@ static inline float calculate_angle(float base, float hypotenuse) {
return angle_degrees; return angle_degrees;
} }
static inline std::string get_direction(int16_t speed) { static bool validate_header_footer(const uint8_t *header_footer, const uint8_t *buffer) {
static const char *const APPROACHING = "Approaching"; for (uint8_t i = 0; i < HEADER_FOOTER_SIZE; i++) {
static const char *const MOVING_AWAY = "Moving away"; if (header_footer[i] != buffer[i]) {
static const char *const STATIONARY = "Stationary"; return false; // Mismatch in header/footer
}
if (speed > 0) {
return MOVING_AWAY;
} }
if (speed < 0) { return true; // Valid header/footer
return APPROACHING;
}
return STATIONARY;
} }
void LD2450Component::setup() { void LD2450Component::setup() {
@ -192,84 +199,93 @@ void LD2450Component::setup() {
} }
void LD2450Component::dump_config() { void LD2450Component::dump_config() {
ESP_LOGCONFIG(TAG, "LD2450:"); std::string mac_str =
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
this->version_[4], this->version_[3], this->version_[2]);
ESP_LOGCONFIG(TAG,
"LD2450:\n"
" Firmware version: %s\n"
" MAC address: %s\n"
" Throttle: %u ms",
version.c_str(), mac_str.c_str(), this->throttle_);
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
LOG_BINARY_SENSOR(" ", "TargetBinarySensor", this->target_binary_sensor_); ESP_LOGCONFIG(TAG, "Binary Sensors:");
LOG_BINARY_SENSOR(" ", "MovingTargetBinarySensor", this->moving_target_binary_sensor_); LOG_BINARY_SENSOR(" ", "MovingTarget", this->moving_target_binary_sensor_);
LOG_BINARY_SENSOR(" ", "StillTargetBinarySensor", this->still_target_binary_sensor_); LOG_BINARY_SENSOR(" ", "StillTarget", this->still_target_binary_sensor_);
#endif LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_);
#ifdef USE_SWITCH
LOG_SWITCH(" ", "BluetoothSwitch", this->bluetooth_switch_);
LOG_SWITCH(" ", "MultiTargetSwitch", this->multi_target_switch_);
#endif
#ifdef USE_BUTTON
LOG_BUTTON(" ", "ResetButton", this->reset_button_);
LOG_BUTTON(" ", "RestartButton", this->restart_button_);
#endif #endif
#ifdef USE_SENSOR #ifdef USE_SENSOR
LOG_SENSOR(" ", "TargetCountSensor", this->target_count_sensor_); ESP_LOGCONFIG(TAG, "Sensors:");
LOG_SENSOR(" ", "StillTargetCountSensor", this->still_target_count_sensor_); LOG_SENSOR(" ", "MovingTargetCount", this->moving_target_count_sensor_);
LOG_SENSOR(" ", "MovingTargetCountSensor", this->moving_target_count_sensor_); LOG_SENSOR(" ", "StillTargetCount", this->still_target_count_sensor_);
LOG_SENSOR(" ", "TargetCount", this->target_count_sensor_);
for (sensor::Sensor *s : this->move_x_sensors_) { for (sensor::Sensor *s : this->move_x_sensors_) {
LOG_SENSOR(" ", "NthTargetXSensor", s); LOG_SENSOR(" ", "TargetX", s);
} }
for (sensor::Sensor *s : this->move_y_sensors_) { for (sensor::Sensor *s : this->move_y_sensors_) {
LOG_SENSOR(" ", "NthTargetYSensor", s); LOG_SENSOR(" ", "TargetY", s);
}
for (sensor::Sensor *s : this->move_speed_sensors_) {
LOG_SENSOR(" ", "NthTargetSpeedSensor", s);
} }
for (sensor::Sensor *s : this->move_angle_sensors_) { for (sensor::Sensor *s : this->move_angle_sensors_) {
LOG_SENSOR(" ", "NthTargetAngleSensor", s); LOG_SENSOR(" ", "TargetAngle", s);
} }
for (sensor::Sensor *s : this->move_distance_sensors_) { for (sensor::Sensor *s : this->move_distance_sensors_) {
LOG_SENSOR(" ", "NthTargetDistanceSensor", s); LOG_SENSOR(" ", "TargetDistance", s);
} }
for (sensor::Sensor *s : this->move_resolution_sensors_) { for (sensor::Sensor *s : this->move_resolution_sensors_) {
LOG_SENSOR(" ", "NthTargetResolutionSensor", s); LOG_SENSOR(" ", "TargetResolution", s);
}
for (sensor::Sensor *s : this->move_speed_sensors_) {
LOG_SENSOR(" ", "TargetSpeed", s);
} }
for (sensor::Sensor *s : this->zone_target_count_sensors_) { for (sensor::Sensor *s : this->zone_target_count_sensors_) {
LOG_SENSOR(" ", "NthZoneTargetCountSensor", s); LOG_SENSOR(" ", "ZoneTargetCount", s);
}
for (sensor::Sensor *s : this->zone_still_target_count_sensors_) {
LOG_SENSOR(" ", "NthZoneStillTargetCountSensor", s);
} }
for (sensor::Sensor *s : this->zone_moving_target_count_sensors_) { for (sensor::Sensor *s : this->zone_moving_target_count_sensors_) {
LOG_SENSOR(" ", "NthZoneMovingTargetCountSensor", s); LOG_SENSOR(" ", "ZoneMovingTargetCount", s);
}
for (sensor::Sensor *s : this->zone_still_target_count_sensors_) {
LOG_SENSOR(" ", "ZoneStillTargetCount", s);
} }
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
LOG_TEXT_SENSOR(" ", "VersionTextSensor", this->version_text_sensor_); ESP_LOGCONFIG(TAG, "Text Sensors:");
LOG_TEXT_SENSOR(" ", "MacTextSensor", this->mac_text_sensor_); LOG_TEXT_SENSOR(" ", "Version", this->version_text_sensor_);
LOG_TEXT_SENSOR(" ", "Mac", this->mac_text_sensor_);
for (text_sensor::TextSensor *s : this->direction_text_sensors_) { for (text_sensor::TextSensor *s : this->direction_text_sensors_) {
LOG_TEXT_SENSOR(" ", "NthDirectionTextSensor", s); LOG_TEXT_SENSOR(" ", "Direction", s);
} }
#endif #endif
#ifdef USE_NUMBER #ifdef USE_NUMBER
ESP_LOGCONFIG(TAG, "Numbers:");
LOG_NUMBER(" ", "PresenceTimeout", this->presence_timeout_number_);
for (auto n : this->zone_numbers_) { for (auto n : this->zone_numbers_) {
LOG_NUMBER(" ", "ZoneX1Number", n.x1); LOG_NUMBER(" ", "ZoneX1", n.x1);
LOG_NUMBER(" ", "ZoneY1Number", n.y1); LOG_NUMBER(" ", "ZoneY1", n.y1);
LOG_NUMBER(" ", "ZoneX2Number", n.x2); LOG_NUMBER(" ", "ZoneX2", n.x2);
LOG_NUMBER(" ", "ZoneY2Number", n.y2); LOG_NUMBER(" ", "ZoneY2", n.y2);
} }
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
LOG_SELECT(" ", "BaudRateSelect", this->baud_rate_select_); ESP_LOGCONFIG(TAG, "Selects:");
LOG_SELECT(" ", "ZoneTypeSelect", this->zone_type_select_); LOG_SELECT(" ", "BaudRate", this->baud_rate_select_);
LOG_SELECT(" ", "ZoneType", this->zone_type_select_);
#endif #endif
#ifdef USE_NUMBER #ifdef USE_SWITCH
LOG_NUMBER(" ", "PresenceTimeoutNumber", this->presence_timeout_number_); ESP_LOGCONFIG(TAG, "Switches:");
LOG_SWITCH(" ", "Bluetooth", this->bluetooth_switch_);
LOG_SWITCH(" ", "MultiTarget", this->multi_target_switch_);
#endif
#ifdef USE_BUTTON
ESP_LOGCONFIG(TAG, "Buttons:");
LOG_BUTTON(" ", "FactoryReset", this->factory_reset_button_);
LOG_BUTTON(" ", "Restart", this->restart_button_);
#endif #endif
ESP_LOGCONFIG(TAG,
" Throttle: %ums\n"
" MAC Address: %s\n"
" Firmware version: %s",
this->throttle_, this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_.c_str(), this->version_.c_str());
} }
void LD2450Component::loop() { void LD2450Component::loop() {
while (this->available()) { while (this->available()) {
this->readline_(read(), this->buffer_data_, MAX_LINE_LENGTH); this->readline_(this->read());
} }
} }
@ -304,7 +320,7 @@ void LD2450Component::set_radar_zone(int32_t zone_type, int32_t zone1_x1, int32_
this->zone_type_ = zone_type; this->zone_type_ = zone_type;
int zone_parameters[12] = {zone1_x1, zone1_y1, zone1_x2, zone1_y2, zone2_x1, zone2_y1, int zone_parameters[12] = {zone1_x1, zone1_y1, zone1_x2, zone1_y2, zone2_x1, zone2_y1,
zone2_x2, zone2_y2, zone3_x1, zone3_y1, zone3_x2, zone3_y2}; zone2_x2, zone2_y2, zone3_x1, zone3_y1, zone3_x2, zone3_y2};
for (int i = 0; i < MAX_ZONES; i++) { for (uint8_t i = 0; i < MAX_ZONES; i++) {
this->zone_config_[i].x1 = zone_parameters[i * 4]; this->zone_config_[i].x1 = zone_parameters[i * 4];
this->zone_config_[i].y1 = zone_parameters[i * 4 + 1]; this->zone_config_[i].y1 = zone_parameters[i * 4 + 1];
this->zone_config_[i].x2 = zone_parameters[i * 4 + 2]; this->zone_config_[i].x2 = zone_parameters[i * 4 + 2];
@ -318,15 +334,15 @@ void LD2450Component::send_set_zone_command_() {
uint8_t cmd_value[26] = {}; uint8_t cmd_value[26] = {};
uint8_t zone_type_bytes[2] = {static_cast<uint8_t>(this->zone_type_), 0x00}; uint8_t zone_type_bytes[2] = {static_cast<uint8_t>(this->zone_type_), 0x00};
uint8_t area_config[24] = {}; uint8_t area_config[24] = {};
for (int i = 0; i < MAX_ZONES; i++) { for (uint8_t i = 0; i < MAX_ZONES; i++) {
int values[4] = {this->zone_config_[i].x1, this->zone_config_[i].y1, this->zone_config_[i].x2, int values[4] = {this->zone_config_[i].x1, this->zone_config_[i].y1, this->zone_config_[i].x2,
this->zone_config_[i].y2}; this->zone_config_[i].y2};
ld2450::convert_int_values_to_hex(values, area_config + (i * 8)); ld2450::convert_int_values_to_hex(values, area_config + (i * 8));
} }
std::memcpy(cmd_value, zone_type_bytes, 2); std::memcpy(cmd_value, zone_type_bytes, sizeof(zone_type_bytes));
std::memcpy(cmd_value + 2, area_config, 24); std::memcpy(cmd_value + 2, area_config, sizeof(area_config));
this->set_config_mode_(true); this->set_config_mode_(true);
this->send_command_(CMD_SET_ZONE, cmd_value, 26); this->send_command_(CMD_SET_ZONE, cmd_value, sizeof(cmd_value));
this->set_config_mode_(false); this->set_config_mode_(false);
} }
@ -342,14 +358,14 @@ bool LD2450Component::get_timeout_status_(uint32_t check_millis) {
} }
// Extract, store and publish zone details LD2450 buffer // Extract, store and publish zone details LD2450 buffer
void LD2450Component::process_zone_(uint8_t *buffer) { void LD2450Component::process_zone_() {
uint8_t index, start; uint8_t index, start;
for (index = 0; index < MAX_ZONES; index++) { for (index = 0; index < MAX_ZONES; index++) {
start = 12 + index * 8; start = 12 + index * 8;
this->zone_config_[index].x1 = ld2450::hex_to_signed_int(buffer, start); this->zone_config_[index].x1 = ld2450::hex_to_signed_int(this->buffer_data_, start);
this->zone_config_[index].y1 = ld2450::hex_to_signed_int(buffer, start + 2); this->zone_config_[index].y1 = ld2450::hex_to_signed_int(this->buffer_data_, start + 2);
this->zone_config_[index].x2 = ld2450::hex_to_signed_int(buffer, start + 4); this->zone_config_[index].x2 = ld2450::hex_to_signed_int(this->buffer_data_, start + 4);
this->zone_config_[index].y2 = ld2450::hex_to_signed_int(buffer, start + 6); this->zone_config_[index].y2 = ld2450::hex_to_signed_int(this->buffer_data_, start + 6);
#ifdef USE_NUMBER #ifdef USE_NUMBER
// only one null check as all coordinates are required for a single zone // only one null check as all coordinates are required for a single zone
if (this->zone_numbers_[index].x1 != nullptr) { if (this->zone_numbers_[index].x1 != nullptr) {
@ -395,27 +411,25 @@ void LD2450Component::restart_and_read_all_info() {
// Send command with values to LD2450 // Send command with values to LD2450
void LD2450Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) { void LD2450Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) {
ESP_LOGV(TAG, "Sending command %02X", command); ESP_LOGV(TAG, "Sending COMMAND %02X", command);
// frame header // frame header bytes
this->write_array(CMD_FRAME_HEADER, 4); this->write_array(CMD_FRAME_HEADER, sizeof(CMD_FRAME_HEADER));
// length bytes // length bytes
int len = 2; uint8_t len = 2;
if (command_value != nullptr) { if (command_value != nullptr) {
len += command_value_len; len += command_value_len;
} }
this->write_byte(lowbyte(len)); uint8_t len_cmd[] = {lowbyte(len), highbyte(len), command, 0x00};
this->write_byte(highbyte(len)); this->write_array(len_cmd, sizeof(len_cmd));
// command
this->write_byte(lowbyte(command));
this->write_byte(highbyte(command));
// command value bytes // command value bytes
if (command_value != nullptr) { if (command_value != nullptr) {
for (int i = 0; i < command_value_len; i++) { for (uint8_t i = 0; i < command_value_len; i++) {
this->write_byte(command_value[i]); this->write_byte(command_value[i]);
} }
} }
// footer // frame footer bytes
this->write_array(CMD_FRAME_END, 4); this->write_array(CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER));
// FIXME to remove // FIXME to remove
delay(50); // NOLINT delay(50); // NOLINT
} }
@ -423,26 +437,23 @@ void LD2450Component::send_command_(uint8_t command, const uint8_t *command_valu
// LD2450 Radar data message: // LD2450 Radar data message:
// [AA FF 03 00] [0E 03 B1 86 10 00 40 01] [00 00 00 00 00 00 00 00] [00 00 00 00 00 00 00 00] [55 CC] // [AA FF 03 00] [0E 03 B1 86 10 00 40 01] [00 00 00 00 00 00 00 00] [00 00 00 00 00 00 00 00] [55 CC]
// Header Target 1 Target 2 Target 3 End // Header Target 1 Target 2 Target 3 End
void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) { void LD2450Component::handle_periodic_data_() {
// Early throttle check - moved before any processing to save CPU cycles // Early throttle check - moved before any processing to save CPU cycles
if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) { if (App.get_loop_component_start_time() - this->last_periodic_millis_ < this->throttle_) {
ESP_LOGV(TAG, "Throttling: %d", this->throttle_);
return; return;
} }
if (len < 29) { // header (4 bytes) + 8 x 3 target data + footer (2 bytes) if (this->buffer_pos_ < 29) { // header (4 bytes) + 8 x 3 target data + footer (2 bytes)
ESP_LOGE(TAG, "Invalid message length"); ESP_LOGE(TAG, "Invalid length");
return; return;
} }
if (buffer[0] != 0xAA || buffer[1] != 0xFF || buffer[2] != 0x03 || buffer[3] != 0x00) { // header if (!ld2450::validate_header_footer(DATA_FRAME_HEADER, this->buffer_data_) ||
ESP_LOGE(TAG, "Invalid message header"); this->buffer_data_[this->buffer_pos_ - 2] != DATA_FRAME_FOOTER[0] ||
this->buffer_data_[this->buffer_pos_ - 1] != DATA_FRAME_FOOTER[1]) {
ESP_LOGE(TAG, "Invalid header/footer");
return; return;
} }
if (buffer[len - 2] != 0x55 || buffer[len - 1] != 0xCC) { // footer // Save the timestamp after validating the frame so, if invalid, we'll take the next frame immediately
ESP_LOGE(TAG, "Invalid message footer");
return;
}
this->last_periodic_millis_ = App.get_loop_component_start_time(); this->last_periodic_millis_ = App.get_loop_component_start_time();
int16_t target_count = 0; int16_t target_count = 0;
@ -450,13 +461,13 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
int16_t moving_target_count = 0; int16_t moving_target_count = 0;
int16_t start = 0; int16_t start = 0;
int16_t val = 0; int16_t val = 0;
uint8_t index = 0;
int16_t tx = 0; int16_t tx = 0;
int16_t ty = 0; int16_t ty = 0;
int16_t td = 0; int16_t td = 0;
int16_t ts = 0; int16_t ts = 0;
int16_t angle = 0; int16_t angle = 0;
std::string direction{}; uint8_t index = 0;
Direction direction{DIRECTION_UNDEFINED};
bool is_moving = false; bool is_moving = false;
#if defined(USE_BINARY_SENSOR) || defined(USE_SENSOR) || defined(USE_TEXT_SENSOR) #if defined(USE_BINARY_SENSOR) || defined(USE_SENSOR) || defined(USE_TEXT_SENSOR)
@ -468,7 +479,7 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
is_moving = false; is_moving = false;
sensor::Sensor *sx = this->move_x_sensors_[index]; sensor::Sensor *sx = this->move_x_sensors_[index];
if (sx != nullptr) { if (sx != nullptr) {
val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]); val = ld2450::decode_coordinate(this->buffer_data_[start], this->buffer_data_[start + 1]);
tx = val; tx = val;
if (this->cached_target_data_[index].x != val) { if (this->cached_target_data_[index].x != val) {
sx->publish_state(val); sx->publish_state(val);
@ -479,7 +490,7 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
start = TARGET_Y + index * 8; start = TARGET_Y + index * 8;
sensor::Sensor *sy = this->move_y_sensors_[index]; sensor::Sensor *sy = this->move_y_sensors_[index];
if (sy != nullptr) { if (sy != nullptr) {
val = ld2450::decode_coordinate(buffer[start], buffer[start + 1]); val = ld2450::decode_coordinate(this->buffer_data_[start], this->buffer_data_[start + 1]);
ty = val; ty = val;
if (this->cached_target_data_[index].y != val) { if (this->cached_target_data_[index].y != val) {
sy->publish_state(val); sy->publish_state(val);
@ -490,7 +501,7 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
start = TARGET_RESOLUTION + index * 8; start = TARGET_RESOLUTION + index * 8;
sensor::Sensor *sr = this->move_resolution_sensors_[index]; sensor::Sensor *sr = this->move_resolution_sensors_[index];
if (sr != nullptr) { if (sr != nullptr) {
val = (buffer[start + 1] << 8) | buffer[start]; val = (this->buffer_data_[start + 1] << 8) | this->buffer_data_[start];
if (this->cached_target_data_[index].resolution != val) { if (this->cached_target_data_[index].resolution != val) {
sr->publish_state(val); sr->publish_state(val);
this->cached_target_data_[index].resolution = val; this->cached_target_data_[index].resolution = val;
@ -499,7 +510,7 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
#endif #endif
// SPEED // SPEED
start = TARGET_SPEED + index * 8; start = TARGET_SPEED + index * 8;
val = ld2450::decode_speed(buffer[start], buffer[start + 1]); val = ld2450::decode_speed(this->buffer_data_[start], this->buffer_data_[start + 1]);
ts = val; ts = val;
if (val) { if (val) {
is_moving = true; is_moving = true;
@ -532,7 +543,7 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
} }
} }
// ANGLE // ANGLE
angle = calculate_angle(static_cast<float>(ty), static_cast<float>(td)); angle = ld2450::calculate_angle(static_cast<float>(ty), static_cast<float>(td));
if (tx > 0) { if (tx > 0) {
angle = angle * -1; angle = angle * -1;
} }
@ -547,14 +558,19 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
// DIRECTION // DIRECTION
direction = get_direction(ts);
if (td == 0) { if (td == 0) {
direction = "NA"; direction = DIRECTION_NA;
} else if (ts > 0) {
direction = DIRECTION_MOVING_AWAY;
} else if (ts < 0) {
direction = DIRECTION_APPROACHING;
} else {
direction = DIRECTION_STATIONARY;
} }
text_sensor::TextSensor *tsd = this->direction_text_sensors_[index]; text_sensor::TextSensor *tsd = this->direction_text_sensors_[index];
if (tsd != nullptr) { if (tsd != nullptr) {
if (this->cached_target_data_[index].direction != direction) { if (this->cached_target_data_[index].direction != direction) {
tsd->publish_state(direction); tsd->publish_state(find_str(ld2450::DIRECTION_BY_UINT, direction));
this->cached_target_data_[index].direction = direction; this->cached_target_data_[index].direction = direction;
} }
} }
@ -678,117 +694,139 @@ void LD2450Component::handle_periodic_data_(uint8_t *buffer, uint8_t len) {
#endif #endif
} }
bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) { bool LD2450Component::handle_ack_data_() {
ESP_LOGV(TAG, "Handling ack data for command %02X", buffer[COMMAND]); ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", this->buffer_data_[COMMAND]);
if (len < 10) { if (this->buffer_pos_ < 10) {
ESP_LOGE(TAG, "Invalid ack length"); ESP_LOGE(TAG, "Invalid length");
return true; return true;
} }
if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // frame header if (!ld2450::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) {
ESP_LOGE(TAG, "Invalid ack header (command %02X)", buffer[COMMAND]); ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str());
return true; return true;
} }
if (buffer[COMMAND_STATUS] != 0x01) { if (this->buffer_data_[COMMAND_STATUS] != 0x01) {
ESP_LOGE(TAG, "Invalid ack status"); ESP_LOGE(TAG, "Invalid status");
return true; return true;
} }
if (buffer[8] || buffer[9]) { if (this->buffer_data_[8] || this->buffer_data_[9]) {
ESP_LOGE(TAG, "Last buffer was %u, %u", buffer[8], buffer[9]); ESP_LOGW(TAG, "Invalid command: %02X, %02X", this->buffer_data_[8], this->buffer_data_[9]);
return true; return true;
} }
switch (buffer[COMMAND]) { switch (this->buffer_data_[COMMAND]) {
case lowbyte(CMD_ENABLE_CONF): case CMD_ENABLE_CONF:
ESP_LOGV(TAG, "Enable conf command"); ESP_LOGV(TAG, "Enable conf");
break; break;
case lowbyte(CMD_DISABLE_CONF):
ESP_LOGV(TAG, "Disable conf command"); case CMD_DISABLE_CONF:
ESP_LOGV(TAG, "Disabled conf");
break; break;
case lowbyte(CMD_SET_BAUD_RATE):
ESP_LOGV(TAG, "Baud rate change command"); case CMD_SET_BAUD_RATE:
ESP_LOGV(TAG, "Baud rate change");
#ifdef USE_SELECT #ifdef USE_SELECT
if (this->baud_rate_select_ != nullptr) { if (this->baud_rate_select_ != nullptr) {
ESP_LOGV(TAG, "Change baud rate to %s", this->baud_rate_select_->state.c_str()); ESP_LOGE(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->state.c_str());
} }
#endif #endif
break; break;
case lowbyte(CMD_VERSION):
this->version_ = str_sprintf(VERSION_FMT, buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], buffer[14]); case CMD_QUERY_VERSION: {
ESP_LOGV(TAG, "Firmware version: %s", this->version_.c_str()); std::memcpy(this->version_, &this->buffer_data_[12], sizeof(this->version_));
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
this->version_[4], this->version_[3], this->version_[2]);
ESP_LOGV(TAG, "Firmware version: %s", version.c_str());
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
if (this->version_text_sensor_ != nullptr) { if (this->version_text_sensor_ != nullptr) {
this->version_text_sensor_->publish_state(this->version_); this->version_text_sensor_->publish_state(version);
} }
#endif #endif
break; break;
case lowbyte(CMD_MAC): }
if (len < 20) {
case CMD_QUERY_MAC_ADDRESS: {
if (this->buffer_pos_ < 20) {
return false; return false;
} }
this->mac_ = format_mac_address_pretty(&buffer[10]);
ESP_LOGV(TAG, "MAC address: %s", this->mac_.c_str()); this->bluetooth_on_ = std::memcmp(&this->buffer_data_[10], NO_MAC, sizeof(NO_MAC)) != 0;
if (this->bluetooth_on_) {
std::memcpy(this->mac_address_, &this->buffer_data_[10], sizeof(this->mac_address_));
}
std::string mac_str =
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
ESP_LOGV(TAG, "MAC address: %s", mac_str.c_str());
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
if (this->mac_text_sensor_ != nullptr) { if (this->mac_text_sensor_ != nullptr) {
this->mac_text_sensor_->publish_state(this->mac_ == NO_MAC ? UNKNOWN_MAC : this->mac_); this->mac_text_sensor_->publish_state(mac_str);
} }
#endif #endif
#ifdef USE_SWITCH #ifdef USE_SWITCH
if (this->bluetooth_switch_ != nullptr) { if (this->bluetooth_switch_ != nullptr) {
this->bluetooth_switch_->publish_state(this->mac_ != NO_MAC); this->bluetooth_switch_->publish_state(this->bluetooth_on_);
} }
#endif #endif
break; break;
case lowbyte(CMD_BLUETOOTH): }
ESP_LOGV(TAG, "Bluetooth command");
case CMD_BLUETOOTH:
ESP_LOGV(TAG, "Bluetooth");
break; break;
case lowbyte(CMD_SINGLE_TARGET_MODE):
ESP_LOGV(TAG, "Single target conf command"); case CMD_SINGLE_TARGET_MODE:
ESP_LOGV(TAG, "Single target conf");
#ifdef USE_SWITCH #ifdef USE_SWITCH
if (this->multi_target_switch_ != nullptr) { if (this->multi_target_switch_ != nullptr) {
this->multi_target_switch_->publish_state(false); this->multi_target_switch_->publish_state(false);
} }
#endif #endif
break; break;
case lowbyte(CMD_MULTI_TARGET_MODE):
ESP_LOGV(TAG, "Multi target conf command"); case CMD_MULTI_TARGET_MODE:
ESP_LOGV(TAG, "Multi target conf");
#ifdef USE_SWITCH #ifdef USE_SWITCH
if (this->multi_target_switch_ != nullptr) { if (this->multi_target_switch_ != nullptr) {
this->multi_target_switch_->publish_state(true); this->multi_target_switch_->publish_state(true);
} }
#endif #endif
break; break;
case lowbyte(CMD_QUERY_TARGET_MODE):
ESP_LOGV(TAG, "Query target tracking mode command"); case CMD_QUERY_TARGET_MODE:
ESP_LOGV(TAG, "Query target tracking mode");
#ifdef USE_SWITCH #ifdef USE_SWITCH
if (this->multi_target_switch_ != nullptr) { if (this->multi_target_switch_ != nullptr) {
this->multi_target_switch_->publish_state(buffer[10] == 0x02); this->multi_target_switch_->publish_state(this->buffer_data_[10] == 0x02);
} }
#endif #endif
break; break;
case lowbyte(CMD_QUERY_ZONE):
ESP_LOGV(TAG, "Query zone conf command"); case CMD_QUERY_ZONE:
this->zone_type_ = std::stoi(std::to_string(buffer[10]), nullptr, 16); ESP_LOGV(TAG, "Query zone conf");
this->zone_type_ = std::stoi(std::to_string(this->buffer_data_[10]), nullptr, 16);
this->publish_zone_type(); this->publish_zone_type();
#ifdef USE_SELECT #ifdef USE_SELECT
if (this->zone_type_select_ != nullptr) { if (this->zone_type_select_ != nullptr) {
ESP_LOGV(TAG, "Change zone type to: %s", this->zone_type_select_->state.c_str()); ESP_LOGV(TAG, "Change zone type to: %s", this->zone_type_select_->state.c_str());
} }
#endif #endif
if (buffer[10] == 0x00) { if (this->buffer_data_[10] == 0x00) {
ESP_LOGV(TAG, "Zone: Disabled"); ESP_LOGV(TAG, "Zone: Disabled");
} }
if (buffer[10] == 0x01) { if (this->buffer_data_[10] == 0x01) {
ESP_LOGV(TAG, "Zone: Area detection"); ESP_LOGV(TAG, "Zone: Area detection");
} }
if (buffer[10] == 0x02) { if (this->buffer_data_[10] == 0x02) {
ESP_LOGV(TAG, "Zone: Area filter"); ESP_LOGV(TAG, "Zone: Area filter");
} }
this->process_zone_(buffer); this->process_zone_();
break; break;
case lowbyte(CMD_SET_ZONE):
ESP_LOGV(TAG, "Set zone conf command"); case CMD_SET_ZONE:
ESP_LOGV(TAG, "Set zone conf");
this->query_zone_info(); this->query_zone_info();
break; break;
default: default:
break; break;
} }
@ -796,55 +834,57 @@ bool LD2450Component::handle_ack_data_(uint8_t *buffer, uint8_t len) {
} }
// Read LD2450 buffer data // Read LD2450 buffer data
void LD2450Component::readline_(int readch, uint8_t *buffer, uint8_t len) { void LD2450Component::readline_(int readch) {
if (readch < 0) { if (readch < 0) {
return; return; // No data available
} }
if (this->buffer_pos_ < len - 1) {
buffer[this->buffer_pos_++] = readch; if (this->buffer_pos_ < MAX_LINE_LENGTH - 1) {
buffer[this->buffer_pos_] = 0; this->buffer_data_[this->buffer_pos_++] = readch;
this->buffer_data_[this->buffer_pos_] = 0;
} else { } else {
// We should never get here, but just in case...
ESP_LOGW(TAG, "Max command length exceeded; ignoring");
this->buffer_pos_ = 0; this->buffer_pos_ = 0;
} }
if (this->buffer_pos_ < 4) { if (this->buffer_pos_ < 4) {
return; return; // Not enough data to process yet
} }
if (buffer[this->buffer_pos_ - 2] == 0x55 && buffer[this->buffer_pos_ - 1] == 0xCC) { if (this->buffer_data_[this->buffer_pos_ - 2] == DATA_FRAME_FOOTER[0] &&
ESP_LOGV(TAG, "Handle periodic radar data"); this->buffer_data_[this->buffer_pos_ - 1] == DATA_FRAME_FOOTER[1]) {
this->handle_periodic_data_(buffer, this->buffer_pos_); ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
this->handle_periodic_data_();
this->buffer_pos_ = 0; // Reset position index for next frame this->buffer_pos_ = 0; // Reset position index for next frame
} else if (buffer[this->buffer_pos_ - 4] == 0x04 && buffer[this->buffer_pos_ - 3] == 0x03 && } else if (ld2450::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
buffer[this->buffer_pos_ - 2] == 0x02 && buffer[this->buffer_pos_ - 1] == 0x01) { ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
ESP_LOGV(TAG, "Handle command ack data"); if (this->handle_ack_data_()) {
if (this->handle_ack_data_(buffer, this->buffer_pos_)) { this->buffer_pos_ = 0; // Reset position index for next message
this->buffer_pos_ = 0; // Reset position index for next frame
} else { } else {
ESP_LOGV(TAG, "Command ack data invalid"); ESP_LOGV(TAG, "Ack Data incomplete");
} }
} }
} }
// Set Config Mode - Pre-requisite sending commands // Set Config Mode - Pre-requisite sending commands
void LD2450Component::set_config_mode_(bool enable) { void LD2450Component::set_config_mode_(bool enable) {
uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF; const uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
uint8_t cmd_value[2] = {0x01, 0x00}; const uint8_t cmd_value[2] = {0x01, 0x00};
this->send_command_(cmd, enable ? cmd_value : nullptr, 2); this->send_command_(cmd, enable ? cmd_value : nullptr, sizeof(cmd_value));
} }
// Set Bluetooth Enable/Disable // Set Bluetooth Enable/Disable
void LD2450Component::set_bluetooth(bool enable) { void LD2450Component::set_bluetooth(bool enable) {
this->set_config_mode_(true); this->set_config_mode_(true);
uint8_t enable_cmd_value[2] = {0x01, 0x00}; const uint8_t cmd_value[2] = {enable ? (uint8_t) 0x01 : (uint8_t) 0x00, 0x00};
uint8_t disable_cmd_value[2] = {0x00, 0x00}; this->send_command_(CMD_BLUETOOTH, cmd_value, sizeof(cmd_value));
this->send_command_(CMD_BLUETOOTH, enable ? enable_cmd_value : disable_cmd_value, 2);
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); }); this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
} }
// Set Baud rate // Set Baud rate
void LD2450Component::set_baud_rate(const std::string &state) { void LD2450Component::set_baud_rate(const std::string &state) {
this->set_config_mode_(true); this->set_config_mode_(true);
uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00}; const uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00};
this->send_command_(CMD_SET_BAUD_RATE, cmd_value, 2); this->send_command_(CMD_SET_BAUD_RATE, cmd_value, sizeof(cmd_value));
this->set_timeout(200, [this]() { this->restart_(); }); this->set_timeout(200, [this]() { this->restart_(); });
} }
@ -885,12 +925,12 @@ void LD2450Component::factory_reset() {
void LD2450Component::restart_() { this->send_command_(CMD_RESTART, nullptr, 0); } void LD2450Component::restart_() { this->send_command_(CMD_RESTART, nullptr, 0); }
// Get LD2450 firmware version // Get LD2450 firmware version
void LD2450Component::get_version_() { this->send_command_(CMD_VERSION, nullptr, 0); } void LD2450Component::get_version_() { this->send_command_(CMD_QUERY_VERSION, nullptr, 0); }
// Get LD2450 mac address // Get LD2450 mac address
void LD2450Component::get_mac_() { void LD2450Component::get_mac_() {
uint8_t cmd_value[2] = {0x01, 0x00}; uint8_t cmd_value[2] = {0x01, 0x00};
this->send_command_(CMD_MAC, cmd_value, 2); this->send_command_(CMD_QUERY_MAC_ADDRESS, cmd_value, 2);
} }
// Query for target tracking mode // Query for target tracking mode

View File

@ -38,10 +38,18 @@ namespace ld2450 {
// Constants // Constants
static const uint8_t DEFAULT_PRESENCE_TIMEOUT = 5; // Timeout to reset presense status 5 sec. static const uint8_t DEFAULT_PRESENCE_TIMEOUT = 5; // Timeout to reset presense status 5 sec.
static const uint8_t MAX_LINE_LENGTH = 60; // Max characters for serial buffer static const uint8_t MAX_LINE_LENGTH = 41; // Max characters for serial buffer
static const uint8_t MAX_TARGETS = 3; // Max 3 Targets in LD2450 static const uint8_t MAX_TARGETS = 3; // Max 3 Targets in LD2450
static const uint8_t MAX_ZONES = 3; // Max 3 Zones in LD2450 static const uint8_t MAX_ZONES = 3; // Max 3 Zones in LD2450
enum Direction : uint8_t {
DIRECTION_APPROACHING = 0,
DIRECTION_MOVING_AWAY = 1,
DIRECTION_STATIONARY = 2,
DIRECTION_NA = 3,
DIRECTION_UNDEFINED = 4,
};
// Target coordinate struct // Target coordinate struct
struct Target { struct Target {
int16_t x; int16_t x;
@ -67,19 +75,22 @@ struct ZoneOfNumbers {
#endif #endif
class LD2450Component : public Component, public uart::UARTDevice { class LD2450Component : public Component, public uart::UARTDevice {
#ifdef USE_SENSOR
SUB_SENSOR(target_count)
SUB_SENSOR(still_target_count)
SUB_SENSOR(moving_target_count)
#endif
#ifdef USE_BINARY_SENSOR #ifdef USE_BINARY_SENSOR
SUB_BINARY_SENSOR(target)
SUB_BINARY_SENSOR(moving_target) SUB_BINARY_SENSOR(moving_target)
SUB_BINARY_SENSOR(still_target) SUB_BINARY_SENSOR(still_target)
SUB_BINARY_SENSOR(target)
#endif
#ifdef USE_SENSOR
SUB_SENSOR(moving_target_count)
SUB_SENSOR(still_target_count)
SUB_SENSOR(target_count)
#endif #endif
#ifdef USE_TEXT_SENSOR #ifdef USE_TEXT_SENSOR
SUB_TEXT_SENSOR(version)
SUB_TEXT_SENSOR(mac) SUB_TEXT_SENSOR(mac)
SUB_TEXT_SENSOR(version)
#endif
#ifdef USE_NUMBER
SUB_NUMBER(presence_timeout)
#endif #endif
#ifdef USE_SELECT #ifdef USE_SELECT
SUB_SELECT(baud_rate) SUB_SELECT(baud_rate)
@ -90,12 +101,9 @@ class LD2450Component : public Component, public uart::UARTDevice {
SUB_SWITCH(multi_target) SUB_SWITCH(multi_target)
#endif #endif
#ifdef USE_BUTTON #ifdef USE_BUTTON
SUB_BUTTON(reset) SUB_BUTTON(factory_reset)
SUB_BUTTON(restart) SUB_BUTTON(restart)
#endif #endif
#ifdef USE_NUMBER
SUB_NUMBER(presence_timeout)
#endif
public: public:
void setup() override; void setup() override;
@ -138,10 +146,10 @@ class LD2450Component : public Component, public uart::UARTDevice {
protected: protected:
void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len); void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len);
void set_config_mode_(bool enable); void set_config_mode_(bool enable);
void handle_periodic_data_(uint8_t *buffer, uint8_t len); void handle_periodic_data_();
bool handle_ack_data_(uint8_t *buffer, uint8_t len); bool handle_ack_data_();
void process_zone_(uint8_t *buffer); void process_zone_();
void readline_(int readch, uint8_t *buffer, uint8_t len); void readline_(int readch);
void get_version_(); void get_version_();
void get_mac_(); void get_mac_();
void query_target_tracking_mode_(); void query_target_tracking_mode_();
@ -159,13 +167,14 @@ class LD2450Component : public Component, public uart::UARTDevice {
uint32_t moving_presence_millis_ = 0; uint32_t moving_presence_millis_ = 0;
uint16_t throttle_ = 0; uint16_t throttle_ = 0;
uint16_t timeout_ = 5; uint16_t timeout_ = 5;
uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer
uint8_t buffer_data_[MAX_LINE_LENGTH]; uint8_t buffer_data_[MAX_LINE_LENGTH];
uint8_t mac_address_[6] = {0, 0, 0, 0, 0, 0};
uint8_t version_[6] = {0, 0, 0, 0, 0, 0};
uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer
uint8_t zone_type_ = 0; uint8_t zone_type_ = 0;
bool bluetooth_on_{false};
Target target_info_[MAX_TARGETS]; Target target_info_[MAX_TARGETS];
Zone zone_config_[MAX_ZONES]; Zone zone_config_[MAX_ZONES];
std::string version_{};
std::string mac_{};
// Change detection - cache previous values to avoid redundant publishes // Change detection - cache previous values to avoid redundant publishes
// All values are initialized to sentinel values that are outside the valid sensor ranges // All values are initialized to sentinel values that are outside the valid sensor ranges
@ -176,8 +185,8 @@ class LD2450Component : public Component, public uart::UARTDevice {
int16_t speed = std::numeric_limits<int16_t>::min(); // -32768, outside practical sensor range int16_t speed = std::numeric_limits<int16_t>::min(); // -32768, outside practical sensor range
uint16_t resolution = std::numeric_limits<uint16_t>::max(); // 65535, unlikely resolution value uint16_t resolution = std::numeric_limits<uint16_t>::max(); // 65535, unlikely resolution value
uint16_t distance = std::numeric_limits<uint16_t>::max(); // 65535, outside range of 0 to ~8990 uint16_t distance = std::numeric_limits<uint16_t>::max(); // 65535, outside range of 0 to ~8990
Direction direction = DIRECTION_UNDEFINED; // Undefined, will differ from any real direction
float angle = NAN; // NAN, safe sentinel for floats float angle = NAN; // NAN, safe sentinel for floats
std::string direction = ""; // Empty string, will differ from any real direction
} cached_target_data_[MAX_TARGETS]; } cached_target_data_[MAX_TARGETS];
struct CachedZoneData { struct CachedZoneData {