Compare commits

..
Author SHA1 Message Date
J. Nick Koston ff25bc334a Merge remote-tracking branch 'origin/dev' into assign-bits-migrate-misc
# Conflicts:
#	esphome/components/ufire_ec/ufire_ec.cpp
#	esphome/components/ufire_ise/ufire_ise.cpp
2026-10-26 04:56:24 -10:00
J. Nick Koston 0af09caddc Merge remote-tracking branch 'origin/dev' into assign-bits-migrate-misc
# Conflicts:
#	esphome/components/rx8111/rx8111.cpp
2026-10-22 15:22:51 -10:00
J. Nick Koston 1154941f25 Merge remote-tracking branch 'origin/assign-bits-helper' into assign-bits-migrate-misc
# Conflicts:
#	esphome/components/light/light_call.h
2026-10-22 07:35:44 -10:00
J. Nick Koston 9a65dda424 Merge remote-tracking branch 'origin/dev' into assign-bits-helper
# Conflicts:
#	AGENTS.md
2026-10-22 07:33:53 -10:00
J. Nick Koston 81191f3d5c Merge remote-tracking branch 'origin/dev' into assign-bits-helper 2026-10-21 07:49:56 -10:00
J. Nick Koston 26c852f169 Include helpers.h where assign_bits is used 2026-10-20 04:05:33 -10:00
J. Nick Koston ebdf95be94 Set or clear entity, socket and sensor bits with assign_bits 2026-10-20 03:55:45 -10:00
J. Nick Koston 29259ca74e Always inline assign_bits 2026-10-20 03:52:03 -10:00
J. Nick Koston 6a1b5d517b Note the unsigned mask for wide registers 2026-10-20 03:36:19 -10:00
J. Nick Koston 9687c0e0a8 Take the mask at the target width 2026-10-20 03:31:51 -10:00
J. Nick Koston 230c120898 Add assign_bits to set or clear register bits from a bool 2026-10-20 03:28:19 -10:00
238 changed files with 2748 additions and 3275 deletions

No files matched your search

