Add HLK-LD2420 mmWave Radar module component (#4847)

Co-authored-by: descipher <120155735+GelidusResearch@users.noreply.github.com>
This commit is contained in:
Mike La Spina 2023-11-02 18:02:23 -05:00 committed by GitHub
parent d74a8abf9a
commit 22cdb8dfc3
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
23 changed files with 1865 additions and 0 deletions

View file

@ -152,6 +152,7 @@ esphome/components/key_provider/* @ssieb
esphome/components/kuntze/* @ssieb esphome/components/kuntze/* @ssieb
esphome/components/lcd_menu/* @numo68 esphome/components/lcd_menu/* @numo68
esphome/components/ld2410/* @regevbr @sebcaps esphome/components/ld2410/* @regevbr @sebcaps
esphome/components/ld2420/* @descipher
esphome/components/ledc/* @OttoWinter esphome/components/ledc/* @OttoWinter
esphome/components/libretiny/* @kuba2k2 esphome/components/libretiny/* @kuba2k2
esphome/components/libretiny_pwm/* @kuba2k2 esphome/components/libretiny_pwm/* @kuba2k2

View file

@ -0,0 +1,39 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import uart
from esphome.const import CONF_ID
CODEOWNERS = ["@descipher"]
DEPENDENCIES = ["uart"]
MULTI_CONF = True
ld2420_ns = cg.esphome_ns.namespace("ld2420")
LD2420Component = ld2420_ns.class_("LD2420Component", cg.Component, uart.UARTDevice)
CONF_LD2420_ID = "ld2420_id"
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(LD2420Component),
}
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"ld2420_uart",
require_tx=True,
require_rx=True,
parity="NONE",
stop_bits=1,
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
await uart.register_uart_device(var, config)

View file

@ -0,0 +1,33 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import binary_sensor
from esphome.const import CONF_ID, DEVICE_CLASS_OCCUPANCY
from .. import ld2420_ns, LD2420Component, CONF_LD2420_ID
LD2420BinarySensor = ld2420_ns.class_(
"LD2420BinarySensor", binary_sensor.BinarySensor, cg.Component
)
CONF_HAS_TARGET = "has_target"
CONFIG_SCHEMA = cv.All(
cv.COMPONENT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(LD2420BinarySensor),
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_OCCUPANCY
),
}
),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
if CONF_HAS_TARGET in config:
sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_TARGET])
cg.add(var.set_presence_sensor(sens))
ld2420 = await cg.get_variable(config[CONF_LD2420_ID])
cg.add(ld2420.register_listener(var))

View file

@ -0,0 +1,16 @@
#include "ld2420_binary_sensor.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ld2420 {
static const char *const TAG = "LD2420.binary_sensor";
void LD2420BinarySensor::dump_config() {
ESP_LOGCONFIG(TAG, "LD2420 BinarySensor:");
LOG_BINARY_SENSOR(" ", "Presence", this->presence_bsensor_);
}
} // namespace ld2420
} // namespace esphome

View file

@ -0,0 +1,25 @@
#pragma once
#include "../ld2420.h"
#include "esphome/components/binary_sensor/binary_sensor.h"
namespace esphome {
namespace ld2420 {
class LD2420BinarySensor : public LD2420Listener, public Component, binary_sensor::BinarySensor {
public:
void dump_config() override;
void set_presence_sensor(binary_sensor::BinarySensor *bsensor) { this->presence_bsensor_ = bsensor; };
void on_presence(bool presence) override {
if (this->presence_bsensor_ != nullptr) {
if (this->presence_bsensor_->state != presence)
this->presence_bsensor_->publish_state(presence);
}
}
protected:
binary_sensor::BinarySensor *presence_bsensor_{nullptr};
};
} // namespace ld2420
} // namespace esphome

View file

@ -0,0 +1,69 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import (
DEVICE_CLASS_RESTART,
ENTITY_CATEGORY_DIAGNOSTIC,
ENTITY_CATEGORY_CONFIG,
ICON_RESTART,
ICON_RESTART_ALERT,
ICON_DATABASE,
)
from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns
LD2420ApplyConfigButton = ld2420_ns.class_("LD2420ApplyConfigButton", button.Button)
LD2420RevertConfigButton = ld2420_ns.class_("LD2420RevertConfigButton", button.Button)
LD2420RestartModuleButton = ld2420_ns.class_("LD2420RestartModuleButton", button.Button)
LD2420FactoryResetButton = ld2420_ns.class_("LD2420FactoryResetButton", button.Button)
CONF_APPLY_CONFIG = "apply_config"
CONF_REVERT_CONFIG = "revert_config"
CONF_RESTART_MODULE = "restart_module"
CONF_FACTORY_RESET = "factory_reset"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
cv.Required(CONF_APPLY_CONFIG): button.button_schema(
LD2420ApplyConfigButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART_ALERT,
),
cv.Optional(CONF_REVERT_CONFIG): button.button_schema(
LD2420RevertConfigButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART,
),
cv.Optional(CONF_RESTART_MODULE): button.button_schema(
LD2420RestartModuleButton,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_DATABASE,
),
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
LD2420FactoryResetButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_DATABASE,
),
}
async def to_code(config):
ld2420_component = await cg.get_variable(config[CONF_LD2420_ID])
if apply_config := config.get(CONF_APPLY_CONFIG):
b = await button.new_button(apply_config)
await cg.register_parented(b, config[CONF_LD2420_ID])
cg.add(ld2420_component.set_apply_config_button(b))
if revert_config := config.get(CONF_REVERT_CONFIG):
b = await button.new_button(revert_config)
await cg.register_parented(b, config[CONF_LD2420_ID])
cg.add(ld2420_component.set_revert_config_button(b))
if restart_config := config.get(CONF_RESTART_MODULE):
b = await button.new_button(restart_config)
await cg.register_parented(b, config[CONF_LD2420_ID])
cg.add(ld2420_component.set_restart_module_button(b))
if factory_reset := config.get(CONF_FACTORY_RESET):
b = await button.new_button(factory_reset)
await cg.register_parented(b, config[CONF_LD2420_ID])
cg.add(ld2420_component.set_factory_reset_button(b))

View file

@ -0,0 +1,16 @@
#include "reconfig_buttons.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
static const char *const TAG = "LD2420.button";
namespace esphome {
namespace ld2420 {
void LD2420ApplyConfigButton::press_action() { this->parent_->apply_config_action(); }
void LD2420RevertConfigButton::press_action() { this->parent_->revert_config_action(); }
void LD2420RestartModuleButton::press_action() { this->parent_->restart_module_action(); }
void LD2420FactoryResetButton::press_action() { this->parent_->factory_reset_action(); }
} // namespace ld2420
} // namespace esphome

View file

@ -0,0 +1,42 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2420.h"
namespace esphome {
namespace ld2420 {
class LD2420ApplyConfigButton : public button::Button, public Parented<LD2420Component> {
public:
LD2420ApplyConfigButton() = default;
protected:
void press_action() override;
};
class LD2420RevertConfigButton : public button::Button, public Parented<LD2420Component> {
public:
LD2420RevertConfigButton() = default;
protected:
void press_action() override;
};
class LD2420RestartModuleButton : public button::Button, public Parented<LD2420Component> {
public:
LD2420RestartModuleButton() = default;
protected:
void press_action() override;
};
class LD2420FactoryResetButton : public button::Button, public Parented<LD2420Component> {
public:
LD2420FactoryResetButton() = default;
protected:
void press_action() override;
};
} // namespace ld2420
} // namespace esphome

View file

@ -0,0 +1,775 @@
#include "ld2420.h"
#include "esphome/core/helpers.h"
/*
Configure commands - little endian
No command can exceed 64 bytes, otherwise they would need be to be split up into multiple sends.
All send command frames will have:
Header = FD FC FB FA, Bytes 0 - 3, uint32_t 0xFAFBFCFD
Length, bytes 4 - 5, uint16_t 0x0002, must be at least 2 for the command byte if no addon data.
Command bytes 6 - 7, uint16_t
Footer = 04 03 02 01 - uint32_t 0x01020304, Always last 4 Bytes.
Receive
Error bytes 8-9 uint16_t, 0 = success, all other positive values = error
Enable config mode:
Send:
UART Tx: FD FC FB FA 04 00 FF 00 02 00 04 03 02 01
Command = FF 00 - uint16_t 0x00FF
Protocol version = 02 00, can be 1 or 2 - uint16_t 0x0002
Reply:
UART Rx: FD FC FB FA 06 00 FF 01 00 00 02 00 04 03 02 01
Disable config mode:
Send:
UART Tx: FD FC FB FA 02 00 FE 00 04 03 02 01
Command = FE 00 - uint16_t 0x00FE
Receive:
UART Rx: FD FC FB FA 04 00 FE 01 00 00 04 03 02 01
Configure system parameters:
UART Tx: FD FC FB FA 08 00 12 00 00 00 64 00 00 00 04 03 02 01 Set system parms
Command = 12 00 - uint16_t 0x0012, Param
There are three documented parameters for modes:
00 64 = Basic status mode
This mode outputs text as presence "ON" or "OFF" and "Range XXXX"
where XXXX is a decimal value for distance in cm
00 04 = Energy output mode
This mode outputs detailed signal energy values for each gate and the target distance.
The data format consist of the following.
Header HH, Length LL, Persence PP, Distance DD, Range Gate GG, 16 Gate Energies EE, Footer FF
HH HH HH HH LL LL PP DD DD GG GG EE EE .. 16x .. FF FF FF FF
F4 F3 F2 F1 00 23 00 00 00 00 01 00 00 .. .. .. .. F8 F7 F6 F5
00 00 = debug output mode
This mode outputs detailed values consisting of 20 Dopplers, 16 Ranges for a total 20 * 16 * 4 bytes
The data format consist of the following.
Header HH, Doppler DD, Range RR, Footer FF
HH HH HH HH DD DD DD DD .. 20x .. RR RR RR RR .. 16x .. FF FF FF FF
AA BF 10 14 00 00 00 00 .. .. .. .. 00 00 00 00 .. .. .. .. FD FC FB FA
Configure gate sensitivity parameters:
UART Tx: FD FC FB FA 0E 00 07 00 10 00 60 EA 00 00 20 00 60 EA 00 00 04 03 02 01
Command = 12 00 - uint16_t 0x0007
Gate 0 high thresh = 10 00 uint16_t 0x0010, Threshold value = 60 EA 00 00 uint32_t 0x0000EA60
Gate 0 low thresh = 20 00 uint16_t 0x0020, Threshold value = 60 EA 00 00 uint32_t 0x0000EA60
*/
namespace esphome {
namespace ld2420 {
static const char *const TAG = "ld2420";
float LD2420Component::get_setup_priority() const { return setup_priority::BUS; }
void LD2420Component::dump_config() {
ESP_LOGCONFIG(TAG, "LD2420:");
ESP_LOGCONFIG(TAG, " Firmware Version : %7s", this->ld2420_firmware_ver_);
ESP_LOGCONFIG(TAG, "LD2420 Number:");
LOG_NUMBER(TAG, " Gate Timeout:", this->gate_timeout_number_);
LOG_NUMBER(TAG, " Gate Max Distance:", this->max_gate_distance_number_);
LOG_NUMBER(TAG, " Gate Min Distance:", this->min_gate_distance_number_);
LOG_NUMBER(TAG, " Gate Select:", this->gate_select_number_);
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
LOG_NUMBER(TAG, " Gate Move Threshold:", this->gate_move_threshold_numbers_[gate]);
LOG_NUMBER(TAG, " Gate Still Threshold::", this->gate_still_threshold_numbers_[gate]);
}
LOG_BUTTON(TAG, " Apply Config:", this->apply_config_button_);
LOG_BUTTON(TAG, " Revert Edits:", this->revert_config_button_);
LOG_BUTTON(TAG, " Factory Reset:", this->factory_reset_button_);
LOG_BUTTON(TAG, " Restart Module:", this->restart_module_button_);
ESP_LOGCONFIG(TAG, "LD2420 Select:");
LOG_SELECT(TAG, " Operating Mode", this->operating_selector_);
if (this->get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
ESP_LOGW(TAG, "LD2420 Firmware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_);
}
}
uint8_t LD2420Component::calc_checksum(void *data, size_t size) {
uint8_t checksum = 0;
uint8_t *data_bytes = (uint8_t *) data;
for (size_t i = 0; i < size; i++) {
checksum ^= data_bytes[i]; // XOR operation
}
return checksum;
}
int LD2420Component::get_firmware_int_(const char *version_string) {
std::string version_str = version_string;
if (version_str[0] == 'v') {
version_str = version_str.substr(1);
}
version_str.erase(remove(version_str.begin(), version_str.end(), '.'), version_str.end());
int version_integer = stoi(version_str);
return version_integer;
}
void LD2420Component::setup() {
ESP_LOGCONFIG(TAG, "Setting up LD2420...");
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
this->mark_failed();
return;
}
this->get_min_max_distances_timeout_();
#ifdef USE_NUMBER
this->init_gate_config_numbers();
#endif
this->get_firmware_version_();
const char *pfw = this->ld2420_firmware_ver_;
std::string fw_str(pfw);
for (auto &listener : listeners_) {
listener->on_fw_version(fw_str);
}
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
delay_microseconds_safe(125);
this->get_gate_threshold_(gate);
}
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
if (get_firmware_int_(ld2420_firmware_ver_) < CALIBRATE_VERSION_MIN) {
this->set_operating_mode(OP_SIMPLE_MODE_STRING);
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
this->set_mode_(CMD_SYSTEM_MODE_SIMPLE);
ESP_LOGW(TAG, "LD2420 Frimware Version %s and older are only supported in Simple Mode", ld2420_firmware_ver_);
} else {
this->set_mode_(CMD_SYSTEM_MODE_ENERGY);
this->operating_selector_->publish_state(OP_NORMAL_MODE_STRING);
}
#ifdef USE_NUMBER
this->init_gate_config_numbers();
#endif
this->set_system_mode(this->system_mode_);
this->set_config_mode(false);
ESP_LOGCONFIG(TAG, "LD2420 setup complete.");
}
void LD2420Component::apply_config_action() {
const uint8_t checksum = calc_checksum(&this->new_config, sizeof(this->new_config));
if (checksum == calc_checksum(&this->current_config, sizeof(this->current_config))) {
ESP_LOGCONFIG(TAG, "No configuration change detected");
return;
}
ESP_LOGCONFIG(TAG, "Reconfiguring LD2420...");
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
this->mark_failed();
return;
}
this->set_min_max_distances_timeout(this->new_config.max_gate, this->new_config.min_gate, this->new_config.timeout);
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
delay_microseconds_safe(125);
this->set_gate_threshold(gate);
}
memcpy(&current_config, &new_config, sizeof(new_config));
#ifdef USE_NUMBER
this->init_gate_config_numbers();
#endif
this->set_system_mode(this->system_mode_);
this->set_config_mode(false); // Disable config mode to save new values in LD2420 nvm
this->set_operating_mode(OP_NORMAL_MODE_STRING);
ESP_LOGCONFIG(TAG, "LD2420 reconfig complete.");
}
void LD2420Component::factory_reset_action() {
ESP_LOGCONFIG(TAG, "Setiing factory defaults...");
if (this->set_config_mode(true) == LD2420_ERROR_TIMEOUT) {
ESP_LOGE(TAG, "LD2420 module has failed to respond, check baud rate and serial connections.");
this->mark_failed();
return;
}
this->set_min_max_distances_timeout(FACTORY_MAX_GATE, FACTORY_MIN_GATE, FACTORY_TIMEOUT);
this->gate_timeout_number_->state = FACTORY_TIMEOUT;
this->min_gate_distance_number_->state = FACTORY_MIN_GATE;
this->max_gate_distance_number_->state = FACTORY_MAX_GATE;
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
this->new_config.move_thresh[gate] = FACTORY_MOVE_THRESH[gate];
this->new_config.still_thresh[gate] = FACTORY_STILL_THRESH[gate];
delay_microseconds_safe(125);
this->set_gate_threshold(gate);
}
memcpy(&this->current_config, &this->new_config, sizeof(this->new_config));
this->set_system_mode(this->system_mode_);
this->set_config_mode(false);
#ifdef USE_NUMBER
this->init_gate_config_numbers();
this->refresh_gate_config_numbers();
#endif
ESP_LOGCONFIG(TAG, "LD2420 factory reset complete.");
}
void LD2420Component::restart_module_action() {
ESP_LOGCONFIG(TAG, "Restarting LD2420 module...");
this->send_module_restart();
delay_microseconds_safe(45000);
this->set_config_mode(true);
this->set_system_mode(system_mode_);
this->set_config_mode(false);
ESP_LOGCONFIG(TAG, "LD2420 Restarted.");
}
void LD2420Component::revert_config_action() {
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
#ifdef USE_NUMBER
this->init_gate_config_numbers();
#endif
ESP_LOGCONFIG(TAG, "Reverted config number edits.");
}
void LD2420Component::loop() {
// If there is a active send command do not process it here, the send command call will handle it.
if (!get_cmd_active_()) {
if (!available())
return;
static uint8_t buffer[2048];
static uint8_t rx_data;
while (available()) {
rx_data = read();
this->readline_(rx_data, buffer, sizeof(buffer));
}
}
}
void LD2420Component::update_radar_data(uint16_t const *gate_energy, uint8_t sample_number) {
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) {
this->radar_data[gate][sample_number] = gate_energy[gate];
}
this->total_sample_number_counter++;
}
void LD2420Component::auto_calibrate_sensitivity() {
// Calculate average and peak values for each gate
const float move_factor = gate_move_sensitivity_factor + 1;
const float still_factor = (gate_still_sensitivity_factor / 2) + 1;
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) {
uint32_t sum = 0;
uint16_t peak = 0;
for (uint8_t sample_number = 0; sample_number < CALIBRATE_SAMPLES; ++sample_number) {
// Calculate average
sum += this->radar_data[gate][sample_number];
// Calculate max value
if (this->radar_data[gate][sample_number] > peak) {
peak = this->radar_data[gate][sample_number];
}
}
// Store average and peak values
this->gate_avg[gate] = sum / CALIBRATE_SAMPLES;
if (this->gate_peak[gate] < peak)
this->gate_peak[gate] = peak;
uint32_t calculated_value =
(static_cast<uint32_t>(this->gate_peak[gate]) + (move_factor * static_cast<uint32_t>(this->gate_peak[gate])));
this->new_config.move_thresh[gate] = static_cast<uint16_t>(calculated_value <= 65535 ? calculated_value : 65535);
calculated_value =
(static_cast<uint32_t>(this->gate_peak[gate]) + (still_factor * static_cast<uint32_t>(this->gate_peak[gate])));
this->new_config.still_thresh[gate] = static_cast<uint16_t>(calculated_value <= 65535 ? calculated_value : 65535);
}
}
void LD2420Component::report_gate_data() {
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; ++gate) {
// Output results
ESP_LOGI(TAG, "Gate: %2d Avg: %5d Peak: %5d", gate, this->gate_avg[gate], this->gate_peak[gate]);
}
ESP_LOGI(TAG, "Total samples: %d", this->total_sample_number_counter);
}
void LD2420Component::set_operating_mode(const std::string &state) {
// If unsupported firmware ignore mode select
if (get_firmware_int_(ld2420_firmware_ver_) >= CALIBRATE_VERSION_MIN) {
this->current_operating_mode = OP_MODE_TO_UINT.at(state);
// Entering Auto Calibrate we need to clear the privoiuos data collection
this->operating_selector_->publish_state(state);
if (current_operating_mode == OP_CALIBRATE_MODE) {
this->set_calibration_(true);
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
this->gate_avg[gate] = 0;
this->gate_peak[gate] = 0;
for (uint8_t i = 0; i < CALIBRATE_SAMPLES; i++) {
this->radar_data[gate][i] = 0;
}
this->total_sample_number_counter = 0;
}
} else {
// Set the current data back so we don't have new data that can be applied in error.
if (this->get_calibration_())
memcpy(&this->new_config, &this->current_config, sizeof(this->current_config));
this->set_calibration_(false);
}
} else {
this->current_operating_mode = OP_SIMPLE_MODE;
this->operating_selector_->publish_state(OP_SIMPLE_MODE_STRING);
}
}
void LD2420Component::readline_(int rx_data, uint8_t *buffer, int len) {
static int pos = 0;
if (rx_data >= 0) {
if (pos < len - 1) {
buffer[pos++] = rx_data;
buffer[pos] = 0;
} else {
pos = 0;
}
if (pos >= 4) {
if (memcmp(&buffer[pos - 4], &CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER)) == 0) {
this->set_cmd_active_(false); // Set command state to inactive after responce.
this->handle_ack_data_(buffer, pos);
pos = 0;
} else if ((buffer[pos - 2] == 0x0D && buffer[pos - 1] == 0x0A) && (get_mode_() == CMD_SYSTEM_MODE_SIMPLE)) {
this->handle_simple_mode_(buffer, pos);
pos = 0;
} else if ((memcmp(&buffer[pos - 4], &ENERGY_FRAME_FOOTER, sizeof(ENERGY_FRAME_FOOTER)) == 0) &&
(get_mode_() == CMD_SYSTEM_MODE_ENERGY)) {
this->handle_energy_mode_(buffer, pos);
pos = 0;
}
}
}
}
void LD2420Component::handle_energy_mode_(uint8_t *buffer, int len) {
uint8_t index = 6; // Start at presence byte position
uint16_t range;
const uint8_t elements = sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]);
this->set_presence_(buffer[index]);
index++;
memcpy(&range, &buffer[index], sizeof(range));
index += sizeof(range);
this->set_distance_(range);
for (uint8_t i = 0; i < elements; i++) { // NOLINT
memcpy(&this->gate_energy_[i], &buffer[index], sizeof(this->gate_energy_[0]));
index += sizeof(this->gate_energy_[0]);
}
if (this->current_operating_mode == OP_CALIBRATE_MODE) {
this->update_radar_data(gate_energy_, sample_number_counter);
this->sample_number_counter > CALIBRATE_SAMPLES ? this->sample_number_counter = 0 : this->sample_number_counter++;
}
// Resonable refresh rate for home assistant database size health
const int32_t current_millis = millis();
if (current_millis - this->last_periodic_millis < REFRESH_RATE_MS)
return;
this->last_periodic_millis = current_millis;
for (auto &listener : this->listeners_) {
listener->on_distance(get_distance_());
listener->on_presence(get_presence_());
listener->on_energy(this->gate_energy_, sizeof(this->gate_energy_) / sizeof(this->gate_energy_[0]));
}
if (this->current_operating_mode == OP_CALIBRATE_MODE) {
this->auto_calibrate_sensitivity();
if (current_millis - this->report_periodic_millis > REFRESH_RATE_MS * CALIBRATE_REPORT_INTERVAL) {
this->report_periodic_millis = current_millis;
this->report_gate_data();
}
}
}
void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
const uint8_t bufsize = 16;
uint8_t index{0};
uint8_t pos{0};
char *endptr{nullptr};
char outbuf[bufsize]{0};
while (true) {
if (inbuf[pos - 2] == 'O' && inbuf[pos - 1] == 'F' && inbuf[pos] == 'F') {
set_presence_(false);
} else if (inbuf[pos - 1] == 'O' && inbuf[pos] == 'N') {
set_presence_(true);
}
if (inbuf[pos] >= '0' && inbuf[pos] <= '9') {
if (index < bufsize - 1) {
outbuf[index++] = inbuf[pos];
pos++;
}
} else {
if (pos < len - 1) {
pos++;
} else {
break;
}
}
}
outbuf[index] = '\0';
if (index > 1)
set_distance_(strtol(outbuf, &endptr, 10));
if (get_mode_() == CMD_SYSTEM_MODE_SIMPLE) {
// Resonable refresh rate for home assistant database size health
const int32_t current_millis = millis();
if (current_millis - this->last_normal_periodic_millis < REFRESH_RATE_MS)
return;
this->last_normal_periodic_millis = current_millis;
for (auto &listener : this->listeners_)
listener->on_distance(get_distance_());
for (auto &listener : this->listeners_)
listener->on_presence(get_presence_());
}
}
void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
this->cmd_reply_.command = buffer[CMD_FRAME_COMMAND];
this->cmd_reply_.length = buffer[CMD_FRAME_DATA_LENGTH];
uint8_t reg_element = 0;
uint8_t data_element = 0;
uint16_t data_pos = 0;
if (this->cmd_reply_.length > CMD_MAX_BYTES) {
ESP_LOGW(TAG, "LD2420 reply - received command reply frame is corrupt, length exceeds %d bytes.", CMD_MAX_BYTES);
return;
} else if (this->cmd_reply_.length < 2) {
ESP_LOGW(TAG, "LD2420 reply - received command frame is corrupt, length is less than 2 bytes.");
return;
}
memcpy(&this->cmd_reply_.error, &buffer[CMD_ERROR_WORD], sizeof(this->cmd_reply_.error));
const char *result = this->cmd_reply_.error ? "failure" : "success";
if (this->cmd_reply_.error > 0) {
return;
};
this->cmd_reply_.ack = true;
switch ((uint16_t) this->cmd_reply_.command) {
case (CMD_ENABLE_CONF):
ESP_LOGD(TAG, "LD2420 reply - set config enable: CMD = %2X %s", CMD_ENABLE_CONF, result);
break;
case (CMD_DISABLE_CONF):
ESP_LOGD(TAG, "LD2420 reply - set config disable: CMD = %2X %s", CMD_DISABLE_CONF, result);
break;
case (CMD_READ_REGISTER):
ESP_LOGD(TAG, "LD2420 reply - read register: CMD = %2X %s", CMD_READ_REGISTER, result);
// TODO Read/Write register is not implemented yet, this will get flushed out to a proper header file
data_pos = 0x0A;
for (uint16_t index = 0; index < (CMD_REG_DATA_REPLY_SIZE * // NOLINT
((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_REG_DATA_REPLY_SIZE));
index += CMD_REG_DATA_REPLY_SIZE) {
memcpy(&this->cmd_reply_.data[reg_element], &buffer[data_pos + index], sizeof(CMD_REG_DATA_REPLY_SIZE));
byteswap(this->cmd_reply_.data[reg_element]);
reg_element++;
}
break;
case (CMD_WRITE_REGISTER):
ESP_LOGD(TAG, "LD2420 reply - write register: CMD = %2X %s", CMD_WRITE_REGISTER, result);
break;
case (CMD_WRITE_ABD_PARAM):
ESP_LOGD(TAG, "LD2420 reply - write gate parameter(s): %2X %s", CMD_WRITE_ABD_PARAM, result);
break;
case (CMD_READ_ABD_PARAM):
ESP_LOGD(TAG, "LD2420 reply - read gate parameter(s): %2X %s", CMD_READ_ABD_PARAM, result);
data_pos = CMD_ABD_DATA_REPLY_START;
for (uint16_t index = 0; index < (CMD_ABD_DATA_REPLY_SIZE * // NOLINT
((buffer[CMD_FRAME_DATA_LENGTH] - 4) / CMD_ABD_DATA_REPLY_SIZE));
index += CMD_ABD_DATA_REPLY_SIZE) {
memcpy(&this->cmd_reply_.data[data_element], &buffer[data_pos + index],
sizeof(this->cmd_reply_.data[data_element]));
byteswap(this->cmd_reply_.data[data_element]);
data_element++;
}
break;
case (CMD_WRITE_SYS_PARAM):
ESP_LOGD(TAG, "LD2420 reply - set system parameter(s): %2X %s", CMD_WRITE_SYS_PARAM, result);
break;
case (CMD_READ_VERSION):
memcpy(this->ld2420_firmware_ver_, &buffer[12], buffer[10]);
ESP_LOGD(TAG, "LD2420 reply - module firmware version: %7s %s", this->ld2420_firmware_ver_, result);
break;
default:
break;
}
}
int LD2420Component::send_cmd_from_array(CmdFrameT frame) {
uint8_t error = 0;
uint8_t ack_buffer[64];
uint8_t cmd_buffer[64];
uint16_t loop_count;
this->cmd_reply_.ack = false;
if (frame.command != CMD_RESTART)
this->set_cmd_active_(true); // Restart does not reply, thus no ack state required.
uint8_t retry = 3;
while (retry) {
// TODO setup a dynamic method e.g. millis time count etc. to tune for non ESP32 240Mhz devices
// this is ok for now since the module firmware is changing like the weather atm
frame.length = 0;
loop_count = 1250;
uint16_t frame_data_bytes = frame.data_length + 2; // Always add two bytes for the cmd size
memcpy(&cmd_buffer[frame.length], &frame.header, sizeof(frame.header));
frame.length += sizeof(frame.header);
memcpy(&cmd_buffer[frame.length], &frame_data_bytes, sizeof(frame.data_length));
frame.length += sizeof(frame.data_length);
memcpy(&cmd_buffer[frame.length], &frame.command, sizeof(frame.command));
frame.length += sizeof(frame.command);
for (uint16_t index = 0; index < frame.data_length; index++) {
memcpy(&cmd_buffer[frame.length], &frame.data[index], sizeof(frame.data[index]));
frame.length += sizeof(frame.data[index]);
}
memcpy(cmd_buffer + frame.length, &frame.footer, sizeof(frame.footer));
frame.length += sizeof(frame.footer);
for (uint16_t index = 0; index < frame.length; index++) {
this->write_byte(cmd_buffer[index]);
}
delay_microseconds_safe(500); // give the module a moment to process it
error = 0;
if (frame.command == CMD_RESTART) {
delay_microseconds_safe(25000); // Wait for the restart
return 0; // restart does not reply exit now
}
while (!this->cmd_reply_.ack) {
while (available()) {
this->readline_(read(), ack_buffer, sizeof(ack_buffer));
}
delay_microseconds_safe(250);
if (loop_count <= 0) {
error = LD2420_ERROR_TIMEOUT;
retry--;
break;
}
loop_count--;
}
if (this->cmd_reply_.ack)
retry = 0;
if (this->cmd_reply_.error > 0)
handle_cmd_error(error);
}
return error;
}
uint8_t LD2420Component::set_config_mode(bool enable) {
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
if (enable) {
memcpy(&cmd_frame.data[0], &CMD_PROTOCOL_VER, sizeof(CMD_PROTOCOL_VER));
cmd_frame.data_length += sizeof(CMD_PROTOCOL_VER);
}
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending set config %s command: %2X", enable ? "enable" : "disable", cmd_frame.command);
return this->send_cmd_from_array(cmd_frame);
}
// Sends a restart and set system running mode to normal
void LD2420Component::send_module_restart() { this->ld2420_restart(); }
void LD2420Component::ld2420_restart() {
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_RESTART;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending restart command: %2X", cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
void LD2420Component::get_reg_value_(uint16_t reg) {
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_READ_REGISTER;
cmd_frame.data[1] = reg;
cmd_frame.data_length += 2;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending read register %4X command: %2X", reg, cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
void LD2420Component::set_reg_value(uint16_t reg, uint16_t value) {
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_WRITE_REGISTER;
memcpy(&cmd_frame.data[cmd_frame.data_length], &reg, sizeof(CMD_REG_DATA_REPLY_SIZE));
cmd_frame.data_length += 2;
memcpy(&cmd_frame.data[cmd_frame.data_length], &value, sizeof(CMD_REG_DATA_REPLY_SIZE));
cmd_frame.data_length += 2;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending write register %4X command: %2X data = %4X", reg, cmd_frame.command, value);
this->send_cmd_from_array(cmd_frame);
}
void LD2420Component::handle_cmd_error(uint8_t error) { ESP_LOGI(TAG, "Command failed: %s", ERR_MESSAGE[error]); }
int LD2420Component::get_gate_threshold_(uint8_t gate) {
uint8_t error;
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_READ_ABD_PARAM;
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_MOVE_THRESH[gate], sizeof(CMD_GATE_MOVE_THRESH[gate]));
cmd_frame.data_length += 2;
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_GATE_STILL_THRESH[gate], sizeof(CMD_GATE_STILL_THRESH[gate]));
cmd_frame.data_length += 2;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending read gate %d high/low theshold command: %2X", gate, cmd_frame.command);
error = this->send_cmd_from_array(cmd_frame);
if (error == 0) {
this->current_config.move_thresh[gate] = cmd_reply_.data[0];
this->current_config.still_thresh[gate] = cmd_reply_.data[1];
}
return error;
}
int LD2420Component::get_min_max_distances_timeout_() {
uint8_t error;
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_READ_ABD_PARAM;
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG,
sizeof(CMD_MIN_GATE_REG)); // Register: global min detect gate number
cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG);
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG,
sizeof(CMD_MAX_GATE_REG)); // Register: global max detect gate number
cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG);
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG,
sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending read gate min max and timeout command: %2X", cmd_frame.command);
error = this->send_cmd_from_array(cmd_frame);
if (error == 0) {
this->current_config.min_gate = (uint16_t) cmd_reply_.data[0];
this->current_config.max_gate = (uint16_t) cmd_reply_.data[1];
this->current_config.timeout = (uint16_t) cmd_reply_.data[2];
}
return error;
}
void LD2420Component::set_system_mode(uint16_t mode) {
CmdFrameT cmd_frame;
uint16_t unknown_parm = 0x0000;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_WRITE_SYS_PARAM;
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_SYSTEM_MODE, sizeof(CMD_SYSTEM_MODE));
cmd_frame.data_length += sizeof(CMD_SYSTEM_MODE);
memcpy(&cmd_frame.data[cmd_frame.data_length], &mode, sizeof(mode));
cmd_frame.data_length += sizeof(mode);
memcpy(&cmd_frame.data[cmd_frame.data_length], &unknown_parm, sizeof(unknown_parm));
cmd_frame.data_length += sizeof(unknown_parm);
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending write system mode command: %2X", cmd_frame.command);
if (this->send_cmd_from_array(cmd_frame) == 0)
set_mode_(mode);
}
void LD2420Component::get_firmware_version_() {
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_READ_VERSION;
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending read firmware version command: %2X", cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
void LD2420Component::set_min_max_distances_timeout(uint32_t max_gate_distance, uint32_t min_gate_distance, // NOLINT
uint32_t timeout) {
// Header H, Length L, Register R, Value V, Footer F
// |Min Gate |Max Gate |Timeout |
// HH HH HH HH LL LL CC CC RR RR VV VV VV VV RR RR VV VV VV VV RR RR VV VV VV VV FF FF FF FF
// FD FC FB FA 14 00 07 00 00 00 01 00 00 00 01 00 09 00 00 00 04 00 0A 00 00 00 04 03 02 01 e.g.
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_WRITE_ABD_PARAM;
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MIN_GATE_REG,
sizeof(CMD_MIN_GATE_REG)); // Register: global min detect gate number
cmd_frame.data_length += sizeof(CMD_MIN_GATE_REG);
memcpy(&cmd_frame.data[cmd_frame.data_length], &min_gate_distance, sizeof(min_gate_distance));
cmd_frame.data_length += sizeof(min_gate_distance);
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_MAX_GATE_REG,
sizeof(CMD_MAX_GATE_REG)); // Register: global max detect gate number
cmd_frame.data_length += sizeof(CMD_MAX_GATE_REG);
memcpy(&cmd_frame.data[cmd_frame.data_length], &max_gate_distance, sizeof(max_gate_distance));
cmd_frame.data_length += sizeof(max_gate_distance);
memcpy(&cmd_frame.data[cmd_frame.data_length], &CMD_TIMEOUT_REG,
sizeof(CMD_TIMEOUT_REG)); // Register: global delay time
cmd_frame.data_length += sizeof(CMD_TIMEOUT_REG);
memcpy(&cmd_frame.data[cmd_frame.data_length], &timeout, sizeof(timeout));
;
cmd_frame.data_length += sizeof(timeout);
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending write gate min max and timeout command: %2X", cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
void LD2420Component::set_gate_threshold(uint8_t gate) {
// Header H, Length L, Command C, Register R, Value V, Footer F
// HH HH HH HH LL LL CC CC RR RR VV VV VV VV RR RR VV VV VV VV FF FF FF FF
// FD FC FB FA 14 00 07 00 10 00 00 FF 00 00 00 01 00 0F 00 00 04 03 02 01
uint16_t move_threshold_gate = CMD_GATE_MOVE_THRESH[gate];
uint16_t still_threshold_gate = CMD_GATE_STILL_THRESH[gate];
CmdFrameT cmd_frame;
cmd_frame.data_length = 0;
cmd_frame.header = CMD_FRAME_HEADER;
cmd_frame.command = CMD_WRITE_ABD_PARAM;
memcpy(&cmd_frame.data[cmd_frame.data_length], &move_threshold_gate, sizeof(move_threshold_gate));
cmd_frame.data_length += sizeof(move_threshold_gate);
memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.move_thresh[gate],
sizeof(this->new_config.move_thresh[gate]));
cmd_frame.data_length += sizeof(this->new_config.move_thresh[gate]);
memcpy(&cmd_frame.data[cmd_frame.data_length], &still_threshold_gate, sizeof(still_threshold_gate));
cmd_frame.data_length += sizeof(still_threshold_gate);
memcpy(&cmd_frame.data[cmd_frame.data_length], &this->new_config.still_thresh[gate],
sizeof(this->new_config.still_thresh[gate]));
cmd_frame.data_length += sizeof(this->new_config.still_thresh[gate]);
cmd_frame.footer = CMD_FRAME_FOOTER;
ESP_LOGD(TAG, "Sending set gate %4X sensitivity command: %2X", gate, cmd_frame.command);
this->send_cmd_from_array(cmd_frame);
}
#ifdef USE_NUMBER
void LD2420Component::init_gate_config_numbers() {
if (this->gate_timeout_number_ != nullptr)
this->gate_timeout_number_->publish_state(static_cast<uint16_t>(this->current_config.timeout));
if (this->gate_select_number_ != nullptr)
this->gate_select_number_->publish_state(0);
if (this->min_gate_distance_number_ != nullptr)
this->min_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.min_gate));
if (this->max_gate_distance_number_ != nullptr)
this->max_gate_distance_number_->publish_state(static_cast<uint16_t>(this->current_config.max_gate));
if (this->gate_move_sensitivity_factor_number_ != nullptr)
this->gate_move_sensitivity_factor_number_->publish_state(this->gate_move_sensitivity_factor);
if (this->gate_still_sensitivity_factor_number_ != nullptr)
this->gate_still_sensitivity_factor_number_->publish_state(this->gate_still_sensitivity_factor);
for (uint8_t gate = 0; gate < LD2420_TOTAL_GATES; gate++) {
if (this->gate_still_threshold_numbers_[gate] != nullptr) {
this->gate_still_threshold_numbers_[gate]->publish_state(
static_cast<uint16_t>(this->current_config.still_thresh[gate]));
}
if (this->gate_move_threshold_numbers_[gate] != nullptr) {
this->gate_move_threshold_numbers_[gate]->publish_state(
static_cast<uint16_t>(this->current_config.move_thresh[gate]));
}
}
}
void LD2420Component::refresh_gate_config_numbers() {
this->gate_timeout_number_->publish_state(this->new_config.timeout);
this->min_gate_distance_number_->publish_state(this->new_config.min_gate);
this->max_gate_distance_number_->publish_state(this->new_config.max_gate);
}
#endif
} // namespace ld2420
} // namespace esphome

