From 0bf6e21e1a862b5a2686b8c7815ad08314b52c83 Mon Sep 17 00:00:00 2001 From: sebcaps Date: Mon, 6 Feb 2023 23:47:50 +0100 Subject: [PATCH] Add Ld2410 Support (#3919) Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com> --- CODEOWNERS | 1 + esphome/components/ld2410/__init__.py | 158 +++++++++++ esphome/components/ld2410/binary_sensor.py | 36 +++ esphome/components/ld2410/ld2410.cpp | 313 +++++++++++++++++++++ esphome/components/ld2410/ld2410.h | 146 ++++++++++ esphome/components/ld2410/sensor.py | 55 ++++ esphome/const.py | 1 + tests/test1.yaml | 37 +++ 8 files changed, 747 insertions(+) create mode 100644 esphome/components/ld2410/__init__.py create mode 100644 esphome/components/ld2410/binary_sensor.py create mode 100644 esphome/components/ld2410/ld2410.cpp create mode 100644 esphome/components/ld2410/ld2410.h create mode 100644 esphome/components/ld2410/sensor.py diff --git a/CODEOWNERS b/CODEOWNERS index 49b3df9b50..da48761551 100644 --- a/CODEOWNERS +++ b/CODEOWNERS @@ -119,6 +119,7 @@ esphome/components/kalman_combinator/* @Cat-Ion esphome/components/key_collector/* @ssieb esphome/components/key_provider/* @ssieb esphome/components/lcd_menu/* @numo68 +esphome/components/ld2410/* @sebcaps esphome/components/ledc/* @OttoWinter esphome/components/light/* @esphome/core esphome/components/lilygo_t5_47/touchscreen/* @jesserockz diff --git a/esphome/components/ld2410/__init__.py b/esphome/components/ld2410/__init__.py new file mode 100644 index 0000000000..be39cc2979 --- /dev/null +++ b/esphome/components/ld2410/__init__.py @@ -0,0 +1,158 @@ +import esphome.codegen as cg +import esphome.config_validation as cv +from esphome.components import uart +from esphome.const import CONF_ID, CONF_TIMEOUT +from esphome import automation +from esphome.automation import maybe_simple_id + +DEPENDENCIES = ["uart"] +CODEOWNERS = ["@sebcaps"] +MULTI_CONF = True + +ld2410_ns = cg.esphome_ns.namespace("ld2410") +LD2410Component = ld2410_ns.class_("LD2410Component", cg.Component, uart.UARTDevice) +LD2410Restart = ld2410_ns.class_("LD2410Restart", automation.Action) +CONF_LD2410_ID = "ld2410_id" +CONF_MAX_MOVE_DISTANCE = "max_move_distance" +CONF_MAX_STILL_DISTANCE = "max_still_distance" +CONF_G0_MOVE_THRESHOLD = "g0_move_threshold" +CONF_G0_STILL_THRESHOLD = "g0_still_threshold" +CONF_G1_MOVE_THRESHOLD = "g1_move_threshold" +CONF_G1_STILL_THRESHOLD = "g1_still_threshold" +CONF_G2_MOVE_THRESHOLD = "g2_move_threshold" +CONF_G2_STILL_THRESHOLD = "g2_still_threshold" +CONF_G3_MOVE_THRESHOLD = "g3_move_threshold" +CONF_G3_STILL_THRESHOLD = "g3_still_threshold" +CONF_G4_MOVE_THRESHOLD = "g4_move_threshold" +CONF_G4_STILL_THRESHOLD = "g4_still_threshold" +CONF_G5_MOVE_THRESHOLD = "g5_move_threshold" +CONF_G5_STILL_THRESHOLD = "g5_still_threshold" +CONF_G6_MOVE_THRESHOLD = "g6_move_threshold" +CONF_G6_STILL_THRESHOLD = "g6_still_threshold" +CONF_G7_MOVE_THRESHOLD = "g7_move_threshold" +CONF_G7_STILL_THRESHOLD = "g7_still_threshold" +CONF_G8_MOVE_THRESHOLD = "g8_move_threshold" +CONF_G8_STILL_THRESHOLD = "g8_still_threshold" + +DISTANCES = [0.75, 1.5, 2.25, 3, 3.75, 4.5, 5.25, 6] + +CONFIG_SCHEMA = cv.All( + cv.Schema( + { + cv.GenerateID(): cv.declare_id(LD2410Component), + cv.Optional(CONF_MAX_MOVE_DISTANCE, default="4.5m"): cv.All( + cv.distance, cv.one_of(*DISTANCES, float=True) + ), + cv.Optional(CONF_MAX_STILL_DISTANCE, default="4.5m"): cv.All( + cv.distance, cv.one_of(*DISTANCES, float=True) + ), + cv.Optional(CONF_TIMEOUT, default="5s"): cv.All( + cv.positive_time_period_seconds, + cv.Range(max=cv.TimePeriod(seconds=32767)), + ), + cv.Optional(CONF_G0_MOVE_THRESHOLD, default=50): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G0_STILL_THRESHOLD, default=0): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G1_MOVE_THRESHOLD, default=50): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G1_STILL_THRESHOLD, default=0): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G2_MOVE_THRESHOLD, default=40): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G2_STILL_THRESHOLD, default=40): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G3_MOVE_THRESHOLD, default=40): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G3_STILL_THRESHOLD, default=40): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G4_MOVE_THRESHOLD, default=40): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G4_STILL_THRESHOLD, default=40): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G5_MOVE_THRESHOLD, default=40): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G5_STILL_THRESHOLD, default=40): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G6_MOVE_THRESHOLD, default=30): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G6_STILL_THRESHOLD, default=15): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G7_MOVE_THRESHOLD, default=30): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G7_STILL_THRESHOLD, default=15): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G8_MOVE_THRESHOLD, default=30): cv.int_range( + min=0, max=100 + ), + cv.Optional(CONF_G8_STILL_THRESHOLD, default=15): cv.int_range( + min=0, max=100 + ), + } + ) + .extend(uart.UART_DEVICE_SCHEMA) + .extend(cv.COMPONENT_SCHEMA) +) + +FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema( + "ld2410", + baud_rate=256000, + 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) + cg.add(var.set_timeout(config[CONF_TIMEOUT])) + cg.add(var.set_max_move_distance(int(config[CONF_MAX_MOVE_DISTANCE] / 0.75))) + cg.add(var.set_max_still_distance(int(config[CONF_MAX_STILL_DISTANCE] / 0.75))) + cg.add( + var.set_range_config( + config[CONF_G0_MOVE_THRESHOLD], + config[CONF_G0_STILL_THRESHOLD], + config[CONF_G1_MOVE_THRESHOLD], + config[CONF_G1_STILL_THRESHOLD], + config[CONF_G2_MOVE_THRESHOLD], + config[CONF_G2_STILL_THRESHOLD], + config[CONF_G3_MOVE_THRESHOLD], + config[CONF_G3_STILL_THRESHOLD], + config[CONF_G4_MOVE_THRESHOLD], + config[CONF_G4_STILL_THRESHOLD], + config[CONF_G5_MOVE_THRESHOLD], + config[CONF_G5_STILL_THRESHOLD], + config[CONF_G6_MOVE_THRESHOLD], + config[CONF_G6_STILL_THRESHOLD], + config[CONF_G7_MOVE_THRESHOLD], + config[CONF_G7_STILL_THRESHOLD], + config[CONF_G8_MOVE_THRESHOLD], + config[CONF_G8_STILL_THRESHOLD], + ) + ) + + +CALIBRATION_ACTION_SCHEMA = maybe_simple_id( + { + cv.Required(CONF_ID): cv.use_id(LD2410Component), + } +) diff --git a/esphome/components/ld2410/binary_sensor.py b/esphome/components/ld2410/binary_sensor.py new file mode 100644 index 0000000000..02f73d57b7 --- /dev/null +++ b/esphome/components/ld2410/binary_sensor.py @@ -0,0 +1,36 @@ +import esphome.codegen as cg +from esphome.components import binary_sensor +import esphome.config_validation as cv +from esphome.const import DEVICE_CLASS_MOTION, DEVICE_CLASS_OCCUPANCY +from . import CONF_LD2410_ID, LD2410Component + +DEPENDENCIES = ["ld2410"] +CONF_HAS_TARGET = "has_target" +CONF_HAS_MOVING_TARGET = "has_moving_target" +CONF_HAS_STILL_TARGET = "has_still_target" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), + cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_OCCUPANCY + ), + cv.Optional(CONF_HAS_MOVING_TARGET): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_MOTION + ), + cv.Optional(CONF_HAS_STILL_TARGET): binary_sensor.binary_sensor_schema( + device_class=DEVICE_CLASS_OCCUPANCY + ), +} + + +async def to_code(config): + ld2410_component = await cg.get_variable(config[CONF_LD2410_ID]) + if CONF_HAS_TARGET in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_TARGET]) + cg.add(ld2410_component.set_target_sensor(sens)) + if CONF_HAS_MOVING_TARGET in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_MOVING_TARGET]) + cg.add(ld2410_component.set_moving_target_sensor(sens)) + if CONF_HAS_STILL_TARGET in config: + sens = await binary_sensor.new_binary_sensor(config[CONF_HAS_STILL_TARGET]) + cg.add(ld2410_component.set_still_target_sensor(sens)) diff --git a/esphome/components/ld2410/ld2410.cpp b/esphome/components/ld2410/ld2410.cpp new file mode 100644 index 0000000000..8fb84d785e --- /dev/null +++ b/esphome/components/ld2410/ld2410.cpp @@ -0,0 +1,313 @@ +#include "ld2410.h" + +#define highbyte(val) (uint8_t)((val) >> 8) +#define lowbyte(val) (uint8_t)((val) &0xff) + +namespace esphome { +namespace ld2410 { + +static const char *const TAG = "ld2410"; + +void LD2410Component::dump_config() { + ESP_LOGCONFIG(TAG, "LD2410:"); +#ifdef USE_BINARY_SENSOR + LOG_BINARY_SENSOR(" ", "HasTargetSensor", this->target_binary_sensor_); + LOG_BINARY_SENSOR(" ", "MovingSensor", this->moving_binary_sensor_); + LOG_BINARY_SENSOR(" ", "StillSensor", this->still_binary_sensor_); +#endif +#ifdef USE_SENSOR + LOG_SENSOR(" ", "Moving Distance", this->moving_target_distance_sensor_); + LOG_SENSOR(" ", "Still Distance", this->still_target_distance_sensor_); + LOG_SENSOR(" ", "Moving Energy", this->moving_target_energy_sensor_); + LOG_SENSOR(" ", "Still Energy", this->still_target_energy_sensor_); + LOG_SENSOR(" ", "Detection Distance", this->detection_distance_sensor_); +#endif + this->set_config_mode_(true); + this->get_version_(); + this->set_config_mode_(false); + ESP_LOGCONFIG(" ", "Firmware Version : %u.%u.%u%u%u%u", this->version_[0], this->version_[1], this->version_[2], + this->version_[3], this->version_[4], this->version_[5]); +} + +void LD2410Component::setup() { + this->set_config_mode_(true); + this->set_max_distances_timeout_(this->max_move_distance_, this->max_still_distance_, this->timeout_); + // Configure Gates sensitivity + this->set_gate_threshold_(0, this->rg0_move_threshold_, this->rg0_still_threshold_); + this->set_gate_threshold_(1, this->rg1_move_threshold_, this->rg1_still_threshold_); + this->set_gate_threshold_(2, this->rg2_move_threshold_, this->rg2_still_threshold_); + this->set_gate_threshold_(3, this->rg3_move_threshold_, this->rg3_still_threshold_); + this->set_gate_threshold_(4, this->rg4_move_threshold_, this->rg4_still_threshold_); + this->set_gate_threshold_(5, this->rg5_move_threshold_, this->rg5_still_threshold_); + this->set_gate_threshold_(6, this->rg6_move_threshold_, this->rg6_still_threshold_); + this->set_gate_threshold_(7, this->rg7_move_threshold_, this->rg7_still_threshold_); + this->set_gate_threshold_(8, this->rg8_move_threshold_, this->rg8_still_threshold_); + this->get_version_(); + this->set_config_mode_(false); + ESP_LOGI(" ", "Firmware Version : %u.%u.%u%u%u%u", this->version_[0], this->version_[1], this->version_[2], + this->version_[3], this->version_[4], this->version_[5]); +} + +void LD2410Component::loop() { + const int max_line_length = 80; + static uint8_t buffer[max_line_length]; + + while (available()) { + this->readline_(read(), buffer, max_line_length); + } +} + +void LD2410Component::send_command_(uint8_t command, uint8_t *command_value, int command_value_len) { + // lastCommandSuccess->publish_state(false); + + // frame start bytes + this->write_array(CMD_FRAME_HEADER, 4); + // length bytes + int len = 2; + if (command_value != nullptr) + len += command_value_len; + this->write_byte(lowbyte(len)); + this->write_byte(highbyte(len)); + + // command + this->write_byte(lowbyte(command)); + this->write_byte(highbyte(command)); + + // command value bytes + if (command_value != nullptr) { + for (int i = 0; i < command_value_len; i++) { + this->write_byte(command_value[i]); + } + } + // frame end bytes + this->write_array(CMD_FRAME_END, 4); + // FIXME to remove + delay(50); // NOLINT +} + +void LD2410Component::handle_periodic_data_(uint8_t *buffer, int len) { + if (len < 12) + return; // 4 frame start bytes + 2 length bytes + 1 data end byte + 1 crc byte + 4 frame end bytes + if (buffer[0] != 0xF4 || buffer[1] != 0xF3 || buffer[2] != 0xF2 || buffer[3] != 0xF1) // check 4 frame start bytes + return; + if (buffer[7] != HEAD || buffer[len - 6] != END || buffer[len - 5] != CHECK) // Check constant values + return; // data head=0xAA, data end=0x55, crc=0x00 + + /* + Data Type: 6th + 0x01: Engineering mode + 0x02: Normal mode + */ + // char data_type = buffer[DATA_TYPES]; + /* + Target states: 9th + 0x00 = No target + 0x01 = Moving targets + 0x02 = Still targets + 0x03 = Moving+Still targets + */ +#ifdef USE_BINARY_SENSOR + char target_state = buffer[TARGET_STATES]; + if (this->target_binary_sensor_ != nullptr) { + this->target_binary_sensor_->publish_state(target_state != 0x00); + } +#endif + + /* + Reduce data update rate to prevent home assistant database size grow fast + */ + int32_t current_millis = millis(); + if (current_millis - last_periodic_millis < 1000) + return; + last_periodic_millis = current_millis; + +#ifdef USE_BINARY_SENSOR + if (this->moving_binary_sensor_ != nullptr) { + this->moving_binary_sensor_->publish_state(CHECK_BIT(target_state, 0)); + } + if (this->still_binary_sensor_ != nullptr) { + this->still_binary_sensor_->publish_state(CHECK_BIT(target_state, 1)); + } +#endif + /* + Moving target distance: 10~11th bytes + Moving target energy: 12th byte + Still target distance: 13~14th bytes + Still target energy: 15th byte + Detect distance: 16~17th bytes + */ +#ifdef USE_SENSOR + if (this->moving_target_distance_sensor_ != nullptr) { + int new_moving_target_distance = this->two_byte_to_int_(buffer[MOVING_TARGET_LOW], buffer[MOVING_TARGET_HIGH]); + if (this->moving_target_distance_sensor_->get_state() != new_moving_target_distance) + this->moving_target_distance_sensor_->publish_state(new_moving_target_distance); + } + if (this->moving_target_energy_sensor_ != nullptr) { + int new_moving_target_energy = buffer[MOVING_ENERGY]; + if (this->moving_target_energy_sensor_->get_state() != new_moving_target_energy) + this->moving_target_energy_sensor_->publish_state(new_moving_target_energy); + } + if (this->still_target_distance_sensor_ != nullptr) { + int new_still_target_distance = this->two_byte_to_int_(buffer[STILL_TARGET_LOW], buffer[STILL_TARGET_HIGH]); + if (this->still_target_distance_sensor_->get_state() != new_still_target_distance) + this->still_target_distance_sensor_->publish_state(new_still_target_distance); + } + if (this->still_target_energy_sensor_ != nullptr) { + int new_still_target_energy = buffer[STILL_ENERGY]; + if (this->still_target_energy_sensor_->get_state() != new_still_target_energy) + this->still_target_energy_sensor_->publish_state(new_still_target_energy); + } + if (this->detection_distance_sensor_ != nullptr) { + int new_detect_distance = this->two_byte_to_int_(buffer[DETECT_DISTANCE_LOW], buffer[DETECT_DISTANCE_HIGH]); + if (this->detection_distance_sensor_->get_state() != new_detect_distance) + this->detection_distance_sensor_->publish_state(new_detect_distance); + } +#endif +} + +void LD2410Component::handle_ack_data_(uint8_t *buffer, int len) { + ESP_LOGI(TAG, "Handling ACK DATA for COMMAND"); + if (len < 10) { + ESP_LOGE(TAG, "Error with last command : incorrect length"); + return; + } + if (buffer[0] != 0xFD || buffer[1] != 0xFC || buffer[2] != 0xFB || buffer[3] != 0xFA) { // check 4 frame start bytes + ESP_LOGE(TAG, "Error with last command : incorrect Header"); + return; + } + if (buffer[COMMAND_STATUS] != 0x01) { + ESP_LOGE(TAG, "Error with last command : status != 0x01"); + return; + } + if (this->two_byte_to_int_(buffer[8], buffer[9]) != 0x00) { + ESP_LOGE(TAG, "Error with last command , last buffer was: %u , %u", buffer[8], buffer[9]); + return; + } + + switch (buffer[COMMAND]) { + case lowbyte(CMD_ENABLE_CONF): + ESP_LOGD(TAG, "Handled Enable conf command"); + break; + case lowbyte(CMD_DISABLE_CONF): + ESP_LOGD(TAG, "Handled Disabled conf command"); + break; + case lowbyte(CMD_VERSION): + ESP_LOGD(TAG, "FW Version is: %u.%u.%u%u%u%u", buffer[13], buffer[12], buffer[17], buffer[16], buffer[15], + buffer[14]); + this->version_[0] = buffer[13]; + this->version_[1] = buffer[12]; + this->version_[2] = buffer[17]; + this->version_[3] = buffer[16]; + this->version_[4] = buffer[15]; + this->version_[5] = buffer[14]; + + break; + case lowbyte(CMD_GATE_SENS): + ESP_LOGD(TAG, "Handled sensitivity command"); + break; + case lowbyte(CMD_QUERY): // Query parameters response + { + if (buffer[10] != 0xAA) + return; // value head=0xAA + /* + Moving distance range: 13th byte + Still distance range: 14th byte + */ + // TODO + // maxMovingDistanceRange->publish_state(buffer[12]); + // maxStillDistanceRange->publish_state(buffer[13]); + /* + Moving Sensitivities: 15~23th bytes + Still Sensitivities: 24~32th bytes + */ + for (int i = 0; i < 9; i++) { + moving_sensitivities[i] = buffer[14 + i]; + } + for (int i = 0; i < 9; i++) { + still_sensitivities[i] = buffer[23 + i]; + } + /* + None Duration: 33~34th bytes + */ + // noneDuration->publish_state(this->two_byte_to_int_(buffer[32], buffer[33])); + } break; + default: + break; + } +} + +void LD2410Component::readline_(int readch, uint8_t *buffer, int len) { + static int pos = 0; + + if (readch >= 0) { + if (pos < len - 1) { + buffer[pos++] = readch; + buffer[pos] = 0; + } else { + pos = 0; + } + if (pos >= 4) { + if (buffer[pos - 4] == 0xF8 && buffer[pos - 3] == 0xF7 && buffer[pos - 2] == 0xF6 && buffer[pos - 1] == 0xF5) { + ESP_LOGV(TAG, "Will handle Periodic Data"); + this->handle_periodic_data_(buffer, pos); + pos = 0; // Reset position index ready for next time + } else if (buffer[pos - 4] == 0x04 && buffer[pos - 3] == 0x03 && buffer[pos - 2] == 0x02 && + buffer[pos - 1] == 0x01) { + ESP_LOGV(TAG, "Will handle ACK Data"); + this->handle_ack_data_(buffer, pos); + pos = 0; // Reset position index ready for next time + } + } + } +} + +void LD2410Component::set_config_mode_(bool enable) { + uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF; + uint8_t cmd_value[2] = {0x01, 0x00}; + this->send_command_(cmd, enable ? cmd_value : nullptr, 2); +} + +void LD2410Component::query_parameters_() { this->send_command_(CMD_QUERY, nullptr, 0); } +void LD2410Component::get_version_() { this->send_command_(CMD_VERSION, nullptr, 0); } + +void LD2410Component::set_max_distances_timeout_(uint8_t max_moving_distance_range, uint8_t max_still_distance_range, + uint16_t timeout) { + uint8_t value[18] = {0x00, + 0x00, + lowbyte(max_moving_distance_range), + highbyte(max_moving_distance_range), + 0x00, + 0x00, + 0x01, + 0x00, + lowbyte(max_still_distance_range), + highbyte(max_still_distance_range), + 0x00, + 0x00, + 0x02, + 0x00, + lowbyte(timeout), + highbyte(timeout), + 0x00, + 0x00}; + this->send_command_(CMD_MAXDIST_DURATION, value, 18); + this->query_parameters_(); +} +void LD2410Component::set_gate_threshold_(uint8_t gate, uint8_t motionsens, uint8_t stillsens) { + // reference + // https://drive.google.com/drive/folders/1p4dhbEJA3YubyIjIIC7wwVsSo8x29Fq-?spm=a2g0o.detail.1000023.17.93465697yFwVxH + // Send data: configure the motion sensitivity of distance gate 3 to 40, and the static sensitivity of 40 + // 00 00 (gate) + // 03 00 00 00 (gate number) + // 01 00 (motion sensitivity) + // 28 00 00 00 (value) + // 02 00 (still sensitivtiy) + // 28 00 00 00 (value) + uint8_t value[18] = {0x00, 0x00, lowbyte(gate), highbyte(gate), 0x00, 0x00, + 0x01, 0x00, lowbyte(motionsens), highbyte(motionsens), 0x00, 0x00, + 0x02, 0x00, lowbyte(stillsens), highbyte(stillsens), 0x00, 0x00}; + this->send_command_(CMD_GATE_SENS, value, 18); +} + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/ld2410.h b/esphome/components/ld2410/ld2410.h new file mode 100644 index 0000000000..5a35798bc2 --- /dev/null +++ b/esphome/components/ld2410/ld2410.h @@ -0,0 +1,146 @@ +#pragma once +#include "esphome/core/defines.h" +#include "esphome/core/component.h" +#ifdef USE_BINARY_SENSOR +#include "esphome/components/binary_sensor/binary_sensor.h" +#endif +#ifdef USE_SENSOR +#include "esphome/components/sensor/sensor.h" +#endif +#include "esphome/components/uart/uart.h" +#include "esphome/core/automation.h" +#include "esphome/core/helpers.h" + +namespace esphome { +namespace ld2410 { + +#define CHECK_BIT(var, pos) (((var) >> (pos)) & 1) + +// Commands +static const uint8_t CMD_ENABLE_CONF = 0x00FF; +static const uint8_t CMD_DISABLE_CONF = 0x00FE; +static const uint8_t CMD_MAXDIST_DURATION = 0x0060; +static const uint8_t CMD_QUERY = 0x0061; +static const uint8_t CMD_GATE_SENS = 0x0064; +static const uint8_t CMD_VERSION = 0x00A0; + +// Commands values +static const uint8_t CMD_MAX_MOVE_VALUE = 0x0000; +static const uint8_t CMD_MAX_STILL_VALUE = 0x0001; +static const uint8_t CMD_DURATION_VALUE = 0x0002; +// 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}; +// Data Header & Footer +static const uint8_t DATA_FRAME_HEADER[4] = {0xF4, 0xF3, 0xF2, 0xF1}; +static const uint8_t DATA_FRAME_END[4] = {0xF8, 0xF7, 0xF6, 0xF5}; +/* +Data Type: 6th byte +Target states: 9th byte + Moving target distance: 10~11th bytes + Moving target energy: 12th byte + Still target distance: 13~14th bytes + Still target energy: 15th byte + Detect distance: 16~17th bytes +*/ +enum PeriodicDataStructure : uint8_t { + DATA_TYPES = 5, + TARGET_STATES = 8, + MOVING_TARGET_LOW = 9, + MOVING_TARGET_HIGH = 10, + MOVING_ENERGY = 11, + STILL_TARGET_LOW = 12, + STILL_TARGET_HIGH = 13, + STILL_ENERGY = 14, + DETECT_DISTANCE_LOW = 15, + DETECT_DISTANCE_HIGH = 16, +}; +enum PeriodicDataValue : uint8_t { HEAD = 0XAA, END = 0x55, CHECK = 0x00 }; + +enum AckDataStructure : uint8_t { COMMAND = 6, COMMAND_STATUS = 7 }; + +// char cmd[2] = {enable ? 0xFF : 0xFE, 0x00}; +class LD2410Component : public Component, public uart::UARTDevice { +#ifdef USE_SENSOR + SUB_SENSOR(moving_target_distance) + SUB_SENSOR(still_target_distance) + SUB_SENSOR(moving_target_energy) + SUB_SENSOR(still_target_energy) + SUB_SENSOR(detection_distance) +#endif + + public: + void setup() override; + void dump_config() override; + void loop() override; + +#ifdef USE_BINARY_SENSOR + void set_target_sensor(binary_sensor::BinarySensor *sens) { this->target_binary_sensor_ = sens; }; + void set_moving_target_sensor(binary_sensor::BinarySensor *sens) { this->moving_binary_sensor_ = sens; }; + void set_still_target_sensor(binary_sensor::BinarySensor *sens) { this->still_binary_sensor_ = sens; }; +#endif + + void set_timeout(uint16_t value) { this->timeout_ = value; }; + void set_max_move_distance(uint8_t value) { this->max_move_distance_ = value; }; + void set_max_still_distance(uint8_t value) { this->max_still_distance_ = value; }; + void set_range_config(int rg0_move, int rg0_still, int rg1_move, int rg1_still, int rg2_move, int rg2_still, + int rg3_move, int rg3_still, int rg4_move, int rg4_still, int rg5_move, int rg5_still, + int rg6_move, int rg6_still, int rg7_move, int rg7_still, int rg8_move, int rg8_still) { + this->rg0_move_threshold_ = rg0_move; + this->rg0_still_threshold_ = rg0_still; + this->rg1_move_threshold_ = rg1_move; + this->rg1_still_threshold_ = rg1_still; + this->rg2_move_threshold_ = rg2_move; + this->rg2_still_threshold_ = rg2_still; + this->rg3_move_threshold_ = rg3_move; + this->rg3_still_threshold_ = rg3_still; + this->rg4_move_threshold_ = rg4_move; + this->rg4_still_threshold_ = rg4_still; + this->rg5_move_threshold_ = rg5_move; + this->rg5_still_threshold_ = rg5_still; + this->rg6_move_threshold_ = rg6_move; + this->rg6_still_threshold_ = rg6_still; + this->rg7_move_threshold_ = rg7_move; + this->rg7_still_threshold_ = rg7_still; + this->rg8_move_threshold_ = rg8_move; + this->rg8_still_threshold_ = rg8_still; + }; + int moving_sensitivities[9] = {0}; + int still_sensitivities[9] = {0}; + + int32_t last_periodic_millis = millis(); + + protected: +#ifdef USE_BINARY_SENSOR + binary_sensor::BinarySensor *target_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *moving_binary_sensor_{nullptr}; + binary_sensor::BinarySensor *still_binary_sensor_{nullptr}; +#endif + + std::vector rx_buffer_; + int two_byte_to_int_(char firstbyte, char secondbyte) { return (int16_t)(secondbyte << 8) + firstbyte; } + void send_command_(uint8_t command_str, uint8_t *command_value, int command_value_len); + + void set_max_distances_timeout_(uint8_t max_moving_distance_range, uint8_t max_still_distance_range, + uint16_t timeout); + void set_gate_threshold_(uint8_t gate, uint8_t motionsens, uint8_t stillsens); + void set_config_mode_(bool enable); + void handle_periodic_data_(uint8_t *buffer, int len); + void handle_ack_data_(uint8_t *buffer, int len); + void readline_(int readch, uint8_t *buffer, int len); + void query_parameters_(); + void get_version_(); + + uint16_t timeout_; + uint8_t max_move_distance_; + uint8_t max_still_distance_; + + uint8_t version_[6]; + uint8_t rg0_move_threshold_, rg0_still_threshold_, rg1_move_threshold_, rg1_still_threshold_, rg2_move_threshold_, + rg2_still_threshold_, rg3_move_threshold_, rg3_still_threshold_, rg4_move_threshold_, rg4_still_threshold_, + rg5_move_threshold_, rg5_still_threshold_, rg6_move_threshold_, rg6_still_threshold_, rg7_move_threshold_, + rg7_still_threshold_, rg8_move_threshold_, rg8_still_threshold_; +}; + +} // namespace ld2410 +} // namespace esphome diff --git a/esphome/components/ld2410/sensor.py b/esphome/components/ld2410/sensor.py new file mode 100644 index 0000000000..b941263134 --- /dev/null +++ b/esphome/components/ld2410/sensor.py @@ -0,0 +1,55 @@ +import esphome.codegen as cg +from esphome.components import sensor +import esphome.config_validation as cv +from esphome.const import ( + DEVICE_CLASS_DISTANCE, + DEVICE_CLASS_ENERGY, + UNIT_CENTIMETER, + UNIT_PERCENT, +) +from . import CONF_LD2410_ID, LD2410Component + +DEPENDENCIES = ["ld2410"] +CONF_MOVING_DISTANCE = "moving_distance" +CONF_STILL_DISTANCE = "still_distance" +CONF_MOVING_ENERGY = "moving_energy" +CONF_STILL_ENERGY = "still_energy" +CONF_DETECTION_DISTANCE = "detection_distance" + +CONFIG_SCHEMA = { + cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component), + cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema( + device_class=DEVICE_CLASS_DISTANCE, unit_of_measurement=UNIT_CENTIMETER + ), + cv.Optional(CONF_STILL_DISTANCE): sensor.sensor_schema( + device_class=DEVICE_CLASS_DISTANCE, unit_of_measurement=UNIT_CENTIMETER + ), + cv.Optional(CONF_MOVING_ENERGY): sensor.sensor_schema( + device_class=DEVICE_CLASS_ENERGY, unit_of_measurement=UNIT_PERCENT + ), + cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema( + device_class=DEVICE_CLASS_ENERGY, unit_of_measurement=UNIT_PERCENT + ), + cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema( + device_class=DEVICE_CLASS_DISTANCE, unit_of_measurement=UNIT_CENTIMETER + ), +} + + +async def to_code(config): + ld2410_component = await cg.get_variable(config[CONF_LD2410_ID]) + if CONF_MOVING_DISTANCE in config: + sens = await sensor.new_sensor(config[CONF_MOVING_DISTANCE]) + cg.add(ld2410_component.set_moving_target_distance_sensor(sens)) + if CONF_STILL_DISTANCE in config: + sens = await sensor.new_sensor(config[CONF_STILL_DISTANCE]) + cg.add(ld2410_component.set_still_target_distance_sensor(sens)) + if CONF_MOVING_ENERGY in config: + sens = await sensor.new_sensor(config[CONF_MOVING_ENERGY]) + cg.add(ld2410_component.set_moving_target_energy_sensor(sens)) + if CONF_STILL_ENERGY in config: + sens = await sensor.new_sensor(config[CONF_STILL_ENERGY]) + cg.add(ld2410_component.set_still_target_energy_sensor(sens)) + if CONF_DETECTION_DISTANCE in config: + sens = await sensor.new_sensor(config[CONF_DETECTION_DISTANCE]) + cg.add(ld2410_component.set_detection_distance_sensor(sens)) diff --git a/esphome/const.py b/esphome/const.py index 889c6e94d7..b02088ffbb 100644 --- a/esphome/const.py +++ b/esphome/const.py @@ -862,6 +862,7 @@ UNIT_AMPERE = "A" UNIT_BECQUEREL_PER_CUBIC_METER = "Bq/m³" UNIT_BYTES = "B" UNIT_CELSIUS = "°C" +UNIT_CENTIMETER = "cm" UNIT_COUNT_DECILITRE = "/dL" UNIT_COUNTS_PER_CUBIC_METER = "#/m³" UNIT_CUBIC_METER = "m³" diff --git a/tests/test1.yaml b/tests/test1.yaml index 0448093001..abbfe8adec 100644 --- a/tests/test1.yaml +++ b/tests/test1.yaml @@ -222,6 +222,12 @@ uart: rx_pin: GPIO26 baud_rate: 115200 rx_buffer_size: 1024 + - id: ld2410_uart + tx_pin: 18 + rx_pin: 23 + baud_rate: 256000 + parity: NONE + stop_bits: 1 ota: safe_mode: true @@ -1200,6 +1206,17 @@ sensor: pressure: name: "MPL3115A2 Pressure" update_interval: 10s + - platform: ld2410 + moving_distance: + name: "Moving distance (cm)" + still_distance: + name: "Still Distance (cm)" + moving_energy: + name: "Move Energy" + still_energy: + name: "Still Energy" + detection_distance: + name: "Distance Detection" esp32_touch: setup_mode: false @@ -1461,6 +1478,13 @@ binary_sensor: id: close_sensor - platform: template id: close_obstacle_sensor + - platform: ld2410 + has_target: + name: presence + has_moving_target: + name: movement + has_still_target: + name: still pca9685: frequency: 500 @@ -3143,6 +3167,19 @@ button: on_press: midea_ac.power_toggle: +ld2410: + id: my_ld2410 + uart_id: ld2410_uart + timeout: 150s + max_move_distance: 6m + max_still_distance: 0.75m + g0_move_threshold: 10 + g0_still_threshold: 20 + g2_move_threshold: 20 + g2_still_threshold: 21 + g8_move_threshold: 80 + g8_still_threshold: 81 + lcd_menu: display_id: my_lcd_gpio mark_back: 0x5e