[ld2450] Add new component (#5674)
CI / Create common environment (push) Waiting to run
CI / Check ruff (push) Blocked by required conditions
CI / Check flake8 (push) Blocked by required conditions
CI / Check pylint (push) Blocked by required conditions
CI / Check pyupgrade (push) Blocked by required conditions
CI / Run script/ci-custom (push) Blocked by required conditions
CI / Run pytest (macOS-latest, 3.11) (push) Blocked by required conditions
CI / Run pytest (ubuntu-latest, 3.10) (push) Blocked by required conditions
CI / Run pytest (ubuntu-latest, 3.11) (push) Blocked by required conditions
CI / Run pytest (ubuntu-latest, 3.12) (push) Blocked by required conditions
CI / Run pytest (ubuntu-latest, 3.9) (push) Blocked by required conditions
CI / Run pytest (windows-latest, 3.11) (push) Blocked by required conditions
CI / Check clang-format (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 1/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 2/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 3/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 Arduino 4/4 (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP32 IDF (push) Blocked by required conditions
CI / Run script/clang-tidy for ESP8266 (push) Blocked by required conditions
CI / list-components (push) Blocked by required conditions
CI / Component test ${{ matrix.file }} (push) Blocked by required conditions
CI / Split components for testing into 20 groups maximum (push) Blocked by required conditions
CI / Test split components (push) Blocked by required conditions
CI / CI Status (push) Blocked by required conditions
YAML lint / yamllint (push) Waiting to run
CodeQL Advanced / Analyze (python) (push) Has been cancelled

Co-authored-by: Jesse Hills <3060199+jesserockz@users.noreply.github.com>
Co-authored-by: Marcus Better <marcus@better.se>
Co-authored-by: Trevor Schirmer <24777085+TrevorSchirmer@users.noreply.github.com>
Co-authored-by: Keith Burzinski <kbx81x@gmail.com>
This commit is contained in:
Hareesh M U
2025-02-20 14:46:08 +05:30
committed by GitHub
parent 3020083564
commit bf739506c3
34 changed files with 2116 additions and 0 deletions
+1
View File
@@ -234,6 +234,7 @@ esphome/components/kuntze/* @ssieb
esphome/components/lcd_menu/* @numo68
esphome/components/ld2410/* @regevbr @sebcaps
esphome/components/ld2420/* @descipher
esphome/components/ld2450/* @hareeshmu
esphome/components/ledc/* @OttoWinter
esphome/components/libretiny/* @kuba2k2
esphome/components/libretiny_pwm/* @kuba2k2
+51
View File
@@ -0,0 +1,51 @@
import esphome.codegen as cg
import esphome.config_validation as cv
from esphome.components import uart
from esphome.const import (
CONF_ID,
CONF_THROTTLE,
)
DEPENDENCIES = ["uart"]
CODEOWNERS = ["@hareeshmu"]
MULTI_CONF = True
ld2450_ns = cg.esphome_ns.namespace("ld2450")
LD2450Component = ld2450_ns.class_("LD2450Component", cg.Component, uart.UARTDevice)
CONF_LD2450_ID = "ld2450_id"
CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(LD2450Component),
cv.Optional(CONF_THROTTLE, default="1000ms"): cv.All(
cv.positive_time_period_milliseconds,
cv.Range(min=cv.TimePeriod(milliseconds=1)),
),
}
)
.extend(uart.UART_DEVICE_SCHEMA)
.extend(cv.COMPONENT_SCHEMA)
)
LD2450BaseSchema = cv.Schema(
{
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
},
)
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
"ld2450",
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_throttle(config[CONF_THROTTLE]))
@@ -0,0 +1,47 @@
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,
)
from . import CONF_LD2450_ID, LD2450Component
DEPENDENCIES = ["ld2450"]
ICON_MEDITATION = "mdi:meditation"
ICON_SHIELD_ACCOUNT = "mdi:shield-account"
ICON_TARGET_ACCOUNT = "mdi:target-account"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
cv.Optional(CONF_HAS_TARGET): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_OCCUPANCY,
icon=ICON_SHIELD_ACCOUNT,
),
cv.Optional(CONF_HAS_MOVING_TARGET): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_MOTION,
icon=ICON_TARGET_ACCOUNT,
),
cv.Optional(CONF_HAS_STILL_TARGET): binary_sensor.binary_sensor_schema(
device_class=DEVICE_CLASS_OCCUPANCY,
icon=ICON_MEDITATION,
),
}
async def to_code(config):
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
if has_target_config := config.get(CONF_HAS_TARGET):
sens = await binary_sensor.new_binary_sensor(has_target_config)
cg.add(ld2450_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(ld2450_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(ld2450_component.set_still_target_binary_sensor(sens))
@@ -0,0 +1,45 @@
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_RESTART,
ICON_RESTART_ALERT,
)
from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns
ResetButton = ld2450_ns.class_("ResetButton", button.Button)
RestartButton = ld2450_ns.class_("RestartButton", button.Button)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
ResetButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART_ALERT,
),
cv.Optional(CONF_RESTART): button.button_schema(
RestartButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_RESTART,
),
}
async def to_code(config):
ld2450_component = await cg.get_variable(config[CONF_LD2450_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_LD2450_ID])
cg.add(ld2450_component.set_reset_button(b))
if restart_config := config.get(CONF_RESTART):
b = await button.new_button(restart_config)
await cg.register_parented(b, config[CONF_LD2450_ID])
cg.add(ld2450_component.set_restart_button(b))
@@ -0,0 +1,9 @@
#include "reset_button.h"
namespace esphome {
namespace ld2450 {
void ResetButton::press_action() { this->parent_->factory_reset(); }
} // namespace ld2450
} // namespace esphome
@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2450.h"
namespace esphome {
namespace ld2450 {
class ResetButton : public button::Button, public Parented<LD2450Component> {
public:
ResetButton() = default;
protected:
void press_action() override;
};
} // namespace ld2450
} // namespace esphome
@@ -0,0 +1,9 @@
#include "restart_button.h"
namespace esphome {
namespace ld2450 {
void RestartButton::press_action() { this->parent_->restart_and_read_all_info(); }
} // namespace ld2450
} // namespace esphome
@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2450.h"
namespace esphome {
namespace ld2450 {
class RestartButton : public button::Button, public Parented<LD2450Component> {
public:
RestartButton() = default;
protected:
void press_action() override;
};
} // namespace ld2450
} // namespace esphome
File diff suppressed because it is too large Load Diff
+231
View File
@@ -0,0 +1,231 @@
#pragma once
#include <iomanip>
#include <map>
#include "esphome/components/uart/uart.h"
#include "esphome/core/component.h"
#include "esphome/core/defines.h"
#include "esphome/core/helpers.h"
#include "esphome/core/preferences.h"
#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
#ifdef USE_BINARY_SENSOR
#include "esphome/components/binary_sensor/binary_sensor.h"
#endif
#ifndef M_PI
#define M_PI 3.14
#endif
namespace esphome {
namespace ld2450 {
// Constants
static const uint8_t DEFAULT_PRESENCE_TIMEOUT = 5; // Timeout to reset presense status 5 sec.
static const uint8_t MAX_LINE_LENGTH = 60; // Max characters for serial buffer
static const uint8_t MAX_TARGETS = 3; // Max 3 Targets in LD2450
static const uint8_t MAX_ZONES = 3; // Max 3 Zones in LD2450
// Target coordinate struct
struct Target {
int16_t x;
int16_t y;
bool is_moving;
};
// Zone coordinate struct
struct Zone {
int16_t x1 = 0;
int16_t y1 = 0;
int16_t x2 = 0;
int16_t y2 = 0;
};
enum BaudRateStructure : 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
};
// Convert baud rate enum to int
static const std::map<std::string, uint8_t> BAUD_RATE_ENUM_TO_INT{
{"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}};
// Zone type struct
enum ZoneTypeStructure : uint8_t { ZONE_DISABLED = 0, ZONE_DETECTION = 1, ZONE_FILTER = 2 };
// Convert zone type int to enum
static const std::map<ZoneTypeStructure, std::string> ZONE_TYPE_INT_TO_ENUM{
{ZONE_DISABLED, "Disabled"}, {ZONE_DETECTION, "Detection"}, {ZONE_FILTER, "Filter"}};
// Convert zone type enum to int
static const std::map<std::string, uint8_t> ZONE_TYPE_ENUM_TO_INT{
{"Disabled", ZONE_DISABLED}, {"Detection", ZONE_DETECTION}, {"Filter", ZONE_FILTER}};
// LD2450 serial command header & footer
static const uint8_t CMD_FRAME_HEADER[4] = {0xFD, 0xFC, 0xFB, 0xFA};
static const uint8_t CMD_FRAME_END[4] = {0x04, 0x03, 0x02, 0x01};
enum PeriodicDataStructure : uint8_t {
TARGET_X = 4,
TARGET_Y = 6,
TARGET_SPEED = 8,
TARGET_RESOLUTION = 10,
};
enum PeriodicDataValue : uint8_t { HEAD = 0XAA, END = 0x55, CHECK = 0x00 };
enum AckDataStructure : uint8_t { COMMAND = 6, COMMAND_STATUS = 7 };
class LD2450Component : public Component, public uart::UARTDevice {
#ifdef USE_SENSOR
SUB_SENSOR(target_count)
SUB_SENSOR(still_target_count)
SUB_SENSOR(moving_target_count)
#endif
#ifdef USE_BINARY_SENSOR
SUB_BINARY_SENSOR(target)
SUB_BINARY_SENSOR(moving_target)
SUB_BINARY_SENSOR(still_target)
#endif
#ifdef USE_TEXT_SENSOR
SUB_TEXT_SENSOR(version)
SUB_TEXT_SENSOR(mac)
#endif
#ifdef USE_SELECT
SUB_SELECT(baud_rate)
SUB_SELECT(zone_type)
#endif
#ifdef USE_SWITCH
SUB_SWITCH(bluetooth)
SUB_SWITCH(multi_target)
#endif
#ifdef USE_BUTTON
SUB_BUTTON(reset)
SUB_BUTTON(restart)
#endif
#ifdef USE_NUMBER
SUB_NUMBER(presence_timeout)
#endif
public:
LD2450Component();
void setup() override;
void dump_config() override;
void loop() override;
void set_presence_timeout();
void set_throttle(uint16_t value) { this->throttle_ = value; };
void read_all_info();
void query_zone_info();
void restart_and_read_all_info();
void set_bluetooth(bool enable);
void set_multi_target(bool enable);
void set_baud_rate(const std::string &state);
void set_zone_type(const std::string &state);
void publish_zone_type();
void factory_reset();
#ifdef USE_TEXT_SENSOR
void set_direction_text_sensor(uint8_t target, text_sensor::TextSensor *s);
#endif
#ifdef USE_NUMBER
void set_zone_coordinate(uint8_t zone);
void set_zone_x1_number(uint8_t zone, number::Number *n);
void set_zone_y1_number(uint8_t zone, number::Number *n);
void set_zone_x2_number(uint8_t zone, number::Number *n);
void set_zone_y2_number(uint8_t zone, number::Number *n);
#endif
#ifdef USE_SENSOR
void set_move_x_sensor(uint8_t target, sensor::Sensor *s);
void set_move_y_sensor(uint8_t target, sensor::Sensor *s);
void set_move_speed_sensor(uint8_t target, sensor::Sensor *s);
void set_move_angle_sensor(uint8_t target, sensor::Sensor *s);
void set_move_distance_sensor(uint8_t target, sensor::Sensor *s);
void set_move_resolution_sensor(uint8_t target, sensor::Sensor *s);
void set_zone_target_count_sensor(uint8_t zone, sensor::Sensor *s);
void set_zone_still_target_count_sensor(uint8_t zone, sensor::Sensor *s);
void set_zone_moving_target_count_sensor(uint8_t zone, sensor::Sensor *s);
#endif
void reset_radar_zone();
void set_radar_zone(int32_t zone_type, int32_t zone1_x1, int32_t zone1_y1, int32_t zone1_x2, int32_t zone1_y2,
int32_t zone2_x1, int32_t zone2_y1, int32_t zone2_x2, int32_t zone2_y2, int32_t zone3_x1,
int32_t zone3_y1, int32_t zone3_x2, int32_t zone3_y2);
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_(uint8_t *buffer, uint8_t len);
bool handle_ack_data_(uint8_t *buffer, uint8_t len);
void process_zone_(uint8_t *buffer);
void readline_(int readch, uint8_t *buffer, uint8_t len);
void get_version_();
void get_mac_();
void query_target_tracking_mode_();
void query_zone_();
void restart_();
void send_set_zone_command_();
void save_to_flash_(float value);
float restore_from_flash_();
bool get_timeout_status_(uint32_t check_millis);
uint8_t count_targets_in_zone_(const Zone &zone, bool is_moving);
Target target_info_[MAX_TARGETS];
Zone zone_config_[MAX_ZONES];
uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer
uint8_t buffer_data_[MAX_LINE_LENGTH];
uint32_t last_periodic_millis_ = 0;
uint32_t presence_millis_ = 0;
uint32_t still_presence_millis_ = 0;
uint32_t moving_presence_millis_ = 0;
uint16_t throttle_ = 0;
uint16_t timeout_ = 5;
uint8_t zone_type_ = 0;
std::string version_{};
std::string mac_{};
#ifdef USE_NUMBER
ESPPreferenceObject pref_; // only used when numbers are in use
std::vector<number::Number *> zone_x1_numbers_ = std::vector<number::Number *>(MAX_ZONES);
std::vector<number::Number *> zone_y1_numbers_ = std::vector<number::Number *>(MAX_ZONES);
std::vector<number::Number *> zone_x2_numbers_ = std::vector<number::Number *>(MAX_ZONES);
std::vector<number::Number *> zone_y2_numbers_ = std::vector<number::Number *>(MAX_ZONES);
#endif
#ifdef USE_SENSOR
std::vector<sensor::Sensor *> move_x_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS);
std::vector<sensor::Sensor *> move_y_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS);
std::vector<sensor::Sensor *> move_speed_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS);
std::vector<sensor::Sensor *> move_angle_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS);
std::vector<sensor::Sensor *> move_distance_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS);
std::vector<sensor::Sensor *> move_resolution_sensors_ = std::vector<sensor::Sensor *>(MAX_TARGETS);
std::vector<sensor::Sensor *> zone_target_count_sensors_ = std::vector<sensor::Sensor *>(MAX_ZONES);
std::vector<sensor::Sensor *> zone_still_target_count_sensors_ = std::vector<sensor::Sensor *>(MAX_ZONES);
std::vector<sensor::Sensor *> zone_moving_target_count_sensors_ = std::vector<sensor::Sensor *>(MAX_ZONES);
#endif
#ifdef USE_TEXT_SENSOR
std::vector<text_sensor::TextSensor *> direction_text_sensors_ = std::vector<text_sensor::TextSensor *>(3);
#endif
};
} // namespace ld2450
} // namespace esphome
@@ -0,0 +1,120 @@
import esphome.codegen as cg
from esphome.components import number
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
DEVICE_CLASS_DISTANCE,
ENTITY_CATEGORY_CONFIG,
ICON_TIMELAPSE,
UNIT_MILLIMETER,
UNIT_SECOND,
)
from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns
CONF_PRESENCE_TIMEOUT = "presence_timeout"
CONF_X1 = "x1"
CONF_X2 = "x2"
CONF_Y1 = "y1"
CONF_Y2 = "y2"
ICON_ARROW_BOTTOM_RIGHT = "mdi:arrow-bottom-right"
ICON_ARROW_BOTTOM_RIGHT_BOLD_BOX_OUTLINE = "mdi:arrow-bottom-right-bold-box-outline"
ICON_ARROW_TOP_LEFT = "mdi:arrow-top-left"
ICON_ARROW_TOP_LEFT_BOLD_BOX_OUTLINE = "mdi:arrow-top-left-bold-box-outline"
MAX_ZONES = 3
PresenceTimeoutNumber = ld2450_ns.class_("PresenceTimeoutNumber", number.Number)
ZoneCoordinateNumber = ld2450_ns.class_("ZoneCoordinateNumber", number.Number)
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
cv.Required(CONF_PRESENCE_TIMEOUT): number.number_schema(
PresenceTimeoutNumber,
unit_of_measurement=UNIT_SECOND,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_TIMELAPSE,
),
}
)
CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
{
cv.Optional(f"zone_{n + 1}"): cv.Schema(
{
cv.Required(CONF_X1): number.number_schema(
ZoneCoordinateNumber,
device_class=DEVICE_CLASS_DISTANCE,
unit_of_measurement=UNIT_MILLIMETER,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_ARROW_TOP_LEFT_BOLD_BOX_OUTLINE,
),
cv.Required(CONF_Y1): number.number_schema(
ZoneCoordinateNumber,
device_class=DEVICE_CLASS_DISTANCE,
unit_of_measurement=UNIT_MILLIMETER,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_ARROW_TOP_LEFT,
),
cv.Required(CONF_X2): number.number_schema(
ZoneCoordinateNumber,
device_class=DEVICE_CLASS_DISTANCE,
unit_of_measurement=UNIT_MILLIMETER,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_ARROW_BOTTOM_RIGHT_BOLD_BOX_OUTLINE,
),
cv.Required(CONF_Y2): number.number_schema(
ZoneCoordinateNumber,
device_class=DEVICE_CLASS_DISTANCE,
unit_of_measurement=UNIT_MILLIMETER,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_ARROW_BOTTOM_RIGHT,
),
}
)
for n in range(MAX_ZONES)
}
)
async def to_code(config):
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
if presence_timeout_config := config.get(CONF_PRESENCE_TIMEOUT):
n = await number.new_number(
presence_timeout_config,
min_value=0,
max_value=3600,
step=1,
)
await cg.register_parented(n, config[CONF_LD2450_ID])
cg.add(ld2450_component.set_presence_timeout_number(n))
for x in range(MAX_ZONES):
if zone_conf := config.get(f"zone_{x + 1}"):
if zone_x1_config := zone_conf.get(CONF_X1):
n = cg.new_Pvariable(zone_x1_config[CONF_ID], x)
await number.register_number(
n, zone_x1_config, min_value=-4860, max_value=4860, step=1
)
await cg.register_parented(n, config[CONF_LD2450_ID])
cg.add(ld2450_component.set_zone_x1_number(x, n))
if zone_y1_config := zone_conf.get(CONF_Y1):
n = cg.new_Pvariable(zone_y1_config[CONF_ID], x)
await number.register_number(
n, zone_y1_config, min_value=0, max_value=7560, step=1
)
await cg.register_parented(n, config[CONF_LD2450_ID])
cg.add(ld2450_component.set_zone_y1_number(x, n))
if zone_x2_config := zone_conf.get(CONF_X2):
n = cg.new_Pvariable(zone_x2_config[CONF_ID], x)
await number.register_number(
n, zone_x2_config, min_value=-4860, max_value=4860, step=1
)
await cg.register_parented(n, config[CONF_LD2450_ID])
cg.add(ld2450_component.set_zone_x2_number(x, n))
if zone_y2_config := zone_conf.get(CONF_Y2):
n = cg.new_Pvariable(zone_y2_config[CONF_ID], x)
await number.register_number(
n, zone_y2_config, min_value=0, max_value=7560, step=1
)
await cg.register_parented(n, config[CONF_LD2450_ID])
cg.add(ld2450_component.set_zone_y2_number(x, n))
@@ -0,0 +1,12 @@
#include "presence_timeout_number.h"
namespace esphome {
namespace ld2450 {
void PresenceTimeoutNumber::control(float value) {
this->publish_state(value);
this->parent_->set_presence_timeout();
}
} // namespace ld2450
} // namespace esphome
@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/number/number.h"
#include "../ld2450.h"
namespace esphome {
namespace ld2450 {
class PresenceTimeoutNumber : public number::Number, public Parented<LD2450Component> {
public:
PresenceTimeoutNumber() = default;
protected:
void control(float value) override;
};
} // namespace ld2450
} // namespace esphome
@@ -0,0 +1,14 @@
#include "zone_coordinate_number.h"
namespace esphome {
namespace ld2450 {
ZoneCoordinateNumber::ZoneCoordinateNumber(uint8_t zone) : zone_(zone) {}
void ZoneCoordinateNumber::control(float value) {
this->publish_state(value);
this->parent_->set_zone_coordinate(this->zone_);
}
} // namespace ld2450
} // namespace esphome
@@ -0,0 +1,19 @@
#pragma once
#include "esphome/components/number/number.h"
#include "../ld2450.h"
namespace esphome {
namespace ld2450 {
class ZoneCoordinateNumber : public number::Number, public Parented<LD2450Component> {
public:
ZoneCoordinateNumber(uint8_t zone);
protected:
uint8_t zone_;
void control(float value) override;
};
} // namespace ld2450
} // namespace esphome
@@ -0,0 +1,56 @@
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_THERMOMETER
from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns
CONF_ZONE_TYPE = "zone_type"
BaudRateSelect = ld2450_ns.class_("BaudRateSelect", select.Select)
ZoneTypeSelect = ld2450_ns.class_("ZoneTypeSelect", select.Select)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
cv.Optional(CONF_BAUD_RATE): select.select_schema(
BaudRateSelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_THERMOMETER,
),
cv.Optional(CONF_ZONE_TYPE): select.select_schema(
ZoneTypeSelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_THERMOMETER,
),
}
async def to_code(config):
ld2450_component = await cg.get_variable(config[CONF_LD2450_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_LD2450_ID])
cg.add(ld2450_component.set_baud_rate_select(s))
if zone_type_config := config.get(CONF_ZONE_TYPE):
s = await select.new_select(
zone_type_config,
options=[
"Disabled",
"Detection",
"Filter",
],
)
await cg.register_parented(s, config[CONF_LD2450_ID])
cg.add(ld2450_component.set_zone_type_select(s))
@@ -0,0 +1,12 @@
#include "baud_rate_select.h"
namespace esphome {
namespace ld2450 {
void BaudRateSelect::control(const std::string &value) {
this->publish_state(value);
this->parent_->set_baud_rate(state);
}
} // namespace ld2450
} // namespace esphome
@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/select/select.h"
#include "../ld2450.h"
namespace esphome {
namespace ld2450 {
class BaudRateSelect : public select::Select, public Parented<LD2450Component> {
public:
BaudRateSelect() = default;
protected:
void control(const std::string &value) override;
};
} // namespace ld2450
} // namespace esphome
@@ -0,0 +1,12 @@
#include "zone_type_select.h"
namespace esphome {
namespace ld2450 {
void ZoneTypeSelect::control(const std::string &value) {
this->publish_state(value);
this->parent_->set_zone_type(state);
}
} // namespace ld2450
} // namespace esphome
@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/select/select.h"
#include "../ld2450.h"
namespace esphome {
namespace ld2450 {
class ZoneTypeSelect : public select::Select, public Parented<LD2450Component> {
public:
ZoneTypeSelect() = default;
protected:
void control(const std::string &value) override;
};
} // namespace ld2450
} // namespace esphome
+156
View File
@@ -0,0 +1,156 @@
import esphome.codegen as cg
from esphome.components import sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_ANGLE,
CONF_DISTANCE,
CONF_RESOLUTION,
CONF_SPEED,
DEVICE_CLASS_DISTANCE,
DEVICE_CLASS_SPEED,
UNIT_DEGREES,
UNIT_MILLIMETER,
)
from . import CONF_LD2450_ID, LD2450Component
DEPENDENCIES = ["ld2450"]
CONF_MOVING_TARGET_COUNT = "moving_target_count"
CONF_STILL_TARGET_COUNT = "still_target_count"
CONF_TARGET_COUNT = "target_count"
CONF_X = "x"
CONF_Y = "y"
ICON_ACCOUNT_GROUP = "mdi:account-group"
ICON_ACCOUNT_SWITCH = "mdi:account-switch"
ICON_ALPHA_X_BOX_OUTLINE = "mdi:alpha-x-box-outline"
ICON_ALPHA_Y_BOX_OUTLINE = "mdi:alpha-y-box-outline"
ICON_FORMAT_TEXT_ROTATION_ANGLE_UP = "mdi:format-text-rotation-angle-up"
ICON_HUMAN_GREETING_PROXIMITY = "mdi:human-greeting-proximity"
ICON_MAP_MARKER_ACCOUNT = "mdi:map-marker-account"
ICON_MAP_MARKER_DISTANCE = "mdi:map-marker-distance"
ICON_RELATION_ZERO_OR_ONE_TO_ZERO_OR_ONE = "mdi:relation-zero-or-one-to-zero-or-one"
ICON_SPEEDOMETER_SLOW = "mdi:speedometer-slow"
MAX_TARGETS = 3
MAX_ZONES = 3
UNIT_MILLIMETER_PER_SECOND = "mm/s"
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema(
icon=ICON_ACCOUNT_GROUP,
),
cv.Optional(CONF_STILL_TARGET_COUNT): sensor.sensor_schema(
icon=ICON_HUMAN_GREETING_PROXIMITY,
),
cv.Optional(CONF_MOVING_TARGET_COUNT): sensor.sensor_schema(
icon=ICON_ACCOUNT_SWITCH,
),
}
)
CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
{
cv.Optional(f"target_{n + 1}"): cv.Schema(
{
cv.Optional(CONF_X): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE,
unit_of_measurement=UNIT_MILLIMETER,
icon=ICON_ALPHA_X_BOX_OUTLINE,
),
cv.Optional(CONF_Y): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE,
unit_of_measurement=UNIT_MILLIMETER,
icon=ICON_ALPHA_Y_BOX_OUTLINE,
),
cv.Optional(CONF_SPEED): sensor.sensor_schema(
device_class=DEVICE_CLASS_SPEED,
unit_of_measurement=UNIT_MILLIMETER_PER_SECOND,
icon=ICON_SPEEDOMETER_SLOW,
),
cv.Optional(CONF_ANGLE): sensor.sensor_schema(
unit_of_measurement=UNIT_DEGREES,
icon=ICON_FORMAT_TEXT_ROTATION_ANGLE_UP,
),
cv.Optional(CONF_DISTANCE): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE,
unit_of_measurement=UNIT_MILLIMETER,
icon=ICON_MAP_MARKER_DISTANCE,
),
cv.Optional(CONF_RESOLUTION): sensor.sensor_schema(
device_class=DEVICE_CLASS_DISTANCE,
unit_of_measurement=UNIT_MILLIMETER,
icon=ICON_RELATION_ZERO_OR_ONE_TO_ZERO_OR_ONE,
),
}
)
for n in range(MAX_TARGETS)
},
{
cv.Optional(f"zone_{n + 1}"): cv.Schema(
{
cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema(
icon=ICON_MAP_MARKER_ACCOUNT,
),
cv.Optional(CONF_STILL_TARGET_COUNT): sensor.sensor_schema(
icon=ICON_MAP_MARKER_ACCOUNT,
),
cv.Optional(CONF_MOVING_TARGET_COUNT): sensor.sensor_schema(
icon=ICON_MAP_MARKER_ACCOUNT,
),
}
)
for n in range(MAX_ZONES)
},
)
async def to_code(config):
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
if target_count_config := config.get(CONF_TARGET_COUNT):
sens = await sensor.new_sensor(target_count_config)
cg.add(ld2450_component.set_target_count_sensor(sens))
if still_target_count_config := config.get(CONF_STILL_TARGET_COUNT):
sens = await sensor.new_sensor(still_target_count_config)
cg.add(ld2450_component.set_still_target_count_sensor(sens))
if moving_target_count_config := config.get(CONF_MOVING_TARGET_COUNT):
sens = await sensor.new_sensor(moving_target_count_config)
cg.add(ld2450_component.set_moving_target_count_sensor(sens))
for n in range(MAX_TARGETS):
if target_conf := config.get(f"target_{n + 1}"):
if x_config := target_conf.get(CONF_X):
sens = await sensor.new_sensor(x_config)
cg.add(ld2450_component.set_move_x_sensor(n, sens))
if y_config := target_conf.get(CONF_Y):
sens = await sensor.new_sensor(y_config)
cg.add(ld2450_component.set_move_y_sensor(n, sens))
if speed_config := target_conf.get(CONF_SPEED):
sens = await sensor.new_sensor(speed_config)
cg.add(ld2450_component.set_move_speed_sensor(n, sens))
if angle_config := target_conf.get(CONF_ANGLE):
sens = await sensor.new_sensor(angle_config)
cg.add(ld2450_component.set_move_angle_sensor(n, sens))
if distance_config := target_conf.get(CONF_DISTANCE):
sens = await sensor.new_sensor(distance_config)
cg.add(ld2450_component.set_move_distance_sensor(n, sens))
if resolution_config := target_conf.get(CONF_RESOLUTION):
sens = await sensor.new_sensor(resolution_config)
cg.add(ld2450_component.set_move_resolution_sensor(n, sens))
for n in range(MAX_ZONES):
if zone_config := config.get(f"zone_{n + 1}"):
if target_count_config := zone_config.get(CONF_TARGET_COUNT):
sens = await sensor.new_sensor(target_count_config)
cg.add(ld2450_component.set_zone_target_count_sensor(n, sens))
if still_target_count_config := zone_config.get(CONF_STILL_TARGET_COUNT):
sens = await sensor.new_sensor(still_target_count_config)
cg.add(ld2450_component.set_zone_still_target_count_sensor(n, sens))
if moving_target_count_config := zone_config.get(CONF_MOVING_TARGET_COUNT):
sens = await sensor.new_sensor(moving_target_count_config)
cg.add(ld2450_component.set_zone_moving_target_count_sensor(n, sens))
@@ -0,0 +1,45 @@
import esphome.codegen as cg
from esphome.components import switch
import esphome.config_validation as cv
from esphome.const import (
DEVICE_CLASS_SWITCH,
ENTITY_CATEGORY_CONFIG,
ICON_BLUETOOTH,
ICON_PULSE,
)
from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns
BluetoothSwitch = ld2450_ns.class_("BluetoothSwitch", switch.Switch)
MultiTargetSwitch = ld2450_ns.class_("MultiTargetSwitch", switch.Switch)
CONF_BLUETOOTH = "bluetooth"
CONF_MULTI_TARGET = "multi_target"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
cv.Optional(CONF_BLUETOOTH): switch.switch_schema(
BluetoothSwitch,
device_class=DEVICE_CLASS_SWITCH,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_BLUETOOTH,
),
cv.Optional(CONF_MULTI_TARGET): switch.switch_schema(
MultiTargetSwitch,
device_class=DEVICE_CLASS_SWITCH,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_PULSE,
),
}
async def to_code(config):
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
if bluetooth_config := config.get(CONF_BLUETOOTH):
s = await switch.new_switch(bluetooth_config)
await cg.register_parented(s, config[CONF_LD2450_ID])
cg.add(ld2450_component.set_bluetooth_switch(s))
if multi_target_config := config.get(CONF_MULTI_TARGET):
s = await switch.new_switch(multi_target_config)
await cg.register_parented(s, config[CONF_LD2450_ID])
cg.add(ld2450_component.set_multi_target_switch(s))
@@ -0,0 +1,12 @@
#include "bluetooth_switch.h"
namespace esphome {
namespace ld2450 {
void BluetoothSwitch::write_state(bool state) {
this->publish_state(state);
this->parent_->set_bluetooth(state);
}
} // namespace ld2450
} // namespace esphome
@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "../ld2450.h"
namespace esphome {
namespace ld2450 {
class BluetoothSwitch : public switch_::Switch, public Parented<LD2450Component> {
public:
BluetoothSwitch() = default;
protected:
void write_state(bool state) override;
};
} // namespace ld2450
} // namespace esphome
@@ -0,0 +1,12 @@
#include "multi_target_switch.h"
namespace esphome {
namespace ld2450 {
void MultiTargetSwitch::write_state(bool state) {
this->publish_state(state);
this->parent_->set_multi_target(state);
}
} // namespace ld2450
} // namespace esphome
@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "../ld2450.h"
namespace esphome {
namespace ld2450 {
class MultiTargetSwitch : public switch_::Switch, public Parented<LD2450Component> {
public:
MultiTargetSwitch() = default;
protected:
void write_state(bool state) override;
};
} // namespace ld2450
} // namespace esphome
+62
View File
@@ -0,0 +1,62 @@
import esphome.codegen as cg
from esphome.components import text_sensor
import esphome.config_validation as cv
from esphome.const import (
CONF_DIRECTION,
CONF_MAC_ADDRESS,
CONF_VERSION,
ENTITY_CATEGORY_DIAGNOSTIC,
ENTITY_CATEGORY_NONE,
ICON_BLUETOOTH,
ICON_CHIP,
ICON_SIGN_DIRECTION,
)
from . import CONF_LD2450_ID, LD2450Component
DEPENDENCIES = ["ld2450"]
MAX_TARGETS = 3
CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
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,
),
}
)
CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
{
cv.Optional(f"target_{n + 1}"): cv.Schema(
{
cv.Optional(CONF_DIRECTION): text_sensor.text_sensor_schema(
entity_category=ENTITY_CATEGORY_NONE,
icon=ICON_SIGN_DIRECTION,
),
}
)
for n in range(MAX_TARGETS)
}
)
async def to_code(config):
ld2450_component = await cg.get_variable(config[CONF_LD2450_ID])
if version_config := config.get(CONF_VERSION):
sens = await text_sensor.new_text_sensor(version_config)
cg.add(ld2450_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(ld2450_component.set_mac_text_sensor(sens))
for n in range(MAX_TARGETS):
if direction_conf := config.get(f"target_{n + 1}"):
if direction_config := direction_conf.get(CONF_DIRECTION):
sens = await text_sensor.new_text_sensor(direction_config)
cg.add(ld2450_component.set_direction_text_sensor(n, sens))
+168
View File
@@ -0,0 +1,168 @@
uart:
- id: ld2450_uart
tx_pin: ${tx_pin}
rx_pin: ${rx_pin}
baud_rate: 256000
parity: NONE
stop_bits: 1
ld2450:
- id: ld2450_radar
uart_id: ld2450_uart
throttle: 1000ms
button:
- platform: ld2450
ld2450_id: ld2450_radar
factory_reset:
name: LD2450 Factory Reset
entity_category: config
restart:
name: LD2450 Restart
entity_category: config
sensor:
- platform: ld2450
ld2450_id: ld2450_radar
target_count:
name: Presence Target Count
still_target_count:
name: Still Target Count
moving_target_count:
name: Moving Target Count
target_1:
x:
name: Target-1 X
y:
name: Target-1 Y
speed:
name: Target-1 Speed
angle:
name: Target-1 Angle
distance:
name: Target-1 Distance
resolution:
name: Target-1 Resolution
target_2:
x:
name: Target-2 X
y:
name: Target-2 Y
speed:
name: Target-2 Speed
angle:
name: Target-2 Angle
distance:
name: Target-2 Distance
resolution:
name: Target-2 Resolution
target_3:
x:
name: Target-3 X
y:
name: Target-3 Y
speed:
name: Target-3 Speed
angle:
name: Target-3 Angle
distance:
name: Target-3 Distance
resolution:
name: Target-3 Resolution
zone_1:
target_count:
name: Zone-1 All Target Count
still_target_count:
name: Zone-1 Still Target Count
moving_target_count:
name: Zone-1 Moving Target Count
zone_2:
target_count:
name: Zone-2 All Target Count
still_target_count:
name: Zone-2 Still Target Count
moving_target_count:
name: Zone-2 Moving Target Count
zone_3:
target_count:
name: Zone-3 All Target Count
still_target_count:
name: Zone-3 Still Target Count
moving_target_count:
name: Zone-3 Moving Target Count
binary_sensor:
- platform: ld2450
ld2450_id: ld2450_radar
has_target:
name: Presence
has_moving_target:
name: Moving Target
has_still_target:
name: Still Target
switch:
- platform: ld2450
ld2450_id: ld2450_radar
bluetooth:
name: Bluetooth
multi_target:
name: Multi Target Tracking
text_sensor:
- platform: ld2450
ld2450_id: ld2450_radar
version:
name: LD2450 Firmware
mac_address:
name: LD2450 BT MAC
target_1:
direction:
name: Target-1 Direction
target_2:
direction:
name: Target-2 Direction
target_3:
direction:
name: Target-3 Direction
number:
- platform: ld2450
ld2450_id: ld2450_radar
presence_timeout:
name: Timeout
zone_1:
x1:
name: Zone-1 X1
y1:
name: Zone-1 Y1
x2:
name: Zone-1 X2
y2:
name: Zone-1 Y2
zone_2:
x1:
name: Zone-2 X1
y1:
name: Zone-2 Y1
x2:
name: Zone-2 X2
y2:
name: Zone-2 Y2
zone_3:
x1:
name: Zone-3 X1
y1:
name: Zone-3 Y1
x2:
name: Zone-3 X2
y2:
name: Zone-3 Y2
select:
- platform: ld2450
ld2450_id: ld2450_radar
baud_rate:
name: Baud Rate
zone_type:
name: Zone Type
@@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO17
rx_pin: GPIO16
<<: !include common.yaml
@@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO4
rx_pin: GPIO5
<<: !include common.yaml
@@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO4
rx_pin: GPIO5
<<: !include common.yaml
@@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO17
rx_pin: GPIO16
<<: !include common.yaml
@@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO4
rx_pin: GPIO5
<<: !include common.yaml
@@ -0,0 +1,5 @@
substitutions:
tx_pin: GPIO4
rx_pin: GPIO5
<<: !include common.yaml