View file

@ -0,0 +1,272 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/automation.h"
#include "esphome/core/helpers.h"
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
#ifdef USE_SELECT
#include "esphome/components/select/select.h"
#endif
#ifdef USE_NUMBER
#include "esphome/components/number/number.h"
#endif
#ifdef USE_BUTTON
#include "esphome/components/button/button.h"
#endif
#include <map>
#include <functional>
namespace esphome {
namespace ld2420 {
// Local const's
static const uint16_t REFRESH_RATE_MS = 1000;
// Command sets
static const uint8_t CMD_ABD_DATA_REPLY_SIZE = 0x04;
static const uint8_t CMD_ABD_DATA_REPLY_START = 0x0A;
static const uint16_t CMD_DISABLE_CONF = 0x00FE;
static const uint16_t CMD_ENABLE_CONF = 0x00FF;
static const uint8_t CMD_MAX_BYTES = 0x64;
static const uint16_t CMD_PARM_HIGH_TRESH = 0x0012;
static const uint16_t CMD_PARM_LOW_TRESH = 0x0021;
static const uint16_t CMD_PROTOCOL_VER = 0x0002;
static const uint16_t CMD_READ_ABD_PARAM = 0x0008;
static const uint16_t CMD_READ_REG_ADDR = 0x0020;
static const uint16_t CMD_READ_REGISTER = 0x0002;
static const uint16_t CMD_READ_SERIAL_NUM = 0x0011;
static const uint16_t CMD_READ_SYS_PARAM = 0x0013;
static const uint16_t CMD_READ_VERSION = 0x0000;
static const uint8_t CMD_REG_DATA_REPLY_SIZE = 0x02;
static const uint16_t CMD_RESTART = 0x0068;
static const uint16_t CMD_SYSTEM_MODE = 0x0000;
static const uint16_t CMD_SYSTEM_MODE_GR = 0x0003;
static const uint16_t CMD_SYSTEM_MODE_MTT = 0x0001;
static const uint16_t CMD_SYSTEM_MODE_SIMPLE = 0x0064;
static const uint16_t CMD_SYSTEM_MODE_DEBUG = 0x0000;
static const uint16_t CMD_SYSTEM_MODE_ENERGY = 0x0004;
static const uint16_t CMD_SYSTEM_MODE_VS = 0x0002;
static const uint16_t CMD_WRITE_ABD_PARAM = 0x0007;
static const uint16_t CMD_WRITE_REGISTER = 0x0001;
static const uint16_t CMD_WRITE_SYS_PARAM = 0x0012;
static const uint8_t LD2420_ERROR_NONE = 0x00;
static const uint8_t LD2420_ERROR_TIMEOUT = 0x02;
static const uint8_t LD2420_ERROR_UNKNOWN = 0x01;
static const uint8_t LD2420_TOTAL_GATES = 16;
static const uint8_t CALIBRATE_SAMPLES = 64;
// Register address values
static const uint16_t CMD_MIN_GATE_REG = 0x0000;
static const uint16_t CMD_MAX_GATE_REG = 0x0001;
static const uint16_t CMD_TIMEOUT_REG = 0x0004;
static const uint16_t CMD_GATE_MOVE_THRESH[LD2420_TOTAL_GATES] = {0x0010, 0x0011, 0x0012, 0x0013, 0x0014, 0x0015,
0x0016, 0x0017, 0x0018, 0x0019, 0x001A, 0x001B,
0x001C, 0x001D, 0x001E, 0x001F};
static const uint16_t CMD_GATE_STILL_THRESH[LD2420_TOTAL_GATES] = {0x0020, 0x0021, 0x0022, 0x0023, 0x0024, 0x0025,
0x0026, 0x0027, 0x0028, 0x0029, 0x002A, 0x002B,
0x002C, 0x002D, 0x002E, 0x002F};
static const uint32_t FACTORY_MOVE_THRESH[LD2420_TOTAL_GATES] = {60000, 30000, 400, 250, 250, 250, 250, 250,
250, 250, 250, 250, 250, 250, 250, 250};
static const uint32_t FACTORY_STILL_THRESH[LD2420_TOTAL_GATES] = {40000, 20000, 200, 200, 200, 200, 200, 150,
150, 100, 100, 100, 100, 100, 100, 100};
static const uint16_t FACTORY_TIMEOUT = 120;
static const uint16_t FACTORY_MIN_GATE = 1;
static const uint16_t FACTORY_MAX_GATE = 12;
// COMMAND_BYTE Header & Footer
static const uint8_t CMD_FRAME_COMMAND = 6;
static const uint8_t CMD_FRAME_DATA_LENGTH = 4;
static const uint32_t CMD_FRAME_FOOTER = 0x01020304;
static const uint32_t CMD_FRAME_HEADER = 0xFAFBFCFD;
static const uint32_t DEBUG_FRAME_FOOTER = 0xFAFBFCFD;
static const uint32_t DEBUG_FRAME_HEADER = 0x1410BFAA;
static const uint32_t ENERGY_FRAME_FOOTER = 0xF5F6F7F8;
static const uint32_t ENERGY_FRAME_HEADER = 0xF1F2F3F4;
static const uint8_t CMD_FRAME_STATUS = 7;
static const uint8_t CMD_ERROR_WORD = 8;
static const uint8_t ENERGY_SENSOR_START = 9;
static const uint8_t CALIBRATE_REPORT_INTERVAL = 4;
static const int CALIBRATE_VERSION_MIN = 154;
static const std::string OP_NORMAL_MODE_STRING = "Normal";
static const std::string OP_SIMPLE_MODE_STRING = "Simple";
enum OpModeStruct : uint8_t { OP_NORMAL_MODE = 1, OP_CALIBRATE_MODE = 2, OP_SIMPLE_MODE = 3 };
static const std::map<std::string, uint8_t> OP_MODE_TO_UINT{
{"Normal", OP_NORMAL_MODE}, {"Calibrate", OP_CALIBRATE_MODE}, {"Simple", OP_SIMPLE_MODE}};
static constexpr const char *ERR_MESSAGE[] = {"None", "Unknown", "Timeout"};
class LD2420Listener {
public:
virtual void on_presence(bool presence){};
virtual void on_distance(uint16_t distance){};
virtual void on_energy(uint16_t *sensor_energy, size_t size){};
virtual void on_fw_version(std::string &fw){};
};
class LD2420Component : public Component, public uart::UARTDevice {
public:
void setup() override;
void dump_config() override;
void loop() override;
#ifdef USE_SELECT
void set_operating_mode_select(select::Select *selector) { this->operating_selector_ = selector; };
#endif
#ifdef USE_NUMBER
void set_gate_timeout_number(number::Number *number) { this->gate_timeout_number_ = number; };
void set_gate_select_number(number::Number *number) { this->gate_select_number_ = number; };
void set_min_gate_distance_number(number::Number *number) { this->min_gate_distance_number_ = number; };
void set_max_gate_distance_number(number::Number *number) { this->max_gate_distance_number_ = number; };
void set_gate_move_sensitivity_factor_number(number::Number *number) {
this->gate_move_sensitivity_factor_number_ = number;
};
void set_gate_still_sensitivity_factor_number(number::Number *number) {
this->gate_still_sensitivity_factor_number_ = number;
};
void set_gate_still_threshold_numbers(int gate, number::Number *n) { this->gate_still_threshold_numbers_[gate] = n; };
void set_gate_move_threshold_numbers(int gate, number::Number *n) { this->gate_move_threshold_numbers_[gate] = n; };
bool is_gate_select() { return gate_select_number_ != nullptr; };
uint8_t get_gate_select_value() { return static_cast<uint8_t>(this->gate_select_number_->state); };
float get_min_gate_distance_value() { return min_gate_distance_number_->state; };
float get_max_gate_distance_value() { return max_gate_distance_number_->state; };
void publish_gate_move_threshold(uint8_t gate) {
// With gate_select we only use 1 number pointer, thus we hard code [0]
this->gate_move_threshold_numbers_[0]->publish_state(this->new_config.move_thresh[gate]);
};
void publish_gate_still_threshold(uint8_t gate) {
this->gate_still_threshold_numbers_[0]->publish_state(this->new_config.still_thresh[gate]);
};
void init_gate_config_numbers();
void refresh_gate_config_numbers();
#endif
#ifdef USE_BUTTON
void set_apply_config_button(button::Button *button) { this->apply_config_button_ = button; };
void set_revert_config_button(button::Button *button) { this->revert_config_button_ = button; };
void set_restart_module_button(button::Button *button) { this->restart_module_button_ = button; };
void set_factory_reset_button(button::Button *button) { this->factory_reset_button_ = button; };
#endif
void register_listener(LD2420Listener *listener) { this->listeners_.push_back(listener); }
struct CmdFrameT {
uint32_t header{0};
uint16_t length{0};
uint16_t command{0};
uint8_t data[18];
uint16_t data_length{0};
uint32_t footer{0};
};
struct RegConfigT {
uint16_t min_gate{0};
uint16_t max_gate{0};
uint16_t timeout{0};
uint32_t move_thresh[LD2420_TOTAL_GATES];
uint32_t still_thresh[LD2420_TOTAL_GATES];
};
void send_module_restart();
void restart_module_action();
void apply_config_action();
void factory_reset_action();
void revert_config_action();
float get_setup_priority() const override;
int send_cmd_from_array(CmdFrameT cmd_frame);
void report_gate_data();
void handle_cmd_error(uint8_t error);
void set_operating_mode(const std::string &state);
void auto_calibrate_sensitivity();
void update_radar_data(uint16_t const *gate_energy, uint8_t sample_number);
uint8_t calc_checksum(void *data, size_t size);
RegConfigT current_config;
RegConfigT new_config;
int32_t last_periodic_millis = millis();
int32_t report_periodic_millis = millis();
int32_t monitor_periodic_millis = millis();
int32_t last_normal_periodic_millis = millis();
bool output_energy_state{false};
uint8_t current_operating_mode{OP_NORMAL_MODE};
uint16_t radar_data[LD2420_TOTAL_GATES][CALIBRATE_SAMPLES];
uint16_t gate_avg[LD2420_TOTAL_GATES];
uint16_t gate_peak[LD2420_TOTAL_GATES];
uint8_t sample_number_counter{0};
uint16_t total_sample_number_counter{0};
float gate_move_sensitivity_factor{0.5};
float gate_still_sensitivity_factor{0.5};
#ifdef USE_SELECT
select::Select *operating_selector_{nullptr};
#endif
#ifdef USE_BUTTON
button::Button *apply_config_button_{nullptr};
button::Button *revert_config_button_{nullptr};
button::Button *restart_module_button_{nullptr};
button::Button *factory_reset_button_{nullptr};
#endif
void set_min_max_distances_timeout(uint32_t max_gate_distance, uint32_t min_gate_distance, uint32_t timeout);
void set_gate_threshold(uint8_t gate);
void set_reg_value(uint16_t reg, uint16_t value);
uint8_t set_config_mode(bool enable);
void set_system_mode(uint16_t mode);
void ld2420_restart();
protected:
struct CmdReplyT {
uint8_t command;
uint8_t status;
uint32_t data[4];
uint8_t length;
uint16_t error;
volatile bool ack;
};
int get_firmware_int_(const char *version_string);
void get_firmware_version_();
int get_gate_threshold_(uint8_t gate);
void get_reg_value_(uint16_t reg);
int get_min_max_distances_timeout_();
uint16_t get_mode_() { return this->system_mode_; };
void set_mode_(uint16_t mode) { this->system_mode_ = mode; };
bool get_presence_() { return this->presence_; };
void set_presence_(bool presence) { this->presence_ = presence; };
uint16_t get_distance_() { return this->distance_; };
void set_distance_(uint16_t distance) { this->distance_ = distance; };
bool get_cmd_active_() { return this->cmd_active_; };
void set_cmd_active_(bool active) { this->cmd_active_ = active; };
void handle_simple_mode_(const uint8_t *inbuf, int len);
void handle_energy_mode_(uint8_t *buffer, int len);
void handle_ack_data_(uint8_t *buffer, int len);
void readline_(int rx_data, uint8_t *buffer, int len);
void set_calibration_(bool state) { this->calibration_ = state; };
bool get_calibration_() { return this->calibration_; };
#ifdef USE_NUMBER
number::Number *gate_timeout_number_{nullptr};
number::Number *gate_select_number_{nullptr};
number::Number *min_gate_distance_number_{nullptr};
number::Number *max_gate_distance_number_{nullptr};
number::Number *gate_move_sensitivity_factor_number_{nullptr};
number::Number *gate_still_sensitivity_factor_number_{nullptr};
std::vector<number::Number *> gate_still_threshold_numbers_ = std::vector<number::Number *>(16);
std::vector<number::Number *> gate_move_threshold_numbers_ = std::vector<number::Number *>(16);
#endif
uint16_t gate_energy_[LD2420_TOTAL_GATES];
CmdReplyT cmd_reply_;
uint32_t timeout_;
uint32_t max_distance_gate_;
uint32_t min_distance_gate_;
uint16_t system_mode_{CMD_SYSTEM_MODE_ENERGY};
bool cmd_active_{false};
char ld2420_firmware_ver_[8];
bool presence_{false};
bool calibration_{false};
uint16_t distance_{0};
uint8_t config_checksum_{0};
std::vector<LD2420Listener *> listeners_{};
};
} // namespace ld2420
} // namespace esphome