@@ -1,6 +1,7 @@
from esphome import automation
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_CODE,
@@ -9,11 +10,11 @@ from esphome.const import (
CONF_ID,
CONF_MQTT_ID,
CONF_ON_STATE,
CONF_WEB_SERVER,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority, entity_helpers
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import (
entity_duplicate_validator,
mqtt_component_class,
queue_entity_register,
setup_entity,
)
@@ -43,12 +44,12 @@ AlarmControlPanelState = alarm_control_panel_ns.enum("AlarmControlPanelState")
_ALARM_CONTROL_PANEL_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(entity_helpers.WEBSERVER_SORTING_SCHEMA)
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt_component_class("MQTTAlarmControlPanelComponent")
mqtt.MQTTAlarmControlPanelComponent
),
cv.Optional(CONF_ON_STATE): automation.validate_automation({}),
cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation({}),
@@ -165,7 +166,11 @@ _CALLBACK_AUTOMATIONS = (
@setup_entity("alarm_control_panel")
async def setup_alarm_control_panel_core_(var, config):
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
await entity_helpers.setup_entity_integrations(var, config)
if web_server_config := config.get(CONF_WEB_SERVER):
await web_server.add_entity_config(var, web_server_config)
if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
async def register_alarm_control_panel(var, config):
+7 -6
View File
@@ -4,13 +4,14 @@ import esphome.config_validation as cv
from esphome.const import DEVICE_CLASS_SWITCH, ICON_WIFI
from esphome.types import ConfigType
from .. import CONF_AT581X_ID, AT581XComponent
from .. import CONF_AT581X_ID, AT581XComponent, at581x_ns
DEPENDENCIES = ["at581x"]
RFSwitch = at581x_ns.class_("RFSwitch", switch.Switch)
CONFIG_SCHEMA = switch.switch_schema(
switch.ApplySwitch,
RFSwitch,
device_class=DEVICE_CLASS_SWITCH,
icon=ICON_WIFI,
).extend(
@@ -23,7 +24,7 @@ CONFIG_SCHEMA = switch.switch_schema(
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_AT581X_ID])
await switch.sub_switches(config, parent=hub).create(
config, hub.set_rf_power_switch, apply="set_rf_mode(state)", publish="before"
)
at581x_component = await cg.get_variable(config[CONF_AT581X_ID])
s = await switch.new_switch(config)
await cg.register_parented(s, config[CONF_AT581X_ID])
cg.add(at581x_component.set_rf_power_switch(s))
@@ -0,0 +1,10 @@
#include "rf_switch.h"
namespace esphome::at581x {
void RFSwitch::write_state(bool state) {
this->publish_state(state);
this->parent_->set_rf_mode(state);
}
} // namespace esphome::at581x
@@ -0,0 +1,13 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "../at581x.h"
namespace esphome::at581x {
class RFSwitch final : public switch_::Switch, public Parented<AT581XComponent> {
protected:
void write_state(bool state) override;
};
} // namespace esphome::at581x
+4 -2
View File
@@ -63,7 +63,6 @@ from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority, entity
from esphome.core.entity_helpers import (
SubEntities,
entity_duplicate_validator,
lazy_load_setup,
lazy_load_validator,
mqtt_component_class,
queue_entity_register,
@@ -622,7 +621,10 @@ async def setup_binary_sensor_core_(var, config):
await entity_helpers.setup_entity_integrations(var, config)
await lazy_load_setup("zigbee", "setup_binary_sensor", var, config)
if "zigbee" in CORE.loaded_integrations:
from esphome.components import zigbee
await zigbee.setup_binary_sensor(var, config)
async def register_binary_sensor(var, config):
+3 -6
View File
@@ -1,5 +1,4 @@
#include "bl0939.h"
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
#include <cinttypes>
@@ -21,7 +20,7 @@ static const uint8_t BL0939_REG_SOFT_RESET = 0x19;
static const uint8_t BL0939_REG_USR_WRPROT = 0x1A;
static const uint8_t BL0939_REG_TPS_CTRL = 0x1B;
static constexpr uint8_t BL0939_INIT[6][6] PROGMEM = {
const uint8_t BL0939_INIT[6][6] = {
// Reset to default
{BL0939_WRITE_COMMAND, BL0939_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x33},
// Enable User Operation Write
@@ -67,10 +66,8 @@ void BL0939::update() {
}
void BL0939::setup() {
uint8_t cmd[sizeof(BL0939_INIT[0])];
for (const auto *i : BL0939_INIT) {
progmem_memcpy(cmd, i, sizeof(cmd));
this->write_array(cmd, sizeof(cmd));
for (auto *i : BL0939_INIT) {
this->write_array(i, 6);
delay(1);
}
this->flush();
@@ -0,0 +1,25 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_BUTTON
#include "bq25186.h"
#include "esphome/components/button/button.h"
namespace esphome::bq25186 {
/// Writes one SHIP_RST field when pressed
class BQ25186Button final : public button::Button, public Parented<BQ25186Component> {
public:
BQ25186Button(uint8_t mask, uint8_t value) : mask_(mask), value_(value) {}
protected:
void press_action() override { this->parent_->write_ship_rst(this->mask_, this->value_); }
const uint8_t mask_;
const uint8_t value_;
};
} // namespace esphome::bq25186
#endif // USE_BUTTON
+7 -4
View File
@@ -1,3 +1,4 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import (
@@ -9,10 +10,11 @@ from esphome.const import (
)
from esphome.types import ConfigType
from . import BQ25186_CHILD_SCHEMA, CONF_BQ25186_ID
from . import BQ25186_CHILD_SCHEMA, CONF_BQ25186_ID, bq25186_ns
DEPENDENCIES = ["bq25186"]
BQ25186Button = bq25186_ns.class_("BQ25186Button", button.Button)
# SHIP_RST: bit 7 is the software reset, bits 6:5 the shutdown, ship and hardware reset codes
SOFTWARE_RESET_MASK = 0x80
@@ -31,7 +33,7 @@ CONFIG_SCHEMA = cv.All(
BQ25186_CHILD_SCHEMA.extend(
{
cv.Optional(key): button.button_schema(
button.ApplyButton,
BQ25186Button,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=icon,
device_class=device_class,
@@ -44,6 +46,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config: ConfigType) -> None:
buttons = button.sub_buttons(config, parent=config[CONF_BQ25186_ID])
for key, mask, value, _, _ in _BUTTONS:
await buttons(key, None, apply=f"write_ship_rst({mask:#04x}, {value:#04x})")
if (conf := config.get(key)) is not None:
var = await button.new_button(conf, mask, value)
await cg.register_parented(var, config[CONF_BQ25186_ID])
+11 -7
View File
@@ -1,6 +1,7 @@
from esphome import automation
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_DEVICE_CLASS,
@@ -9,6 +10,7 @@ from esphome.const import (
CONF_ID,
CONF_MQTT_ID,
CONF_ON_PRESS,
CONF_WEB_SERVER,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_IDENTIFY,
DEVICE_CLASS_RESTART,
@@ -19,12 +21,11 @@ from esphome.const import (
ICON_RESTART,
ICON_RESTART_ALERT,
)
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority, entity_helpers
from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import (
ApplyEntity,
SubEntities,
entity_duplicate_validator,
mqtt_component_class,
queue_entity_register,
setup_device_class,
setup_entity,
@@ -52,13 +53,11 @@ validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
_BUTTON_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(entity_helpers.WEBSERVER_SORTING_SCHEMA)
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt_component_class("MQTTButtonComponent")
),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
cv.Optional(
CONF_DEVICE_CLASS, visibility=cv.Visibility.ADVANCED
): validate_device_class,
@@ -136,7 +135,12 @@ async def setup_button_core_(var: MockObj, config: ConfigType) -> None:
setup_device_class(config)
await entity_helpers.setup_entity_integrations(var, config)
if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if web_server_config := config.get(CONF_WEB_SERVER):
await web_server.add_entity_config(var, web_server_config)
async def register_button(var: MockObj, config: ConfigType) -> None:
+12 -8
View File
@@ -3,6 +3,7 @@ from typing import Any
from esphome import automation
import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_ACTION_STATE_TOPIC,
@@ -48,15 +49,15 @@ from esphome.const import (
CONF_TEMPERATURE_STEP,
CONF_UNIT_OF_MEASUREMENT,
CONF_VISUAL,
CONF_WEB_SERVER,
UNIT_KELVIN,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority, entity_helpers
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import (
TemperatureUnitResolver,
convert_visual_temperatures,
declare_fixed_temperature_unit,
entity_duplicate_validator,
mqtt_component_class,
queue_entity_register,
setup_entity,
setup_entity_temperature_unit,
@@ -176,13 +177,11 @@ _visual_temperatures_in_entity_unit = partial(
_CLIMATE_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(entity_helpers.WEBSERVER_SORTING_SCHEMA)
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt_component_class("MQTTClimateComponent")
),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
cv.Optional(CONF_VISUAL, default={}): cv.Schema(
{
cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature_with_unit,
@@ -354,8 +353,10 @@ async def setup_climate_core_(var: MockObj, config: ConfigType) -> None:
cg.add(var.set_visual_max_humidity_override(max_humidity))
setup_entity_temperature_unit(var, config)
mqtt_ = await entity_helpers.setup_entity_integrations(var, config)
if mqtt_ is not None:
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (action_state_topic := config.get(CONF_ACTION_STATE_TOPIC)) is not None:
cg.add(mqtt_.set_custom_action_state_topic(action_state_topic))
if (away_command_topic := config.get(CONF_AWAY_COMMAND_TOPIC)) is not None:
@@ -485,6 +486,9 @@ async def setup_climate_core_(var: MockObj, config: ConfigType) -> None:
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
if web_server_config := config.get(CONF_WEB_SERVER):
await web_server.add_entity_config(var, web_server_config)
def set_custom_fan_modes(var: MockObj, modes: list[str]) -> None:
"""Point the climate at a shared flash table of custom fan modes."""
+12 -8
View File
@@ -3,6 +3,7 @@ import logging
from esphome import automation
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_DEVICE_CLASS,
@@ -22,6 +23,7 @@ from esphome.const import (
CONF_TILT_COMMAND_TOPIC,
CONF_TILT_STATE_TOPIC,
CONF_TRIGGER_ID,
CONF_WEB_SERVER,
DEVICE_CLASS_AWNING,
DEVICE_CLASS_BLIND,
DEVICE_CLASS_CURTAIN,
@@ -34,10 +36,9 @@ from esphome.const import (
DEVICE_CLASS_SHUTTER,
DEVICE_CLASS_WINDOW,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority, entity_helpers
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import (
entity_duplicate_validator,
mqtt_component_class,
queue_entity_register,
setup_device_class,
setup_entity,
@@ -111,13 +112,11 @@ TRIGGERS = {
_COVER_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(entity_helpers.WEBSERVER_SORTING_SCHEMA)
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt_component_class("MQTTCoverComponent")
),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
cv.Optional(CONF_MQTT_JSON_STATE_PAYLOAD): cv.All(
cv.requires_component("mqtt"), cv.boolean
),
@@ -203,8 +202,10 @@ async def setup_cover_core_(var: MockObj, config: ConfigType) -> None:
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
mqtt_ = await entity_helpers.setup_entity_integrations(var, config)
if mqtt_ is not None:
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (position_state_topic := config.get(CONF_POSITION_STATE_TOPIC)) is not None:
cg.add(mqtt_.set_custom_position_state_topic(position_state_topic))
if (
@@ -219,6 +220,9 @@ async def setup_cover_core_(var: MockObj, config: ConfigType) -> None:
if (tilt_command_topic := config.get(CONF_TILT_COMMAND_TOPIC)) is not None:
cg.add(mqtt_.set_custom_tilt_command_topic(tilt_command_topic))
if web_server_config := config.get(CONF_WEB_SERVER):
await web_server.add_entity_config(var, web_server_config)
async def register_cover(var: MockObj, config: ConfigType) -> None:
if not CORE.has_id(config[CONF_ID]):
+22 -15
View File
@@ -28,23 +28,30 @@ void CSE7766Component::loop() {
// Read all available bytes in batches to reduce UART call overhead.
// At 4800 baud (~480 bytes/sec) with ~122 Hz loop rate, typically ~4 bytes per call.
this->read_available<CSE7766_RAW_DATA_SIZE>(
[this](uint8_t byte) {
this->raw_data_[this->raw_data_index_] = byte;
if (!this->check_byte_()) {
this->raw_data_index_ = 0;
this->status_set_warning();
return;
}
uint8_t buf[CSE7766_RAW_DATA_SIZE];
while (avail > 0) {
size_t to_read = std::min(avail, sizeof(buf));
if (!this->read_array(buf, to_read)) {
break;
}
avail -= to_read;
if (this->raw_data_index_ == CSE7766_RAW_DATA_SIZE - 1) {
this->parse_data_();
this->status_clear_warning();
}
for (size_t i = 0; i < to_read; i++) {
this->raw_data_[this->raw_data_index_] = buf[i];
if (!this->check_byte_()) {
this->raw_data_index_ = 0;
this->status_set_warning();
continue;
}
this->raw_data_index_ = (this->raw_data_index_ + 1) % CSE7766_RAW_DATA_SIZE;
},
avail);
if (this->raw_data_index_ == CSE7766_RAW_DATA_SIZE - 1) {
this->parse_data_();
this->status_clear_warning();
}
this->raw_data_index_ = (this->raw_data_index_ + 1) % CSE7766_RAW_DATA_SIZE;
}
}
}
bool CSE7766Component::check_byte_() {
+14 -17
View File
@@ -2,6 +2,7 @@ from collections.abc import Callable
from esphome import automation
import esphome.codegen as cg
from esphome.components import mqtt, time, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_DATE,
@@ -19,12 +20,12 @@ from esphome.const import (
CONF_TIME_ID,
CONF_TRIGGER_ID,
CONF_TYPE,
CONF_WEB_SERVER,
CONF_YEAR,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority, entity_helpers
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import (
entity_duplicate_validator,
mqtt_component_class,
queue_entity_register,
setup_entity,
)
@@ -37,10 +38,6 @@ DOMAIN = "datetime"
IS_PLATFORM_COMPONENT = True
datetime_ns = cg.esphome_ns.namespace("datetime")
# Matches time.RealTimeClock by name without importing the time component
_RealTimeClock = cg.esphome_ns.namespace("time").class_(
"RealTimeClock", cg.PollingComponent
)
DateTimeBase = datetime_ns.class_("DateTimeBase", cg.EntityBase)
DateEntity = datetime_ns.class_("DateEntity", DateTimeBase)
TimeEntity = datetime_ns.class_("TimeEntity", DateTimeBase)
@@ -67,7 +64,7 @@ DATETIME_MODES = [
def _validate_time_present(config: ConfigType) -> ConfigType:
config = config.copy()
if CONF_ON_TIME in config and CONF_TIME_ID not in config:
time_id = cv.use_id(_RealTimeClock)(None)
time_id = cv.use_id(time.RealTimeClock)(None)
config[CONF_TIME_ID] = time_id
return config
@@ -76,10 +73,10 @@ _DATETIME_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
cv.Schema(
{
cv.Optional(CONF_ON_VALUE): automation.validate_automation({}),
cv.Optional(CONF_TIME_ID): cv.use_id(_RealTimeClock),
cv.Optional(CONF_TIME_ID): cv.use_id(time.RealTimeClock),
}
)
.extend(entity_helpers.WEBSERVER_SORTING_SCHEMA)
.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
).add_extra(_validate_time_present)
@@ -90,9 +87,7 @@ def date_schema(class_: MockObjClass) -> cv.Schema:
schema = cv.Schema(
{
cv.GenerateID(): cv.declare_id(class_),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt_component_class("MQTTDateComponent")
),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTDateComponent),
cv.Optional(CONF_TYPE, default="DATE"): cv.one_of("DATE", upper=True),
}
)
@@ -103,9 +98,7 @@ def time_schema(class_: MockObjClass) -> cv.Schema:
schema = cv.Schema(
{
cv.GenerateID(): cv.declare_id(class_),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt_component_class("MQTTTimeComponent")
),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTimeComponent),
cv.Optional(CONF_TYPE, default="TIME"): cv.one_of("TIME", upper=True),
cv.Optional(CONF_ON_TIME): automation.validate_automation(
{
@@ -122,7 +115,7 @@ def datetime_schema(class_: MockObjClass) -> cv.Schema:
{
cv.GenerateID(): cv.declare_id(class_),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt_component_class("MQTTDateTimeComponent")
mqtt.MQTTDateTimeComponent
),
cv.Optional(CONF_TYPE, default="DATETIME"): cv.one_of(
"DATETIME", upper=True
@@ -139,7 +132,11 @@ def datetime_schema(class_: MockObjClass) -> cv.Schema:
@setup_entity("datetime")
async def setup_datetime_core_(var: MockObj, config: ConfigType) -> None:
await entity_helpers.setup_entity_integrations(var, config)
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if web_server_config := config.get(CONF_WEB_SERVER):
await web_server.add_entity_config(var, web_server_config)
# The state callback carries nothing; the automation gets the value as an ESPTime.
for conf in config.get(CONF_ON_VALUE, []):
await automation.build_callback_automation(
+146 -133
View File
@@ -135,140 +135,153 @@ void DFPlayer::send_cmd_(uint8_t cmd, uint16_t argument) {
}
void DFPlayer::loop() {
this->read_available([this](uint8_t byte) {
if (this->read_pos_ == DFPLAYER_READ_BUFFER_LENGTH)
this->read_pos_ = 0;
switch (this->read_pos_) {
case 0: // Start mark
if (byte != 0x7E)
return;
break;
case 1: // Version
if (byte != 0xFF) {
ESP_LOGW(TAG, "Expected Version 0xFF, got %#02x", byte);
this->read_pos_ = 0;
return;
}
break;
case 2: // Buffer length
if (byte != 0x06) {
ESP_LOGW(TAG, "Expected Buffer length 0x06, got %#02x", byte);
this->read_pos_ = 0;
return;
}
break;
case 9: // End byte
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
char byte_sequence[100];
byte_sequence[0] = '\0';
for (size_t i = 0; i < this->read_pos_ + 1; ++i) {
snprintf(byte_sequence + strlen(byte_sequence), sizeof(byte_sequence) - strlen(byte_sequence), "%02X ",
this->read_buffer_[i]);
}
ESP_LOGVV(TAG, "Received byte sequence: %s", byte_sequence);
#endif
if (byte != 0xEF) {
ESP_LOGW(TAG, "Expected end byte 0xEF, got %#02x", byte);
this->read_pos_ = 0;
return;
}
// Parse valid received command
uint8_t cmd = this->read_buffer_[3];
uint16_t argument = (this->read_buffer_[5] << 8) | this->read_buffer_[6];
ESP_LOGV(TAG, "Received message cmd: %#02x arg %#04x", cmd, argument);
switch (cmd) {
case 0x3A:
if (argument == 1) {
ESP_LOGI(TAG, "USB loaded");
} else if (argument == 2) {
ESP_LOGI(TAG, "TF Card loaded");
}
break;
case 0x3B:
if (argument == 1) {
ESP_LOGI(TAG, "USB unloaded");
} else if (argument == 2) {
ESP_LOGI(TAG, "TF Card unloaded");
}
break;
case 0x3F:
if (argument == 1) {
ESP_LOGI(TAG, "USB available");
} else if (argument == 2) {
ESP_LOGI(TAG, "TF Card available");
} else if (argument == 3) {
ESP_LOGI(TAG, "USB, TF Card available");
}
break;
case 0x40:
ESP_LOGV(TAG, "Nack");
this->ack_set_is_playing_ = false;
this->ack_reset_is_playing_ = false;
switch (argument) {
case 0x01:
ESP_LOGE(TAG, "Module is busy or uninitialized");
break;
case 0x02:
ESP_LOGE(TAG, "Module is in sleep mode");
break;
case 0x03:
ESP_LOGE(TAG, "Serial receive error");
break;
case 0x04:
ESP_LOGE(TAG, "Checksum incorrect");
break;
case 0x05:
ESP_LOGE(TAG, "Specified track is out of current track scope");
this->is_playing_ = false;
break;
case 0x06:
ESP_LOGE(TAG, "Specified track is not found");
this->is_playing_ = false;
break;
case 0x07:
ESP_LOGE(TAG, "Insertion error (an inserting operation only can be done when a track is being played)");
break;
case 0x08:
ESP_LOGE(TAG, "SD card reading failed (SD card pulled out or damaged)");
break;
case 0x09:
ESP_LOGE(TAG, "Entered into sleep mode");
this->is_playing_ = false;
break;
default:
break;
}
break;
case 0x41:
ESP_LOGV(TAG, "Ack ok");
this->is_playing_ |= this->ack_set_is_playing_;
this->is_playing_ &= !this->ack_reset_is_playing_;
this->ack_set_is_playing_ = false;
this->ack_reset_is_playing_ = false;
break;
case 0x3C:
ESP_LOGV(TAG, "Playback finished (USB drive)");
this->is_playing_ = false;
this->on_finished_playback_callback_.call();
break;
case 0x3D:
ESP_LOGV(TAG, "Playback finished (SD card)");
this->is_playing_ = false;
this->on_finished_playback_callback_.call();
break;
default:
ESP_LOGE(TAG, "Received unknown cmd %#02x arg %#04x", cmd, argument);
}
this->sent_cmd_ = 0;
this->read_pos_ = 0;
return;
// Read all available bytes in batches to reduce UART call overhead.
size_t avail = this->available();
uint8_t buf[64];
while (avail > 0) {
size_t to_read = std::min(avail, sizeof(buf));
if (!this->read_array(buf, to_read)) {
break;
}
this->read_buffer_[this->read_pos_] = byte;
this->read_pos_++;
});
avail -= to_read;
for (size_t bi = 0; bi < to_read; bi++) {
uint8_t byte = buf[bi];
if (this->read_pos_ == DFPLAYER_READ_BUFFER_LENGTH)
this->read_pos_ = 0;
switch (this->read_pos_) {
case 0: // Start mark
if (byte != 0x7E)
continue;
break;
case 1: // Version
if (byte != 0xFF) {
ESP_LOGW(TAG, "Expected Version 0xFF, got %#02x", byte);
this->read_pos_ = 0;
continue;
}
break;
case 2: // Buffer length
if (byte != 0x06) {
ESP_LOGW(TAG, "Expected Buffer length 0x06, got %#02x", byte);
this->read_pos_ = 0;
continue;
}
break;
case 9: // End byte
#ifdef ESPHOME_LOG_HAS_VERY_VERBOSE
char byte_sequence[100];
byte_sequence[0] = '\0';
for (size_t i = 0; i < this->read_pos_ + 1; ++i) {
snprintf(byte_sequence + strlen(byte_sequence), sizeof(byte_sequence) - strlen(byte_sequence), "%02X ",
this->read_buffer_[i]);
}
ESP_LOGVV(TAG, "Received byte sequence: %s", byte_sequence);
#endif
if (byte != 0xEF) {
ESP_LOGW(TAG, "Expected end byte 0xEF, got %#02x", byte);
this->read_pos_ = 0;
continue;
}
// Parse valid received command
uint8_t cmd = this->read_buffer_[3];
uint16_t argument = (this->read_buffer_[5] << 8) | this->read_buffer_[6];
ESP_LOGV(TAG, "Received message cmd: %#02x arg %#04x", cmd, argument);
switch (cmd) {
case 0x3A:
if (argument == 1) {
ESP_LOGI(TAG, "USB loaded");
} else if (argument == 2) {
ESP_LOGI(TAG, "TF Card loaded");
}
break;
case 0x3B:
if (argument == 1) {
ESP_LOGI(TAG, "USB unloaded");
} else if (argument == 2) {
ESP_LOGI(TAG, "TF Card unloaded");
}
break;
case 0x3F:
if (argument == 1) {
ESP_LOGI(TAG, "USB available");
} else if (argument == 2) {
ESP_LOGI(TAG, "TF Card available");
} else if (argument == 3) {
ESP_LOGI(TAG, "USB, TF Card available");
}
break;
case 0x40:
ESP_LOGV(TAG, "Nack");
this->ack_set_is_playing_ = false;
this->ack_reset_is_playing_ = false;
switch (argument) {
case 0x01:
ESP_LOGE(TAG, "Module is busy or uninitialized");
break;
case 0x02:
ESP_LOGE(TAG, "Module is in sleep mode");
break;
case 0x03:
ESP_LOGE(TAG, "Serial receive error");
break;
case 0x04:
ESP_LOGE(TAG, "Checksum incorrect");
break;
case 0x05:
ESP_LOGE(TAG, "Specified track is out of current track scope");
this->is_playing_ = false;
break;
case 0x06:
ESP_LOGE(TAG, "Specified track is not found");
this->is_playing_ = false;
break;
case 0x07:
ESP_LOGE(TAG,
"Insertion error (an inserting operation only can be done when a track is being played)");
break;
case 0x08:
ESP_LOGE(TAG, "SD card reading failed (SD card pulled out or damaged)");
break;
case 0x09:
ESP_LOGE(TAG, "Entered into sleep mode");
this->is_playing_ = false;
break;
default:
break;
}
break;
case 0x41:
ESP_LOGV(TAG, "Ack ok");
this->is_playing_ |= this->ack_set_is_playing_;
this->is_playing_ &= !this->ack_reset_is_playing_;
this->ack_set_is_playing_ = false;
this->ack_reset_is_playing_ = false;
break;
case 0x3C:
ESP_LOGV(TAG, "Playback finished (USB drive)");
this->is_playing_ = false;
this->on_finished_playback_callback_.call();
break;
case 0x3D:
ESP_LOGV(TAG, "Playback finished (SD card)");
this->is_playing_ = false;
this->on_finished_playback_callback_.call();
break;
default:
ESP_LOGE(TAG, "Received unknown cmd %#02x arg %#04x", cmd, argument);
}
this->sent_cmd_ = 0;
this->read_pos_ = 0;
continue;
}
this->read_buffer_[this->read_pos_] = byte;
this->read_pos_++;
}
}
}
void DFPlayer::dump_config() { ESP_LOGCONFIG(TAG, "DFPlayer:"); }
@@ -1,7 +1,10 @@
#include "dfrobot_sen0465_uart.h"
#include <algorithm>
namespace esphome::dfrobot_sen0465_uart {
using dfrobot_sen0465_base::FRAME_HEAD;
using dfrobot_sen0465_base::FRAME_SIZE;
using dfrobot_sen0465_base::RESPONSE_TIMEOUT_ID;
@@ -15,21 +18,30 @@ void DFRobotSEN0465UARTComponent::setup() {
}
void DFRobotSEN0465UARTComponent::loop() {
// The tail of a reading the probe sent on its own may precede the answer; the reader skips to the frame head.
const uint8_t *frame = nullptr;
this->read_available<FRAME_SIZE>([&](uint8_t c) { return (frame = this->reader_.feed(c)) == nullptr; });
if (frame == nullptr)
// The tail of a reading the probe sent on its own may precede the answer; start at the next frame head.
uint8_t byte;
while (this->rx_count_ == 0 && this->peek_byte(&byte) && byte != FRAME_HEAD)
this->read_byte(&byte);
const size_t available = this->available();
if (available == 0)
return;
const size_t len = std::min<size_t>(available, FRAME_SIZE - this->rx_count_);
if (!this->read_array(this->rx_buffer_ + this->rx_count_, len))
return;
this->rx_count_ += len;
if (this->rx_count_ < FRAME_SIZE)
return;
this->rx_count_ = 0;
this->cancel_timeout(RESPONSE_TIMEOUT_ID);
this->disable_loop();
this->handle_response_(frame);
this->handle_response_(this->rx_buffer_);
}
bool DFRobotSEN0465UARTComponent::write_frame(const uint8_t *frame) {
// Drop anything left over, such as the answer to the mode change or readings sent before passive mode was set
this->discard_available();
this->reader_.reset();
this->rx_count_ = 0;
this->write_array(frame, FRAME_SIZE);
return true;
}
@@ -38,7 +50,7 @@ void DFRobotSEN0465UARTComponent::read_response() {
this->enable_loop();
this->set_timeout(RESPONSE_TIMEOUT_ID, RESPONSE_TIMEOUT_MS, [this]() {
this->disable_loop();
this->reader_.reset();
this->rx_count_ = 0;
this->handle_no_response_();
});
}
@@ -2,7 +2,6 @@
#include "esphome/components/dfrobot_sen0465_base/dfrobot_sen0465_base.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/frame_reader.h"
namespace esphome::dfrobot_sen0465_uart {
@@ -18,7 +17,8 @@ class DFRobotSEN0465UARTComponent final : public dfrobot_sen0465_base::DFRobotSE
bool write_frame(const uint8_t *frame) override;
void read_response() override;
FrameReader<dfrobot_sen0465_base::FRAME_SIZE, dfrobot_sen0465_base::FRAME_HEAD> reader_;
uint8_t rx_buffer_[dfrobot_sen0465_base::FRAME_SIZE];
uint8_t rx_count_{0};
};
} // namespace esphome::dfrobot_sen0465_uart
@@ -5,16 +5,19 @@ import esphome.config_validation as cv
from esphome.const import CONF_FACTORY_RESET, CONF_ID, CONF_RESTART
from esphome.types import ConfigType
from .. import CONF_DFROBOT_SEN0609_ID, DFRobotSEN0609Component
from .. import CONF_DFROBOT_SEN0609_ID, DFRobotSEN0609Component, dfrobot_sen0609_ns
DFRobotSEN0609Button = dfrobot_sen0609_ns.class_("DFRobotSEN0609Button", button.Button)
ButtonAction = dfrobot_sen0609_ns.enum("ButtonAction")
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_DFROBOT_SEN0609_ID): cv.use_id(DFRobotSEN0609Component),
cv.Optional(CONF_FACTORY_RESET): button.factory_reset_button_schema(
button.ApplyButton
DFRobotSEN0609Button
),
cv.Optional(CONF_QUERY_PARAMS): button.query_button_schema(button.ApplyButton),
cv.Optional(CONF_RESTART): button.restart_button_schema(button.ApplyButton),
cv.Optional(CONF_QUERY_PARAMS): button.query_button_schema(DFRobotSEN0609Button),
cv.Optional(CONF_RESTART): button.restart_button_schema(DFRobotSEN0609Button),
}
@@ -22,7 +25,11 @@ async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_DFROBOT_SEN0609_ID])
buttons = button.sub_buttons(config, parent=hub)
await buttons(
CONF_FACTORY_RESET, hub.set_factory_reset_button, apply="factory_reset()"
CONF_FACTORY_RESET,
hub.set_factory_reset_button,
ButtonAction.BUTTON_FACTORY_RESET,
)
await buttons(CONF_QUERY_PARAMS, hub.set_query_button, apply="query_params()")
await buttons(CONF_RESTART, hub.set_restart_button, apply="restart()")
await buttons(
CONF_QUERY_PARAMS, hub.set_query_button, ButtonAction.BUTTON_QUERY_PARAMS
)
await buttons(CONF_RESTART, hub.set_restart_button, ButtonAction.BUTTON_RESTART)
@@ -0,0 +1,30 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../dfrobot_sen0609.h"
namespace esphome::dfrobot_sen0609 {
class DFRobotSEN0609Button final : public button::Button, public Parented<DFRobotSEN0609Component> {
public:
explicit DFRobotSEN0609Button(ButtonAction action) : action_(action) {}
protected:
void press_action() override {
switch (this->action_) {
case BUTTON_RESTART:
this->parent_->restart();
break;
case BUTTON_FACTORY_RESET:
this->parent_->factory_reset();
break;
case BUTTON_QUERY_PARAMS:
this->parent_->query_params();
break;
}
}
ButtonAction action_;
};
} // namespace esphome::dfrobot_sen0609
@@ -5,6 +5,7 @@
#include "esphome/core/log.h"
#include "esphome/core/progmem.h"
#include <algorithm>
#include <cmath>
#include <cstdlib>
#include <cstring>
@@ -174,11 +175,21 @@ void DFRobotSEN0609Component::setup() {
}
void DFRobotSEN0609Component::loop() {
if (this->read_available<MAX_LINE_LENGTH>([this](uint8_t b) { this->readline_(b); })) {
uint8_t buf[MAX_LINE_LENGTH];
size_t avail = this->available();
while (avail > 0) {
const size_t to_read = std::min(avail, sizeof(buf));
if (!this->read_array(buf, to_read)) {
if (!this->read_failed_) {
ESP_LOGW(TAG, "UART read failed");
}
this->read_failed_ = true;
break;
}
this->read_failed_ = false;
} else if (!this->read_failed_) {
ESP_LOGW(TAG, "UART read failed");
this->read_failed_ = true;
avail -= to_read;
for (size_t i = 0; i < to_read; i++)
this->readline_(buf[i]);
}
this->run_sequencer_();
}
@@ -443,15 +454,6 @@ void DFRobotSEN0609Component::control_number(NumberSetting setting, float value)
this->request_write_(static_cast<Param>(progmem_read_byte(&NUMBER_PARAMS[setting])));
}
void DFRobotSEN0609Component::control_number(number::Number *number, float value) {
for (uint8_t i = 0; i < NUMBER_COUNT; i++) {
if (this->numbers_[i] == number) {
this->control_number(static_cast<NumberSetting>(i), value);
return;
}
}
}
void DFRobotSEN0609Component::forget_number_changes_(Param param, bool unknown) {
for (uint8_t i = 0; i < NUMBER_COUNT; i++) {
if (progmem_read_byte(&NUMBER_PARAMS[i]) != param)
@@ -62,6 +62,12 @@ enum SwitchSetting : uint8_t {
SWITCH_COUNT,
};
enum ButtonAction : uint8_t {
BUTTON_RESTART,
BUTTON_FACTORY_RESET,
BUTTON_QUERY_PARAMS,
};
/// Module parameters; a write or read of each is one command. Pending work runs in this order.
enum Param : uint8_t {
PARAM_RUN_APP,
@@ -124,8 +130,6 @@ class DFRobotSEN0609Component final : public Component, public uart::UARTDevice
#ifdef USE_NUMBER
void set_number(NumberSetting setting, number::Number *number) { this->numbers_[setting] = number; }
void control_number(NumberSetting setting, float value);
// Finds the setting by pointer so every number shares one Apply function
void control_number(number::Number *number, float value);
#endif
#ifdef USE_SWITCH
void set_switch(SwitchSetting setting, switch_::Switch *sw) { this->switches_[setting] = sw; }
@@ -31,6 +31,7 @@ from .. import (
final_validate_mode,
)
DFRobotSEN0609Number = dfrobot_sen0609_ns.class_("DFRobotSEN0609Number", number.Number)
NumberSetting = dfrobot_sen0609_ns.enum("NumberSetting")
CONF_HOLD_SENSITIVITY = "hold_sensitivity"
@@ -54,7 +55,7 @@ class Setting(NamedTuple):
def _distance(icon: str = ICON_RULER) -> cv.Schema:
return number.number_schema(
number.ApplyNumber,
DFRobotSEN0609Number,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=icon,
@@ -64,7 +65,7 @@ def _distance(icon: str = ICON_RULER) -> cv.Schema:
def _duration() -> cv.Schema:
return number.number_schema(
number.ApplyNumber,
DFRobotSEN0609Number,
device_class=DEVICE_CLASS_DURATION,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_TIMELAPSE,
@@ -74,7 +75,7 @@ def _duration() -> cv.Schema:
def _plain() -> cv.Schema:
return number.number_schema(
number.ApplyNumber,
DFRobotSEN0609Number,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
)
@@ -186,8 +187,8 @@ async def to_code(config: ConfigType) -> None:
await numbers(
s.key,
lambda n, setting=s.setting: hub.set_number(setting, n),
s.setting,
min_value=s.min_value,
max_value=max_range if s.max_value is None else s.max_value,
step=s.step,
apply="control_number(entity, value)",
)
@@ -0,0 +1,18 @@
#pragma once
#include "esphome/components/number/number.h"
#include "../dfrobot_sen0609.h"
namespace esphome::dfrobot_sen0609 {
class DFRobotSEN0609Number final : public number::Number, public Parented<DFRobotSEN0609Component> {
public:
explicit DFRobotSEN0609Number(NumberSetting setting) : setting_(setting) {}
protected:
void control(float value) override { this->parent_->control_number(this->setting_, value); }
NumberSetting setting_;
};
} // namespace esphome::dfrobot_sen0609
+36 -15
View File
@@ -1,5 +1,7 @@
#include "ds1603l.h"
#include <cstring>
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
@@ -8,28 +10,35 @@ namespace esphome::ds1603l {
ESPHOME_LOG_TAG(TAG, "ds1603l.sensor");
void DS1603L::loop() {
this->read_available([this](uint8_t byte) {
const uint8_t *frame = this->reader_.feed(byte);
if (frame == nullptr) {
if (this->reader_.length() == 0) {
ESP_LOGV(TAG, "Skipping byte 0x%02X while looking for header", byte);
}
return;
// Assemble frames one byte at a time so a stream that starts mid-frame can realign
uint8_t byte;
while (this->available() > 0 && this->read_byte(&byte)) {
if (this->rx_count_ == 0 && byte != HEADER_BYTE) {
ESP_LOGV(TAG, "Skipping byte 0x%02X while looking for header", byte);
continue;
}
if (!this->parse_data_(frame)) {
this->rx_buffer_[this->rx_count_++] = byte;
if (this->rx_count_ < FRAME_SIZE) {
continue;
}
if (this->parse_data_()) {
this->rx_count_ = 0;
} else {
// The header byte was part of the payload of a misaligned frame, so realign instead of dropping everything
this->reader_.resync();
this->resync_();
}
});
}
}
void DS1603L::dump_config() { LOG_SENSOR("", "DS1603L", this); }
bool DS1603L::parse_data_(const uint8_t *frame) {
uint8_t header = frame[0];
uint8_t data_h = frame[1];
uint8_t data_l = frame[2];
uint8_t checksum = frame[3];
bool DS1603L::parse_data_() {
uint8_t header = this->rx_buffer_[0];
uint8_t data_h = this->rx_buffer_[1];
uint8_t data_l = this->rx_buffer_[2];
uint8_t checksum = this->rx_buffer_[3];
uint8_t computed_checksum = (header + data_h + data_l) & 0xFF;
@@ -44,4 +53,16 @@ bool DS1603L::parse_data_(const uint8_t *frame) {
return true;
}
void DS1603L::resync_() {
// Drop the byte that was treated as the header, then look for the next candidate header in what is left
size_t start = 1;
while (start < this->rx_count_ && this->rx_buffer_[start] != HEADER_BYTE) {
start++;
}
this->rx_count_ -= start;
if (this->rx_count_ > 0) {
memmove(this->rx_buffer_, this->rx_buffer_ + start, this->rx_count_);
}
}
} // namespace esphome::ds1603l
+6 -4
View File
@@ -6,7 +6,6 @@
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/component.h"
#include "esphome/core/frame_reader.h"
namespace esphome::ds1603l {
@@ -19,10 +18,13 @@ class DS1603L final : public sensor::Sensor, public Component, public uart::UART
static constexpr uint8_t HEADER_BYTE = 0xFF;
static constexpr size_t FRAME_SIZE = 4;
// Validates the checksum of the frame and publishes it. Returns false if the frame is invalid.
bool parse_data_(const uint8_t *frame);
// Validates the checksum of the frame in rx_buffer_ and publishes it. Returns false if the frame is invalid.
bool parse_data_();
// Drops the first buffered byte and realigns the buffer on the next possible header byte.
void resync_();
FrameReader<FRAME_SIZE, HEADER_BYTE> reader_;
uint8_t rx_buffer_[FRAME_SIZE]; // Buffer for the frame being assembled
size_t rx_count_{0}; // Number of bytes currently in rx_buffer_
};
} // namespace esphome::ds1603l
@@ -1,5 +1,6 @@
#include "ds248x_one_wire_bus.h"
#include "ds248x.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome::ds248x {
@@ -151,11 +152,7 @@ uint64_t DS248xOneWireBus::search_int() {
}
// Update address based on direction taken
if (dir_taken) {
address |= bit_mask;
} else {
address &= ~bit_mask;
}
assign_bits(address, bit_mask, dir_taken);
}
// Search successful
@@ -1,18 +1,20 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import ENTITY_CATEGORY_CONFIG
from esphome.types import ConfigType
from .. import CONF_ESPECTRE_ID, ESPectreComponent
from .. import CONF_ESPECTRE_ID, ESPectreComponent, espectre_ns
DEPENDENCIES = ["espectre"]
RecalibrateButton = espectre_ns.class_("RecalibrateButton", button.Button)
CONFIG_SCHEMA = button.button_schema(
button.ApplyButton, entity_category=ENTITY_CATEGORY_CONFIG
RecalibrateButton, entity_category=ENTITY_CATEGORY_CONFIG
).extend({cv.GenerateID(CONF_ESPECTRE_ID): cv.use_id(ESPectreComponent)})
async def to_code(config: ConfigType) -> None:
buttons = button.sub_buttons(config, parent=config[CONF_ESPECTRE_ID])
await buttons.create(config, None, apply="recalibrate()")
var = await button.new_button(config)
await cg.register_parented(var, config[CONF_ESPECTRE_ID])
@@ -0,0 +1,19 @@
#pragma once
#include "esphome/core/defines.h"
#ifdef USE_ESPECTRE
#include "esphome/components/button/button.h"
#include "../espectre.h"
namespace esphome::espectre {
class RecalibrateButton final : public button::Button, public Parented<ESPectreComponent> {
protected:
void press_action() override { this->parent_->recalibrate(); }
};
} // namespace esphome::espectre
#endif // USE_ESPECTRE
+11 -7
View File
@@ -1,5 +1,6 @@
from esphome import automation
import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_DEVICE_CLASS,
@@ -9,15 +10,15 @@ from esphome.const import (
CONF_ID,
CONF_MQTT_ID,
CONF_ON_EVENT,
CONF_WEB_SERVER,
DEVICE_CLASS_BUTTON,
DEVICE_CLASS_DOORBELL,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_MOTION,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority, entity_helpers
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import (
entity_duplicate_validator,
mqtt_component_class,
queue_entity_register,
setup_device_class,
setup_entity,
@@ -43,13 +44,11 @@ EventPtr = Event.operator("ptr")
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
_EVENT_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(entity_helpers.WEBSERVER_SORTING_SCHEMA)
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMPONENT_SCHEMA)
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt_component_class("MQTTEventComponent")
),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTEventComponent),
cv.GenerateID(): cv.declare_id(Event),
cv.Optional(
CONF_DEVICE_CLASS, visibility=cv.Visibility.ADVANCED
@@ -105,7 +104,12 @@ async def setup_event_core_(
setup_device_class(config)
await entity_helpers.setup_entity_integrations(var, config)
if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if web_server_config := config.get(CONF_WEB_SERVER):
await web_server.add_entity_config(var, web_server_config)
async def register_event(
+17 -25
View File
@@ -1,7 +1,6 @@
#include "ezo_pmp.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esphome/core/progmem.h"
namespace esphome::ezo_pmp {
@@ -32,24 +31,11 @@ static const uint16_t EZO_PMP_COMMAND_READ_ABSOLUTE_TOTAL_VOLUME_DOSED = 65;
static const uint16_t EZO_PMP_COMMAND_READ_CALIBRATION_STATUS = 129;
static const uint16_t EZO_PMP_COMMAND_READ_PUMP_VOLTAGE = 257;
#ifdef USE_TEXT_SENSOR
enum DosingMode : uint8_t {
DOSING_MODE_NONE,
DOSING_MODE_VOLUME,
DOSING_MODE_VOLUME_OVER_TIME,
DOSING_MODE_CONSTANT_FLOW_RATE,
DOSING_MODE_CONTINUOUS,
};
PROGMEM_STRING_TABLE(DosingModeStrings, "None", "Volume", "Volume/Time", "Constant Flow Rate", "Continuous");
static void publish_dosing_mode(text_sensor::TextSensor *sensor, DosingMode mode) {
if (sensor == nullptr)
return;
const ProgmemStr name = DosingModeStrings::get_progmem_str(mode, DOSING_MODE_NONE);
if (ESPHOME_strcmp_P(sensor->state.c_str(), reinterpret_cast<ESPHOME_PGM_P>(name)) != 0)
sensor->publish_state(name);
}
#endif
static const std::string DOSING_MODE_NONE = "None";
static const std::string DOSING_MODE_VOLUME = "Volume";
static const std::string DOSING_MODE_VOLUME_OVER_TIME = "Volume/Time";
static const std::string DOSING_MODE_CONSTANT_FLOW_RATE = "Constant Flow Rate";
static const std::string DOSING_MODE_CONTINUOUS = "Continuous";
void EzoPMP::dump_config() {
LOG_I2C_DEVICE(this);
@@ -226,7 +212,8 @@ void EzoPMP::read_command_result_() {
#ifdef USE_TEXT_SENSOR
if (!this->is_dosing_flag_ && !this->is_paused_flag_) {
// If pump is not paused and not dispensing
publish_dosing_mode(this->dosing_mode_, DOSING_MODE_NONE);
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_NONE)
this->dosing_mode_->publish_state(DOSING_MODE_NONE);
}
#endif
@@ -298,25 +285,29 @@ void EzoPMP::read_command_result_() {
case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55)
#ifdef USE_TEXT_SENSOR
publish_dosing_mode(this->dosing_mode_, DOSING_MODE_VOLUME);
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_VOLUME)
this->dosing_mode_->publish_state(DOSING_MODE_VOLUME);
#endif
break;
case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56)
#ifdef USE_TEXT_SENSOR
publish_dosing_mode(this->dosing_mode_, DOSING_MODE_VOLUME_OVER_TIME);
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_VOLUME_OVER_TIME)
this->dosing_mode_->publish_state(DOSING_MODE_VOLUME_OVER_TIME);
#endif
break;
case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57)
#ifdef USE_TEXT_SENSOR
publish_dosing_mode(this->dosing_mode_, DOSING_MODE_CONSTANT_FLOW_RATE);
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_CONSTANT_FLOW_RATE)
this->dosing_mode_->publish_state(DOSING_MODE_CONSTANT_FLOW_RATE);
#endif
break;
case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54)
#ifdef USE_TEXT_SENSOR
publish_dosing_mode(this->dosing_mode_, DOSING_MODE_CONTINUOUS);
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_CONTINUOUS)
this->dosing_mode_->publish_state(DOSING_MODE_CONTINUOUS);
#endif
break;
@@ -327,7 +318,8 @@ void EzoPMP::read_command_result_() {
this->is_paused_->publish_state(this->is_paused_flag_);
#endif
#ifdef USE_TEXT_SENSOR
publish_dosing_mode(this->dosing_mode_, DOSING_MODE_NONE);
if (this->dosing_mode_ && this->dosing_mode_->state != DOSING_MODE_NONE)
this->dosing_mode_->publish_state(DOSING_MODE_NONE);
#endif
break;
+12 -8
View File
@@ -1,6 +1,7 @@
from esphome import automation
from esphome.automation import maybe_simple_id
import esphome.codegen as cg
from esphome.components import mqtt, web_server
import esphome.config_validation as cv
from esphome.const import (
CONF_DIRECTION,
@@ -28,11 +29,11 @@ from esphome.const import (
CONF_SPEED_LEVEL_STATE_TOPIC,
CONF_SPEED_STATE_TOPIC,
CONF_TRIGGER_ID,
CONF_WEB_SERVER,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority, entity_helpers
from esphome.core import CORE, CoroPriority, coroutine_with_priority
from esphome.core.entity_helpers import (
entity_duplicate_validator,
mqtt_component_class,
queue_entity_register,
setup_entity,
)
@@ -77,16 +78,14 @@ FanPresetSetTrigger = fan_ns.class_(
)
_FAN_SCHEMA = (
cv.ENTITY_BASE_SCHEMA.extend(entity_helpers.WEBSERVER_SORTING_SCHEMA)
cv.ENTITY_BASE_SCHEMA.extend(web_server.WEBSERVER_SORTING_SCHEMA)
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
.extend(
{
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
RESTORE_MODES, upper=True, space="_"
),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt_component_class("MQTTFanComponent")
),
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent),
cv.Optional(CONF_DIRECTION_STATE_TOPIC): cv.All(
cv.requires_component("mqtt"), cv.publish_topic
),
@@ -241,8 +240,10 @@ _TRIGGER_AUTOMATIONS = (
async def setup_fan_core_(var, config):
cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))
mqtt_ = await entity_helpers.setup_entity_integrations(var, config)
if mqtt_ is not None:
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
mqtt_ = cg.new_Pvariable(mqtt_id, var)
await mqtt.register_mqtt_component(mqtt_, config)
if (
direction_state_topic := config.get(CONF_DIRECTION_STATE_TOPIC)
) is not None:
@@ -276,6 +277,9 @@ async def setup_fan_core_(var, config):
if (speed_command_topic := config.get(CONF_SPEED_COMMAND_TOPIC)) is not None:
cg.add(mqtt_.set_custom_speed_command_topic(speed_command_topic))
if web_server_config := config.get(CONF_WEB_SERVER):
await web_server.add_entity_config(var, web_server_config)
for conf in config.get(CONF_ON_STATE, []):
await automation.build_parent_callback_automation(
var, "add_on_state_callback", (Fan.operator("ptr"), "x"), conf
+11 -8
View File
@@ -1,10 +1,12 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.types import ConfigType
from ..climate import CONF_HAIER_ID, HonClimate
from ..climate import CONF_HAIER_ID, HonClimate, haier_ns
CODEOWNERS = ["@paveldn"]
SelfCleaningButton = haier_ns.class_("SelfCleaningButton", button.Button)
SteriCleaningButton = haier_ns.class_("SteriCleaningButton", button.Button)
# Haier buttons
@@ -18,18 +20,19 @@ CONFIG_SCHEMA = cv.Schema(
{
cv.GenerateID(CONF_HAIER_ID): cv.use_id(HonClimate),
cv.Optional(CONF_SELF_CLEANING): button.button_schema(
button.ApplyButton,
SelfCleaningButton,
icon=ICON_SPRAY_BOTTLE,
),
cv.Optional(CONF_STERI_CLEANING): button.button_schema(
button.ApplyButton,
SteriCleaningButton,
icon=ICON_SPRAY_BOTTLE,
),
}
)
async def to_code(config: ConfigType) -> None:
buttons = button.sub_buttons(config, parent=config[CONF_HAIER_ID])
await buttons(CONF_SELF_CLEANING, None, apply="start_self_cleaning()")
await buttons(CONF_STERI_CLEANING, None, apply="start_steri_cleaning()")
async def to_code(config):
for button_type in [CONF_SELF_CLEANING, CONF_STERI_CLEANING]:
if conf := config.get(button_type):
btn = await button.new_button(conf)
await cg.register_parented(btn, config[CONF_HAIER_ID])
@@ -0,0 +1,7 @@
#include "self_cleaning.h"
namespace esphome::haier {
void SelfCleaningButton::press_action() { this->parent_->start_self_cleaning(); }
} // namespace esphome::haier
@@ -0,0 +1,16 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../hon_climate.h"
namespace esphome::haier {
class SelfCleaningButton final : public button::Button, public Parented<HonClimate> {
public:
SelfCleaningButton() = default;
protected:
void press_action() override;
};
} // namespace esphome::haier
@@ -0,0 +1,7 @@
#include "steri_cleaning.h"
namespace esphome::haier {
void SteriCleaningButton::press_action() { this->parent_->start_steri_cleaning(); }
} // namespace esphome::haier
@@ -0,0 +1,16 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../hon_climate.h"
namespace esphome::haier {
class SteriCleaningButton final : public button::Button, public Parented<HonClimate> {
public:
SteriCleaningButton() = default;
protected:
void press_action() override;
};
} // namespace esphome::haier
@@ -1,9 +1,10 @@
import esphome.codegen as cg
from esphome.components import button
import esphome.config_validation as cv
from esphome.const import ICON_AIR_FILTER
from esphome.types import ConfigType
from .. import CONF_HOERMANN_HCP_ID, HoermannHcp
from .. import CONF_HOERMANN_HCP_ID, HoermannHcp, hoermann_hcp_ns
DEPENDENCIES = ["hoermann_hcp"]
@@ -12,6 +13,10 @@ CONF_VENT = "vent"
ICON_GARAGE_OPEN_VARIANT = "mdi:garage-open-variant"
HoermannHcpVentButton = hoermann_hcp_ns.class_("HoermannHcpVentButton", button.Button)
HoermannHcpHalfOpenButton = hoermann_hcp_ns.class_(
"HoermannHcpHalfOpenButton", button.Button
)
BUTTON_KEYS = (CONF_VENT, CONF_HALF_OPEN)
@@ -20,10 +25,10 @@ CONFIG_SCHEMA = cv.All(
{
cv.GenerateID(CONF_HOERMANN_HCP_ID): cv.use_id(HoermannHcp),
cv.Optional(CONF_VENT): button.button_schema(
button.ApplyButton, icon=ICON_AIR_FILTER
HoermannHcpVentButton, icon=ICON_AIR_FILTER
),
cv.Optional(CONF_HALF_OPEN): button.button_schema(
button.ApplyButton, icon=ICON_GARAGE_OPEN_VARIANT
HoermannHcpHalfOpenButton, icon=ICON_GARAGE_OPEN_VARIANT
),
}
),
@@ -32,6 +37,7 @@ CONFIG_SCHEMA = cv.All(
async def to_code(config: ConfigType) -> None:
buttons = button.sub_buttons(config, parent=config[CONF_HOERMANN_HCP_ID])
await buttons(CONF_VENT, None, apply="vent_door()")
await buttons(CONF_HALF_OPEN, None, apply="half_open_door()")
parent = await cg.get_variable(config[CONF_HOERMANN_HCP_ID])
for key in BUTTON_KEYS:
if (conf := config.get(key)) is not None:
await button.new_button(conf, parent)
@@ -0,0 +1,34 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../hoermann_hcp.h"
namespace esphome::hoermann_hcp {
// The door commands the cover has no equivalent for. A refused command is already reported by the hub and
// leaves nothing to correct here, because a button carries no state of its own.
class HoermannHcpButton : public button::Button {
public:
explicit HoermannHcpButton(HoermannHcp *parent) : parent_(parent) {}
protected:
HoermannHcp *const parent_;
};
class HoermannHcpVentButton final : public HoermannHcpButton {
public:
using HoermannHcpButton::HoermannHcpButton;
protected:
void press_action() override { this->parent_->vent_door(); }
};
class HoermannHcpHalfOpenButton final : public HoermannHcpButton {
public:
using HoermannHcpButton::HoermannHcpButton;
protected:
void press_action() override { this->parent_->half_open_door(); }
};
} // namespace esphome::hoermann_hcp
+3 -6
View File
@@ -1,6 +1,7 @@
#include "htu21d.h"
#include "esphome/core/log.h"
#include "esphome/core/hal.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
namespace esphome::htu21d {
@@ -112,11 +113,7 @@ void HTU21DComponent::set_heater(bool status) {
this->status_set_warning();
return;
}
if (status) {
raw_heater |= (1 << HTU21D_REG_HTRE_BIT);
} else {
raw_heater &= ~(1 << HTU21D_REG_HTRE_BIT);
}
assign_bits(raw_heater, 1 << HTU21D_REG_HTRE_BIT, status);
if (this->write_register(HTU21D_WRITERHT_REG_CMD, &raw_heater, 1) != i2c::ERROR_OK) {
this->status_set_warning();
return;
+3 -41
View File
@@ -41,13 +41,11 @@ from esphome.const import (
CONF_FREQUENCY,
CONF_I2C_ID,
CONF_ID,
CONF_INVERTED,
CONF_LOW_POWER_MODE,
CONF_SCAN,
CONF_SCL,
CONF_SDA,
CONF_TIMEOUT,
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_HOST,
@@ -70,7 +68,6 @@ ArduinoI2CBus = i2c_ns.class_("ArduinoI2CBus", InternalI2CBus, cg.Component)
IDFI2CBus = i2c_ns.class_("IDFI2CBus", InternalI2CBus, cg.Component)
ZephyrI2CBus = i2c_ns.class_("ZephyrI2CBus", I2CBus, cg.Component)
HostI2CBus = i2c_ns.class_("HostI2CBus", I2CBus, cg.Component)
SoftwareI2CBus = i2c_ns.class_("SoftwareI2CBus", I2CBus, cg.Component)
I2CDevice = i2c_ns.class_("I2CDevice")
ESP32_I2C_CAPABILITIES = {
@@ -111,8 +108,6 @@ def validate_device(value: str) -> str:
def _bus_declare_type(value: Any) -> ID:
if CORE.is_esp32:
return cv.declare_id(IDFI2CBus)(value)
if CORE.is_libretiny:
return cv.declare_id(SoftwareI2CBus)(value)
if CORE.using_arduino:
return cv.declare_id(ArduinoI2CBus)(value)
if CORE.using_zephyr:
@@ -133,28 +128,7 @@ def _rp2040_i2c_controller(pin: int) -> int:
return (pin // 2) % 2
def _i2c_pin_number(value: Any) -> Any:
# The software bus on LibreTiny drives the pins through the GPIO pin objects and
# switches them between input with pull-up and output at runtime.
if CORE.is_libretiny:
return pins.internal_gpio_input_pullup_pin_schema(value)
return pins.internal_gpio_pin_number(value)
def validate_config(config: ConfigType) -> ConfigType:
if CORE.is_libretiny:
if int(config[CONF_FREQUENCY]) < 1:
raise cv.Invalid("The frequency must be at least 1Hz", [CONF_FREQUENCY])
# no default pins on LibreTiny, the bus is bit-banged on any two GPIOs
for key in (CONF_SDA, CONF_SCL):
if key not in config:
raise cv.Invalid(f"'{key}' is required on this platform", [key])
if config[key].get(CONF_INVERTED):
# the software bus pulls a line low by writing a low level to the pin
raise cv.Invalid(
f"'{CONF_INVERTED}' is not supported on the I2C pins",
[key, CONF_INVERTED],
)
if CORE.is_esp32:
return cv.require_framework_version(
esp_idf=cv.Version(5, 4, 2), esp32_arduino=cv.Version(3, 2, 1)
@@ -207,7 +181,7 @@ CONFIG_SCHEMA = cv.All(
esp8266="SDA",
rp2="SDA",
nrf52="SDA",
): _i2c_pin_number,
): pins.internal_gpio_pin_number,
cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32=True): cv.All(
cv.only_on_esp32, cv.boolean
),
@@ -217,7 +191,7 @@ CONFIG_SCHEMA = cv.All(
esp8266="SCL",
rp2="SCL",
nrf52="SCL",
): _i2c_pin_number,
): pins.internal_gpio_pin_number,
cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32=True): cv.All(
cv.only_on_esp32, cv.boolean
),
@@ -228,7 +202,6 @@ CONFIG_SCHEMA = cv.All(
rp2="50kHz",
nrf52="100kHz",
host="50kHz",
bk72xx="100kHz",
): cv.All(
cv.frequency,
cv.float_range(min=0, min_included=False),
@@ -252,7 +225,6 @@ CONFIG_SCHEMA = cv.All(
).extend(cv.COMPONENT_SCHEMA),
cv.only_on(
[
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
PLATFORM_RP2,
@@ -371,16 +343,6 @@ async def to_code(config: ConfigType) -> None:
cg.add(var.set_timeout(int(config[CONF_TIMEOUT].total_microseconds)))
if CONF_LOW_POWER_MODE in config:
cg.add(var.set_lp_mode(bool(config[CONF_LOW_POWER_MODE])))
elif CORE.is_libretiny:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
cg.add(var.set_sda_pin(await cg.gpio_pin_expression(config[CONF_SDA])))
cg.add(var.set_scl_pin(await cg.gpio_pin_expression(config[CONF_SCL])))
cg.add(var.set_frequency(int(config[CONF_FREQUENCY])))
cg.add(var.set_scan(config[CONF_SCAN]))
if CONF_TIMEOUT in config:
cg.add(var.set_timeout(int(config[CONF_TIMEOUT].total_microseconds)))
else:
var = cg.new_Pvariable(config[CONF_ID])
await cg.register_component(var, config)
@@ -500,6 +462,7 @@ _platform_filter = filter_source_files_from_platform(
"i2c_bus_arduino.cpp": {
PlatformFramework.ESP8266_ARDUINO,
PlatformFramework.RP2_ARDUINO,
PlatformFramework.BK72XX_ARDUINO,
PlatformFramework.RTL87XX_ARDUINO,
PlatformFramework.LN882X_ARDUINO,
},
@@ -507,7 +470,6 @@ _platform_filter = filter_source_files_from_platform(
PlatformFramework.ESP32_ARDUINO,
PlatformFramework.ESP32_IDF,
},
"i2c_bus_software.cpp": {PlatformFramework.BK72XX_ARDUINO},
"i2c_bus_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
"i2c_bus_host.cpp": {PlatformFramework.HOST_NATIVE},
}
-265
View File
@@ -1,265 +0,0 @@
#ifdef USE_LIBRETINY
#include "i2c_bus_software.h"
#include <cinttypes>
#include "esphome/core/hal.h"
#include "esphome/core/log.h"
namespace esphome::i2c {
ESPHOME_LOG_TAG(TAG, "i2c.software");
// Maximum bytes to log in hex format (truncates larger transfers)
static constexpr size_t I2C_MAX_LOG_BYTES = 32;
// Clock pulses used to free a device that is holding SDA low
static constexpr uint8_t RECOVERY_CLOCK_PULSES = 9;
// A line is released by switching the pin to input with pull-up and pulled low by driving it as output.
static inline void release_line(InternalGPIOPin *pin) { pin->pin_mode(gpio::FLAG_INPUT | gpio::FLAG_PULLUP); }
static inline void pull_line_low(InternalGPIOPin *pin) {
pin->digital_write(false);
pin->pin_mode(gpio::FLAG_OUTPUT);
}
void SoftwareI2CBus::setup() {
this->update_half_period_();
this->sda_pin_->setup();
this->scl_pin_->setup();
release_line(this->sda_pin_);
release_line(this->scl_pin_);
if (!this->recover_()) {
// A line that stays low would be read as an ACK for every address and as zero data
ESP_LOGE(TAG, "SDA or SCL is held low, the bus cannot be used");
this->mark_failed();
return;
}
this->initialized_ = true;
if (this->scan_) {
ESP_LOGV(TAG, "Scanning bus for active devices");
this->i2c_scan_();
}
}
void SoftwareI2CBus::dump_config() {
ESP_LOGCONFIG(TAG, "I2C Bus (software):");
LOG_PIN(" SDA Pin: ", this->sda_pin_);
LOG_PIN(" SCL Pin: ", this->scl_pin_);
ESP_LOGCONFIG(TAG,
" Frequency: %" PRIu32 " Hz\n"
" Clock stretch timeout: %" PRIu32 " us",
this->frequency_, this->timeout_);
if (this->recovered_) {
ESP_LOGCONFIG(TAG, " Recovery: bus successfully recovered");
}
if (this->scan_) {
ESP_LOGI(TAG, "Results from bus scan:");
if (this->scan_results_.empty()) {
ESP_LOGI(TAG, "Found no devices");
} else {
for (const auto &s : this->scan_results_) {
if (s.second) {
ESP_LOGI(TAG, "Found device at address 0x%02X", s.first);
} else {
ESP_LOGE(TAG, "Unknown error at address 0x%02X", s.first);
}
}
}
}
}
// Half of one clock period in microseconds, rounded up. The frequency is validated to be non-zero.
void SoftwareI2CBus::update_half_period_() {
this->half_period_us_ = (500000 + this->frequency_ - 1) / this->frequency_;
}
#ifdef I2C_PORT_FREQUENCY_COUNT
ErrorCode SoftwareI2CBus::switch_frequency(uint32_t frequency) {
if (frequency == 0)
return ERROR_INVALID_ARGUMENT;
this->frequency_ = frequency;
this->update_half_period_();
return ERROR_OK;
}
#endif
bool SoftwareI2CBus::recover_() {
this->timed_out_ = false;
// A device may still be stretching the clock, give SCL the configured time to rise before looking at SDA
this->release_scl_();
// A device that was interrupted in the middle of a transfer can hold SDA low; clock it out.
bool clocked = false;
for (uint8_t i = 0; i < RECOVERY_CLOCK_PULSES && !this->sda_pin_->digital_read(); i++) {
pull_line_low(this->scl_pin_);
delayMicroseconds(this->half_period_us_);
// wait for the rising edge, the device may stretch the clock
if (!this->release_scl_())
break;
delayMicroseconds(this->half_period_us_);
clocked = true;
}
bool ok = this->sda_pin_->digital_read() && this->scl_pin_->digital_read();
if (ok && clocked) {
// end the interrupted transfer with a STOP so every device returns to idle
this->recovered_ = true;
pull_line_low(this->sda_pin_);
delayMicroseconds(this->half_period_us_);
release_line(this->scl_pin_);
delayMicroseconds(this->half_period_us_);
release_line(this->sda_pin_);
}
if (!ok) {
ESP_LOGW(TAG, "Bus recovery failed, a line is held low");
}
this->timed_out_ = false;
return ok;
}
// Release SCL and wait for it to go high, a device may stretch the clock.
bool SoftwareI2CBus::release_scl_() {
release_line(this->scl_pin_);
uint32_t start = micros();
while (!this->scl_pin_->digital_read()) {
if (micros() - start > this->timeout_) {
this->timed_out_ = true;
return false;
}
}
return true;
}
void SoftwareI2CBus::start_() {
// Also generates a repeated start when SCL is low and SDA is released
release_line(this->sda_pin_);
// After a byte the device may still hold SDA low for its ACK. Without SDA being high before SCL rises there is
// no falling edge, so the (repeated) start would not be seen. Wait until the device lets go.
uint32_t start = micros();
while (!this->sda_pin_->digital_read()) {
if (micros() - start > this->timeout_) {
this->timed_out_ = true;
return;
}
}
delayMicroseconds(this->half_period_us_);
this->release_scl_();
delayMicroseconds(this->half_period_us_);
pull_line_low(this->sda_pin_);
delayMicroseconds(this->half_period_us_);
pull_line_low(this->scl_pin_);
}
void SoftwareI2CBus::stop_() {
if (this->timed_out_) {
// SCL is stuck low, a STOP cannot be generated; just release both lines
release_line(this->scl_pin_);
release_line(this->sda_pin_);
return;
}
pull_line_low(this->sda_pin_);
delayMicroseconds(this->half_period_us_);
this->release_scl_();
delayMicroseconds(this->half_period_us_);
release_line(this->sda_pin_);
delayMicroseconds(this->half_period_us_);
}
/// Shift out one byte, returns true if the device acknowledged it.
bool SoftwareI2CBus::write_byte_(uint8_t data) {
for (uint8_t mask = 0x80; mask != 0 && !this->timed_out_; mask >>= 1) {
if (data & mask) {
release_line(this->sda_pin_);
} else {
pull_line_low(this->sda_pin_);
}
delayMicroseconds(this->half_period_us_);
this->release_scl_();
delayMicroseconds(this->half_period_us_);
pull_line_low(this->scl_pin_);
}
if (this->timed_out_)
return false;
release_line(this->sda_pin_);
delayMicroseconds(this->half_period_us_);
if (!this->release_scl_())
return false;
delayMicroseconds(this->half_period_us_);
bool ack = !this->sda_pin_->digital_read();
pull_line_low(this->scl_pin_);
return ack;
}
uint8_t SoftwareI2CBus::read_byte_(bool ack) {
uint8_t data = 0;
release_line(this->sda_pin_);
for (uint8_t i = 0; i < 8 && !this->timed_out_; i++) {
delayMicroseconds(this->half_period_us_);
this->release_scl_();
delayMicroseconds(this->half_period_us_);
data = (data << 1) | (this->sda_pin_->digital_read() ? 1 : 0);
pull_line_low(this->scl_pin_);
}
if (this->timed_out_)
return data;
if (ack) {
pull_line_low(this->sda_pin_);
}
delayMicroseconds(this->half_period_us_);
this->release_scl_();
delayMicroseconds(this->half_period_us_);
pull_line_low(this->scl_pin_);
release_line(this->sda_pin_);
return data;
}
ErrorCode SoftwareI2CBus::write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count,
uint8_t *read_buffer, size_t read_count) {
if (!this->initialized_) {
ESP_LOGD(TAG, "i2c bus not initialized!");
return ERROR_NOT_INITIALIZED;
}
// After a failed recovery the bus stays blocked until a later attempt frees both lines
if (this->bus_stuck_ && !this->recover_()) {
return ERROR_TIMEOUT;
}
this->bus_stuck_ = false;
ESP_LOGV_HEX(TAG, write_buffer, write_count, I2C_MAX_LOG_BYTES, "0x%02X TX %s", address);
this->timed_out_ = false;
ErrorCode err = ERROR_OK;
this->start_();
// An empty transfer only addresses the device (used by the bus scan)
if (write_count != 0 || read_count == 0) {
if (!this->write_byte_(address << 1)) {
err = ERROR_NOT_ACKNOWLEDGED;
}
for (size_t i = 0; err == ERROR_OK && i < write_count; i++) {
if (!this->write_byte_(write_buffer[i])) {
ESP_LOGVV(TAG, "TX of byte %u to 0x%02X not acknowledged", (unsigned) i, address);
err = ERROR_NOT_ACKNOWLEDGED;
}
}
}
if (err == ERROR_OK && read_count != 0) {
if (write_count != 0) {
this->start_(); // repeated start
}
if (!this->write_byte_((address << 1) | 1)) {
err = ERROR_NOT_ACKNOWLEDGED;
}
for (size_t i = 0; err == ERROR_OK && !this->timed_out_ && i < read_count; i++) {
read_buffer[i] = this->read_byte_(i + 1 != read_count);
}
}
this->stop_();
if (this->timed_out_) {
ESP_LOGVV(TAG, "Bus line held low during transfer to 0x%02X for more than %" PRIu32 " us", address, this->timeout_);
this->bus_stuck_ = !this->recover_();
return ERROR_TIMEOUT;
}
return err;
}
} // namespace esphome::i2c
#endif // USE_LIBRETINY
-55
View File
@@ -1,55 +0,0 @@
#pragma once
#ifdef USE_LIBRETINY
#include "esphome/core/component.h"
#include "esphome/core/gpio.h"
#include "i2c_bus.h"
namespace esphome::i2c {
/// Bit-banged I2C controller for LibreTiny chips without an Arduino Wire implementation (BK72xx).
/// SDA and SCL can be any two GPIO pins; both lines are driven open-drain style (pulled low or released),
/// so the bus needs pull-ups (the internal pull-up is enabled while released).
class SoftwareI2CBus final : public I2CBus, public Component {
public:
void setup() override;
void dump_config() override;
ErrorCode write_readv(uint8_t address, const uint8_t *write_buffer, size_t write_count, uint8_t *read_buffer,
size_t read_count) override;
float get_setup_priority() const override { return setup_priority::BUS; }
void set_scan(bool scan) { this->scan_ = scan; }
void set_sda_pin(InternalGPIOPin *sda_pin) { this->sda_pin_ = sda_pin; }
void set_scl_pin(InternalGPIOPin *scl_pin) { this->scl_pin_ = scl_pin; }
void set_frequency(uint32_t frequency) { this->frequency_ = frequency; }
#ifdef I2C_PORT_FREQUENCY_COUNT
ErrorCode switch_frequency(uint32_t frequency) override;
uint32_t get_frequency() const override { return this->frequency_; }
#endif
/// Maximum time in microseconds a device may hold SCL low (clock stretching).
void set_timeout(uint32_t timeout) { this->timeout_ = timeout; }
protected:
bool recover_();
void update_half_period_();
void start_();
void stop_();
bool release_scl_();
bool write_byte_(uint8_t data);
uint8_t read_byte_(bool ack);
InternalGPIOPin *sda_pin_{nullptr};
InternalGPIOPin *scl_pin_{nullptr};
uint32_t frequency_{100000};
uint32_t timeout_{1000};
uint32_t half_period_us_{5};
bool timed_out_{false};
bool recovered_{false};
bool initialized_{false};
bool bus_stuck_{false};
};
} // namespace esphome::i2c
#endif // USE_LIBRETINY
+11 -11
View File
@@ -5,26 +5,26 @@ import esphome.config_validation as cv
from esphome.const import CONF_FACTORY_RESET, CONF_ID, CONF_RESTART
from esphome.types import ConfigType
from .. import CONF_LD2410_ID, LD2410Component
from .. import CONF_LD2410_ID, LD2410Component, ld2410_ns
FactoryResetButton = ld2410_ns.class_("FactoryResetButton", button.Button)
QueryButton = ld2410_ns.class_("QueryButton", button.Button)
RestartButton = ld2410_ns.class_("RestartButton", button.Button)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
cv.Optional(CONF_FACTORY_RESET): button.factory_reset_button_schema(
button.ApplyButton
FactoryResetButton
),
cv.Optional(CONF_RESTART): button.restart_button_schema(button.ApplyButton),
cv.Optional(CONF_QUERY_PARAMS): button.query_button_schema(button.ApplyButton),
cv.Optional(CONF_RESTART): button.restart_button_schema(RestartButton),
cv.Optional(CONF_QUERY_PARAMS): button.query_button_schema(QueryButton),
}
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_LD2410_ID])
buttons = button.sub_buttons(config, parent=hub)
await buttons(
CONF_FACTORY_RESET, hub.set_factory_reset_button, apply="factory_reset()"
)
await buttons(
CONF_RESTART, hub.set_restart_button, apply="restart_and_read_all_info()"
)
await buttons(CONF_QUERY_PARAMS, hub.set_query_button, apply="read_all_info()")
await buttons(CONF_FACTORY_RESET, hub.set_factory_reset_button)
await buttons(CONF_RESTART, hub.set_restart_button)
await buttons(CONF_QUERY_PARAMS, hub.set_query_button)
@@ -0,0 +1,7 @@
#include "factory_reset_button.h"
namespace esphome::ld2410 {
void FactoryResetButton::press_action() { this->parent_->factory_reset(); }
} // namespace esphome::ld2410
@@ -0,0 +1,16 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
class FactoryResetButton final : public button::Button, public Parented<LD2410Component> {
public:
FactoryResetButton() = default;
protected:
void press_action() override;
};
} // namespace esphome::ld2410
@@ -0,0 +1,7 @@
#include "query_button.h"
namespace esphome::ld2410 {
void QueryButton::press_action() { this->parent_->read_all_info(); }
} // namespace esphome::ld2410
@@ -0,0 +1,16 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
class QueryButton final : public button::Button, public Parented<LD2410Component> {
public:
QueryButton() = default;
protected:
void press_action() override;
};
} // namespace esphome::ld2410
@@ -0,0 +1,7 @@
#include "restart_button.h"
namespace esphome::ld2410 {
void RestartButton::press_action() { this->parent_->restart_and_read_all_info(); }
} // namespace esphome::ld2410
@@ -0,0 +1,16 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
class RestartButton final : public button::Button, public Parented<LD2410Component> {
public:
RestartButton() = default;
protected:
void press_action() override;
};
} // namespace esphome::ld2410
+112 -58
View File
@@ -9,12 +9,27 @@
#include "esphome/core/application.h"
#include "esphome/core/helpers.h"
#include "esphome/core/progmem.h"
namespace esphome::ld2410 {
ESPHOME_LOG_TAG(TAG, "ld2410");
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 = 0x01,
DISTANCE_RESOLUTION_0_75 = 0x00,
};
enum LightFunction : uint8_t {
LIGHT_FUNCTION_OFF = 0x00,
LIGHT_FUNCTION_BELOW = 0x01,
@@ -54,17 +69,73 @@ enum AckData : uint8_t {
COMMAND_STATUS = 7,
};
// Index + 1 is the BaudRate value; the select options use the same order
PROGMEM_STRING_TABLE(BaudRateStrings, "9600", "19200", "38400", "57600", "115200", "230400", "256000", "460800");
static_assert(BAUD_RATE_9600 == 1 && BAUD_RATE_460800 == BaudRateStrings::COUNT, "BaudRate is index + 1");
// Same order as DISTANCE_RESOLUTIONS
PROGMEM_STRING_TABLE(DistanceResolutionStrings, "0.2m", "0.75m");
// The light function and out pin level select options are in enum order
static_assert(LIGHT_FUNCTION_OFF == 0 && LIGHT_FUNCTION_BELOW == 1 && LIGHT_FUNCTION_ABOVE == 2,
"LightFunction is the select option index");
static_assert(OUT_PIN_LEVEL_LOW == 0 && OUT_PIN_LEVEL_HIGH == 1, "OutPinLevel is the select option index");
// Memory-efficient lookup tables
struct StringToUint8 {
const char *str;
const uint8_t value;
};
static constexpr uint32_t BAUD_RATES[] PROGMEM = {9600, 19200, 38400, 57600, 115200, 230400, 256000, 460800};
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.75m", DISTANCE_RESOLUTION_0_75},
};
constexpr Uint8ToString DISTANCE_RESOLUTIONS_BY_UINT[] = {
{DISTANCE_RESOLUTION_0_2, "0.2m"},
{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"},
};
constexpr uint32_t BAUD_RATES[] = {9600, 19200, 38400, 57600, 115200, 230400, 256000, 460800};
// Helper functions for lookups
template<size_t N> uint8_t find_uint8(const StringToUint8 (&arr)[N], const char *str) {
for (const auto &entry : arr) {
if (strcmp(str, entry.str) == 0)
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
}
// Commands
static constexpr uint8_t CMD_ENABLE_CONF = 0xFF;
@@ -201,7 +272,20 @@ void LD2410Component::restart_and_read_all_info() {
}
void LD2410Component::loop() {
this->read_available<MAX_LINE_LENGTH>([this](uint8_t b) { this->readline_(b); });
// Read all available bytes in batches to reduce UART call overhead.
size_t avail = this->available();
uint8_t buf[MAX_LINE_LENGTH];
while (avail > 0) {
size_t to_read = std::min(avail, sizeof(buf));
if (!this->read_array(buf, to_read)) {
break;
}
avail -= to_read;
for (size_t i = 0; i < to_read; i++) {
this->readline_(buf[i]);
}
}
}
void LD2410Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) {
@@ -381,16 +465,11 @@ void LD2410Component::handle_ack_data_() {
if (this->buffer_pos_ < 11 + HEADER_FOOTER_SIZE) {
break; // too short to hold the resolution
}
const auto index = ld24xx::find_index(DISTANCE_RESOLUTIONS, this->buffer_data_[10]);
if (!index) {
ESP_LOGW(TAG, "Unknown distance resolution %u", this->buffer_data_[10]);
break;
}
ESP_LOGV(TAG, "Distance resolution: %s",
LOG_STR_ARG(DistanceResolutionStrings::get_log_str(*index, DistanceResolutionStrings::LAST_INDEX)));
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(*index);
this->distance_resolution_select_->publish_state(distance_resolution);
}
#endif
break;
@@ -403,14 +482,16 @@ void LD2410Component::handle_ack_data_() {
this->light_function_ = this->buffer_data_[10];
this->light_threshold_ = this->buffer_data_[11];
this->out_pin_level_ = this->buffer_data_[12];
ESP_LOGV(TAG, "Light function: %u, threshold: %u, out pin level: %u", this->light_function_,
this->light_threshold_, this->out_pin_level_);
const auto *light_function_str = find_str(LIGHT_FUNCTIONS_BY_UINT, this->light_function_);
const auto *out_pin_level_str = find_str(OUT_PIN_LEVELS_BY_UINT, this->out_pin_level_);
ESP_LOGV(TAG, "Light function: %s, threshold: %u, out pin level: %s", light_function_str, this->light_threshold_,
out_pin_level_str);
#ifdef USE_SELECT
if (this->light_function_select_ != nullptr) {
this->light_function_select_->publish_state(static_cast<size_t>(this->light_function_));
this->light_function_select_->publish_state(light_function_str);
}
if (this->out_pin_level_select_ != nullptr) {
this->out_pin_level_select_->publish_state(static_cast<size_t>(this->out_pin_level_));
this->out_pin_level_select_->publish_state(out_pin_level_str);
}
#endif
#ifdef USE_NUMBER
@@ -547,29 +628,15 @@ void LD2410Component::set_bluetooth(bool enable) {
}
void LD2410Component::set_distance_resolution(const char *state) {
const int16_t index = progmem_string_table_find<DistanceResolutionStrings>(state, strlen(state));
if (index >= 0) {
this->set_distance_resolution(static_cast<DistanceResolution>(progmem_read_byte(&DISTANCE_RESOLUTIONS[index])));
}
}
void LD2410Component::set_distance_resolution(DistanceResolution resolution) {
this->set_config_mode_(true);
const uint8_t cmd_value[2] = {resolution, 0x00};
const uint8_t cmd_value[2] = {find_uint8(DISTANCE_RESOLUTIONS_BY_STR, state), 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 LD2410Component::set_baud_rate(const char *state) {
const int16_t index = progmem_string_table_find<BaudRateStrings>(state, strlen(state));
if (index >= 0) {
this->set_baud_rate(static_cast<BaudRate>(index + 1));
}
}
void LD2410Component::set_baud_rate(BaudRate rate) {
this->set_config_mode_(true);
const uint8_t cmd_value[2] = {rate, 0x00};
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_(); });
}
@@ -648,16 +715,7 @@ void LD2410Component::set_max_distances_timeout() {
this->set_config_mode_(false);
}
void LD2410Component::set_gate_threshold(number::Number *n) {
// Found by pointer: up to 18 compares per write beat storing the gate in each number
uint8_t gate = 0;
while (gate < TOTAL_GATES && this->gate_move_threshold_numbers_[gate] != n &&
this->gate_still_threshold_numbers_[gate] != n)
gate++;
if (gate == TOTAL_GATES) {
ESP_LOGW(TAG, "Threshold number is not a known gate");
return;
}
void LD2410Component::set_gate_threshold(uint8_t gate) {
number::Number *motionsens = this->gate_move_threshold_numbers_[gate];
number::Number *stillsens = this->gate_still_threshold_numbers_[gate];
@@ -701,15 +759,11 @@ void LD2410Component::set_light_out_control() {
}
#endif
#ifdef USE_SELECT
if (this->light_function_select_ != nullptr) {
if (const auto index = this->light_function_select_->active_index()) {
this->light_function_ = *index;
}
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_->current_option().c_str());
}
if (this->out_pin_level_select_ != nullptr) {
if (const auto index = this->out_pin_level_select_->active_index()) {
this->out_pin_level_ = *index;
}
if (this->out_pin_level_select_ != nullptr && this->out_pin_level_select_->has_state()) {
this->out_pin_level_ = find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option().c_str());
}
#endif
this->set_config_mode_(true);
+1 -22
View File
@@ -37,25 +37,6 @@ using namespace ld24xx;
static constexpr uint8_t MAX_LINE_LENGTH = 50;
static constexpr uint8_t TOTAL_GATES = 9; // Total number of gates supported by the LD2410
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 = 0x01,
DISTANCE_RESOLUTION_0_75 = 0x00,
};
// Device values of the distance resolution select options, in option order; in flash on ESP8266
static constexpr uint8_t DISTANCE_RESOLUTIONS[] PROGMEM = {DISTANCE_RESOLUTION_0_2, DISTANCE_RESOLUTION_0_75};
class LD2410Component final : public Component, public uart::UARTDevice {
#ifdef USE_BINARY_SENSOR
SUB_BINARY_SENSOR(out_pin_presence_status)
@@ -106,7 +87,7 @@ class LD2410Component final : public Component, public uart::UARTDevice {
void set_gate_still_threshold_number(uint8_t gate, number::Number *n);
void set_gate_move_threshold_number(uint8_t gate, number::Number *n);
void set_max_distances_timeout();
void set_gate_threshold(number::Number *n);
void set_gate_threshold(uint8_t gate);
#endif
#ifdef USE_SENSOR
void set_gate_move_sensor(uint8_t gate, sensor::Sensor *s);
@@ -118,9 +99,7 @@ class LD2410Component final : public Component, public uart::UARTDevice {
void restart_and_read_all_info();
void set_bluetooth(bool enable);
void set_distance_resolution(const char *state);
void set_distance_resolution(DistanceResolution resolution);
void set_baud_rate(const char *state);
void set_baud_rate(BaudRate rate);
void factory_reset();
protected:
+25 -35
View File
@@ -1,5 +1,3 @@
from functools import partial
import esphome.codegen as cg
from esphome.components import number
import esphome.config_validation as cv
@@ -20,7 +18,11 @@ from esphome.const import (
)
from esphome.types import ConfigType
from .. import CONF_LD2410_ID, LD2410Component
from .. import CONF_LD2410_ID, LD2410Component, ld2410_ns
GateThresholdNumber = ld2410_ns.class_("GateThresholdNumber", number.Number)
LightThresholdNumber = ld2410_ns.class_("LightThresholdNumber", number.Number)
MaxDistanceTimeoutNumber = ld2410_ns.class_("MaxDistanceTimeoutNumber", number.Number)
CONF_MAX_MOVE_DISTANCE_GATE = "max_move_distance_gate"
CONF_MAX_STILL_DISTANCE_GATE = "max_still_distance_gate"
@@ -33,25 +35,25 @@ CONFIG_SCHEMA = cv.Schema(
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
cv.Inclusive(CONF_TIMEOUT, TIMEOUT_GROUP): number.number_schema(
number.ApplyNumber,
MaxDistanceTimeoutNumber,
unit_of_measurement=UNIT_SECOND,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_TIMELAPSE,
),
cv.Inclusive(CONF_MAX_MOVE_DISTANCE_GATE, TIMEOUT_GROUP): number.number_schema(
number.ApplyNumber,
MaxDistanceTimeoutNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Inclusive(CONF_MAX_STILL_DISTANCE_GATE, TIMEOUT_GROUP): number.number_schema(
number.ApplyNumber,
MaxDistanceTimeoutNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Optional(CONF_LIGHT_THRESHOLD): number.number_schema(
number.ApplyNumber,
LightThresholdNumber,
device_class=DEVICE_CLASS_ILLUMINANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_LIGHTBULB,
@@ -64,14 +66,14 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
cv.Optional(f"g{x}"): cv.Schema(
{
cv.Required(CONF_MOVE_THRESHOLD): number.number_schema(
number.ApplyNumber,
GateThresholdNumber,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
unit_of_measurement=UNIT_PERCENT,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Required(CONF_STILL_THRESHOLD): number.number_schema(
number.ApplyNumber,
GateThresholdNumber,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
unit_of_measurement=UNIT_PERCENT,
entity_category=ENTITY_CATEGORY_CONFIG,
@@ -87,14 +89,8 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_LD2410_ID])
numbers = number.sub_numbers(config, parent=hub)
timeouts = {"apply": "set_max_distances_timeout()", "publish": "before"}
await numbers(
CONF_TIMEOUT,
hub.set_timeout_number,
min_value=0,
max_value=65535,
step=1,
**timeouts,
CONF_TIMEOUT, hub.set_timeout_number, min_value=0, max_value=65535, step=1
)
await numbers(
CONF_MAX_MOVE_DISTANCE_GATE,
@@ -102,7 +98,6 @@ async def to_code(config: ConfigType) -> None:
min_value=2,
max_value=8,
step=1,
**timeouts,
)
await numbers(
CONF_MAX_STILL_DISTANCE_GATE,
@@ -110,7 +105,6 @@ async def to_code(config: ConfigType) -> None:
min_value=2,
max_value=8,
step=1,
**timeouts,
)
await numbers(
CONF_LIGHT_THRESHOLD,
@@ -118,25 +112,21 @@ async def to_code(config: ConfigType) -> None:
min_value=0,
max_value=255,
step=1,
apply="set_light_out_control()",
publish="before",
)
threshold = {
"min_value": 0,
"max_value": 100,
"step": 1,
"apply": "set_gate_threshold(entity)",
"publish": "before",
}
for x in range(9):
if gate_conf := config.get(f"g{x}"):
await numbers.create(
gate_conf[CONF_MOVE_THRESHOLD],
partial(hub.set_gate_move_threshold_number, x),
**threshold,
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 numbers.create(
gate_conf[CONF_STILL_THRESHOLD],
partial(hub.set_gate_still_threshold_number, x),
**threshold,
await cg.register_parented(n, hub)
cg.add(hub.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, hub)
cg.add(hub.set_gate_still_threshold_number(x, n))
@@ -0,0 +1,12 @@
#include "gate_threshold_number.h"
namespace esphome::ld2410 {
GateThresholdNumber::GateThresholdNumber(uint8_t gate) : gate_(gate) {}
void GateThresholdNumber::control(float value) {
this->publish_state(value);
this->parent_->set_gate_threshold(this->gate_);
}
} // namespace esphome::ld2410
@@ -0,0 +1,17 @@
#pragma once
#include "esphome/components/number/number.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
class GateThresholdNumber final : public number::Number, public Parented<LD2410Component> {
public:
GateThresholdNumber(uint8_t gate);
protected:
uint8_t gate_;
void control(float value) override;
};
} // namespace esphome::ld2410
@@ -0,0 +1,10 @@
#include "light_threshold_number.h"
namespace esphome::ld2410 {
void LightThresholdNumber::control(float value) {
this->publish_state(value);
this->parent_->set_light_out_control();
}
} // namespace esphome::ld2410
@@ -0,0 +1,16 @@
#pragma once
#include "esphome/components/number/number.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
class LightThresholdNumber final : public number::Number, public Parented<LD2410Component> {
public:
LightThresholdNumber() = default;
protected:
void control(float value) override;
};
} // namespace esphome::ld2410
@@ -0,0 +1,10 @@
#include "max_distance_timeout_number.h"
namespace esphome::ld2410 {
void MaxDistanceTimeoutNumber::control(float value) {
this->publish_state(value);
this->parent_->set_max_distances_timeout();
}
} // namespace esphome::ld2410
@@ -0,0 +1,16 @@
#pragma once
#include "esphome/components/number/number.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
class MaxDistanceTimeoutNumber final : public number::Number, public Parented<LD2410Component> {
public:
MaxDistanceTimeoutNumber() = default;
protected:
void control(float value) override;
};
} // namespace esphome::ld2410
@@ -52,7 +52,6 @@ CONFIG_SCHEMA = {
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_LD2410_ID])
selects = select.sub_selects(config, parent=hub)
# Option lists follow the order of the C++ enums and tables; the hub maps by index
await selects(
CONF_DISTANCE_RESOLUTION,
hub.set_distance_resolution_select,
@@ -4,7 +4,7 @@ namespace esphome::ld2410 {
void BaudRateSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_baud_rate(static_cast<BaudRate>(index + 1));
this->parent_->set_baud_rate(this->option_at(index));
}
} // namespace esphome::ld2410
@@ -4,8 +4,7 @@ namespace esphome::ld2410 {
void DistanceResolutionSelect::control(size_t index) {
this->publish_state(index);
this->parent_->set_distance_resolution(
static_cast<DistanceResolution>(progmem_read_byte(&DISTANCE_RESOLUTIONS[index])));
this->parent_->set_distance_resolution(this->option_at(index));
}
} // namespace esphome::ld2410
+8 -15
View File
@@ -11,7 +11,10 @@ from esphome.const import (
)
from esphome.types import ConfigType
from .. import CONF_LD2410_ID, LD2410Component
from .. import CONF_LD2410_ID, LD2410Component, ld2410_ns
BluetoothSwitch = ld2410_ns.class_("BluetoothSwitch", switch.Switch)
EngineeringModeSwitch = ld2410_ns.class_("EngineeringModeSwitch", switch.Switch)
CONF_ENGINEERING_MODE = "engineering_mode"
@@ -19,13 +22,13 @@ CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
cv.Optional(CONF_ENGINEERING_MODE): switch.switch_schema(
switch.ApplySwitch,
EngineeringModeSwitch,
device_class=DEVICE_CLASS_SWITCH,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_PULSE,
),
cv.Optional(CONF_BLUETOOTH): switch.switch_schema(
switch.ApplySwitch,
BluetoothSwitch,
device_class=DEVICE_CLASS_SWITCH,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_BLUETOOTH,
@@ -36,15 +39,5 @@ CONFIG_SCHEMA = {
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_LD2410_ID])
switches = switch.sub_switches(config, parent=hub)
await switches(
CONF_ENGINEERING_MODE,
hub.set_engineering_mode_switch,
apply="set_engineering_mode(state)",
publish="before",
)
await switches(
CONF_BLUETOOTH,
hub.set_bluetooth_switch,
apply="set_bluetooth(state)",
publish="before",
)
await switches(CONF_ENGINEERING_MODE, hub.set_engineering_mode_switch)
await switches(CONF_BLUETOOTH, hub.set_bluetooth_switch)
@@ -0,0 +1,10 @@
#include "bluetooth_switch.h"
namespace esphome::ld2410 {
void BluetoothSwitch::write_state(bool state) {
this->publish_state(state);
this->parent_->set_bluetooth(state);
}
} // namespace esphome::ld2410
@@ -0,0 +1,16 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
class BluetoothSwitch final : public switch_::Switch, public Parented<LD2410Component> {
public:
BluetoothSwitch() = default;
protected:
void write_state(bool state) override;
};
} // namespace esphome::ld2410
@@ -0,0 +1,10 @@
#include "engineering_mode_switch.h"
namespace esphome::ld2410 {
void EngineeringModeSwitch::write_state(bool state) {
this->publish_state(state);
this->parent_->set_engineering_mode(state);
}
} // namespace esphome::ld2410
@@ -0,0 +1,16 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "../ld2410.h"
namespace esphome::ld2410 {
class EngineeringModeSwitch final : public switch_::Switch, public Parented<LD2410Component> {
public:
EngineeringModeSwitch() = default;
protected:
void write_state(bool state) override;
};
} // namespace esphome::ld2410
+14 -2
View File
@@ -1,5 +1,6 @@
#include "ld2410s.h"
#include "ld2410s_number.h"
#include <algorithm>
#include <cstring>
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
@@ -94,8 +95,19 @@ void LD2410SComponent::dump_config() {
}
void LD2410SComponent::loop() {
if (!this->read_available<16>([this](uint8_t b) { this->receive_byte_(b); })) {
ESP_LOGV(TAG, "UART read failed");
uint8_t chunk[16];
// Take the count once so one pass handles a bounded number of bytes
size_t avail = this->available();
while (avail > 0) {
const size_t len = std::min(avail, sizeof(chunk));
if (!this->read_array(chunk, len)) {
ESP_LOGV(TAG, "UART read of %u bytes failed", static_cast<unsigned>(len));
break;
}
for (size_t i = 0; i < len; i++) {
this->receive_byte_(chunk[i]);
}
avail -= len;
}
const uint32_t now = App.get_loop_component_start_time();
if (this->sequence_ == nullptr) {
+14 -13
View File
@@ -11,20 +11,26 @@ from esphome.const import (
)
from esphome.types import ConfigType
from .. import CONF_LD2412_ID, LD2412Component
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_START_DYNAMIC_BACKGROUND_CORRECTION = "start_dynamic_background_correction"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(CONF_FACTORY_RESET): button.factory_reset_button_schema(
button.ApplyButton
FactoryResetButton
),
cv.Optional(CONF_QUERY_PARAMS): button.query_button_schema(button.ApplyButton),
cv.Optional(CONF_RESTART): button.restart_button_schema(button.ApplyButton),
cv.Optional(CONF_QUERY_PARAMS): button.query_button_schema(QueryButton),
cv.Optional(CONF_RESTART): button.restart_button_schema(RestartButton),
cv.Optional(CONF_START_DYNAMIC_BACKGROUND_CORRECTION): button.button_schema(
button.ApplyButton,
StartDynamicBackgroundCorrectionButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_PULSE,
),
@@ -34,15 +40,10 @@ CONFIG_SCHEMA = {
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_LD2412_ID])
buttons = button.sub_buttons(config, parent=hub)
await buttons(
CONF_FACTORY_RESET, hub.set_factory_reset_button, apply="factory_reset()"
)
await buttons(CONF_QUERY_PARAMS, hub.set_query_button, apply="read_all_info()")
await buttons(
CONF_RESTART, hub.set_restart_button, apply="restart_and_read_all_info()"
)
await buttons(CONF_FACTORY_RESET, hub.set_factory_reset_button)
await buttons(CONF_QUERY_PARAMS, hub.set_query_button)
await buttons(CONF_RESTART, hub.set_restart_button)
await buttons(
CONF_START_DYNAMIC_BACKGROUND_CORRECTION,
hub.set_start_dynamic_background_correction_button,
apply="start_dynamic_background_correction()",
)
@@ -0,0 +1,7 @@
#include "factory_reset_button.h"
namespace esphome::ld2412 {
void FactoryResetButton::press_action() { this->parent_->factory_reset(); }
} // namespace esphome::ld2412
@@ -0,0 +1,16 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
class FactoryResetButton final : public button::Button, public Parented<LD2412Component> {
public:
FactoryResetButton() = default;
protected:
void press_action() override;
};
} // namespace esphome::ld2412
@@ -0,0 +1,7 @@
#include "query_button.h"
namespace esphome::ld2412 {
void QueryButton::press_action() { this->parent_->read_all_info(); }
} // namespace esphome::ld2412
@@ -0,0 +1,16 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
class QueryButton final : public button::Button, public Parented<LD2412Component> {
public:
QueryButton() = default;
protected:
void press_action() override;
};
} // namespace esphome::ld2412
@@ -0,0 +1,7 @@
#include "restart_button.h"
namespace esphome::ld2412 {
void RestartButton::press_action() { this->parent_->restart_and_read_all_info(); }
} // namespace esphome::ld2412
@@ -0,0 +1,16 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
class RestartButton final : public button::Button, public Parented<LD2412Component> {
public:
RestartButton() = default;
protected:
void press_action() override;
};
} // namespace esphome::ld2412
@@ -0,0 +1,9 @@
#include "start_dynamic_background_correction_button.h"
#include "restart_button.h"
namespace esphome::ld2412 {
void StartDynamicBackgroundCorrectionButton::press_action() { this->parent_->start_dynamic_background_correction(); }
} // namespace esphome::ld2412
@@ -0,0 +1,16 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
class StartDynamicBackgroundCorrectionButton final : public button::Button, public Parented<LD2412Component> {
public:
StartDynamicBackgroundCorrectionButton() = default;
protected:
void press_action() override;
};
} // namespace esphome::ld2412
+46 -84
View File
@@ -79,6 +79,7 @@ static constexpr uint8_t OUT_PIN_LEVELS[] PROGMEM = {OUT_PIN_LEVEL_LOW, OUT_PIN_
static constexpr uint32_t BAUD_RATES[] PROGMEM = {9600, 19200, 38400, 57600, 115200, 230400, 256000, 460800};
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;
@@ -236,7 +237,20 @@ void LD2412Component::restart_and_read_all_info() {
}
void LD2412Component::loop() {
this->read_available<MAX_LINE_LENGTH>([this](uint8_t b) { this->readline_(b); });
// Read all available bytes in batches to reduce UART call overhead.
size_t avail = this->available();
uint8_t buf[MAX_LINE_LENGTH];
while (avail > 0) {
size_t to_read = std::min(avail, sizeof(buf));
if (!this->read_array(buf, to_read)) {
break;
}
avail -= to_read;
for (size_t i = 0; i < to_read; i++) {
this->readline_(buf[i]);
}
}
}
void LD2412Component::send_command_(uint8_t command, const uint8_t *command_value, uint8_t command_value_len) {
@@ -383,10 +397,8 @@ static inline bool has_value(number::Number *n) { return n != nullptr && n->has_
// Timeout ids 0 (still) and 1 (motion) for a write waiting on a re-query
static constexpr uint32_t GATE_QUERY_TIMEOUT_ID = 0;
static constexpr uint32_t GATE_QUERY_TIMEOUT_MS = 2000;
#endif
// Timeout id for a basic configuration write waiting on a re-query, and how long any such write waits
static constexpr uint32_t BASIC_CONF_TIMEOUT_ID = 2;
static constexpr uint32_t QUERY_TIMEOUT_MS = 2000;
void LD2412Component::handle_ack_data_() {
ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", this->buffer_data_[COMMAND]);
@@ -554,44 +566,38 @@ void LD2412Component::handle_ack_data_() {
break;
}
case CMD_QUERY_BASIC_CONF: { // Query parameters response
case CMD_QUERY_BASIC_CONF: // Query parameters response
{
#ifdef USE_NUMBER
if (this->buffer_pos_ < 15 + HEADER_FOOTER_SIZE) {
break; // too short to hold the configuration
}
// True only while a held-back write waits for this answer
const bool pending = this->cancel_timeout(BASIC_CONF_TIMEOUT_ID);
this->basic_conf_read_ = true;
// Min gate: 9th byte, max gate: 10th byte, timeout: 11~12th bytes, output pin level: 13th byte
this->min_gate_ = this->buffer_data_[10];
this->max_gate_ = this->buffer_data_[11];
this->timeout_ = encode_uint16(this->buffer_data_[13], this->buffer_data_[12]);
/*
Moving distance range: 9th byte
Still distance range: 10th byte
*/
set_number_value(this->min_distance_gate_number_, this->buffer_data_[10]);
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
*/
set_number_value(this->timeout_number_, encode_uint16(this->buffer_data_[13], this->buffer_data_[12]));
ESP_LOGV(TAG, "timeout_number_: %u", encode_uint16(this->buffer_data_[13], this->buffer_data_[12]));
/*
Output pin configuration: 13th bytes
*/
this->out_pin_level_ = this->buffer_data_[14];
ESP_LOGV(TAG, "min_distance_gate_number_: %u, max_distance_gate_number_ %u", this->min_gate_, this->max_gate_);
ESP_LOGV(TAG, "timeout_number_: %u", this->timeout_);
// The pending write's user values must survive the answer
#ifdef USE_NUMBER
if (!pending || !has_value(this->min_distance_gate_number_)) {
set_number_value(this->min_distance_gate_number_, this->min_gate_);
}
if (!pending || !has_value(this->max_distance_gate_number_)) {
set_number_value(this->max_distance_gate_number_, this->max_gate_ - 1);
}
if (!pending || !has_value(this->timeout_number_)) {
set_number_value(this->timeout_number_, this->timeout_);
}
#endif
#ifdef USE_SELECT
if (this->out_pin_level_select_ != nullptr && !(pending && this->out_pin_level_select_->has_state())) {
if (this->out_pin_level_select_ != nullptr) {
if (const auto index = ld24xx::find_index(OUT_PIN_LEVELS, this->out_pin_level_)) {
this->out_pin_level_select_->publish_state(*index);
}
}
#endif
if (pending) {
this->set_basic_config();
}
break;
}
#endif
} break;
default:
break;
}
@@ -697,9 +703,8 @@ void LD2412Component::set_engineering_mode(bool enable) {
void LD2412Component::factory_reset() {
this->set_config_mode_(true);
this->send_command_(CMD_FACTORY_RESET, nullptr, 0);
// Reset discards the settings; the next write must wait for the re-read
this->basic_conf_read_ = false;
#ifdef USE_NUMBER
// Reset discards the thresholds; the next write must wait for the re-read
this->gate_move_thresholds_.read = false;
this->gate_still_thresholds_.read = false;
#endif
@@ -722,22 +727,10 @@ void LD2412Component::get_distance_resolution_() { this->send_command_(CMD_QUERY
void LD2412Component::query_light_control_() { this->send_command_(CMD_QUERY_LIGHT_CONTROL, nullptr, 0); }
void LD2412Component::set_basic_config() {
// Entities that are not configured keep what the module last reported
uint8_t min_gate = this->min_gate_;
uint8_t max_gate = this->max_gate_;
uint16_t timeout = this->timeout_;
uint8_t out_pin_level = this->out_pin_level_;
bool from_module = false;
#ifdef USE_NUMBER
from_module = this->min_distance_gate_number_ == nullptr || this->max_distance_gate_number_ == nullptr ||
this->timeout_number_ == nullptr;
#else
from_module = true;
#endif
#ifndef USE_SELECT
from_module = true;
#endif
uint8_t min_gate = 1;
uint8_t max_gate = TOTAL_GATES;
uint16_t timeout = DEFAULT_PRESENCE_TIMEOUT;
uint8_t out_pin_level = 0x01;
#ifdef USE_NUMBER
if (this->min_distance_gate_number_ != nullptr) {
@@ -762,19 +755,8 @@ void LD2412Component::set_basic_config() {
if (!index)
return;
out_pin_level = progmem_read_byte(&OUT_PIN_LEVELS[*index]);
} else {
from_module = true;
}
#endif
if (from_module && !this->basic_conf_read_) {
// The module never reported, so the values on hand are the built in defaults
ESP_LOGW(TAG, "Command %02X held back until the module reports its settings", CMD_BASIC_CONF);
this->set_config_mode_(true);
this->query_parameters_();
this->set_config_mode_(false);
this->set_timeout(BASIC_CONF_TIMEOUT_ID, QUERY_TIMEOUT_MS, [this]() { this->basic_conf_timeout_(); });
return;
}
uint8_t value[5] = {
lowbyte(min_gate), lowbyte(max_gate), lowbyte(timeout), highbyte(timeout), out_pin_level,
@@ -784,26 +766,6 @@ void LD2412Component::set_basic_config() {
this->set_config_mode_(false);
}
void LD2412Component::basic_conf_timeout_() {
ESP_LOGE(TAG, "Command %02X not sent, the module did not report its settings", CMD_BASIC_CONF);
#ifdef USE_NUMBER
// The values on hand never reached the module, so forgetting them loses nothing
for (number::Number *n : {this->min_distance_gate_number_, this->max_distance_gate_number_, this->timeout_number_}) {
if (n != nullptr) {
n->publish_state(NAN);
}
}
#endif
#ifdef USE_SELECT
// Show the last known or default level until the module reports one
if (this->out_pin_level_select_ != nullptr) {
if (const auto index = ld24xx::find_index(OUT_PIN_LEVELS, this->out_pin_level_)) {
this->out_pin_level_select_->publish_state(*index);
}
}
#endif
}
#ifdef USE_NUMBER
void LD2412Component::send_gate_thresholds_(bool motion) {
GateThresholds &group = this->gate_thresholds_(motion);
@@ -821,9 +783,9 @@ void LD2412Component::send_gate_thresholds_(bool motion) {
}
if (configured < TOTAL_GATES && !group.read) {
// Unread gates would go out as zero, which is maximum sensitivity
ESP_LOGW(TAG, "Command %02X held back until the module reports its settings", command);
ESP_LOGW(TAG, "Command %02X held back until the module reports its gate thresholds", command);
this->send_command_(motion ? CMD_QUERY_MOTION_GATE_SENS : CMD_QUERY_STATIC_GATE_SENS, nullptr, 0);
this->set_timeout(GATE_QUERY_TIMEOUT_ID + motion, QUERY_TIMEOUT_MS,
this->set_timeout(GATE_QUERY_TIMEOUT_ID + motion, GATE_QUERY_TIMEOUT_MS,
[this, motion]() { this->gate_query_timeout_(motion); });
return;
}
@@ -831,7 +793,7 @@ void LD2412Component::send_gate_thresholds_(bool motion) {
}
void LD2412Component::gate_query_timeout_(bool motion) {
ESP_LOGE(TAG, "Command %02X not sent, the module did not report its settings",
ESP_LOGE(TAG, "Command %02X not sent, the module did not report its gate thresholds",
motion ? CMD_MOTION_GATE_SENS : CMD_STATIC_GATE_SENS);
// The values on hand never reached the module or are stale, so forgetting them loses nothing
for (number::Number *n : this->gate_thresholds_(motion).numbers) {
+1 -8
View File
@@ -35,7 +35,6 @@ 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
static constexpr uint16_t DEFAULT_PRESENCE_TIMEOUT = 5;
enum BaudRate : uint8_t {
BAUD_RATE_9600 = 1,
@@ -137,7 +136,6 @@ class LD2412Component final : public Component, public uart::UARTDevice {
void handle_periodic_data_();
void handle_ack_data_();
bool handle_frame_(uint8_t frame_len);
void basic_conf_timeout_();
void readline_(int readch);
void query_parameters_();
void get_version_();
@@ -162,12 +160,7 @@ class LD2412Component final : public Component, public uart::UARTDevice {
uint8_t light_function_ = 0;
uint8_t light_threshold_ = 0;
// Basic configuration as last reported by the module; resent for entities that are not configured
bool basic_conf_read_{false}; // answered since boot or the last factory reset
uint16_t timeout_ = DEFAULT_PRESENCE_TIMEOUT;
uint8_t min_gate_ = 1;
uint8_t max_gate_ = TOTAL_GATES;
uint8_t out_pin_level_ = 1;
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_[MAC_ADDRESS_SIZE] = {0, 0, 0, 0, 0, 0};
+20 -31
View File
@@ -1,5 +1,3 @@
from functools import partial
import esphome.codegen as cg
from esphome.components import number
import esphome.config_validation as cv
@@ -20,7 +18,11 @@ from esphome.const import (
)
from esphome.types import ConfigType
from .. import CONF_LD2412_ID, LD2412Component
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"
@@ -33,25 +35,25 @@ CONFIG_SCHEMA = cv.Schema(
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(CONF_LIGHT_THRESHOLD): number.number_schema(
number.ApplyNumber,
LightThresholdNumber,
device_class=DEVICE_CLASS_ILLUMINANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_LIGHTBULB,
),
cv.Optional(CONF_MAX_DISTANCE_GATE): number.number_schema(
number.ApplyNumber,
MaxDistanceTimeoutNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Optional(CONF_MIN_DISTANCE_GATE): number.number_schema(
number.ApplyNumber,
MaxDistanceTimeoutNumber,
device_class=DEVICE_CLASS_DISTANCE,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
),
cv.Optional(CONF_TIMEOUT): number.number_schema(
number.ApplyNumber,
MaxDistanceTimeoutNumber,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_TIMELAPSE,
unit_of_measurement=UNIT_SECOND,
@@ -64,14 +66,14 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
cv.Optional(f"gate_{x}"): (
{
cv.Required(CONF_MOVE_THRESHOLD): number.number_schema(
number.ApplyNumber,
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(
number.ApplyNumber,
GateThresholdNumber,
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_MOTION_SENSOR,
@@ -93,17 +95,13 @@ async def to_code(config: ConfigType) -> None:
min_value=0,
max_value=255,
step=1,
apply="set_light_out_control()",
publish="before",
)
basic = {"apply": "set_basic_config()", "publish": "before"}
await numbers(
CONF_MAX_DISTANCE_GATE,
hub.set_max_distance_gate_number,
min_value=2,
max_value=13,
step=1,
**basic,
)
await numbers(
CONF_MIN_DISTANCE_GATE,
@@ -111,32 +109,23 @@ async def to_code(config: ConfigType) -> None:
min_value=1,
max_value=12,
step=1,
**basic,
)
threshold = {
"min_value": 0,
"max_value": 100,
"step": 1,
"apply": "set_gate_threshold(entity)",
"publish": "before",
}
for x in range(14):
if gate_conf := config.get(f"gate_{x}"):
await numbers.create(
gate_conf[CONF_MOVE_THRESHOLD],
partial(hub.set_gate_move_threshold_number, x),
**threshold,
)
await numbers.create(
gate_conf[CONF_STILL_THRESHOLD],
partial(hub.set_gate_still_threshold_number, x),
**threshold,
move_config = gate_conf[CONF_MOVE_THRESHOLD]
n = await number.new_number(move_config, min_value=0, max_value=100, step=1)
await cg.register_parented(n, hub)
cg.add(hub.set_gate_move_threshold_number(x, n))
still_config = gate_conf[CONF_STILL_THRESHOLD]
n = await number.new_number(
still_config, min_value=0, max_value=100, step=1
)
await cg.register_parented(n, hub)
cg.add(hub.set_gate_still_threshold_number(x, n))
await numbers(
CONF_TIMEOUT,
hub.set_timeout_number,
min_value=0,
max_value=900,
step=1,
**basic,
)
@@ -0,0 +1,10 @@
#include "gate_threshold_number.h"
namespace esphome::ld2412 {
void GateThresholdNumber::control(float value) {
this->publish_state(value);
this->parent_->set_gate_threshold(this);
}
} // namespace esphome::ld2412
@@ -0,0 +1,17 @@
#pragma once
#include "esphome/components/number/number.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
class GateThresholdNumber final : public number::Number, public Parented<LD2412Component> {
public:
// Not "= default": that makes new(p) T() zero-fill the object at every codegen site before the ctor runs.
GateThresholdNumber() {}
protected:
void control(float value) override;
};
} // namespace esphome::ld2412
@@ -0,0 +1,10 @@
#include "light_threshold_number.h"
namespace esphome::ld2412 {
void LightThresholdNumber::control(float value) {
this->publish_state(value);
this->parent_->set_light_out_control();
}
} // namespace esphome::ld2412
@@ -0,0 +1,17 @@
#pragma once
#include "esphome/components/number/number.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
class LightThresholdNumber final : public number::Number, public Parented<LD2412Component> {
public:
// User provided, not "= default": `new(p) LightThresholdNumber()` would zero-fill .bss that is already zero.
LightThresholdNumber() {}
protected:
void control(float value) override;
};
} // namespace esphome::ld2412
@@ -0,0 +1,10 @@
#include "max_distance_timeout_number.h"
namespace esphome::ld2412 {
void MaxDistanceTimeoutNumber::control(float value) {
this->publish_state(value);
this->parent_->set_basic_config();
}
} // namespace esphome::ld2412
@@ -0,0 +1,16 @@
#pragma once
#include "esphome/components/number/number.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
class MaxDistanceTimeoutNumber final : public number::Number, public Parented<LD2412Component> {
public:
MaxDistanceTimeoutNumber() = default;
protected:
void control(float value) override;
};
} // namespace esphome::ld2412
+1 -4
View File
@@ -42,7 +42,7 @@ CONFIG_SCHEMA = {
icon=ICON_LIGHTBULB,
),
cv.Optional(CONF_OUT_PIN_LEVEL): select.select_schema(
select.ApplySelect,
LightOutControlSelect,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_SCALE,
),
@@ -77,11 +77,8 @@ async def to_code(config: ConfigType) -> None:
hub.set_light_function_select,
options=["off", "below", "above"],
)
# The pin level travels in the basic configuration command, not the light control one
await selects(
CONF_OUT_PIN_LEVEL,
hub.set_out_pin_level_select,
options=["low", "high"],
apply="set_basic_config()",
publish="before",
)
+8 -15
View File
@@ -11,7 +11,10 @@ from esphome.const import (
)
from esphome.types import ConfigType
from .. import CONF_LD2412_ID, LD2412Component
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"
@@ -19,13 +22,13 @@ CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
cv.Optional(CONF_BLUETOOTH): switch.switch_schema(
switch.ApplySwitch,
BluetoothSwitch,
device_class=DEVICE_CLASS_SWITCH,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_BLUETOOTH,
),
cv.Optional(CONF_ENGINEERING_MODE): switch.switch_schema(
switch.ApplySwitch,
EngineeringModeSwitch,
device_class=DEVICE_CLASS_SWITCH,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_PULSE,
@@ -36,15 +39,5 @@ CONFIG_SCHEMA = {
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_LD2412_ID])
switches = switch.sub_switches(config, parent=hub)
await switches(
CONF_BLUETOOTH,
hub.set_bluetooth_switch,
apply="set_bluetooth(state)",
publish="before",
)
await switches(
CONF_ENGINEERING_MODE,
hub.set_engineering_mode_switch,
apply="set_engineering_mode(state)",
publish="before",
)
await switches(CONF_BLUETOOTH, hub.set_bluetooth_switch)
await switches(CONF_ENGINEERING_MODE, hub.set_engineering_mode_switch)
@@ -0,0 +1,10 @@
#include "bluetooth_switch.h"
namespace esphome::ld2412 {
void BluetoothSwitch::write_state(bool state) {
this->publish_state(state);
this->parent_->set_bluetooth(state);
}
} // namespace esphome::ld2412
@@ -0,0 +1,17 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
class BluetoothSwitch final : public switch_::Switch, public Parented<LD2412Component> {
public:
// User provided, not "= default": `new(p) BluetoothSwitch()` would zero-fill .bss that is already zero.
BluetoothSwitch() {}
protected:
void write_state(bool state) override;
};
} // namespace esphome::ld2412
@@ -0,0 +1,10 @@
#include "engineering_mode_switch.h"
namespace esphome::ld2412 {
void EngineeringModeSwitch::write_state(bool state) {
this->publish_state(state);
this->parent_->set_engineering_mode(state);
}
} // namespace esphome::ld2412
@@ -0,0 +1,17 @@
#pragma once
#include "esphome/components/switch/switch.h"
#include "../ld2412.h"
namespace esphome::ld2412 {
class EngineeringModeSwitch final : public switch_::Switch, public Parented<LD2412Component> {
public:
// User provided, not "= default": `new(p) EngineeringModeSwitch()` would zero-fill .bss that is already zero.
EngineeringModeSwitch() {}
protected:
void write_state(bool state) override;
};
} // namespace esphome::ld2412
+14 -23
View File
@@ -12,7 +12,12 @@ from esphome.const import (
)
from esphome.types import ConfigType
from .. import CONF_LD2420_ID, LD2420Component
from .. import CONF_LD2420_ID, LD2420Component, ld2420_ns
LD2420ApplyConfigButton = ld2420_ns.class_("LD2420ApplyConfigButton", button.Button)
LD2420RevertConfigButton = ld2420_ns.class_("LD2420RevertConfigButton", button.Button)
LD2420RestartModuleButton = ld2420_ns.class_("LD2420RestartModuleButton", button.Button)
LD2420FactoryResetButton = ld2420_ns.class_("LD2420FactoryResetButton", button.Button)
CONF_APPLY_CONFIG = "apply_config"
CONF_REVERT_CONFIG = "revert_config"
@@ -22,24 +27,24 @@ CONF_RESTART_MODULE = "restart_module"
CONFIG_SCHEMA = {
cv.GenerateID(CONF_LD2420_ID): cv.use_id(LD2420Component),
cv.Required(CONF_APPLY_CONFIG): button.button_schema(
button.ApplyButton,
LD2420ApplyConfigButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART_ALERT,
),
cv.Optional(CONF_REVERT_CONFIG): button.button_schema(
button.ApplyButton,
LD2420RevertConfigButton,
device_class=DEVICE_CLASS_RESTART,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_RESTART,
),
cv.Optional(CONF_RESTART_MODULE): button.button_schema(
button.ApplyButton,
LD2420RestartModuleButton,
entity_category=ENTITY_CATEGORY_DIAGNOSTIC,
icon=ICON_DATABASE,
),
cv.Optional(CONF_FACTORY_RESET): button.button_schema(
button.ApplyButton,
LD2420FactoryResetButton,
entity_category=ENTITY_CATEGORY_CONFIG,
icon=ICON_DATABASE,
),
@@ -49,21 +54,7 @@ CONFIG_SCHEMA = {
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_LD2420_ID])
buttons = button.sub_buttons(config, parent=hub)
await buttons(
CONF_APPLY_CONFIG, hub.set_apply_config_button, apply="apply_config_action()"
)
await buttons(
CONF_REVERT_CONFIG,
hub.set_revert_config_button,
apply="revert_config_action()",
)
await buttons(
CONF_RESTART_MODULE,
hub.set_restart_module_button,
apply="restart_module_action()",
)
await buttons(
CONF_FACTORY_RESET,
hub.set_factory_reset_button,
apply="factory_reset_action()",
)
await buttons(CONF_APPLY_CONFIG, hub.set_apply_config_button)
await buttons(CONF_REVERT_CONFIG, hub.set_revert_config_button)
await buttons(CONF_RESTART_MODULE, hub.set_restart_module_button)
await buttons(CONF_FACTORY_RESET, hub.set_factory_reset_button)
@@ -0,0 +1,14 @@
#include "reconfig_buttons.h"
#include "esphome/core/helpers.h"
#include "esphome/core/log.h"
ESPHOME_LOG_TAG(TAG, "ld2420.button");
namespace esphome::ld2420 {
void LD2420ApplyConfigButton::press_action() { this->parent_->apply_config_action(); }
void LD2420RevertConfigButton::press_action() { this->parent_->revert_config_action(); }
void LD2420RestartModuleButton::press_action() { this->parent_->restart_module_action(); }
void LD2420FactoryResetButton::press_action() { this->parent_->factory_reset_action(); }
} // namespace esphome::ld2420
@@ -0,0 +1,40 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2420.h"
namespace esphome::ld2420 {
class LD2420ApplyConfigButton final : public button::Button, public Parented<LD2420Component> {
public:
LD2420ApplyConfigButton() = default;
protected:
void press_action() override;
};
class LD2420RevertConfigButton final : public button::Button, public Parented<LD2420Component> {
public:
LD2420RevertConfigButton() = default;
protected:
void press_action() override;
};
class LD2420RestartModuleButton final : public button::Button, public Parented<LD2420Component> {
public:
LD2420RestartModuleButton() = default;
protected:
void press_action() override;
};
class LD2420FactoryResetButton final : public button::Button, public Parented<LD2420Component> {
public:
LD2420FactoryResetButton() = default;
protected:
void press_action() override;
};
} // namespace esphome::ld2420
+14 -2
View File
@@ -514,8 +514,20 @@ void LD2420Component::handle_simple_mode_(const uint8_t *inbuf, int len) {
}
void LD2420Component::read_batch_(std::span<uint8_t, MAX_LINE_LENGTH> buffer) {
this->read_available<MAX_LINE_LENGTH>(
[this, &buffer](uint8_t b) { this->readline_(b, buffer.data(), buffer.size()); });
// Read all available bytes in batches to reduce UART call overhead.
size_t avail = this->available();
uint8_t buf[MAX_LINE_LENGTH];
while (avail > 0) {
size_t to_read = std::min(avail, sizeof(buf));
if (!this->read_array(buf, to_read)) {
break;
}
avail -= to_read;
for (size_t i = 0; i < to_read; i++) {
this->readline_(buf[i], buffer.data(), buffer.size());
}
}
}
void LD2420Component::handle_ack_data_(uint8_t *buffer, int len) {
+8 -9
View File
@@ -4,24 +4,23 @@ import esphome.config_validation as cv
from esphome.const import CONF_FACTORY_RESET, CONF_ID, CONF_RESTART
from esphome.types import ConfigType
from .. import CONF_LD2450_ID, LD2450Component
from .. import CONF_LD2450_ID, LD2450Component, ld2450_ns
FactoryResetButton = ld2450_ns.class_("FactoryResetButton", button.Button)
RestartButton = ld2450_ns.class_("RestartButton", button.Button)
CONFIG_SCHEMA = {
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
cv.GenerateID(CONF_LD2450_ID): cv.use_id(LD2450Component),
cv.Optional(CONF_FACTORY_RESET): button.factory_reset_button_schema(
button.ApplyButton
FactoryResetButton
),
cv.Optional(CONF_RESTART): button.restart_button_schema(button.ApplyButton),
cv.Optional(CONF_RESTART): button.restart_button_schema(RestartButton),
}
async def to_code(config: ConfigType) -> None:
hub = await cg.get_variable(config[CONF_LD2450_ID])
buttons = button.sub_buttons(config, parent=hub)
await buttons(
CONF_FACTORY_RESET, hub.set_factory_reset_button, apply="factory_reset()"
)
await buttons(
CONF_RESTART, hub.set_restart_button, apply="restart_and_read_all_info()"
)
await buttons(CONF_FACTORY_RESET, hub.set_factory_reset_button)
await buttons(CONF_RESTART, hub.set_restart_button)
@@ -0,0 +1,7 @@
#include "factory_reset_button.h"
namespace esphome::ld2450 {
void FactoryResetButton::press_action() { this->parent_->factory_reset(); }
} // namespace esphome::ld2450
@@ -0,0 +1,17 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2450.h"
namespace esphome::ld2450 {
class FactoryResetButton : public button::Button, public Parented<LD2450Component> {
public:
// User provided, not "= default": `new(p) FactoryResetButton()` would zero-fill .bss that is already zero.
FactoryResetButton() {}
protected:
void press_action() override;
};
} // namespace esphome::ld2450
@@ -0,0 +1,7 @@
#include "restart_button.h"
namespace esphome::ld2450 {
void RestartButton::press_action() { this->parent_->restart_and_read_all_info(); }
} // namespace esphome::ld2450
@@ -0,0 +1,17 @@
#pragma once
#include "esphome/components/button/button.h"
#include "../ld2450.h"
namespace esphome::ld2450 {
class RestartButton : public button::Button, public Parented<LD2450Component> {
public:
// User provided, not "= default": `new(p) RestartButton()` would zero-fill .bss that is already zero.
RestartButton() {}
protected:
void press_action() override;
};
} // namespace esphome::ld2450
+14 -1
View File
@@ -227,7 +227,20 @@ void LD2450Component::dump_config() {
}
void LD2450Component::loop() {
this->read_available<MAX_LINE_LENGTH>([this](uint8_t b) { this->readline_(b); });
// Read all available bytes in batches to reduce UART call overhead.
size_t avail = this->available();
uint8_t buf[MAX_LINE_LENGTH];
while (avail > 0) {
size_t to_read = std::min(avail, sizeof(buf));
if (!this->read_array(buf, to_read)) {
break;
}
avail -= to_read;
for (size_t i = 0; i < to_read; i++) {
this->readline_(buf[i]);
}
}
}
// Count targets in zone (single pass for both still and moving)
Loaded 100 of 238 files, more files were not shown because too many files have changed in this diff. Show more