mirror of
https://github.com/esphome/esphome.git
synced 2026-02-08 08:41:59 +00:00
Merge remote-tracking branch 'upstream/dev' into integration
# Conflicts: # esphome/components/bedjet/bedjet_hub.cpp # esphome/components/esp8266/preferences.cpp # esphome/components/radon_eye_ble/radon_eye_listener.cpp # esphome/components/radon_eye_rd200/radon_eye_rd200.cpp
This commit is contained in:
@@ -395,6 +395,7 @@ esphome/components/radon_eye_rd200/* @jeffeb3
|
||||
esphome/components/rc522/* @glmnet
|
||||
esphome/components/rc522_i2c/* @glmnet
|
||||
esphome/components/rc522_spi/* @glmnet
|
||||
esphome/components/rd03d/* @jasstrong
|
||||
esphome/components/resampler/speaker/* @kahrendt
|
||||
esphome/components/restart/* @esphome/core
|
||||
esphome/components/rf_bridge/* @jesserockz
|
||||
|
||||
@@ -194,7 +194,7 @@ bool BedJetHub::discover_characteristics_() {
|
||||
result = false;
|
||||
} else if (descr->uuid.get_uuid().len != ESP_UUID_LEN_16 ||
|
||||
descr->uuid.get_uuid().uuid.uuid16 != ESP_GATT_UUID_CHAR_CLIENT_CONFIG) {
|
||||
char uuid_buf[esp32_ble::UUID_STR_LEN];
|
||||
char uuid_buf[espbt::UUID_STR_LEN];
|
||||
ESP_LOGW(TAG, "Config descriptor 0x%x (uuid %s) is not a client config char uuid", this->char_handle_status_,
|
||||
descr->uuid.to_str(uuid_buf));
|
||||
result = false;
|
||||
|
||||
@@ -127,8 +127,8 @@ static bool load_from_rtc(size_t offset, uint32_t *data, size_t len) {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Stack buffer size - 16 words covers up to 15 words of data (60 bytes)
|
||||
// which handles virtually all real-world preferences without heap allocation
|
||||
// Stack buffer size - 16 words total: up to 15 words of preference data + 1 word CRC (60 bytes of preference data)
|
||||
// This handles virtually all real-world preferences without heap allocation
|
||||
static constexpr size_t PREF_BUFFER_WORDS = 16;
|
||||
|
||||
class ESP8266PreferenceBackend : public ESPPreferenceBackend {
|
||||
|
||||
@@ -1,7 +1,6 @@
|
||||
#include "radon_eye_listener.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <algorithm>
|
||||
#include <vector>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
@@ -11,18 +10,11 @@ namespace radon_eye_ble {
|
||||
static const char *const TAG = "radon_eye_ble";
|
||||
|
||||
bool RadonEyeListener::parse_device(const esp32_ble_tracker::ESPBTDevice &device) {
|
||||
if (not device.get_name().empty()) {
|
||||
// Vector containing the prefixes to search for
|
||||
std::vector<std::string> prefixes = {"FR:R", "FR:I", "FR:H"};
|
||||
|
||||
// Check if the device name starts with any of the prefixes
|
||||
if (std::any_of(prefixes.begin(), prefixes.end(),
|
||||
[&](const std::string &prefix) { return device.get_name().starts_with(prefix); })) {
|
||||
// Device found
|
||||
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
ESP_LOGD(TAG, "Found Radon Eye device Name: %s (MAC: %s)", device.get_name().c_str(),
|
||||
device.address_str_to(addr_buf));
|
||||
}
|
||||
// Radon Eye devices have names starting with "FR:"
|
||||
if (device.get_name().starts_with("FR:")) {
|
||||
char addr_buf[MAC_ADDRESS_PRETTY_BUFFER_SIZE];
|
||||
ESP_LOGD(TAG, "Found Radon Eye device Name: %s (MAC: %s)", device.get_name().c_str(),
|
||||
device.address_str_to(addr_buf));
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -1,6 +1,8 @@
|
||||
#include "radon_eye_rd200.h"
|
||||
#include "esphome/components/esp32_ble/ble_uuid.h"
|
||||
|
||||
#include <cstring>
|
||||
|
||||
#ifdef USE_ESP32
|
||||
|
||||
namespace esphome {
|
||||
@@ -8,6 +10,22 @@ namespace radon_eye_rd200 {
|
||||
|
||||
static const char *const TAG = "radon_eye_rd200";
|
||||
|
||||
static const esp32_ble_tracker::ESPBTUUID SERVICE_UUID_V1 =
|
||||
esp32_ble_tracker::ESPBTUUID::from_raw("00001523-1212-efde-1523-785feabcd123");
|
||||
static const esp32_ble_tracker::ESPBTUUID WRITE_CHARACTERISTIC_UUID_V1 =
|
||||
esp32_ble_tracker::ESPBTUUID::from_raw("00001524-1212-efde-1523-785feabcd123");
|
||||
static const esp32_ble_tracker::ESPBTUUID READ_CHARACTERISTIC_UUID_V1 =
|
||||
esp32_ble_tracker::ESPBTUUID::from_raw("00001525-1212-efde-1523-785feabcd123");
|
||||
static const uint8_t WRITE_COMMAND_V1 = 0x50;
|
||||
|
||||
static const esp32_ble_tracker::ESPBTUUID SERVICE_UUID_V2 =
|
||||
esp32_ble_tracker::ESPBTUUID::from_raw("00001523-0000-1000-8000-00805f9b34fb");
|
||||
static const esp32_ble_tracker::ESPBTUUID WRITE_CHARACTERISTIC_UUID_V2 =
|
||||
esp32_ble_tracker::ESPBTUUID::from_raw("00001524-0000-1000-8000-00805f9b34fb");
|
||||
static const esp32_ble_tracker::ESPBTUUID READ_CHARACTERISTIC_UUID_V2 =
|
||||
esp32_ble_tracker::ESPBTUUID::from_raw("00001525-0000-1000-8000-00805f9b34fb");
|
||||
static const uint8_t WRITE_COMMAND_V2 = 0x40;
|
||||
|
||||
void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_t gattc_if,
|
||||
esp_ble_gattc_cb_param_t *param) {
|
||||
switch (event) {
|
||||
@@ -24,6 +42,22 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
}
|
||||
|
||||
case ESP_GATTC_SEARCH_CMPL_EVT: {
|
||||
if (this->parent()->get_service(SERVICE_UUID_V1) != nullptr) {
|
||||
service_uuid_ = SERVICE_UUID_V1;
|
||||
sensors_write_characteristic_uuid_ = WRITE_CHARACTERISTIC_UUID_V1;
|
||||
sensors_read_characteristic_uuid_ = READ_CHARACTERISTIC_UUID_V1;
|
||||
write_command_ = WRITE_COMMAND_V1;
|
||||
} else if (this->parent()->get_service(SERVICE_UUID_V2) != nullptr) {
|
||||
service_uuid_ = SERVICE_UUID_V2;
|
||||
sensors_write_characteristic_uuid_ = WRITE_CHARACTERISTIC_UUID_V2;
|
||||
sensors_read_characteristic_uuid_ = READ_CHARACTERISTIC_UUID_V2;
|
||||
write_command_ = WRITE_COMMAND_V2;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "No supported device has been found, disconnecting");
|
||||
parent()->set_enabled(false);
|
||||
break;
|
||||
}
|
||||
|
||||
this->read_handle_ = 0;
|
||||
auto *chr = this->parent()->get_characteristic(service_uuid_, sensors_read_characteristic_uuid_);
|
||||
if (chr == nullptr) {
|
||||
@@ -35,7 +69,6 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
}
|
||||
this->read_handle_ = chr->handle;
|
||||
|
||||
// Write a 0x50 to the write characteristic.
|
||||
auto *write_chr = this->parent()->get_characteristic(service_uuid_, sensors_write_characteristic_uuid_);
|
||||
if (write_chr == nullptr) {
|
||||
char service_buf[esp32_ble::UUID_STR_LEN];
|
||||
@@ -46,81 +79,106 @@ void RadonEyeRD200::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
|
||||
}
|
||||
this->write_handle_ = write_chr->handle;
|
||||
|
||||
this->node_state = esp32_ble_tracker::ClientState::ESTABLISHED;
|
||||
|
||||
write_query_message_();
|
||||
|
||||
request_read_values_();
|
||||
esp_err_t status =
|
||||
esp_ble_gattc_register_for_notify(gattc_if, this->parent()->get_remote_bda(), this->read_handle_);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error registering for sensor notify, status=%d", status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_READ_CHAR_EVT: {
|
||||
if (param->read.conn_id != this->parent()->get_conn_id())
|
||||
break;
|
||||
if (param->read.status != ESP_GATT_OK) {
|
||||
ESP_LOGW(TAG, "Error reading char at handle %d, status=%d", param->read.handle, param->read.status);
|
||||
case ESP_GATTC_WRITE_DESCR_EVT: {
|
||||
if (param->write.status != ESP_GATT_OK) {
|
||||
ESP_LOGE(TAG, "write descr failed, error status = %x", param->write.status);
|
||||
break;
|
||||
}
|
||||
if (param->read.handle == this->read_handle_) {
|
||||
read_sensors_(param->read.value, param->read.value_len);
|
||||
ESP_LOGV(TAG, "Write descr success, writing 0x%02X at write_handle=%d", this->write_command_,
|
||||
this->write_handle_);
|
||||
esp_err_t status =
|
||||
esp_ble_gattc_write_char(gattc_if, this->parent()->get_conn_id(), this->write_handle_, sizeof(write_command_),
|
||||
(uint8_t *) &write_command_, ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error writing 0x%02x command, status=%d", write_command_, status);
|
||||
}
|
||||
break;
|
||||
}
|
||||
|
||||
case ESP_GATTC_NOTIFY_EVT: {
|
||||
if (param->notify.is_notify) {
|
||||
ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT, receive notify value, %d bytes", param->notify.value_len);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "ESP_GATTC_NOTIFY_EVT, receive indicate value, %d bytes", param->notify.value_len);
|
||||
}
|
||||
read_sensors_(param->notify.value, param->notify.value_len);
|
||||
break;
|
||||
}
|
||||
|
||||
default:
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void RadonEyeRD200::read_sensors_(uint8_t *value, uint16_t value_len) {
|
||||
if (value_len < 20) {
|
||||
ESP_LOGD(TAG, "Invalid read");
|
||||
if (value_len < 1) {
|
||||
ESP_LOGW(TAG, "Unexpected empty message");
|
||||
return;
|
||||
}
|
||||
|
||||
// Example data
|
||||
// [13:08:47][D][radon_eye_rd200:107]: result bytes: 5010 85EBB940 00000000 00000000 2200 2500 0000
|
||||
ESP_LOGV(TAG, "result bytes: %02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X%02X%02X %02X%02X %02X%02X %02X%02X",
|
||||
value[0], value[1], value[2], value[3], value[4], value[5], value[6], value[7], value[8], value[9],
|
||||
value[10], value[11], value[12], value[13], value[14], value[15], value[16], value[17], value[18],
|
||||
value[19]);
|
||||
uint8_t command = value[0];
|
||||
|
||||
if (value[0] != 0x50) {
|
||||
// This isn't a sensor reading.
|
||||
if ((command == WRITE_COMMAND_V1 && value_len < 20) || (command == WRITE_COMMAND_V2 && value_len < 68)) {
|
||||
ESP_LOGW(TAG, "Unexpected command 0x%02X message length %d", command, value_len);
|
||||
return;
|
||||
}
|
||||
|
||||
// Example data V1:
|
||||
// 501085EBB9400000000000000000220025000000
|
||||
// Example data V2:
|
||||
// 4042323230313033525532303338330652443230304e56322e302e3200014a00060a00080000000300010079300000e01108001c00020000003822005c8f423fa4709d3f
|
||||
ESP_LOGV(TAG, "radon sensors raw bytes");
|
||||
ESP_LOG_BUFFER_HEX_LEVEL(TAG, value, value_len, ESP_LOG_VERBOSE);
|
||||
|
||||
// Convert from pCi/L to Bq/m³
|
||||
constexpr float convert_to_bwpm3 = 37.0;
|
||||
|
||||
RadonValue radon_value;
|
||||
radon_value.chars[0] = value[2];
|
||||
radon_value.chars[1] = value[3];
|
||||
radon_value.chars[2] = value[4];
|
||||
radon_value.chars[3] = value[5];
|
||||
float radon_now = radon_value.number * convert_to_bwpm3;
|
||||
if (is_valid_radon_value_(radon_now)) {
|
||||
radon_sensor_->publish_state(radon_now);
|
||||
float radon_now; // in Bq/m³
|
||||
float radon_day; // in Bq/m³
|
||||
float radon_month; // in Bq/m³
|
||||
if (command == WRITE_COMMAND_V1) {
|
||||
// Use memcpy to avoid unaligned memory access
|
||||
float temp;
|
||||
memcpy(&temp, value + 2, sizeof(float));
|
||||
radon_now = temp * convert_to_bwpm3;
|
||||
memcpy(&temp, value + 6, sizeof(float));
|
||||
radon_day = temp * convert_to_bwpm3;
|
||||
memcpy(&temp, value + 10, sizeof(float));
|
||||
radon_month = temp * convert_to_bwpm3;
|
||||
} else if (command == WRITE_COMMAND_V2) {
|
||||
// Use memcpy to avoid unaligned memory access
|
||||
uint16_t temp;
|
||||
memcpy(&temp, value + 33, sizeof(uint16_t));
|
||||
radon_now = temp;
|
||||
memcpy(&temp, value + 35, sizeof(uint16_t));
|
||||
radon_day = temp;
|
||||
memcpy(&temp, value + 37, sizeof(uint16_t));
|
||||
radon_month = temp;
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Unexpected command value: 0x%02X", command);
|
||||
return;
|
||||
}
|
||||
|
||||
radon_value.chars[0] = value[6];
|
||||
radon_value.chars[1] = value[7];
|
||||
radon_value.chars[2] = value[8];
|
||||
radon_value.chars[3] = value[9];
|
||||
float radon_day = radon_value.number * convert_to_bwpm3;
|
||||
if (this->radon_sensor_ != nullptr) {
|
||||
this->radon_sensor_->publish_state(radon_now);
|
||||
}
|
||||
|
||||
radon_value.chars[0] = value[10];
|
||||
radon_value.chars[1] = value[11];
|
||||
radon_value.chars[2] = value[12];
|
||||
radon_value.chars[3] = value[13];
|
||||
float radon_month = radon_value.number * convert_to_bwpm3;
|
||||
|
||||
if (is_valid_radon_value_(radon_month)) {
|
||||
ESP_LOGV(TAG, "Radon Long Term based on month");
|
||||
radon_long_term_sensor_->publish_state(radon_month);
|
||||
} else if (is_valid_radon_value_(radon_day)) {
|
||||
ESP_LOGV(TAG, "Radon Long Term based on day");
|
||||
radon_long_term_sensor_->publish_state(radon_day);
|
||||
if (this->radon_long_term_sensor_ != nullptr) {
|
||||
if (radon_month > 0) {
|
||||
ESP_LOGV(TAG, "Radon Long Term based on month");
|
||||
this->radon_long_term_sensor_->publish_state(radon_month);
|
||||
} else {
|
||||
ESP_LOGV(TAG, "Radon Long Term based on day");
|
||||
this->radon_long_term_sensor_->publish_state(radon_day);
|
||||
}
|
||||
}
|
||||
|
||||
ESP_LOGV(TAG,
|
||||
@@ -135,49 +193,23 @@ void RadonEyeRD200::read_sensors_(uint8_t *value, uint16_t value_len) {
|
||||
parent()->set_enabled(false);
|
||||
}
|
||||
|
||||
bool RadonEyeRD200::is_valid_radon_value_(float radon) { return radon > 0.0 and radon < 37000; }
|
||||
|
||||
void RadonEyeRD200::update() {
|
||||
if (this->node_state != esp32_ble_tracker::ClientState::ESTABLISHED) {
|
||||
if (!parent()->enabled) {
|
||||
ESP_LOGW(TAG, "Reconnecting to device");
|
||||
parent()->set_enabled(true);
|
||||
parent()->connect();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Connection in progress");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RadonEyeRD200::write_query_message_() {
|
||||
ESP_LOGV(TAG, "writing 0x50 to write service");
|
||||
int request = 0x50;
|
||||
auto status = esp_ble_gattc_write_char_descr(this->parent()->get_gattc_if(), this->parent()->get_conn_id(),
|
||||
this->write_handle_, sizeof(request), (uint8_t *) &request,
|
||||
ESP_GATT_WRITE_TYPE_NO_RSP, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error sending write request for sensor, status=%d", status);
|
||||
}
|
||||
}
|
||||
|
||||
void RadonEyeRD200::request_read_values_() {
|
||||
auto status = esp_ble_gattc_read_char(this->parent()->get_gattc_if(), this->parent()->get_conn_id(),
|
||||
this->read_handle_, ESP_GATT_AUTH_REQ_NONE);
|
||||
if (status) {
|
||||
ESP_LOGW(TAG, "Error sending read request for sensor, status=%d", status);
|
||||
}
|
||||
}
|
||||
|
||||
void RadonEyeRD200::dump_config() {
|
||||
LOG_SENSOR(" ", "Radon", this->radon_sensor_);
|
||||
LOG_SENSOR(" ", "Radon Long Term", this->radon_long_term_sensor_);
|
||||
}
|
||||
|
||||
RadonEyeRD200::RadonEyeRD200()
|
||||
: PollingComponent(10000),
|
||||
service_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(SERVICE_UUID)),
|
||||
sensors_write_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(WRITE_CHARACTERISTIC_UUID)),
|
||||
sensors_read_characteristic_uuid_(esp32_ble_tracker::ESPBTUUID::from_raw(READ_CHARACTERISTIC_UUID)) {}
|
||||
RadonEyeRD200::RadonEyeRD200() : PollingComponent(10000) {}
|
||||
|
||||
} // namespace radon_eye_rd200
|
||||
} // namespace esphome
|
||||
|
||||
@@ -14,10 +14,6 @@
|
||||
namespace esphome {
|
||||
namespace radon_eye_rd200 {
|
||||
|
||||
static const char *const SERVICE_UUID = "00001523-1212-efde-1523-785feabcd123";
|
||||
static const char *const WRITE_CHARACTERISTIC_UUID = "00001524-1212-efde-1523-785feabcd123";
|
||||
static const char *const READ_CHARACTERISTIC_UUID = "00001525-1212-efde-1523-785feabcd123";
|
||||
|
||||
class RadonEyeRD200 : public PollingComponent, public ble_client::BLEClientNode {
|
||||
public:
|
||||
RadonEyeRD200();
|
||||
@@ -32,25 +28,17 @@ class RadonEyeRD200 : public PollingComponent, public ble_client::BLEClientNode
|
||||
void set_radon_long_term(sensor::Sensor *radon_long_term) { radon_long_term_sensor_ = radon_long_term; }
|
||||
|
||||
protected:
|
||||
bool is_valid_radon_value_(float radon);
|
||||
|
||||
void read_sensors_(uint8_t *value, uint16_t value_len);
|
||||
void write_query_message_();
|
||||
void request_read_values_();
|
||||
|
||||
sensor::Sensor *radon_sensor_{nullptr};
|
||||
sensor::Sensor *radon_long_term_sensor_{nullptr};
|
||||
|
||||
uint8_t write_command_;
|
||||
uint16_t read_handle_;
|
||||
uint16_t write_handle_;
|
||||
esp32_ble_tracker::ESPBTUUID service_uuid_;
|
||||
esp32_ble_tracker::ESPBTUUID sensors_write_characteristic_uuid_;
|
||||
esp32_ble_tracker::ESPBTUUID sensors_read_characteristic_uuid_;
|
||||
|
||||
union RadonValue {
|
||||
char chars[4];
|
||||
float number;
|
||||
};
|
||||
};
|
||||
|
||||
} // namespace radon_eye_rd200
|
||||
|
||||
50
esphome/components/rd03d/__init__.py
Normal file
50
esphome/components/rd03d/__init__.py
Normal file
@@ -0,0 +1,50 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import uart
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_ID, CONF_THROTTLE
|
||||
|
||||
CODEOWNERS = ["@jasstrong"]
|
||||
DEPENDENCIES = ["uart"]
|
||||
MULTI_CONF = True
|
||||
|
||||
CONF_RD03D_ID = "rd03d_id"
|
||||
CONF_TRACKING_MODE = "tracking_mode"
|
||||
|
||||
rd03d_ns = cg.esphome_ns.namespace("rd03d")
|
||||
RD03DComponent = rd03d_ns.class_("RD03DComponent", cg.Component, uart.UARTDevice)
|
||||
TrackingMode = rd03d_ns.enum("TrackingMode", is_class=True)
|
||||
|
||||
TRACKING_MODES = {
|
||||
"single": TrackingMode.SINGLE_TARGET,
|
||||
"multi": TrackingMode.MULTI_TARGET,
|
||||
}
|
||||
|
||||
CONFIG_SCHEMA = cv.All(
|
||||
cv.Schema(
|
||||
{
|
||||
cv.GenerateID(): cv.declare_id(RD03DComponent),
|
||||
cv.Optional(CONF_TRACKING_MODE): cv.enum(TRACKING_MODES, lower=True),
|
||||
cv.Optional(CONF_THROTTLE): cv.positive_time_period_milliseconds,
|
||||
}
|
||||
)
|
||||
.extend(uart.UART_DEVICE_SCHEMA)
|
||||
.extend(cv.COMPONENT_SCHEMA)
|
||||
)
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = uart.final_validate_device_schema(
|
||||
"rd03d",
|
||||
require_tx=False,
|
||||
require_rx=True,
|
||||
)
|
||||
|
||||
|
||||
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)
|
||||
|
||||
if CONF_TRACKING_MODE in config:
|
||||
cg.add(var.set_tracking_mode(config[CONF_TRACKING_MODE]))
|
||||
|
||||
if CONF_THROTTLE in config:
|
||||
cg.add(var.set_throttle(config[CONF_THROTTLE]))
|
||||
39
esphome/components/rd03d/binary_sensor.py
Normal file
39
esphome/components/rd03d/binary_sensor.py
Normal file
@@ -0,0 +1,39 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import binary_sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import CONF_TARGET, DEVICE_CLASS_OCCUPANCY
|
||||
|
||||
from . import CONF_RD03D_ID, RD03DComponent
|
||||
|
||||
DEPENDENCIES = ["rd03d"]
|
||||
|
||||
MAX_TARGETS = 3
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_RD03D_ID): cv.use_id(RD03DComponent),
|
||||
cv.Optional(CONF_TARGET): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_OCCUPANCY,
|
||||
),
|
||||
}
|
||||
).extend(
|
||||
{
|
||||
cv.Optional(f"target_{i + 1}"): binary_sensor.binary_sensor_schema(
|
||||
device_class=DEVICE_CLASS_OCCUPANCY,
|
||||
)
|
||||
for i in range(MAX_TARGETS)
|
||||
}
|
||||
)
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_RD03D_ID])
|
||||
|
||||
if target_config := config.get(CONF_TARGET):
|
||||
sens = await binary_sensor.new_binary_sensor(target_config)
|
||||
cg.add(hub.set_target_binary_sensor(sens))
|
||||
|
||||
for i in range(MAX_TARGETS):
|
||||
if target_config := config.get(f"target_{i + 1}"):
|
||||
sens = await binary_sensor.new_binary_sensor(target_config)
|
||||
cg.add(hub.set_target_binary_sensor(i, sens))
|
||||
243
esphome/components/rd03d/rd03d.cpp
Normal file
243
esphome/components/rd03d/rd03d.cpp
Normal file
@@ -0,0 +1,243 @@
|
||||
#include "rd03d.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include <cmath>
|
||||
|
||||
namespace esphome::rd03d {
|
||||
|
||||
static const char *const TAG = "rd03d";
|
||||
|
||||
// Delay before sending configuration commands to allow radar to initialize
|
||||
static constexpr uint32_t SETUP_TIMEOUT_MS = 100;
|
||||
|
||||
// Data frame format (radar -> host)
|
||||
static constexpr uint8_t FRAME_HEADER[] = {0xAA, 0xFF, 0x03, 0x00};
|
||||
static constexpr uint8_t FRAME_FOOTER[] = {0x55, 0xCC};
|
||||
|
||||
// Command frame format (host -> radar)
|
||||
static constexpr uint8_t CMD_FRAME_HEADER[] = {0xFD, 0xFC, 0xFB, 0xFA};
|
||||
static constexpr uint8_t CMD_FRAME_FOOTER[] = {0x04, 0x03, 0x02, 0x01};
|
||||
|
||||
// RD-03D tracking mode commands
|
||||
static constexpr uint16_t CMD_SINGLE_TARGET = 0x0080;
|
||||
static constexpr uint16_t CMD_MULTI_TARGET = 0x0090;
|
||||
|
||||
// Decode coordinate/speed value from RD-03D format
|
||||
// Per datasheet: MSB=1 means positive, MSB=0 means negative
|
||||
static constexpr int16_t decode_value(uint8_t low_byte, uint8_t high_byte) {
|
||||
int16_t value = ((high_byte & 0x7F) << 8) | low_byte;
|
||||
if ((high_byte & 0x80) == 0) {
|
||||
value = -value;
|
||||
}
|
||||
return value;
|
||||
}
|
||||
|
||||
void RD03DComponent::setup() {
|
||||
ESP_LOGCONFIG(TAG, "Setting up RD-03D...");
|
||||
this->set_timeout(SETUP_TIMEOUT_MS, [this]() { this->apply_config_(); });
|
||||
}
|
||||
|
||||
void RD03DComponent::dump_config() {
|
||||
ESP_LOGCONFIG(TAG, "RD-03D:");
|
||||
if (this->tracking_mode_.has_value()) {
|
||||
ESP_LOGCONFIG(TAG, " Tracking Mode: %s",
|
||||
*this->tracking_mode_ == TrackingMode::SINGLE_TARGET ? "single" : "multi");
|
||||
}
|
||||
if (this->throttle_ > 0) {
|
||||
ESP_LOGCONFIG(TAG, " Throttle: %ums", this->throttle_);
|
||||
}
|
||||
#ifdef USE_SENSOR
|
||||
LOG_SENSOR(" ", "Target Count", this->target_count_sensor_);
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
LOG_BINARY_SENSOR(" ", "Target", this->target_binary_sensor_);
|
||||
#endif
|
||||
for (uint8_t i = 0; i < MAX_TARGETS; i++) {
|
||||
ESP_LOGCONFIG(TAG, " Target %d:", i + 1);
|
||||
#ifdef USE_SENSOR
|
||||
LOG_SENSOR(" ", "X", this->targets_[i].x);
|
||||
LOG_SENSOR(" ", "Y", this->targets_[i].y);
|
||||
LOG_SENSOR(" ", "Speed", this->targets_[i].speed);
|
||||
LOG_SENSOR(" ", "Distance", this->targets_[i].distance);
|
||||
LOG_SENSOR(" ", "Resolution", this->targets_[i].resolution);
|
||||
LOG_SENSOR(" ", "Angle", this->targets_[i].angle);
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
LOG_BINARY_SENSOR(" ", "Presence", this->target_presence_[i]);
|
||||
#endif
|
||||
}
|
||||
}
|
||||
|
||||
void RD03DComponent::loop() {
|
||||
while (this->available()) {
|
||||
uint8_t byte = this->read();
|
||||
ESP_LOGVV(TAG, "Received byte: 0x%02X, buffer_pos: %d", byte, this->buffer_pos_);
|
||||
|
||||
// Check if we're looking for frame header
|
||||
if (this->buffer_pos_ < FRAME_HEADER_SIZE) {
|
||||
if (byte == FRAME_HEADER[this->buffer_pos_]) {
|
||||
this->buffer_[this->buffer_pos_++] = byte;
|
||||
} else if (byte == FRAME_HEADER[0]) {
|
||||
// Start over if we see a potential new header
|
||||
this->buffer_[0] = byte;
|
||||
this->buffer_pos_ = 1;
|
||||
} else {
|
||||
this->buffer_pos_ = 0;
|
||||
}
|
||||
continue;
|
||||
}
|
||||
|
||||
// Accumulate data bytes
|
||||
this->buffer_[this->buffer_pos_++] = byte;
|
||||
|
||||
// Check if we have a complete frame
|
||||
if (this->buffer_pos_ == FRAME_SIZE) {
|
||||
// Validate footer
|
||||
if (this->buffer_[FRAME_SIZE - 2] == FRAME_FOOTER[0] && this->buffer_[FRAME_SIZE - 1] == FRAME_FOOTER[1]) {
|
||||
this->process_frame_();
|
||||
} else {
|
||||
ESP_LOGW(TAG, "Invalid frame footer: 0x%02X 0x%02X (expected 0x55 0xCC)", this->buffer_[FRAME_SIZE - 2],
|
||||
this->buffer_[FRAME_SIZE - 1]);
|
||||
}
|
||||
this->buffer_pos_ = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void RD03DComponent::process_frame_() {
|
||||
// Apply throttle if configured
|
||||
if (this->throttle_ > 0) {
|
||||
uint32_t now = millis();
|
||||
if (now - this->last_publish_time_ < this->throttle_) {
|
||||
return;
|
||||
}
|
||||
this->last_publish_time_ = now;
|
||||
}
|
||||
|
||||
uint8_t target_count = 0;
|
||||
|
||||
for (uint8_t i = 0; i < MAX_TARGETS; i++) {
|
||||
// Calculate offset for this target's data
|
||||
// Header is 4 bytes, each target is 8 bytes
|
||||
uint8_t offset = FRAME_HEADER_SIZE + (i * TARGET_DATA_SIZE);
|
||||
|
||||
// Extract raw bytes for this target
|
||||
uint8_t x_low = this->buffer_[offset + 0];
|
||||
uint8_t x_high = this->buffer_[offset + 1];
|
||||
uint8_t y_low = this->buffer_[offset + 2];
|
||||
uint8_t y_high = this->buffer_[offset + 3];
|
||||
uint8_t speed_low = this->buffer_[offset + 4];
|
||||
uint8_t speed_high = this->buffer_[offset + 5];
|
||||
uint8_t res_low = this->buffer_[offset + 6];
|
||||
uint8_t res_high = this->buffer_[offset + 7];
|
||||
|
||||
// Decode values per RD-03D format
|
||||
int16_t x = decode_value(x_low, x_high);
|
||||
int16_t y = decode_value(y_low, y_high);
|
||||
int16_t speed = decode_value(speed_low, speed_high);
|
||||
uint16_t resolution = (res_high << 8) | res_low;
|
||||
|
||||
// Check if target is present (non-zero coordinates)
|
||||
bool target_present = (x != 0 || y != 0);
|
||||
if (target_present) {
|
||||
target_count++;
|
||||
}
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
this->publish_target_(i, x, y, speed, resolution);
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
if (this->target_presence_[i] != nullptr) {
|
||||
this->target_presence_[i]->publish_state(target_present);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
if (this->target_count_sensor_ != nullptr) {
|
||||
this->target_count_sensor_->publish_state(target_count);
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
if (this->target_binary_sensor_ != nullptr) {
|
||||
this->target_binary_sensor_->publish_state(target_count > 0);
|
||||
}
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void RD03DComponent::publish_target_(uint8_t target_num, int16_t x, int16_t y, int16_t speed, uint16_t resolution) {
|
||||
TargetSensor &target = this->targets_[target_num];
|
||||
|
||||
// Publish X coordinate (mm)
|
||||
if (target.x != nullptr) {
|
||||
target.x->publish_state(x);
|
||||
}
|
||||
|
||||
// Publish Y coordinate (mm)
|
||||
if (target.y != nullptr) {
|
||||
target.y->publish_state(y);
|
||||
}
|
||||
|
||||
// Publish speed (convert from cm/s to mm/s)
|
||||
if (target.speed != nullptr) {
|
||||
target.speed->publish_state(static_cast<float>(speed) * 10.0f);
|
||||
}
|
||||
|
||||
// Publish resolution (mm)
|
||||
if (target.resolution != nullptr) {
|
||||
target.resolution->publish_state(resolution);
|
||||
}
|
||||
|
||||
// Calculate and publish distance (mm)
|
||||
if (target.distance != nullptr) {
|
||||
float distance = std::hypot(static_cast<float>(x), static_cast<float>(y));
|
||||
target.distance->publish_state(distance);
|
||||
}
|
||||
|
||||
// Calculate and publish angle (degrees)
|
||||
// Angle is measured from the Y axis (radar forward direction)
|
||||
if (target.angle != nullptr) {
|
||||
if (x == 0 && y == 0) {
|
||||
target.angle->publish_state(0);
|
||||
} else {
|
||||
float angle = std::atan2(static_cast<float>(x), static_cast<float>(y)) * 180.0f / M_PI;
|
||||
target.angle->publish_state(angle);
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
void RD03DComponent::send_command_(uint16_t command, const uint8_t *data, uint8_t data_len) {
|
||||
// Send header
|
||||
this->write_array(CMD_FRAME_HEADER, sizeof(CMD_FRAME_HEADER));
|
||||
|
||||
// Send length (command word + data)
|
||||
uint16_t len = 2 + data_len;
|
||||
this->write_byte(len & 0xFF);
|
||||
this->write_byte((len >> 8) & 0xFF);
|
||||
|
||||
// Send command word (little-endian)
|
||||
this->write_byte(command & 0xFF);
|
||||
this->write_byte((command >> 8) & 0xFF);
|
||||
|
||||
// Send data if any
|
||||
if (data != nullptr && data_len > 0) {
|
||||
this->write_array(data, data_len);
|
||||
}
|
||||
|
||||
// Send footer
|
||||
this->write_array(CMD_FRAME_FOOTER, sizeof(CMD_FRAME_FOOTER));
|
||||
|
||||
ESP_LOGD(TAG, "Sent command 0x%04X with %d bytes of data", command, data_len);
|
||||
}
|
||||
|
||||
void RD03DComponent::apply_config_() {
|
||||
if (this->tracking_mode_.has_value()) {
|
||||
uint16_t mode_cmd = (*this->tracking_mode_ == TrackingMode::SINGLE_TARGET) ? CMD_SINGLE_TARGET : CMD_MULTI_TARGET;
|
||||
this->send_command_(mode_cmd);
|
||||
}
|
||||
}
|
||||
|
||||
} // namespace esphome::rd03d
|
||||
93
esphome/components/rd03d/rd03d.h
Normal file
93
esphome/components/rd03d/rd03d.h
Normal file
@@ -0,0 +1,93 @@
|
||||
#pragma once
|
||||
|
||||
#include "esphome/core/defines.h"
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/components/uart/uart.h"
|
||||
#ifdef USE_SENSOR
|
||||
#include "esphome/components/sensor/sensor.h"
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
#include "esphome/components/binary_sensor/binary_sensor.h"
|
||||
#endif
|
||||
|
||||
#include <array>
|
||||
|
||||
namespace esphome::rd03d {
|
||||
|
||||
static constexpr uint8_t MAX_TARGETS = 3;
|
||||
static constexpr uint8_t FRAME_HEADER_SIZE = 4;
|
||||
static constexpr uint8_t FRAME_FOOTER_SIZE = 2;
|
||||
static constexpr uint8_t TARGET_DATA_SIZE = 8;
|
||||
static constexpr uint8_t FRAME_SIZE =
|
||||
FRAME_HEADER_SIZE + (MAX_TARGETS * TARGET_DATA_SIZE) + FRAME_FOOTER_SIZE; // 30 bytes
|
||||
|
||||
enum class TrackingMode : uint8_t {
|
||||
SINGLE_TARGET = 0,
|
||||
MULTI_TARGET = 1,
|
||||
};
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
struct TargetSensor {
|
||||
sensor::Sensor *x{nullptr};
|
||||
sensor::Sensor *y{nullptr};
|
||||
sensor::Sensor *speed{nullptr};
|
||||
sensor::Sensor *distance{nullptr};
|
||||
sensor::Sensor *resolution{nullptr};
|
||||
sensor::Sensor *angle{nullptr};
|
||||
};
|
||||
#endif
|
||||
|
||||
class RD03DComponent : public Component, public uart::UARTDevice {
|
||||
public:
|
||||
void setup() override;
|
||||
void loop() override;
|
||||
void dump_config() override;
|
||||
float get_setup_priority() const override { return setup_priority::DATA; }
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
void set_target_count_sensor(sensor::Sensor *sensor) { this->target_count_sensor_ = sensor; }
|
||||
void set_x_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].x = sensor; }
|
||||
void set_y_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].y = sensor; }
|
||||
void set_speed_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].speed = sensor; }
|
||||
void set_distance_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].distance = sensor; }
|
||||
void set_resolution_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].resolution = sensor; }
|
||||
void set_angle_sensor(uint8_t target, sensor::Sensor *sensor) { this->targets_[target].angle = sensor; }
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
void set_target_binary_sensor(binary_sensor::BinarySensor *sensor) { this->target_binary_sensor_ = sensor; }
|
||||
void set_target_binary_sensor(uint8_t target, binary_sensor::BinarySensor *sensor) {
|
||||
this->target_presence_[target] = sensor;
|
||||
}
|
||||
#endif
|
||||
|
||||
// Configuration setters (called from code generation)
|
||||
void set_tracking_mode(TrackingMode mode) { this->tracking_mode_ = mode; }
|
||||
void set_throttle(uint32_t throttle) { this->throttle_ = throttle; }
|
||||
|
||||
protected:
|
||||
void apply_config_();
|
||||
void send_command_(uint16_t command, const uint8_t *data = nullptr, uint8_t data_len = 0);
|
||||
void process_frame_();
|
||||
#ifdef USE_SENSOR
|
||||
void publish_target_(uint8_t target_num, int16_t x, int16_t y, int16_t speed, uint16_t resolution);
|
||||
#endif
|
||||
|
||||
#ifdef USE_SENSOR
|
||||
std::array<TargetSensor, MAX_TARGETS> targets_{};
|
||||
sensor::Sensor *target_count_sensor_{nullptr};
|
||||
#endif
|
||||
#ifdef USE_BINARY_SENSOR
|
||||
std::array<binary_sensor::BinarySensor *, MAX_TARGETS> target_presence_{};
|
||||
binary_sensor::BinarySensor *target_binary_sensor_{nullptr};
|
||||
#endif
|
||||
|
||||
// Configuration (only sent if explicitly set)
|
||||
optional<TrackingMode> tracking_mode_{};
|
||||
uint32_t throttle_{0};
|
||||
uint32_t last_publish_time_{0};
|
||||
|
||||
std::array<uint8_t, FRAME_SIZE> buffer_{};
|
||||
uint8_t buffer_pos_{0};
|
||||
};
|
||||
|
||||
} // namespace esphome::rd03d
|
||||
105
esphome/components/rd03d/sensor.py
Normal file
105
esphome/components/rd03d/sensor.py
Normal file
@@ -0,0 +1,105 @@
|
||||
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,
|
||||
CONF_X,
|
||||
CONF_Y,
|
||||
DEVICE_CLASS_DISTANCE,
|
||||
DEVICE_CLASS_SPEED,
|
||||
STATE_CLASS_MEASUREMENT,
|
||||
UNIT_DEGREES,
|
||||
UNIT_MILLIMETER,
|
||||
)
|
||||
|
||||
from . import CONF_RD03D_ID, RD03DComponent
|
||||
|
||||
DEPENDENCIES = ["rd03d"]
|
||||
|
||||
CONF_TARGET_COUNT = "target_count"
|
||||
|
||||
MAX_TARGETS = 3
|
||||
|
||||
UNIT_MILLIMETER_PER_SECOND = "mm/s"
|
||||
|
||||
TARGET_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.Optional(CONF_X): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_Y): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_SPEED): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETER_PER_SECOND,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_SPEED,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_DISTANCE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_RESOLUTION): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_MILLIMETER,
|
||||
accuracy_decimals=0,
|
||||
device_class=DEVICE_CLASS_DISTANCE,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
cv.Optional(CONF_ANGLE): sensor.sensor_schema(
|
||||
unit_of_measurement=UNIT_DEGREES,
|
||||
accuracy_decimals=1,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
)
|
||||
|
||||
CONFIG_SCHEMA = cv.Schema(
|
||||
{
|
||||
cv.GenerateID(CONF_RD03D_ID): cv.use_id(RD03DComponent),
|
||||
cv.Optional(CONF_TARGET_COUNT): sensor.sensor_schema(
|
||||
accuracy_decimals=0,
|
||||
state_class=STATE_CLASS_MEASUREMENT,
|
||||
),
|
||||
}
|
||||
).extend({cv.Optional(f"target_{i + 1}"): TARGET_SCHEMA for i in range(MAX_TARGETS)})
|
||||
|
||||
|
||||
async def to_code(config):
|
||||
hub = await cg.get_variable(config[CONF_RD03D_ID])
|
||||
|
||||
if target_count_config := config.get(CONF_TARGET_COUNT):
|
||||
sens = await sensor.new_sensor(target_count_config)
|
||||
cg.add(hub.set_target_count_sensor(sens))
|
||||
|
||||
for i in range(MAX_TARGETS):
|
||||
if target_config := config.get(f"target_{i + 1}"):
|
||||
if x_config := target_config.get(CONF_X):
|
||||
sens = await sensor.new_sensor(x_config)
|
||||
cg.add(hub.set_x_sensor(i, sens))
|
||||
if y_config := target_config.get(CONF_Y):
|
||||
sens = await sensor.new_sensor(y_config)
|
||||
cg.add(hub.set_y_sensor(i, sens))
|
||||
if speed_config := target_config.get(CONF_SPEED):
|
||||
sens = await sensor.new_sensor(speed_config)
|
||||
cg.add(hub.set_speed_sensor(i, sens))
|
||||
if distance_config := target_config.get(CONF_DISTANCE):
|
||||
sens = await sensor.new_sensor(distance_config)
|
||||
cg.add(hub.set_distance_sensor(i, sens))
|
||||
if resolution_config := target_config.get(CONF_RESOLUTION):
|
||||
sens = await sensor.new_sensor(resolution_config)
|
||||
cg.add(hub.set_resolution_sensor(i, sens))
|
||||
if angle_config := target_config.get(CONF_ANGLE):
|
||||
sens = await sensor.new_sensor(angle_config)
|
||||
cg.add(hub.set_angle_sensor(i, sens))
|
||||
59
tests/components/rd03d/common.yaml
Normal file
59
tests/components/rd03d/common.yaml
Normal file
@@ -0,0 +1,59 @@
|
||||
rd03d:
|
||||
id: rd03d_radar
|
||||
|
||||
sensor:
|
||||
- platform: rd03d
|
||||
rd03d_id: rd03d_radar
|
||||
target_count:
|
||||
name: 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
|
||||
|
||||
binary_sensor:
|
||||
- platform: rd03d
|
||||
rd03d_id: rd03d_radar
|
||||
target:
|
||||
name: Presence
|
||||
target_1:
|
||||
name: Target-1 Presence
|
||||
target_2:
|
||||
name: Target-2 Presence
|
||||
target_3:
|
||||
name: Target-3 Presence
|
||||
4
tests/components/rd03d/test.esp32-idf.yaml
Normal file
4
tests/components/rd03d/test.esp32-idf.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp32-idf.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/rd03d/test.esp8266-ard.yaml
Normal file
4
tests/components/rd03d/test.esp8266-ard.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/esp8266-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
4
tests/components/rd03d/test.rp2040-ard.yaml
Normal file
4
tests/components/rd03d/test.rp2040-ard.yaml
Normal file
@@ -0,0 +1,4 @@
|
||||
packages:
|
||||
uart: !include ../../test_build_components/common/uart/rp2040-ard.yaml
|
||||
|
||||
<<: !include common.yaml
|
||||
Reference in New Issue
Block a user