View file

@ -0,0 +1,183 @@
import esphome.codegen as cg
from esphome.components import number
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
DEVICE_CLASS_DISTANCE,
UNIT_SECOND,
ENTITY_CATEGORY_CONFIG,
ICON_MOTION_SENSOR,
ICON_TIMELAPSE,
ICON_SCALE,
)
from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns
LD2420TimeoutNumber = ld2420_ns.class_("LD2420TimeoutNumber", number.Number)
LD2420MoveSensFactorNumber = ld2420_ns.class_(
"LD2420MoveSensFactorNumber", number.Number
)
LD2420StillSensFactorNumber = ld2420_ns.class_(
"LD2420StillSensFactorNumber", number.Number
)
LD2420MinDistanceNumber = ld2420_ns.class_("LD2420MinDistanceNumber", number.Number)
LD2420MaxDistanceNumber = ld2420_ns.class_("LD2420MaxDistanceNumber", number.Number)
LD2420GateSelectNumber = ld2420_ns.class_("LD2420GateSelectNumber", number.Number)
LD2420MoveThresholdNumbers = ld2420_ns.class_(
"LD2420MoveThresholdNumbers", number.Number
)
LD2420StillThresholdNumbers = ld2420_ns.class_(
"LD2420StillThresholdNumbers", number.Number
)
CONF_MIN_GATE_DISTANCE = "min_gate_distance"
CONF_MAX_GATE_DISTANCE = "max_gate_distance"
CONF_STILL_THRESHOLD = "still_threshold"
CONF_MOVE_THRESHOLD = "move_threshold"
CONF_GATE_MOVE_SENSITIVITY = "gate_move_sensitivity"
CONF_GATE_STILL_SENSITIVITY = "gate_still_sensitivity"
CONF_GATE_SELECT = "gate_select"
CONF_PRESENCE_TIMEOUT = "presence_timeout"
GATE_GROUP = "gate_group"
TIMEOUT_GROUP = "timeout_group"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
cv.Inclusive(CONF_PRESENCE_TIMEOUT, TIMEOUT_GROUP): number.number_schema(
LD2420TimeoutNumber,
unit_of_measurement=UNIT_SECOND,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_TIMELAPSE,
),
cv.Inclusive(CONF_MIN_GATE_DISTANCE, TIMEOUT_GROUP): number.number_schema(
LD2420MinDistanceNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Inclusive(CONF_MAX_GATE_DISTANCE, TIMEOUT_GROUP): number.number_schema(
LD2420MaxDistanceNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Inclusive(CONF_GATE_SELECT, GATE_GROUP): number.number_schema(
LD2420GateSelectNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Inclusive(CONF_STILL_THRESHOLD, GATE_GROUP): number.number_schema(
LD2420StillThresholdNumbers,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Inclusive(CONF_MOVE_THRESHOLD, GATE_GROUP): number.number_schema(
LD2420MoveThresholdNumbers,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Optional(CONF_GATE_MOVE_SENSITIVITY): number.number_schema(
LD2420MoveSensFactorNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
),
cv.Optional(CONF_GATE_STILL_SENSITIVITY): number.number_schema(
LD2420StillSensFactorNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
),
}
)
CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
{
cv.Optional(f"gate_{x}"): (
{
cv.Required(CONF_MOVE_THRESHOLD): number.number_schema(
LD2420MoveThresholdNumbers,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Required(CONF_STILL_THRESHOLD): number.number_schema(
LD2420StillThresholdNumbers,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
}
)
for x in range(16)
}
)
async def to_code(config):
LD2420_component = await cg.get_variable(config[CONF_LD2420_ID])
if gate_timeout_config := config.get(CONF_PRESENCE_TIMEOUT):
n = await number.new_number(
gate_timeout_config, min_value=0, max_value=255, step=5
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_gate_timeout_number(n))
if min_distance_gate_config := config.get(CONF_MIN_GATE_DISTANCE):
n = await number.new_number(
min_distance_gate_config, min_value=0, max_value=15, step=1
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_min_gate_distance_number(n))
if max_distance_gate_config := config.get(CONF_MAX_GATE_DISTANCE):
n = await number.new_number(
max_distance_gate_config, min_value=1, max_value=15, step=1
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_max_gate_distance_number(n))
if gate_move_sensitivity_config := config.get(CONF_GATE_MOVE_SENSITIVITY):
n = await number.new_number(
gate_move_sensitivity_config, min_value=0.05, max_value=1, step=0.025
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_gate_move_sensitivity_factor_number(n))
if gate_still_sensitivity_config := config.get(CONF_GATE_STILL_SENSITIVITY):
n = await number.new_number(
gate_still_sensitivity_config, min_value=0.05, max_value=1, step=0.025
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_gate_still_sensitivity_factor_number(n))
if config.get(CONF_GATE_SELECT):
if gate_number := config.get(CONF_GATE_SELECT):
n = await number.new_number(gate_number, min_value=0, max_value=15, step=1)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_gate_select_number(n))
if gate_still_threshold := config.get(CONF_STILL_THRESHOLD):
n = cg.new_Pvariable(gate_still_threshold[CONF_ID])
await number.register_number(
n, gate_still_threshold, min_value=0, max_value=65535, step=25
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_gate_still_threshold_numbers(0, n))
if gate_move_threshold := config.get(CONF_MOVE_THRESHOLD):
n = cg.new_Pvariable(gate_move_threshold[CONF_ID])
await number.register_number(
n, gate_move_threshold, min_value=0, max_value=65535, step=25
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_gate_move_threshold_numbers(0, n))
else:
for x in range(16):
if gate_conf := config.get(f"gate_{x}"):
move_config = gate_conf[CONF_MOVE_THRESHOLD]
n = cg.new_Pvariable(move_config[CONF_ID], x)
await number.register_number(
n, move_config, min_value=0, max_value=65535, step=25
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_gate_move_threshold_numbers(x, n))
still_config = gate_conf[CONF_STILL_THRESHOLD]
n = cg.new_Pvariable(still_config[CONF_ID], x)
await number.register_number(
n, still_config, min_value=0, max_value=65535, step=25
)
await cg.register_parented(n, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_gate_still_threshold_numbers(x, n))

View file

@ -0,0 +1,73 @@
#include "gate_config_number.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
static const char *const TAG = "LD2420.number";
namespace esphome {
namespace ld2420 {
void LD2420TimeoutNumber::control(float timeout) {
this->publish_state(timeout);
this->parent_->new_config.timeout = timeout;
}
void LD2420MinDistanceNumber::control(float min_gate) {
if ((uint16_t) min_gate > this->parent_->new_config.max_gate) {
min_gate = this->parent_->get_min_gate_distance_value();
} else {
this->parent_->new_config.min_gate = (uint16_t) min_gate;
}
this->publish_state(min_gate);
}
void LD2420MaxDistanceNumber::control(float max_gate) {
if ((uint16_t) max_gate < this->parent_->new_config.min_gate) {
max_gate = this->parent_->get_max_gate_distance_value();
} else {
this->parent_->new_config.max_gate = (uint16_t) max_gate;
}
this->publish_state(max_gate);
}
void LD2420GateSelectNumber::control(float gate_select) {
const uint8_t gate = (uint8_t) gate_select;
this->publish_state(gate_select);
this->parent_->publish_gate_move_threshold(gate);
this->parent_->publish_gate_still_threshold(gate);
}
void LD2420MoveSensFactorNumber::control(float move_factor) {
this->publish_state(move_factor);
this->parent_->gate_move_sensitivity_factor = move_factor;
}
void LD2420StillSensFactorNumber::control(float still_factor) {
this->publish_state(still_factor);
this->parent_->gate_still_sensitivity_factor = still_factor;
}
LD2420MoveThresholdNumbers::LD2420MoveThresholdNumbers(uint8_t gate) : gate_(gate) {}
void LD2420MoveThresholdNumbers::control(float move_threshold) {
this->publish_state(move_threshold);
if (!this->parent_->is_gate_select()) {
this->parent_->new_config.move_thresh[this->gate_] = move_threshold;
} else {
this->parent_->new_config.move_thresh[this->parent_->get_gate_select_value()] = move_threshold;
}
}
LD2420StillThresholdNumbers::LD2420StillThresholdNumbers(uint8_t gate) : gate_(gate) {}
void LD2420StillThresholdNumbers::control(float still_threshold) {
this->publish_state(still_threshold);
if (!this->parent_->is_gate_select()) {
this->parent_->new_config.still_thresh[this->gate_] = still_threshold;
} else {
this->parent_->new_config.still_thresh[this->parent_->get_gate_select_value()] = still_threshold;
}
}
} // namespace ld2420
} // namespace esphome

View file

@ -0,0 +1,78 @@
#pragma once
#include "esphome/components/number/number.h"
#include "../ld2420.h"
namespace esphome {
namespace ld2420 {
class LD2420TimeoutNumber : public number::Number, public Parented<LD2420Component> {
public:
LD2420TimeoutNumber() = default;
protected:
void control(float timeout) override;
};
class LD2420MinDistanceNumber : public number::Number, public Parented<LD2420Component> {
public:
LD2420MinDistanceNumber() = default;
protected:
void control(float min_gate) override;
};
class LD2420MaxDistanceNumber : public number::Number, public Parented<LD2420Component> {
public:
LD2420MaxDistanceNumber() = default;
protected:
void control(float max_gate) override;
};
class LD2420GateSelectNumber : public number::Number, public Parented<LD2420Component> {
public:
LD2420GateSelectNumber() = default;
protected:
void control(float gate_select) override;
};
class LD2420MoveSensFactorNumber : public number::Number, public Parented<LD2420Component> {
public:
LD2420MoveSensFactorNumber() = default;
protected:
void control(float move_factor) override;
};
class LD2420StillSensFactorNumber : public number::Number, public Parented<LD2420Component> {
public:
LD2420StillSensFactorNumber() = default;
protected:
void control(float still_factor) override;
};
class LD2420StillThresholdNumbers : public number::Number, public Parented<LD2420Component> {
public:
LD2420StillThresholdNumbers() = default;
LD2420StillThresholdNumbers(uint8_t gate);
protected:
uint8_t gate_;
void control(float still_threshold) override;
};
class LD2420MoveThresholdNumbers : public number::Number, public Parented<LD2420Component> {
public:
LD2420MoveThresholdNumbers() = default;
LD2420MoveThresholdNumbers(uint8_t gate);
protected:
uint8_t gate_;
void control(float move_threshold) override;
};
} // namespace ld2420
} // namespace esphome

View file

@ -0,0 +1,33 @@
import esphome.codegen as cg
from esphome.components import select
import esphome.config_validation as cv
from esphome.const import ENTITY_CATEGORY_CONFIG
from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns
CONF_OPERATING_MODE = "operating_mode"
CONF_SELECTS = [
"Normal",
"Calibrate",
"Simple",
]
LD2420Select = ld2420_ns.class_("LD2420Select", cg.Component)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
cv.Required(CONF_OPERATING_MODE): select.select_schema(
LD2420Select,
entity_category=ENTITY_CATEGORY_CONFIG,
),
}
async def to_code(config):
LD2420_component = await cg.get_variable(config[CONF_LD2420_ID])
if operating_mode_config := config.get(CONF_OPERATING_MODE):
sel = await select.new_select(
operating_mode_config,
options=[CONF_SELECTS],
)
await cg.register_parented(sel, config[CONF_LD2420_ID])
cg.add(LD2420_component.set_operating_mode_select(sel))

View file

@ -0,0 +1,16 @@
#include "operating_mode_select.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ld2420 {
static const char *const TAG = "LD2420.select";
void LD2420Select::control(const std::string &value) {
this->publish_state(value);
this->parent_->set_operating_mode(value);
}
} // namespace ld2420
} // namespace esphome

