1
0
mirror of https://github.com/esphome/esphome.git synced 2025-09-01 10:52:19 +01:00

[ld2412] New component (#9075)

Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
This commit is contained in:
Rihan9
2025-08-12 07:34:37 +02:00
committed by GitHub
parent c65af68e63
commit 0256e0005e
42 changed files with 2220 additions and 0 deletions

View File

@@ -246,6 +246,7 @@ esphome/components/kuntze/* @ssieb
esphome/components/lc709203f/* @ilikecake
esphome/components/lcd_menu/* @numo68
esphome/components/ld2410/* @regevbr @sebcaps
esphome/components/ld2412/* @Rihan9
esphome/components/ld2420/* @descipher
esphome/components/ld2450/* @hareeshmu
esphome/components/ld24xx/* @kbx81

View File

@@ -0,0 +1,46 @@
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_THROTTLE
AUTO_LOAD = ["ld24xx"]
CODEOWNERS = ["@Rihan9"]
DEPENDENCIES = ["uart"]
MULTI_CONF = True
LD2412_ns = cg.esphome_ns.namespace("ld2412")
LD2412Component = LD2412_ns.class_("LD2412Component", cg.Component, uart.UARTDevice)
CONF_LD2412_ID = "ld2412_id"
CONF_MAX_MOVE_DISTANCE = "max_move_distance"
CONF_MAX_STILL_DISTANCE = "max_still_distance"
CONF_MOVE_THRESHOLDS = [f"g{x}_move_threshold" for x in range(9)]
CONF_STILL_THRESHOLDS = [f"g{x}_still_threshold" for x in range(9)]
CONFIG_SCHEMA = (
cv.Schema(
{
cv.GenerateID(): cv.declare_id(LD2412Component),
cv.Optional(CONF_THROTTLE): cv.invalid(
f"{CONF_THROTTLE} has been removed; use per-sensor filters, instead"
),
}
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"ld2412",
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,70 @@
import esphome.codegen as cg
from esphome.components import binary_sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_HAS_MOVING_TARGET,
CONF_HAS_STILL_TARGET,
CONF_HAS_TARGET,
DEVICE_CLASS_MOTION,
DEVICE_CLASS_OCCUPANCY,
DEVICE_CLASS_RUNNING,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_ACCOUNT,
ICON_MOTION_SENSOR,
)
from . import CONF_LD2412_ID, LD2412Component
DEPENDENCIES = ["ld2412"]
CONF_DYNAMIC_BACKGROUND_CORRECTION_STATUS = "dynamic_background_correction_status"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(
CONF_DYNAMIC_BACKGROUND_CORRECTION_STATUS
): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_RUNNING,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_ACCOUNT,
),
cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_OCCUPANCY,
filters=[{"settle": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_ACCOUNT,
),
cv.Optional(CONF_HAS_MOVING_TARGET): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_MOTION,
filters=[{"settle": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_MOTION_SENSOR,
),
cv.Optional(CONF_HAS_STILL_TARGET): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_OCCUPANCY,
filters=[{"settle": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_MOTION_SENSOR,
),
}
async def to_code(config):
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
if dynamic_background_correction_status_config := config.get(
CONF_DYNAMIC_BACKGROUND_CORRECTION_STATUS
):
sens = await binary_sensor.new_binary_sensor(
dynamic_background_correction_status_config
)
cg.add(
LD2412_component.set_dynamic_background_correction_status_binary_sensor(
sens
)
)
if has_target_config := config.get(CONF_HAS_TARGET):
sens = await binary_sensor.new_binary_sensor(has_target_config)
cg.add(LD2412_component.set_target_binary_sensor(sens))
if has_moving_target_config := config.get(CONF_HAS_MOVING_TARGET):
sens = await binary_sensor.new_binary_sensor(has_moving_target_config)
cg.add(LD2412_component.set_moving_target_binary_sensor(sens))
if has_still_target_config := config.get(CONF_HAS_STILL_TARGET):
sens = await binary_sensor.new_binary_sensor(has_still_target_config)
cg.add(LD2412_component.set_still_target_binary_sensor(sens))

View File

@@ -0,0 +1,74 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import (
CONF_FACTORY_RESET,
CONF_RESTART,
DEVICE_CLASS_RESTART,
ENTITY_CATEGORY_CONFIG,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_DATABASE,
ICON_PULSE,
ICON_RESTART,
ICON_RESTART_ALERT,
)
from .. import CONF_LD2412_ID, LD2412_ns, LD2412Component
FactoryResetButton = LD2412_ns.class_("FactoryResetButton", button.Button)
QueryButton = LD2412_ns.class_("QueryButton", button.Button)
RestartButton = LD2412_ns.class_("RestartButton", button.Button)
StartDynamicBackgroundCorrectionButton = LD2412_ns.class_(
"StartDynamicBackgroundCorrectionButton", button.Button
)
CONF_QUERY_PARAMS = "query_params"
CONF_START_DYNAMIC_BACKGROUND_CORRECTION = "start_dynamic_background_correction"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
FactoryResetButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART_ALERT,
),
cv.Optional(CONF_QUERY_PARAMS): button.button_schema(
QueryButton,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_DATABASE,
),
cv.Optional(CONF_RESTART): button.button_schema(
RestartButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_RESTART,
),
cv.Optional(CONF_START_DYNAMIC_BACKGROUND_CORRECTION): button.button_schema(
StartDynamicBackgroundCorrectionButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_PULSE,
),
}
async def to_code(config):
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
if factory_reset_config := config.get(CONF_FACTORY_RESET):
b = await button.new_button(factory_reset_config)
await cg.register_parented(b, config[CONF_LD2412_ID])
cg.add(LD2412_component.set_factory_reset_button(b))
if query_params_config := config.get(CONF_QUERY_PARAMS):
b = await button.new_button(query_params_config)
await cg.register_parented(b, config[CONF_LD2412_ID])
cg.add(LD2412_component.set_query_button(b))
if restart_config := config.get(CONF_RESTART):
b = await button.new_button(restart_config)
await cg.register_parented(b, config[CONF_LD2412_ID])
cg.add(LD2412_component.set_restart_button(b))
if start_dynamic_background_correction_config := config.get(
CONF_START_DYNAMIC_BACKGROUND_CORRECTION
):
b = await button.new_button(start_dynamic_background_correction_config)
await cg.register_parented(b, config[CONF_LD2412_ID])
cg.add(LD2412_component.set_start_dynamic_background_correction_button(b))

View File

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

View File

@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2412.h"
namespace esphome {
namespace ld2412 {
class FactoryResetButton : public button::Button, public Parented<LD2412Component> {
public:
FactoryResetButton() = default;
protected:
void press_action() override;
};
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,9 @@
#include "query_button.h"
namespace esphome {
namespace ld2412 {
void QueryButton::press_action() { this->parent_->read_all_info(); }
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2412.h"
namespace esphome {
namespace ld2412 {
class QueryButton : public button::Button, public Parented<LD2412Component> {
public:
QueryButton() = default;
protected:
void press_action() override;
};
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,9 @@
#include "restart_button.h"
namespace esphome {
namespace ld2412 {
void RestartButton::press_action() { this->parent_->restart_and_read_all_info(); }
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2412.h"
namespace esphome {
namespace ld2412 {
class RestartButton : public button::Button, public Parented<LD2412Component> {
public:
RestartButton() = default;
protected:
void press_action() override;
};
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,11 @@
#include "start_dynamic_background_correction_button.h"
#include "restart_button.h"
namespace esphome {
namespace ld2412 {
void StartDynamicBackgroundCorrectionButton::press_action() { this->parent_->start_dynamic_background_correction(); }
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2412.h"
namespace esphome {
namespace ld2412 {
class StartDynamicBackgroundCorrectionButton : public button::Button, public Parented<LD2412Component> {
public:
StartDynamicBackgroundCorrectionButton() = default;
protected:
void press_action() override;
};
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,861 @@
#include "ld2412.h"
#ifdef USE_NUMBER
#include "esphome/components/number/number.h"
#endif
#ifdef USE_SENSOR
#include "esphome/components/sensor/sensor.h"
#endif
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
namespace esphome {
namespace ld2412 {
static const char *const TAG = "ld2412";
static const char *const UNKNOWN_MAC = "unknown";
static const char *const VERSION_FMT = "%u.%02X.%02X%02X%02X%02X";
enum BaudRate : uint8_t {
BAUD_RATE_9600 = 1,
BAUD_RATE_19200 = 2,
BAUD_RATE_38400 = 3,
BAUD_RATE_57600 = 4,
BAUD_RATE_115200 = 5,
BAUD_RATE_230400 = 6,
BAUD_RATE_256000 = 7,
BAUD_RATE_460800 = 8,
};
enum DistanceResolution : uint8_t {
DISTANCE_RESOLUTION_0_2 = 0x03,
DISTANCE_RESOLUTION_0_5 = 0x01,
DISTANCE_RESOLUTION_0_75 = 0x00,
};
enum LightFunction : uint8_t {
LIGHT_FUNCTION_OFF = 0x00,
LIGHT_FUNCTION_BELOW = 0x01,
LIGHT_FUNCTION_ABOVE = 0x02,
};
enum OutPinLevel : uint8_t {
OUT_PIN_LEVEL_LOW = 0x01,
OUT_PIN_LEVEL_HIGH = 0x00,
};
/*
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 PeriodicData : uint8_t {
DATA_TYPES = 6,
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,
MOVING_SENSOR_START = 17,
STILL_SENSOR_START = 31,
LIGHT_SENSOR = 45,
OUT_PIN_SENSOR = 38,
};
enum PeriodicDataValue : uint8_t {
HEADER = 0XAA,
FOOTER = 0x55,
CHECK = 0x00,
};
enum AckData : uint8_t {
COMMAND = 6,
COMMAND_STATUS = 7,
};
// Memory-efficient lookup tables
struct StringToUint8 {
const char *str;
const uint8_t value;
};
struct Uint8ToString {
const uint8_t value;
const char *str;
};
constexpr StringToUint8 BAUD_RATES_BY_STR[] = {
{"9600", BAUD_RATE_9600}, {"19200", BAUD_RATE_19200}, {"38400", BAUD_RATE_38400},
{"57600", BAUD_RATE_57600}, {"115200", BAUD_RATE_115200}, {"230400", BAUD_RATE_230400},
{"256000", BAUD_RATE_256000}, {"460800", BAUD_RATE_460800},
};
constexpr StringToUint8 DISTANCE_RESOLUTIONS_BY_STR[] = {
{"0.2m", DISTANCE_RESOLUTION_0_2},
{"0.5m", DISTANCE_RESOLUTION_0_5},
{"0.75m", DISTANCE_RESOLUTION_0_75},
};
constexpr Uint8ToString DISTANCE_RESOLUTIONS_BY_UINT[] = {
{DISTANCE_RESOLUTION_0_2, "0.2m"},
{DISTANCE_RESOLUTION_0_5, "0.5m"},
{DISTANCE_RESOLUTION_0_75, "0.75m"},
};
constexpr StringToUint8 LIGHT_FUNCTIONS_BY_STR[] = {
{"off", LIGHT_FUNCTION_OFF},
{"below", LIGHT_FUNCTION_BELOW},
{"above", LIGHT_FUNCTION_ABOVE},
};
constexpr Uint8ToString LIGHT_FUNCTIONS_BY_UINT[] = {
{LIGHT_FUNCTION_OFF, "off"},
{LIGHT_FUNCTION_BELOW, "below"},
{LIGHT_FUNCTION_ABOVE, "above"},
};
constexpr StringToUint8 OUT_PIN_LEVELS_BY_STR[] = {
{"low", OUT_PIN_LEVEL_LOW},
{"high", OUT_PIN_LEVEL_HIGH},
};
constexpr Uint8ToString OUT_PIN_LEVELS_BY_UINT[] = {
{OUT_PIN_LEVEL_LOW, "low"},
{OUT_PIN_LEVEL_HIGH, "high"},
};
// Helper functions for lookups
template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const std::string &str) {
for (const auto &entry : arr) {
if (str == entry.str) {
return entry.value;
}
}
return 0xFF; // Not found
}
template<size_t N> const char *find_str(const Uint8ToString (&arr)[N], uint8_t value) {
for (const auto &entry : arr) {
if (value == entry.value) {
return entry.str;
}
}
return ""; // Not found
}
static constexpr uint8_t DEFAULT_PRESENCE_TIMEOUT = 5; // Default used when number component is not defined
// Commands
static constexpr uint8_t CMD_ENABLE_CONF = 0xFF;
static constexpr uint8_t CMD_DISABLE_CONF = 0xFE;
static constexpr uint8_t CMD_ENABLE_ENG = 0x62;
static constexpr uint8_t CMD_DISABLE_ENG = 0x63;
static constexpr uint8_t CMD_QUERY_BASIC_CONF = 0x12;
static constexpr uint8_t CMD_BASIC_CONF = 0x02;
static constexpr uint8_t CMD_QUERY_VERSION = 0xA0;
static constexpr uint8_t CMD_QUERY_DISTANCE_RESOLUTION = 0x11;
static constexpr uint8_t CMD_SET_DISTANCE_RESOLUTION = 0x01;
static constexpr uint8_t CMD_QUERY_LIGHT_CONTROL = 0x1C;
static constexpr uint8_t CMD_SET_LIGHT_CONTROL = 0x0C;
static constexpr uint8_t CMD_SET_BAUD_RATE = 0xA1;
static constexpr uint8_t CMD_QUERY_MAC_ADDRESS = 0xA5;
static constexpr uint8_t CMD_FACTORY_RESET = 0xA2;
static constexpr uint8_t CMD_RESTART = 0xA3;
static constexpr uint8_t CMD_BLUETOOTH = 0xA4;
static constexpr uint8_t CMD_DYNAMIC_BACKGROUND_CORRECTION = 0x0B;
static constexpr uint8_t CMD_QUERY_DYNAMIC_BACKGROUND_CORRECTION = 0x1B;
static constexpr uint8_t CMD_MOTION_GATE_SENS = 0x03;
static constexpr uint8_t CMD_QUERY_MOTION_GATE_SENS = 0x13;
static constexpr uint8_t CMD_STATIC_GATE_SENS = 0x04;
static constexpr uint8_t CMD_QUERY_STATIC_GATE_SENS = 0x14;
static constexpr uint8_t CMD_NONE = 0x00;
// Commands values
static constexpr uint8_t CMD_MAX_MOVE_VALUE = 0x00;
static constexpr uint8_t CMD_MAX_STILL_VALUE = 0x01;
static constexpr uint8_t CMD_DURATION_VALUE = 0x02;
// Bitmasks for target states
static constexpr uint8_t MOVE_BITMASK = 0x01;
static constexpr uint8_t STILL_BITMASK = 0x02;
// Header & Footer size
static constexpr uint8_t HEADER_FOOTER_SIZE = 4;
// Command Header & Footer
static constexpr uint8_t CMD_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xFD, 0xFC, 0xFB, 0xFA};
static constexpr uint8_t CMD_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0x04, 0x03, 0x02, 0x01};
// Data Header & Footer
static constexpr uint8_t DATA_FRAME_HEADER[HEADER_FOOTER_SIZE] = {0xF4, 0xF3, 0xF2, 0xF1};
static constexpr uint8_t DATA_FRAME_FOOTER[HEADER_FOOTER_SIZE] = {0xF8, 0xF7, 0xF6, 0xF5};
// MAC address the module uses when Bluetooth is disabled
static constexpr uint8_t NO_MAC[] = {0x08, 0x05, 0x04, 0x03, 0x02, 0x01};
static inline int two_byte_to_int(char firstbyte, char secondbyte) { return (int16_t) (secondbyte << 8) + firstbyte; }
static inline bool validate_header_footer(const uint8_t *header_footer, const uint8_t *buffer) {
return std::memcmp(header_footer, buffer, HEADER_FOOTER_SIZE) == 0;
}
void LD2412Component::dump_config() {
std::string mac_str =
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
this->version_[4], this->version_[3], this->version_[2]);
ESP_LOGCONFIG(TAG,
"LD2412:\n"
" Firmware version: %s\n"
" MAC address: %s",
version.c_str(), mac_str.c_str());
#ifdef USE_BINARY_SENSOR
ESP_LOGCONFIG(TAG, "Binary Sensors:");
LOG_BINARY_SENSOR(" ", "DynamicBackgroundCorrectionStatus",
this->dynamic_background_correction_status_binary_sensor_);
LOG_BINARY_SENSOR(" ", "MovingTarget", this->moving_target_binary_sensor_);
LOG_BINARY_SENSOR(" ", "StillTarget", this->still_target_binary_sensor_);
LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_);
#endif
#ifdef USE_SENSOR
ESP_LOGCONFIG(TAG, "Sensors:");
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "Light", this->light_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "DetectionDistance", this->detection_distance_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "MovingTargetDistance", this->moving_target_distance_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "MovingTargetEnergy", this->moving_target_energy_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "StillTargetDistance", this->still_target_distance_sensor_);
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "StillTargetEnergy", this->still_target_energy_sensor_);
for (auto &s : this->gate_still_sensors_) {
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "GateStill", s);
}
for (auto &s : this->gate_move_sensors_) {
LOG_SENSOR_WITH_DEDUP_SAFE(" ", "GateMove", s);
}
#endif
#ifdef USE_TEXT_SENSOR
ESP_LOGCONFIG(TAG, "Text Sensors:");
LOG_TEXT_SENSOR(" ", "MAC address", this->mac_text_sensor_);
LOG_TEXT_SENSOR(" ", "Version", this->version_text_sensor_);
#endif
#ifdef USE_NUMBER
ESP_LOGCONFIG(TAG, "Numbers:");
LOG_NUMBER(" ", "LightThreshold", this->light_threshold_number_);
LOG_NUMBER(" ", "MaxDistanceGate", this->max_distance_gate_number_);
LOG_NUMBER(" ", "MinDistanceGate", this->min_distance_gate_number_);
LOG_NUMBER(" ", "Timeout", this->timeout_number_);
for (number::Number *n : this->gate_move_threshold_numbers_) {
LOG_NUMBER(" ", "Move Thresholds", n);
}
for (number::Number *n : this->gate_still_threshold_numbers_) {
LOG_NUMBER(" ", "Still Thresholds", n);
}
#endif
#ifdef USE_SELECT
ESP_LOGCONFIG(TAG, "Selects:");
LOG_SELECT(" ", "BaudRate", this->baud_rate_select_);
LOG_SELECT(" ", "DistanceResolution", this->distance_resolution_select_);
LOG_SELECT(" ", "LightFunction", this->light_function_select_);
LOG_SELECT(" ", "OutPinLevel", this->out_pin_level_select_);
#endif
#ifdef USE_SWITCH
ESP_LOGCONFIG(TAG, "Switches:");
LOG_SWITCH(" ", "Bluetooth", this->bluetooth_switch_);
LOG_SWITCH(" ", "EngineeringMode", this->engineering_mode_switch_);
#endif
#ifdef USE_BUTTON
ESP_LOGCONFIG(TAG, "Buttons:");
LOG_BUTTON(" ", "FactoryReset", this->factory_reset_button_);
LOG_BUTTON(" ", "Query", this->query_button_);
LOG_BUTTON(" ", "Restart", this->restart_button_);
LOG_BUTTON(" ", "StartDynamicBackgroundCorrection", this->start_dynamic_background_correction_button_);
#endif
}
void LD2412Component::setup() {
ESP_LOGCONFIG(TAG, "Running setup");
this->read_all_info();
}
void LD2412Component::read_all_info() {
this->set_config_mode_(true);
this->get_version_();
delay(10); // NOLINT
this->get_mac_();
delay(10); // NOLINT
this->get_distance_resolution_();
delay(10); // NOLINT
this->query_parameters_();
delay(10); // NOLINT
this->query_dynamic_background_correction_();
delay(10); // NOLINT
this->query_light_control_();
delay(10); // NOLINT
#ifdef USE_NUMBER
this->get_gate_threshold();
delay(10); // NOLINT
#endif
this->set_config_mode_(false);
#ifdef USE_SELECT
const auto baud_rate = std::to_string(this->parent_->get_baud_rate());
if (this->baud_rate_select_ != nullptr) {
this->baud_rate_select_->publish_state(baud_rate);
}
#endif
}
void LD2412Component::restart_and_read_all_info() {
this->set_config_mode_(true);
this->restart_();
this->set_timeout(1000, [this]() { this->read_all_info(); });
}
void LD2412Component::loop() {
while (this->available()) {
this->readline_(this->read());
}
}
void LD2412Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) {
ESP_LOGV(TAG, "Sending COMMAND %02X", command);
// frame header bytes
this->write_array(CMD_FRAME_HEADER, HEADER_FOOTER_SIZE);
// length bytes
uint8_t len = 2;
if (command_value != nullptr) {
len += command_value_len;
}
// 2 length bytes (low, high) + 2 command bytes (low, high)
uint8_t len_cmd[] = {len, 0x00, command, 0x00};
this->write_array(len_cmd, sizeof(len_cmd));
// command value bytes
if (command_value != nullptr) {
this->write_array(command_value, command_value_len);
}
// frame footer bytes
this->write_array(CMD_FRAME_FOOTER, HEADER_FOOTER_SIZE);
if (command != CMD_ENABLE_CONF && command != CMD_DISABLE_CONF) {
delay(30); // NOLINT
}
delay(20); // NOLINT
}
void LD2412Component::handle_periodic_data_() {
// 4 frame header bytes + 2 length bytes + 1 data end byte + 1 crc byte + 4 frame footer bytes
// data header=0xAA, data footer=0x55, crc=0x00
if (this->buffer_pos_ < 12 || !ld2412::validate_header_footer(DATA_FRAME_HEADER, this->buffer_data_) ||
this->buffer_data_[7] != HEADER || this->buffer_data_[this->buffer_pos_ - 6] != FOOTER) {
return;
}
/*
Data Type: 7th
0x01: Engineering mode
0x02: Normal mode
*/
bool engineering_mode = this->buffer_data_[DATA_TYPES] == 0x01;
#ifdef USE_SWITCH
if (this->engineering_mode_switch_ != nullptr) {
this->engineering_mode_switch_->publish_state(engineering_mode);
}
#endif
#ifdef USE_BINARY_SENSOR
/*
Target states: 9th
0x00 = No target
0x01 = Moving targets
0x02 = Still targets
0x03 = Moving+Still targets
*/
char target_state = this->buffer_data_[TARGET_STATES];
if (this->target_binary_sensor_ != nullptr) {
this->target_binary_sensor_->publish_state(target_state != 0x00);
}
if (this->moving_target_binary_sensor_ != nullptr) {
this->moving_target_binary_sensor_->publish_state(target_state & MOVE_BITMASK);
}
if (this->still_target_binary_sensor_ != nullptr) {
this->still_target_binary_sensor_->publish_state(target_state & STILL_BITMASK);
}
#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
SAFE_PUBLISH_SENSOR(
this->moving_target_distance_sensor_,
ld2412::two_byte_to_int(this->buffer_data_[MOVING_TARGET_LOW], this->buffer_data_[MOVING_TARGET_HIGH]))
SAFE_PUBLISH_SENSOR(this->moving_target_energy_sensor_, this->buffer_data_[MOVING_ENERGY])
SAFE_PUBLISH_SENSOR(
this->still_target_distance_sensor_,
ld2412::two_byte_to_int(this->buffer_data_[STILL_TARGET_LOW], this->buffer_data_[STILL_TARGET_HIGH]))
SAFE_PUBLISH_SENSOR(this->still_target_energy_sensor_, this->buffer_data_[STILL_ENERGY])
if (this->detection_distance_sensor_ != nullptr) {
int new_detect_distance = 0;
if (target_state != 0x00 && (target_state & MOVE_BITMASK)) {
new_detect_distance =
ld2412::two_byte_to_int(this->buffer_data_[MOVING_TARGET_LOW], this->buffer_data_[MOVING_TARGET_HIGH]);
} else if (target_state != 0x00) {
new_detect_distance =
ld2412::two_byte_to_int(this->buffer_data_[STILL_TARGET_LOW], this->buffer_data_[STILL_TARGET_HIGH]);
}
this->detection_distance_sensor_->publish_state_if_not_dup(new_detect_distance);
}
if (engineering_mode) {
/*
Moving distance range: 18th byte
Still distance range: 19th byte
Moving energy: 20~28th bytes
*/
for (uint8_t i = 0; i < TOTAL_GATES; i++) {
SAFE_PUBLISH_SENSOR(this->gate_move_sensors_[i], this->buffer_data_[MOVING_SENSOR_START + i])
}
/*
Still energy: 29~37th bytes
*/
for (uint8_t i = 0; i < TOTAL_GATES; i++) {
SAFE_PUBLISH_SENSOR(this->gate_still_sensors_[i], this->buffer_data_[STILL_SENSOR_START + i])
}
/*
Light sensor: 38th bytes
*/
SAFE_PUBLISH_SENSOR(this->light_sensor_, this->buffer_data_[LIGHT_SENSOR])
} else {
for (auto &gate_move_sensor : this->gate_move_sensors_) {
SAFE_PUBLISH_SENSOR_UNKNOWN(gate_move_sensor)
}
for (auto &gate_still_sensor : this->gate_still_sensors_) {
SAFE_PUBLISH_SENSOR_UNKNOWN(gate_still_sensor)
}
SAFE_PUBLISH_SENSOR_UNKNOWN(this->light_sensor_)
}
#endif
// the radar module won't tell us when it's done, so we just have to keep polling...
if (this->dynamic_background_correction_active_) {
this->set_config_mode_(true);
this->query_dynamic_background_correction_();
this->set_config_mode_(false);
}
}
#ifdef USE_NUMBER
std::function<void(void)> set_number_value(number::Number *n, float value) {
if (n != nullptr && (!n->has_state() || n->state != value)) {
n->state = value;
return [n, value]() { n->publish_state(value); };
}
return []() {};
}
#endif
bool LD2412Component::handle_ack_data_() {
ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", this->buffer_data_[COMMAND]);
if (this->buffer_pos_ < 10) {
ESP_LOGW(TAG, "Invalid length");
return true;
}
if (!ld2412::validate_header_footer(CMD_FRAME_HEADER, this->buffer_data_)) {
ESP_LOGW(TAG, "Invalid header: %s", format_hex_pretty(this->buffer_data_, HEADER_FOOTER_SIZE).c_str());
return true;
}
if (this->buffer_data_[COMMAND_STATUS] != 0x01) {
ESP_LOGW(TAG, "Invalid status");
return true;
}
if (this->buffer_data_[8] || this->buffer_data_[9]) {
ESP_LOGW(TAG, "Invalid command: %02X, %02X", this->buffer_data_[8], this->buffer_data_[9]);
return true;
}
switch (this->buffer_data_[COMMAND]) {
case CMD_ENABLE_CONF:
ESP_LOGV(TAG, "Enable conf");
break;
case CMD_DISABLE_CONF:
ESP_LOGV(TAG, "Disabled conf");
break;
case CMD_SET_BAUD_RATE:
ESP_LOGV(TAG, "Baud rate change");
#ifdef USE_SELECT
if (this->baud_rate_select_ != nullptr) {
ESP_LOGW(TAG, "Change baud rate to %s and reinstall", this->baud_rate_select_->state.c_str());
}
#endif
break;
case CMD_QUERY_VERSION: {
std::memcpy(this->version_, &this->buffer_data_[12], sizeof(this->version_));
std::string version = str_sprintf(VERSION_FMT, this->version_[1], this->version_[0], this->version_[5],
this->version_[4], this->version_[3], this->version_[2]);
ESP_LOGV(TAG, "Firmware version: %s", version.c_str());
#ifdef USE_TEXT_SENSOR
if (this->version_text_sensor_ != nullptr) {
this->version_text_sensor_->publish_state(version);
}
#endif
break;
}
case CMD_QUERY_DISTANCE_RESOLUTION: {
const auto *distance_resolution = find_str(DISTANCE_RESOLUTIONS_BY_UINT, this->buffer_data_[10]);
ESP_LOGV(TAG, "Distance resolution: %s", distance_resolution);
#ifdef USE_SELECT
if (this->distance_resolution_select_ != nullptr) {
this->distance_resolution_select_->publish_state(distance_resolution);
}
#endif
break;
}
case CMD_QUERY_LIGHT_CONTROL: {
this->light_function_ = this->buffer_data_[10];
this->light_threshold_ = this->buffer_data_[11];
const auto *light_function_str = find_str(LIGHT_FUNCTIONS_BY_UINT, this->light_function_);
ESP_LOGV(TAG,
"Light function: %s\n"
"Light threshold: %u",
light_function_str, this->light_threshold_);
#ifdef USE_SELECT
if (this->light_function_select_ != nullptr) {
this->light_function_select_->publish_state(light_function_str);
}
#endif
#ifdef USE_NUMBER
if (this->light_threshold_number_ != nullptr) {
this->light_threshold_number_->publish_state(static_cast<float>(this->light_threshold_));
}
#endif
break;
}
case CMD_QUERY_MAC_ADDRESS: {
if (this->buffer_pos_ < 20) {
return false;
}
this->bluetooth_on_ = std::memcmp(&this->buffer_data_[10], NO_MAC, sizeof(NO_MAC)) != 0;
if (this->bluetooth_on_) {
std::memcpy(this->mac_address_, &this->buffer_data_[10], sizeof(this->mac_address_));
}
std::string mac_str =
mac_address_is_valid(this->mac_address_) ? format_mac_address_pretty(this->mac_address_) : UNKNOWN_MAC;
ESP_LOGV(TAG, "MAC address: %s", mac_str.c_str());
#ifdef USE_TEXT_SENSOR
if (this->mac_text_sensor_ != nullptr) {
this->mac_text_sensor_->publish_state(mac_str);
}
#endif
#ifdef USE_SWITCH
if (this->bluetooth_switch_ != nullptr) {
this->bluetooth_switch_->publish_state(this->bluetooth_on_);
}
#endif
break;
}
case CMD_SET_DISTANCE_RESOLUTION:
ESP_LOGV(TAG, "Handled set distance resolution command");
break;
case CMD_QUERY_DYNAMIC_BACKGROUND_CORRECTION: {
ESP_LOGV(TAG, "Handled query dynamic background correction");
bool dynamic_background_correction_active = (this->buffer_data_[10] != 0x00);
#ifdef USE_BINARY_SENSOR
if (this->dynamic_background_correction_status_binary_sensor_ != nullptr) {
this->dynamic_background_correction_status_binary_sensor_->publish_state(dynamic_background_correction_active);
}
#endif
this->dynamic_background_correction_active_ = dynamic_background_correction_active;
break;
}
case CMD_BLUETOOTH:
ESP_LOGV(TAG, "Handled bluetooth command");
break;
case CMD_SET_LIGHT_CONTROL:
ESP_LOGV(TAG, "Handled set light control command");
break;
case CMD_QUERY_MOTION_GATE_SENS: {
#ifdef USE_NUMBER
std::vector<std::function<void(void)>> updates;
updates.reserve(this->gate_still_threshold_numbers_.size());
for (size_t i = 0; i < this->gate_still_threshold_numbers_.size(); i++) {
updates.push_back(set_number_value(this->gate_move_threshold_numbers_[i], this->buffer_data_[10 + i]));
}
for (auto &update : updates) {
update();
}
#endif
break;
}
case CMD_QUERY_STATIC_GATE_SENS: {
#ifdef USE_NUMBER
std::vector<std::function<void(void)>> updates;
updates.reserve(this->gate_still_threshold_numbers_.size());
for (size_t i = 0; i < this->gate_still_threshold_numbers_.size(); i++) {
updates.push_back(set_number_value(this->gate_still_threshold_numbers_[i], this->buffer_data_[10 + i]));
}
for (auto &update : updates) {
update();
}
#endif
break;
}
case CMD_QUERY_BASIC_CONF: // Query parameters response
{
#ifdef USE_NUMBER
/*
Moving distance range: 9th byte
Still distance range: 10th byte
*/
std::vector<std::function<void(void)>> updates;
updates.push_back(set_number_value(this->min_distance_gate_number_, this->buffer_data_[10]));
updates.push_back(set_number_value(this->max_distance_gate_number_, this->buffer_data_[11] - 1));
ESP_LOGV(TAG, "min_distance_gate_number_: %u, max_distance_gate_number_ %u", this->buffer_data_[10],
this->buffer_data_[11]);
/*
None Duration: 11~12th bytes
*/
updates.push_back(set_number_value(this->timeout_number_,
ld2412::two_byte_to_int(this->buffer_data_[12], this->buffer_data_[13])));
ESP_LOGV(TAG, "timeout_number_: %u", ld2412::two_byte_to_int(this->buffer_data_[12], this->buffer_data_[13]));
/*
Output pin configuration: 13th bytes
*/
this->out_pin_level_ = this->buffer_data_[14];
#ifdef USE_SELECT
const auto *out_pin_level_str = find_str(OUT_PIN_LEVELS_BY_UINT, this->out_pin_level_);
if (this->out_pin_level_select_ != nullptr) {
this->out_pin_level_select_->publish_state(out_pin_level_str);
}
#endif
for (auto &update : updates) {
update();
}
#endif
} break;
default:
break;
}
return true;
}
void LD2412Component::readline_(int readch) {
if (readch < 0) {
return; // No data available
}
if (this->buffer_pos_ < HEADER_FOOTER_SIZE && readch != DATA_FRAME_HEADER[this->buffer_pos_] &&
readch != CMD_FRAME_HEADER[this->buffer_pos_]) {
this->buffer_pos_ = 0;
return;
}
if (this->buffer_pos_ < MAX_LINE_LENGTH - 1) {
this->buffer_data_[this->buffer_pos_++] = readch;
this->buffer_data_[this->buffer_pos_] = 0;
} else {
// We should never get here, but just in case...
ESP_LOGW(TAG, "Max command length exceeded; ignoring");
this->buffer_pos_ = 0;
}
if (this->buffer_pos_ < 4) {
return; // Not enough data to process yet
}
if (ld2412::validate_header_footer(DATA_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
ESP_LOGV(TAG, "Handling Periodic Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
this->handle_periodic_data_();
this->buffer_pos_ = 0; // Reset position index for next message
} else if (ld2412::validate_header_footer(CMD_FRAME_FOOTER, &this->buffer_data_[this->buffer_pos_ - 4])) {
ESP_LOGV(TAG, "Handling Ack Data: %s", format_hex_pretty(this->buffer_data_, this->buffer_pos_).c_str());
if (this->handle_ack_data_()) {
this->buffer_pos_ = 0; // Reset position index for next message
} else {
ESP_LOGV(TAG, "Ack Data incomplete");
}
}
}
void LD2412Component::set_config_mode_(bool enable) {
const uint8_t cmd = enable ? CMD_ENABLE_CONF : CMD_DISABLE_CONF;
const uint8_t cmd_value[2] = {0x01, 0x00};
this->send_command_(cmd, enable ? cmd_value : nullptr, sizeof(cmd_value));
}
void LD2412Component::set_bluetooth(bool enable) {
this->set_config_mode_(true);
const uint8_t cmd_value[2] = {enable ? (uint8_t) 0x01 : (uint8_t) 0x00, 0x00};
this->send_command_(CMD_BLUETOOTH, cmd_value, sizeof(cmd_value));
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
}
void LD2412Component::set_distance_resolution(const std::string &state) {
this->set_config_mode_(true);
const uint8_t cmd_value[6] = {find_uint8(DISTANCE_RESOLUTIONS_BY_STR, state), 0x00, 0x00, 0x00, 0x00, 0x00};
this->send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value, sizeof(cmd_value));
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
}
void LD2412Component::set_baud_rate(const std::string &state) {
this->set_config_mode_(true);
const uint8_t cmd_value[2] = {find_uint8(BAUD_RATES_BY_STR, state), 0x00};
this->send_command_(CMD_SET_BAUD_RATE, cmd_value, sizeof(cmd_value));
this->set_timeout(200, [this]() { this->restart_(); });
}
void LD2412Component::query_dynamic_background_correction_() {
this->send_command_(CMD_QUERY_DYNAMIC_BACKGROUND_CORRECTION, nullptr, 0);
}
void LD2412Component::start_dynamic_background_correction() {
if (this->dynamic_background_correction_active_) {
return; // Already in progress
}
#ifdef USE_BINARY_SENSOR
if (this->dynamic_background_correction_status_binary_sensor_ != nullptr) {
this->dynamic_background_correction_status_binary_sensor_->publish_state(true);
}
#endif
this->dynamic_background_correction_active_ = true;
this->set_config_mode_(true);
this->send_command_(CMD_DYNAMIC_BACKGROUND_CORRECTION, nullptr, 0);
this->set_config_mode_(false);
}
void LD2412Component::set_engineering_mode(bool enable) {
const uint8_t cmd = enable ? CMD_ENABLE_ENG : CMD_DISABLE_ENG;
this->set_config_mode_(true);
this->send_command_(cmd, nullptr, 0);
this->set_config_mode_(false);
}
void LD2412Component::factory_reset() {
this->set_config_mode_(true);
this->send_command_(CMD_FACTORY_RESET, nullptr, 0);
this->set_timeout(2000, [this]() { this->restart_and_read_all_info(); });
}
void LD2412Component::restart_() { this->send_command_(CMD_RESTART, nullptr, 0); }
void LD2412Component::query_parameters_() { this->send_command_(CMD_QUERY_BASIC_CONF, nullptr, 0); }
void LD2412Component::get_version_() { this->send_command_(CMD_QUERY_VERSION, nullptr, 0); }
void LD2412Component::get_mac_() {
const uint8_t cmd_value[2] = {0x01, 0x00};
this->send_command_(CMD_QUERY_MAC_ADDRESS, cmd_value, sizeof(cmd_value));
}
void LD2412Component::get_distance_resolution_() { this->send_command_(CMD_QUERY_DISTANCE_RESOLUTION, nullptr, 0); }
void LD2412Component::query_light_control_() { this->send_command_(CMD_QUERY_LIGHT_CONTROL, nullptr, 0); }
void LD2412Component::set_basic_config() {
#ifdef USE_NUMBER
if (!this->min_distance_gate_number_->has_state() || !this->max_distance_gate_number_->has_state() ||
!this->timeout_number_->has_state()) {
return;
}
#endif
#ifdef USE_SELECT
if (!this->out_pin_level_select_->has_state()) {
return;
}
#endif
uint8_t value[5] = {
#ifdef USE_NUMBER
lowbyte(static_cast<int>(this->min_distance_gate_number_->state)),
lowbyte(static_cast<int>(this->max_distance_gate_number_->state) + 1),
lowbyte(static_cast<int>(this->timeout_number_->state)),
highbyte(static_cast<int>(this->timeout_number_->state)),
#else
1, TOTAL_GATES, DEFAULT_PRESENCE_TIMEOUT, 0,
#endif
#ifdef USE_SELECT
find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->state),
#else
0x01, // Default value if not using select
#endif
};
this->set_config_mode_(true);
this->send_command_(CMD_BASIC_CONF, value, sizeof(value));
this->set_config_mode_(false);
}
#ifdef USE_NUMBER
void LD2412Component::set_gate_threshold() {
if (this->gate_move_threshold_numbers_.empty() && this->gate_still_threshold_numbers_.empty()) {
return; // No gate threshold numbers set; nothing to do here
}
uint8_t value[TOTAL_GATES] = {0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0};
this->set_config_mode_(true);
if (!this->gate_move_threshold_numbers_.empty()) {
for (size_t i = 0; i < this->gate_move_threshold_numbers_.size(); i++) {
value[i] = lowbyte(static_cast<int>(this->gate_move_threshold_numbers_[i]->state));
}
this->send_command_(CMD_MOTION_GATE_SENS, value, sizeof(value));
}
if (!this->gate_still_threshold_numbers_.empty()) {
for (size_t i = 0; i < this->gate_still_threshold_numbers_.size(); i++) {
value[i] = lowbyte(static_cast<int>(this->gate_still_threshold_numbers_[i]->state));
}
this->send_command_(CMD_STATIC_GATE_SENS, value, sizeof(value));
}
this->set_config_mode_(false);
}
void LD2412Component::get_gate_threshold() {
this->send_command_(CMD_QUERY_MOTION_GATE_SENS, nullptr, 0);
this->send_command_(CMD_QUERY_STATIC_GATE_SENS, nullptr, 0);
}
void LD2412Component::set_gate_still_threshold_number(uint8_t gate, number::Number *n) {
this->gate_still_threshold_numbers_[gate] = n;
}
void LD2412Component::set_gate_move_threshold_number(uint8_t gate, number::Number *n) {
this->gate_move_threshold_numbers_[gate] = n;
}
#endif
void LD2412Component::set_light_out_control() {
#ifdef USE_NUMBER
if (this->light_threshold_number_ != nullptr && this->light_threshold_number_->has_state()) {
this->light_threshold_ = static_cast<uint8_t>(this->light_threshold_number_->state);
}
#endif
#ifdef USE_SELECT
if (this->light_function_select_ != nullptr && this->light_function_select_->has_state()) {
this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->state);
}
#endif
uint8_t value[2] = {this->light_function_, this->light_threshold_};
this->set_config_mode_(true);
this->send_command_(CMD_SET_LIGHT_CONTROL, value, sizeof(value));
this->query_light_control_();
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
}
#ifdef USE_SENSOR
// These could leak memory, but they are only set once prior to 'setup()' and should never be used again.
void LD2412Component::set_gate_move_sensor(uint8_t gate, sensor::Sensor *s) {
this->gate_move_sensors_[gate] = new SensorWithDedup<uint8_t>(s);
}
void LD2412Component::set_gate_still_sensor(uint8_t gate, sensor::Sensor *s) {
this->gate_still_sensors_[gate] = new SensorWithDedup<uint8_t>(s);
}
#endif
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,141 @@
#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
#ifdef USE_NUMBER
#include "esphome/components/number/number.h"
#endif
#ifdef USE_SWITCH
#include "esphome/components/switch/switch.h"
#endif
#ifdef USE_BUTTON
#include "esphome/components/button/button.h"
#endif
#ifdef USE_SELECT
#include "esphome/components/select/select.h"
#endif
#ifdef USE_TEXT_SENSOR
#include "esphome/components/text_sensor/text_sensor.h"
#endif
#include "esphome/components/ld24xx/ld24xx.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/automation.h"
#include "esphome/core/helpers.h"
#include <array>
namespace esphome {
namespace ld2412 {
using namespace ld24xx;
static constexpr uint8_t MAX_LINE_LENGTH = 54; // Max characters for serial buffer
static constexpr uint8_t TOTAL_GATES = 14; // Total number of gates supported by the LD2412
class LD2412Component : public Component, public uart::UARTDevice {
#ifdef USE_BINARY_SENSOR
SUB_BINARY_SENSOR(dynamic_background_correction_status)
SUB_BINARY_SENSOR(moving_target)
SUB_BINARY_SENSOR(still_target)
SUB_BINARY_SENSOR(target)
#endif
#ifdef USE_SENSOR
SUB_SENSOR_WITH_DEDUP(light, uint8_t)
SUB_SENSOR_WITH_DEDUP(detection_distance, int)
SUB_SENSOR_WITH_DEDUP(moving_target_distance, int)
SUB_SENSOR_WITH_DEDUP(moving_target_energy, uint8_t)
SUB_SENSOR_WITH_DEDUP(still_target_distance, int)
SUB_SENSOR_WITH_DEDUP(still_target_energy, uint8_t)
#endif
#ifdef USE_TEXT_SENSOR
SUB_TEXT_SENSOR(mac)
SUB_TEXT_SENSOR(version)
#endif
#ifdef USE_NUMBER
SUB_NUMBER(light_threshold)
SUB_NUMBER(max_distance_gate)
SUB_NUMBER(min_distance_gate)
SUB_NUMBER(timeout)
#endif
#ifdef USE_SELECT
SUB_SELECT(baud_rate)
SUB_SELECT(distance_resolution)
SUB_SELECT(light_function)
SUB_SELECT(out_pin_level)
#endif
#ifdef USE_SWITCH
SUB_SWITCH(bluetooth)
SUB_SWITCH(engineering_mode)
#endif
#ifdef USE_BUTTON
SUB_BUTTON(factory_reset)
SUB_BUTTON(query)
SUB_BUTTON(restart)
SUB_BUTTON(start_dynamic_background_correction)
#endif
public:
void setup() override;
void dump_config() override;
void loop() override;
void set_light_out_control();
void set_basic_config();
#ifdef USE_NUMBER
void set_gate_move_threshold_number(uint8_t gate, number::Number *n);
void set_gate_still_threshold_number(uint8_t gate, number::Number *n);
void set_gate_threshold();
void get_gate_threshold();
#endif
#ifdef USE_SENSOR
void set_gate_move_sensor(uint8_t gate, sensor::Sensor *s);
void set_gate_still_sensor(uint8_t gate, sensor::Sensor *s);
#endif
void set_engineering_mode(bool enable);
void read_all_info();
void restart_and_read_all_info();
void set_bluetooth(bool enable);
void set_distance_resolution(const std::string &state);
void set_baud_rate(const std::string &state);
void factory_reset();
void start_dynamic_background_correction();
protected:
void send_command_(uint8_t command_str, const uint8_t *command_value, uint8_t command_value_len);
void set_config_mode_(bool enable);
void handle_periodic_data_();
bool handle_ack_data_();
void readline_(int readch);
void query_parameters_();
void get_version_();
void get_mac_();
void get_distance_resolution_();
void query_light_control_();
void restart_();
void query_dynamic_background_correction_();
uint8_t light_function_ = 0;
uint8_t light_threshold_ = 0;
uint8_t out_pin_level_ = 0;
uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer
uint8_t buffer_data_[MAX_LINE_LENGTH];
uint8_t mac_address_[6] = {0, 0, 0, 0, 0, 0};
uint8_t version_[6] = {0, 0, 0, 0, 0, 0};
bool bluetooth_on_{false};
bool dynamic_background_correction_active_{false};
#ifdef USE_NUMBER
std::array<number::Number *, TOTAL_GATES> gate_move_threshold_numbers_{};
std::array<number::Number *, TOTAL_GATES> gate_still_threshold_numbers_{};
#endif
#ifdef USE_SENSOR
std::array<SensorWithDedup<uint8_t> *, TOTAL_GATES> gate_move_sensors_{};
std::array<SensorWithDedup<uint8_t> *, TOTAL_GATES> gate_still_sensors_{};
#endif
};
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,126 @@
import esphome.codegen as cg
from esphome.components import number
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_MOVE_THRESHOLD,
CONF_STILL_THRESHOLD,
CONF_TIMEOUT,
DEVICE_CLASS_DISTANCE,
DEVICE_CLASS_ILLUMINANCE,
DEVICE_CLASS_SIGNAL_STRENGTH,
ENTITY_CATEGORY_CONFIG,
ICON_LIGHTBULB,
ICON_MOTION_SENSOR,
ICON_TIMELAPSE,
UNIT_PERCENT,
UNIT_SECOND,
)
from .. import CONF_LD2412_ID, LD2412_ns, LD2412Component
GateThresholdNumber = LD2412_ns.class_("GateThresholdNumber", number.Number)
LightThresholdNumber = LD2412_ns.class_("LightThresholdNumber", number.Number)
MaxDistanceTimeoutNumber = LD2412_ns.class_("MaxDistanceTimeoutNumber", number.Number)
CONF_LIGHT_THRESHOLD = "light_threshold"
CONF_MAX_DISTANCE_GATE = "max_distance_gate"
CONF_MIN_DISTANCE_GATE = "min_distance_gate"
TIMEOUT_GROUP = "timeout"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(CONF_LIGHT_THRESHOLD): number.number_schema(
LightThresholdNumber,
device_class=DEVICE_CLASS_ILLUMINANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_LIGHTBULB,
),
cv.Optional(CONF_MAX_DISTANCE_GATE): number.number_schema(
MaxDistanceTimeoutNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Optional(CONF_MIN_DISTANCE_GATE): number.number_schema(
MaxDistanceTimeoutNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Optional(CONF_TIMEOUT): number.number_schema(
MaxDistanceTimeoutNumber,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_TIMELAPSE,
unit_of_measurement=UNIT_SECOND,
),
}
)
CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
{
cv.Optional(f"gate_{x}"): (
{
cv.Required(CONF_MOVE_THRESHOLD): number.number_schema(
GateThresholdNumber,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
unit_of_measurement=UNIT_PERCENT,
),
cv.Required(CONF_STILL_THRESHOLD): number.number_schema(
GateThresholdNumber,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
unit_of_measurement=UNIT_PERCENT,
),
}
)
for x in range(14)
}
)
async def to_code(config):
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
if light_threshold_config := config.get(CONF_LIGHT_THRESHOLD):
n = await number.new_number(
light_threshold_config, min_value=0, max_value=255, step=1
)
await cg.register_parented(n, config[CONF_LD2412_ID])
cg.add(LD2412_component.set_light_threshold_number(n))
if max_distance_gate_config := config.get(CONF_MAX_DISTANCE_GATE):
n = await number.new_number(
max_distance_gate_config, min_value=2, max_value=13, step=1
)
await cg.register_parented(n, config[CONF_LD2412_ID])
cg.add(LD2412_component.set_max_distance_gate_number(n))
if min_distance_gate_config := config.get(CONF_MIN_DISTANCE_GATE):
n = await number.new_number(
min_distance_gate_config, min_value=1, max_value=12, step=1
)
await cg.register_parented(n, config[CONF_LD2412_ID])
cg.add(LD2412_component.set_min_distance_gate_number(n))
for x in range(14):
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=100, step=1
)
await cg.register_parented(n, config[CONF_LD2412_ID])
cg.add(LD2412_component.set_gate_move_threshold_number(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=100, step=1
)
await cg.register_parented(n, config[CONF_LD2412_ID])
cg.add(LD2412_component.set_gate_still_threshold_number(x, n))
if timeout_config := config.get(CONF_TIMEOUT):
n = await number.new_number(timeout_config, min_value=0, max_value=900, step=1)
await cg.register_parented(n, config[CONF_LD2412_ID])
cg.add(LD2412_component.set_timeout_number(n))

View File

@@ -0,0 +1,14 @@
#include "gate_threshold_number.h"
namespace esphome {
namespace ld2412 {
GateThresholdNumber::GateThresholdNumber(uint8_t gate) : gate_(gate) {}
void GateThresholdNumber::control(float value) {
this->publish_state(value);
this->parent_->set_gate_threshold();
}
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,19 @@
#pragma once
#include "esphome/components/number/number.h"
#include "../ld2412.h"
namespace esphome {
namespace ld2412 {
class GateThresholdNumber : public number::Number, public Parented<LD2412Component> {
public:
GateThresholdNumber(uint8_t gate);
protected:
uint8_t gate_;
void control(float value) override;
};
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,12 @@
#include "light_threshold_number.h"
namespace esphome {
namespace ld2412 {
void LightThresholdNumber::control(float value) {
this->publish_state(value);
this->parent_->set_light_out_control();
}
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/number/number.h"
#include "../ld2412.h"
namespace esphome {
namespace ld2412 {
class LightThresholdNumber : public number::Number, public Parented<LD2412Component> {
public:
LightThresholdNumber() = default;
protected:
void control(float value) override;
};
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,12 @@
#include "max_distance_timeout_number.h"
namespace esphome {
namespace ld2412 {
void MaxDistanceTimeoutNumber::control(float value) {
this->publish_state(value);
this->parent_->set_basic_config();
}
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/number/number.h"
#include "../ld2412.h"
namespace esphome {
namespace ld2412 {
class MaxDistanceTimeoutNumber : public number::Number, public Parented<LD2412Component> {
public:
MaxDistanceTimeoutNumber() = default;
protected:
void control(float value) override;
};
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,82 @@
import esphome.codegen as cg
from esphome.components import select
import esphome.config_validation as cv
from esphome.const import (
CONF_BAUD_RATE,
ENTITY_CATEGORY_CONFIG,
ICON_LIGHTBULB,
ICON_RULER,
ICON_SCALE,
ICON_THERMOMETER,
)
from .. import CONF_LD2412_ID, LD2412_ns, LD2412Component
BaudRateSelect = LD2412_ns.class_("BaudRateSelect", select.Select)
DistanceResolutionSelect = LD2412_ns.class_("DistanceResolutionSelect", select.Select)
LightOutControlSelect = LD2412_ns.class_("LightOutControlSelect", select.Select)
CONF_DISTANCE_RESOLUTION = "distance_resolution"
CONF_LIGHT_FUNCTION = "light_function"
CONF_OUT_PIN_LEVEL = "out_pin_level"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(CONF_BAUD_RATE): select.select_schema(
BaudRateSelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_THERMOMETER,
),
cv.Optional(CONF_DISTANCE_RESOLUTION): select.select_schema(
DistanceResolutionSelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RULER,
),
cv.Optional(CONF_LIGHT_FUNCTION): select.select_schema(
LightOutControlSelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_LIGHTBULB,
),
cv.Optional(CONF_OUT_PIN_LEVEL): select.select_schema(
LightOutControlSelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
),
}
async def to_code(config):
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
if baud_rate_config := config.get(CONF_BAUD_RATE):
s = await select.new_select(
baud_rate_config,
options=[
"9600",
"19200",
"38400",
"57600",
"115200",
"230400",
"256000",
"460800",
],
)
await cg.register_parented(s, config[CONF_LD2412_ID])
cg.add(LD2412_component.set_baud_rate_select(s))
if distance_resolution_config := config.get(CONF_DISTANCE_RESOLUTION):
s = await select.new_select(
distance_resolution_config, options=["0.2m", "0.5m", "0.75m"]
)
await cg.register_parented(s, config[CONF_LD2412_ID])
cg.add(LD2412_component.set_distance_resolution_select(s))
if light_function_config := config.get(CONF_LIGHT_FUNCTION):
s = await select.new_select(
light_function_config, options=["off", "below", "above"]
)
await cg.register_parented(s, config[CONF_LD2412_ID])
cg.add(LD2412_component.set_light_function_select(s))
if out_pin_level_config := config.get(CONF_OUT_PIN_LEVEL):
s = await select.new_select(out_pin_level_config, options=["low", "high"])
await cg.register_parented(s, config[CONF_LD2412_ID])
cg.add(LD2412_component.set_out_pin_level_select(s))

View File

@@ -0,0 +1,12 @@
#include "baud_rate_select.h"
namespace esphome {
namespace ld2412 {
void BaudRateSelect::control(const std::string &value) {
this->publish_state(value);
this->parent_->set_baud_rate(state);
}
} // namespace ld2412
} // namespace esphome

View File

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

View File

@@ -0,0 +1,12 @@
#include "distance_resolution_select.h"
namespace esphome {
namespace ld2412 {
void DistanceResolutionSelect::control(const std::string &value) {
this->publish_state(value);
this->parent_->set_distance_resolution(state);
}
} // namespace ld2412
} // namespace esphome

View File

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

View File

@@ -0,0 +1,12 @@
#include "light_out_control_select.h"
namespace esphome {
namespace ld2412 {
void LightOutControlSelect::control(const std::string &value) {
this->publish_state(value);
this->parent_->set_light_out_control();
}
} // namespace ld2412
} // namespace esphome

View File

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

View File

@@ -0,0 +1,124 @@
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_LIGHT,
CONF_MOVING_DISTANCE,
DEVICE_CLASS_DISTANCE,
DEVICE_CLASS_ILLUMINANCE,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_FLASH,
ICON_LIGHTBULB,
ICON_MOTION_SENSOR,
ICON_SIGNAL,
UNIT_CENTIMETER,
UNIT_EMPTY,
UNIT_PERCENT,
)
from . import CONF_LD2412_ID, LD2412Component
DEPENDENCIES = ["ld2412"]
CONF_DETECTION_DISTANCE = "detection_distance"
CONF_MOVE_ENERGY = "move_energy"
CONF_MOVING_ENERGY = "moving_energy"
CONF_STILL_DISTANCE = "still_distance"
CONF_STILL_ENERGY = "still_energy"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(CONF_DETECTION_DISTANCE): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE,
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_SIGNAL,
unit_of_measurement=UNIT_CENTIMETER,
),
cv.Optional(CONF_LIGHT): sensor.sensor_schema(
device_class=DEVICE_CLASS_ILLUMINANCE,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_LIGHTBULB,
unit_of_measurement=UNIT_EMPTY, # No standard unit for this light sensor
),
cv.Optional(CONF_MOVING_DISTANCE): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE,
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_SIGNAL,
unit_of_measurement=UNIT_CENTIMETER,
),
cv.Optional(CONF_MOVING_ENERGY): sensor.sensor_schema(
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_MOTION_SENSOR,
unit_of_measurement=UNIT_PERCENT,
),
cv.Optional(CONF_STILL_DISTANCE): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE,
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_SIGNAL,
unit_of_measurement=UNIT_CENTIMETER,
),
cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema(
filters=[{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}],
icon=ICON_FLASH,
unit_of_measurement=UNIT_PERCENT,
),
}
)
CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
{
cv.Optional(f"gate_{x}"): (
{
cv.Optional(CONF_MOVE_ENERGY): sensor.sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
filters=[
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}
],
icon=ICON_MOTION_SENSOR,
unit_of_measurement=UNIT_PERCENT,
),
cv.Optional(CONF_STILL_ENERGY): sensor.sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
filters=[
{"throttle_with_priority": cv.TimePeriod(milliseconds=1000)}
],
icon=ICON_FLASH,
unit_of_measurement=UNIT_PERCENT,
),
}
)
for x in range(14)
}
)
async def to_code(config):
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
if detection_distance_config := config.get(CONF_DETECTION_DISTANCE):
sens = await sensor.new_sensor(detection_distance_config)
cg.add(LD2412_component.set_detection_distance_sensor(sens))
if light_config := config.get(CONF_LIGHT):
sens = await sensor.new_sensor(light_config)
cg.add(LD2412_component.set_light_sensor(sens))
if moving_distance_config := config.get(CONF_MOVING_DISTANCE):
sens = await sensor.new_sensor(moving_distance_config)
cg.add(LD2412_component.set_moving_target_distance_sensor(sens))
if moving_energy_config := config.get(CONF_MOVING_ENERGY):
sens = await sensor.new_sensor(moving_energy_config)
cg.add(LD2412_component.set_moving_target_energy_sensor(sens))
if still_distance_config := config.get(CONF_STILL_DISTANCE):
sens = await sensor.new_sensor(still_distance_config)
cg.add(LD2412_component.set_still_target_distance_sensor(sens))
if still_energy_config := config.get(CONF_STILL_ENERGY):
sens = await sensor.new_sensor(still_energy_config)
cg.add(LD2412_component.set_still_target_energy_sensor(sens))
for x in range(14):
if gate_conf := config.get(f"gate_{x}"):
if move_config := gate_conf.get(CONF_MOVE_ENERGY):
sens = await sensor.new_sensor(move_config)
cg.add(LD2412_component.set_gate_move_sensor(x, sens))
if still_config := gate_conf.get(CONF_STILL_ENERGY):
sens = await sensor.new_sensor(still_config)
cg.add(LD2412_component.set_gate_still_sensor(x, sens))

View File

@@ -0,0 +1,45 @@
import esphome.codegen as cg
from esphome.components import switch
import esphome.config_validation as cv
from esphome.const import (
CONF_BLUETOOTH,
DEVICE_CLASS_SWITCH,
ENTITY_CATEGORY_CONFIG,
ICON_BLUETOOTH,
ICON_PULSE,
)
from .. import CONF_LD2412_ID, LD2412_ns, LD2412Component
BluetoothSwitch = LD2412_ns.class_("BluetoothSwitch", switch.Switch)
EngineeringModeSwitch = LD2412_ns.class_("EngineeringModeSwitch", switch.Switch)
CONF_ENGINEERING_MODE = "engineering_mode"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(CONF_BLUETOOTH): switch.switch_schema(
BluetoothSwitch,
device_class=DEVICE_CLASS_SWITCH,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_BLUETOOTH,
),
cv.Optional(CONF_ENGINEERING_MODE): switch.switch_schema(
EngineeringModeSwitch,
device_class=DEVICE_CLASS_SWITCH,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_PULSE,
),
}
async def to_code(config):
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
if bluetooth_config := config.get(CONF_BLUETOOTH):
s = await switch.new_switch(bluetooth_config)
await cg.register_parented(s, config[CONF_LD2412_ID])
cg.add(LD2412_component.set_bluetooth_switch(s))
if engineering_mode_config := config.get(CONF_ENGINEERING_MODE):
s = await switch.new_switch(engineering_mode_config)
await cg.register_parented(s, config[CONF_LD2412_ID])
cg.add(LD2412_component.set_engineering_mode_switch(s))

View File

@@ -0,0 +1,12 @@
#include "bluetooth_switch.h"
namespace esphome {
namespace ld2412 {
void BluetoothSwitch::write_state(bool state) {
this->publish_state(state);
this->parent_->set_bluetooth(state);
}
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "../ld2412.h"
namespace esphome {
namespace ld2412 {
class BluetoothSwitch : public switch_::Switch, public Parented<LD2412Component> {
public:
BluetoothSwitch() = default;
protected:
void write_state(bool state) override;
};
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,12 @@
#include "engineering_mode_switch.h"
namespace esphome {
namespace ld2412 {
void EngineeringModeSwitch::write_state(bool state) {
this->publish_state(state);
this->parent_->set_engineering_mode(state);
}
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "../ld2412.h"
namespace esphome {
namespace ld2412 {
class EngineeringModeSwitch : public switch_::Switch, public Parented<LD2412Component> {
public:
EngineeringModeSwitch() = default;
protected:
void write_state(bool state) override;
};
} // namespace ld2412
} // namespace esphome

View File

@@ -0,0 +1,34 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_MAC_ADDRESS,
CONF_VERSION,
ENTITY_CATEGORY_DIAGNOSTIC,
ICON_BLUETOOTH,
ICON_CHIP,
)
from . import CONF_LD2412_ID, LD2412Component
DEPENDENCIES = ["ld2412"]
CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(CONF_VERSION): text_sensor.text_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_CHIP
),
cv.Optional(CONF_MAC_ADDRESS): text_sensor.text_sensor_schema(
entity_category=ENTITY_CATEGORY_DIAGNOSTIC, icon=ICON_BLUETOOTH
),
}
async def to_code(config):
LD2412_component = await cg.get_variable(config[CONF_LD2412_ID])
if version_config := config.get(CONF_VERSION):
sens = await text_sensor.new_text_sensor(version_config)
cg.add(LD2412_component.set_version_text_sensor(sens))
if mac_address_config := config.get(CONF_MAC_ADDRESS):
sens = await text_sensor.new_text_sensor(mac_address_config)
cg.add(LD2412_component.set_mac_text_sensor(sens))

View File

@@ -0,0 +1,233 @@
uart:
- id: uart_ld2412
tx_pin: ${tx_pin}
rx_pin: ${rx_pin}
baud_rate: 9600
ld2412:
id: my_ld2412
binary_sensor:
- platform: ld2412
dynamic_background_correction_status:
name: Dynamic Background Correction Status
has_target:
name: Presence
has_moving_target:
name: Moving Target
has_still_target:
name: Still Target
button:
- platform: ld2412
factory_reset:
name: Factory reset
restart:
name: Restart
query_params:
name: Query params
start_dynamic_background_correction:
name: Start Dynamic Background Correction
number:
- platform: ld2412
light_threshold:
name: Light Threshold
timeout:
name: Presence timeout
min_distance_gate:
name: Minimum distance gate
max_distance_gate:
name: Maximum distance gate
gate_0:
move_threshold:
name: Gate 0 Move Threshold
still_threshold:
name: Gate 0 Still Threshold
gate_1:
move_threshold:
name: Gate 1 Move Threshold
still_threshold:
name: Gate 1 Still Threshold
gate_2:
move_threshold:
name: Gate 2 Move Threshold
still_threshold:
name: Gate 2 Still Threshold
gate_3:
move_threshold:
name: Gate 3 Move Threshold
still_threshold:
name: Gate 3 Still Threshold
gate_4:
move_threshold:
name: Gate 4 Move Threshold
still_threshold:
name: Gate 4 Still Threshold
gate_5:
move_threshold:
name: Gate 5 Move Threshold
still_threshold:
name: Gate 5 Still Threshold
gate_6:
move_threshold:
name: Gate 6 Move Threshold
still_threshold:
name: Gate 6 Still Threshold
gate_7:
move_threshold:
name: Gate 7 Move Threshold
still_threshold:
name: Gate 7 Still Threshold
gate_8:
move_threshold:
name: Gate 8 Move Threshold
still_threshold:
name: Gate 8 Still Threshold
gate_9:
move_threshold:
name: Gate 9 Move Threshold
still_threshold:
name: Gate 9 Still Threshold
gate_10:
move_threshold:
name: Gate 10 Move Threshold
still_threshold:
name: Gate 10 Still Threshold
gate_11:
move_threshold:
name: Gate 11 Move Threshold
still_threshold:
name: Gate 11 Still Threshold
gate_12:
move_threshold:
name: Gate 12 Move Threshold
still_threshold:
name: Gate 12 Still Threshold
gate_13:
move_threshold:
name: Gate 13 Move Threshold
still_threshold:
name: Gate 13 Still Threshold
select:
- platform: ld2412
light_function:
name: Light Function
out_pin_level:
name: Hardware output pin level
distance_resolution:
name: Distance resolution
baud_rate:
name: Baud rate
on_value:
- delay: 3s
- lambda: |-
id(uart_ld2412).flush();
uint32_t new_baud_rate = stoi(x);
ESP_LOGD("change_baud_rate", "Changing baud rate from %i to %i",id(uart_ld2412).get_baud_rate(), new_baud_rate);
if (id(uart_ld2412).get_baud_rate() != new_baud_rate) {
id(uart_ld2412).set_baud_rate(new_baud_rate);
#if defined(USE_ESP8266) || defined(USE_ESP32)
id(uart_ld2412).load_settings();
#endif
}
sensor:
- platform: ld2412
light:
name: Light
moving_distance:
name: Moving Distance
still_distance:
name: Still Distance
moving_energy:
name: Move Energy
still_energy:
name: Still Energy
detection_distance:
name: Detection Distance
gate_0:
move_energy:
name: Gate 0 Move Energy
still_energy:
name: Gate 0 Still Energy
gate_1:
move_energy:
name: Gate 1 Move Energy
still_energy:
name: Gate 1 Still Energy
gate_2:
move_energy:
name: Gate 2 Move Energy
still_energy:
name: Gate 2 Still Energy
gate_3:
move_energy:
name: Gate 3 Move Energy
still_energy:
name: Gate 3 Still Energy
gate_4:
move_energy:
name: Gate 4 Move Energy
still_energy:
name: Gate 4 Still Energy
gate_5:
move_energy:
name: Gate 5 Move Energy
still_energy:
name: Gate 5 Still Energy
gate_6:
move_energy:
name: Gate 6 Move Energy
still_energy:
name: Gate 6 Still Energy
gate_7:
move_energy:
name: Gate 7 Move Energy
still_energy:
name: Gate 7 Still Energy
gate_8:
move_energy:
name: Gate 8 Move Energy
still_energy:
name: Gate 8 Still Energy
gate_9:
move_energy:
name: Gate 9 Move Energy
still_energy:
name: Gate 9 Still Energy
gate_10:
move_energy:
name: Gate 10 Move Energy
still_energy:
name: Gate 10 Still Energy
gate_11:
move_energy:
name: Gate 11 Move Energy
still_energy:
name: Gate 11 Still Energy
gate_12:
move_energy:
name: Gate 12 Move Energy
still_energy:
name: Gate 12 Still Energy
gate_13:
move_energy:
name: Gate 13 Move Energy
still_energy:
name: Gate 13 Still Energy
switch:
- platform: ld2412
bluetooth:
name: Bluetooth
engineering_mode:
name: Engineering Mode
text_sensor:
- platform: ld2412
version:
name: Firmware version
mac_address:
name: MAC address

View File

@@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO17
rx_pin: GPIO16
<<: !include common.yaml

View File

@@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO4
rx_pin: GPIO5
<<: !include common.yaml

View File

@@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO4
rx_pin: GPIO5
<<: !include common.yaml

View File

@@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO17
rx_pin: GPIO16
<<: !include common.yaml

View File

@@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO4
rx_pin: GPIO5
<<: !include common.yaml

View File

@@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO4
rx_pin: GPIO5
<<: !include common.yaml