View file

@ -0,0 +1,18 @@
#pragma once
#include "../ld2420.h"
#include "esphome/components/select/select.h"
namespace esphome {
namespace ld2420 {
class LD2420Select : public Component, public select::Select, public Parented<LD2420Component> {
public:
LD2420Select() = default;
protected:
void control(const std::string &value) override;
};
} // namespace ld2420
} // namespace esphome

View file

@ -0,0 +1,35 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import sensor
from esphome.const import CONF_ID, DEVICE_CLASS_DISTANCE, UNIT_CENTIMETER
from .. import ld2420_ns, LD2420Component, CONF_LD2420_ID
LD2420Sensor = ld2420_ns.class_("LD2420Sensor", sensor.Sensor, cg.Component)
CONF_MOVING_DISTANCE = "moving_distance"
CONF_GATE_ENERGY = "gate_energy"
CONFIG_SCHEMA = cv.All(
cv.COMPONENT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(LD2420Sensor),
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE, unit_of_measurement=UNIT_CENTIMETER
),
}
),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
if CONF_MOVING_DISTANCE in config:
sens = await sensor.new_sensor(config[CONF_MOVING_DISTANCE])
cg.add(var.set_distance_sensor(sens))
if CONF_GATE_ENERGY in config:
sens = await sensor.new_sensor(config[CONF_GATE_ENERGY])
cg.add(var.set_energy_sensor(sens))
ld2420 = await cg.get_variable(config[CONF_LD2420_ID])
cg.add(ld2420.register_listener(var))

View file

@ -0,0 +1,16 @@
#include "ld2420_sensor.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ld2420 {
static const char *const TAG = "LD2420.sensor";
void LD2420Sensor::dump_config() {
ESP_LOGCONFIG(TAG, "LD2420 Sensor:");
LOG_SENSOR(" ", "Distance", this->distance_sensor_);
}
} // namespace ld2420
} // namespace esphome

View file

@ -0,0 +1,34 @@
#pragma once
#include "../ld2420.h"
#include "esphome/components/sensor/sensor.h"
namespace esphome {
namespace ld2420 {
class LD2420Sensor : public LD2420Listener, public Component, sensor::Sensor {
public:
void dump_config() override;
void set_distance_sensor(sensor::Sensor *sensor) { this->distance_sensor_ = sensor; }
void on_distance(uint16_t distance) override {
if (this->distance_sensor_ != nullptr) {
if (this->distance_sensor_->get_state() != distance) {
this->distance_sensor_->publish_state(distance);
}
}
}
void on_energy(uint16_t *gate_energy, size_t size) override {
for (size_t active = 0; active < size; active++) {
if (this->energy_sensors_[active] != nullptr) {
this->energy_sensors_[active]->publish_state(gate_energy[active]);
}
}
}
protected:
sensor::Sensor *distance_sensor_{nullptr};
std::vector<sensor::Sensor *> energy_sensors_ = std::vector<sensor::Sensor *>(LD2420_TOTAL_GATES);
};
} // namespace ld2420
} // namespace esphome

View file

@ -0,0 +1,38 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_CHIP,
)
from .. import ld2420_ns, LD2420Component, CONF_LD2420_ID
LD2420TextSensor = ld2420_ns.class_(
"LD2420TextSensor", text_sensor.TextSensor, cg.Component
)
CONF_FW_VERSION = "fw_version"
CONFIG_SCHEMA = cv.All(
cv.COMPONENT_SCHEMA.extend(
{
cv.GenerateID(): cv.declare_id(LD2420TextSensor),
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
cv.Optional(CONF_FW_VERSION): text_sensor.text_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP
),
}
),
)
async def to_code(config):
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
if CONF_FW_VERSION in config:
sens = await text_sensor.new_text_sensor(config[CONF_FW_VERSION])
cg.add(var.set_fw_version_text_sensor(sens))
ld2420 = await cg.get_variable(config[CONF_LD2420_ID])
cg.add(ld2420.register_listener(var))

View file

@ -0,0 +1,16 @@
#include "text_sensor.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome {
namespace ld2420 {
static const char *const TAG = "LD2420.text_sensor";
void LD2420TextSensor::dump_config() {
ESP_LOGCONFIG(TAG, "LD2420 TextSensor:");
LOG_TEXT_SENSOR(" ", "Firmware", this->fw_version_text_sensor_);
}
} // namespace ld2420
} // namespace esphome

View file

@ -0,0 +1,24 @@
#pragma once
#include "../ld2420.h"
#include "esphome/components/text_sensor/text_sensor.h"
namespace esphome {
namespace ld2420 {
class LD2420TextSensor : public LD2420Listener, public Component, text_sensor::TextSensor {
public:
void dump_config() override;
void set_fw_version_text_sensor(text_sensor::TextSensor *tsensor) { this->fw_version_text_sensor_ = tsensor; };
void on_fw_version(std::string &fw) override {
if (this->fw_version_text_sensor_ != nullptr) {
this->fw_version_text_sensor_->publish_state(fw);
}
}
protected:
text_sensor::TextSensor *fw_version_text_sensor_{nullptr};
};
} // namespace ld2420
} // namespace esphome

View file

@ -224,6 +224,12 @@ uart:
tx_pin: 14 tx_pin: 14
rx_pin: 27 rx_pin: 27
baud_rate: 115200 baud_rate: 115200
- id: ld2420_uart
tx_pin: 17
rx_pin: 16
baud_rate: 115200
parity: NONE
stop_bits: 1
- id: gcja5_uart - id: gcja5_uart
rx_pin: GPIO10 rx_pin: GPIO10
parity: EVEN parity: EVEN
@ -1441,6 +1447,9 @@ sensor:
still_energy: still_energy:
name: g8 still energy name: g8 still energy
- platform: ld2420
moving_distance:
name: "Moving distance (cm)"
- platform: sen21231 - platform: sen21231
name: "Person Sensor" name: "Person Sensor"
i2c_id: i2c_bus i2c_id: i2c_bus
@ -3615,6 +3624,10 @@ ld2410:
id: my_ld2410 id: my_ld2410
uart_id: ld2410_uart uart_id: ld2410_uart
ld2420:
id: my_ld2420
uart_id: ld2420_uart
lcd_menu: lcd_menu:
display_id: my_lcd_gpio display_id: my_lcd_gpio
mark_back: 0x5e mark_back: 0x5e