mirror of
https://github.com/esphome/esphome.git
synced 2026-10-26 16:02:13 +00:00
Compare commits
11
Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
ff25bc334a | ||
|
|
0af09caddc | ||
|
|
1154941f25 | ||
|
|
9a65dda424 | ||
|
|
81191f3d5c | ||
|
|
26c852f169 | ||
|
|
ebdf95be94 | ||
|
|
29259ca74e | ||
|
|
6a1b5d517b | ||
|
|
9687c0e0a8 | ||
|
|
230c120898 |
No files matched your search
@@ -1,6 +1,7 @@
|
|||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.automation import maybe_simple_id
|
from esphome.automation import maybe_simple_id
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import mqtt, web_server
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_CODE,
|
CONF_CODE,
|
||||||
@@ -9,11 +10,11 @@ from esphome.const import (
|
|||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_MQTT_ID,
|
CONF_MQTT_ID,
|
||||||
CONF_ON_STATE,
|
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 (
|
from esphome.core.entity_helpers import (
|
||||||
entity_duplicate_validator,
|
entity_duplicate_validator,
|
||||||
mqtt_component_class,
|
|
||||||
queue_entity_register,
|
queue_entity_register,
|
||||||
setup_entity,
|
setup_entity,
|
||||||
)
|
)
|
||||||
@@ -43,12 +44,12 @@ AlarmControlPanelState = alarm_control_panel_ns.enum("AlarmControlPanelState")
|
|||||||
|
|
||||||
|
|
||||||
_ALARM_CONTROL_PANEL_SCHEMA = (
|
_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.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||||
.extend(
|
.extend(
|
||||||
{
|
{
|
||||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
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_STATE): automation.validate_automation({}),
|
||||||
cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation({}),
|
cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation({}),
|
||||||
@@ -165,7 +166,11 @@ _CALLBACK_AUTOMATIONS = (
|
|||||||
@setup_entity("alarm_control_panel")
|
@setup_entity("alarm_control_panel")
|
||||||
async def setup_alarm_control_panel_core_(var, config):
|
async def setup_alarm_control_panel_core_(var, config):
|
||||||
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
|
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):
|
async def register_alarm_control_panel(var, config):
|
||||||
|
|||||||
@@ -4,13 +4,14 @@ import esphome.config_validation as cv
|
|||||||
from esphome.const import DEVICE_CLASS_SWITCH, ICON_WIFI
|
from esphome.const import DEVICE_CLASS_SWITCH, ICON_WIFI
|
||||||
from esphome.types import ConfigType
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
from .. import CONF_AT581X_ID, AT581XComponent
|
from .. import CONF_AT581X_ID, AT581XComponent, at581x_ns
|
||||||
|
|
||||||
DEPENDENCIES = ["at581x"]
|
DEPENDENCIES = ["at581x"]
|
||||||
|
|
||||||
|
RFSwitch = at581x_ns.class_("RFSwitch", switch.Switch)
|
||||||
|
|
||||||
CONFIG_SCHEMA = switch.switch_schema(
|
CONFIG_SCHEMA = switch.switch_schema(
|
||||||
switch.ApplySwitch,
|
RFSwitch,
|
||||||
device_class=DEVICE_CLASS_SWITCH,
|
device_class=DEVICE_CLASS_SWITCH,
|
||||||
icon=ICON_WIFI,
|
icon=ICON_WIFI,
|
||||||
).extend(
|
).extend(
|
||||||
@@ -23,7 +24,7 @@ CONFIG_SCHEMA = switch.switch_schema(
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config: ConfigType) -> None:
|
async def to_code(config: ConfigType) -> None:
|
||||||
hub = await cg.get_variable(config[CONF_AT581X_ID])
|
at581x_component = await cg.get_variable(config[CONF_AT581X_ID])
|
||||||
await switch.sub_switches(config, parent=hub).create(
|
s = await switch.new_switch(config)
|
||||||
config, hub.set_rf_power_switch, apply="set_rf_mode(state)", publish="before"
|
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
|
||||||
@@ -63,7 +63,6 @@ from esphome.core import CORE, ID, CoroPriority, coroutine_with_priority, entity
|
|||||||
from esphome.core.entity_helpers import (
|
from esphome.core.entity_helpers import (
|
||||||
SubEntities,
|
SubEntities,
|
||||||
entity_duplicate_validator,
|
entity_duplicate_validator,
|
||||||
lazy_load_setup,
|
|
||||||
lazy_load_validator,
|
lazy_load_validator,
|
||||||
mqtt_component_class,
|
mqtt_component_class,
|
||||||
queue_entity_register,
|
queue_entity_register,
|
||||||
@@ -622,7 +621,10 @@ async def setup_binary_sensor_core_(var, config):
|
|||||||
|
|
||||||
await entity_helpers.setup_entity_integrations(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):
|
async def register_binary_sensor(var, config):
|
||||||
|
|||||||
@@ -1,5 +1,4 @@
|
|||||||
#include "bl0939.h"
|
#include "bl0939.h"
|
||||||
#include "esphome/core/hal.h"
|
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include <cinttypes>
|
#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_USR_WRPROT = 0x1A;
|
||||||
static const uint8_t BL0939_REG_TPS_CTRL = 0x1B;
|
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
|
// Reset to default
|
||||||
{BL0939_WRITE_COMMAND, BL0939_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x33},
|
{BL0939_WRITE_COMMAND, BL0939_REG_SOFT_RESET, 0x5A, 0x5A, 0x5A, 0x33},
|
||||||
// Enable User Operation Write
|
// Enable User Operation Write
|
||||||
@@ -67,10 +66,8 @@ void BL0939::update() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void BL0939::setup() {
|
void BL0939::setup() {
|
||||||
uint8_t cmd[sizeof(BL0939_INIT[0])];
|
for (auto *i : BL0939_INIT) {
|
||||||
for (const auto *i : BL0939_INIT) {
|
this->write_array(i, 6);
|
||||||
progmem_memcpy(cmd, i, sizeof(cmd));
|
|
||||||
this->write_array(cmd, sizeof(cmd));
|
|
||||||
delay(1);
|
delay(1);
|
||||||
}
|
}
|
||||||
this->flush();
|
this->flush();
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
#include "bme680_bsec.h"
|
#include "bme680_bsec.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/progmem.h"
|
#include <string>
|
||||||
|
|
||||||
namespace esphome::bme680_bsec {
|
namespace esphome::bme680_bsec {
|
||||||
#ifdef USE_BSEC
|
#ifdef USE_BSEC
|
||||||
@@ -9,7 +9,7 @@ ESPHOME_LOG_TAG(TAG, "bme680_bsec.sensor");
|
|||||||
|
|
||||||
static constexpr uint32_t READ_TIMEOUT_ID = 0;
|
static constexpr uint32_t READ_TIMEOUT_ID = 0;
|
||||||
|
|
||||||
PROGMEM_STRING_TABLE(IaqAccuracyStrings, "Stabilizing", "Uncertain", "Calibrating", "Calibrated");
|
static const std::string IAQ_ACCURACY_STATES[4] = {"Stabilizing", "Uncertain", "Calibrating", "Calibrated"};
|
||||||
|
|
||||||
std::vector<BME680BSECComponent *>
|
std::vector<BME680BSECComponent *>
|
||||||
BME680BSECComponent::instances; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
BME680BSECComponent::instances; // NOLINT(cppcoreguidelines-avoid-non-const-global-variables)
|
||||||
@@ -386,11 +386,10 @@ void BME680BSECComponent::publish_(const bsec_output_t *outputs, uint8_t num_out
|
|||||||
switch (outputs[i].sensor_id) {
|
switch (outputs[i].sensor_id) {
|
||||||
case BSEC_OUTPUT_IAQ:
|
case BSEC_OUTPUT_IAQ:
|
||||||
case BSEC_OUTPUT_STATIC_IAQ: {
|
case BSEC_OUTPUT_STATIC_IAQ: {
|
||||||
uint8_t accuracy = std::min<uint8_t>(outputs[i].accuracy, IaqAccuracyStrings::LAST_INDEX);
|
uint8_t accuracy = std::min<uint8_t>(outputs[i].accuracy, std::size(IAQ_ACCURACY_STATES) - 1);
|
||||||
this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_sensor_, signal); });
|
this->queue_push_([this, signal]() { this->publish_sensor_(this->iaq_sensor_, signal); });
|
||||||
this->queue_push_([this, accuracy]() {
|
this->queue_push_([this, accuracy]() {
|
||||||
this->publish_sensor_(this->iaq_accuracy_text_sensor_,
|
this->publish_sensor_(this->iaq_accuracy_text_sensor_, IAQ_ACCURACY_STATES[accuracy]);
|
||||||
IaqAccuracyStrings::get_progmem_str(accuracy, IaqAccuracyStrings::LAST_INDEX));
|
|
||||||
});
|
});
|
||||||
this->queue_push_([this, accuracy]() { this->publish_sensor_(this->iaq_accuracy_sensor_, accuracy, true); });
|
this->queue_push_([this, accuracy]() { this->publish_sensor_(this->iaq_accuracy_sensor_, accuracy, true); });
|
||||||
|
|
||||||
@@ -436,9 +435,8 @@ void BME680BSECComponent::publish_sensor_(sensor::Sensor *sensor, float value, b
|
|||||||
sensor->publish_state(value);
|
sensor->publish_state(value);
|
||||||
}
|
}
|
||||||
|
|
||||||
void BME680BSECComponent::publish_sensor_(text_sensor::TextSensor *sensor, ProgmemStr value) {
|
void BME680BSECComponent::publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value) {
|
||||||
if (!sensor ||
|
if (!sensor || (sensor->has_state() && sensor->state == value)) {
|
||||||
(sensor->has_state() && ESPHOME_strcmp_P(sensor->state.c_str(), reinterpret_cast<ESPHOME_PGM_P>(value)) == 0)) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
sensor->publish_state(value);
|
sensor->publish_state(value);
|
||||||
|
|||||||
@@ -77,7 +77,7 @@ class BME680BSECComponent final : public Component, public i2c::I2CDevice {
|
|||||||
int64_t get_time_ns_();
|
int64_t get_time_ns_();
|
||||||
|
|
||||||
void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false);
|
void publish_sensor_(sensor::Sensor *sensor, float value, bool change_only = false);
|
||||||
void publish_sensor_(text_sensor::TextSensor *sensor, ProgmemStr value);
|
void publish_sensor_(text_sensor::TextSensor *sensor, const std::string &value);
|
||||||
|
|
||||||
void snapshot_state_(); // Fetch the current BSEC library state and save it in the bsec_state_data_ member (volatile
|
void snapshot_state_(); // Fetch the current BSEC library state and save it in the bsec_state_data_ member (volatile
|
||||||
// memory)
|
// memory)
|
||||||
|
|||||||
@@ -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
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
from esphome.components import button
|
from esphome.components import button
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
@@ -9,10 +10,11 @@ from esphome.const import (
|
|||||||
)
|
)
|
||||||
from esphome.types import ConfigType
|
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"]
|
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
|
# SHIP_RST: bit 7 is the software reset, bits 6:5 the shutdown, ship and hardware reset codes
|
||||||
SOFTWARE_RESET_MASK = 0x80
|
SOFTWARE_RESET_MASK = 0x80
|
||||||
@@ -31,7 +33,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
BQ25186_CHILD_SCHEMA.extend(
|
BQ25186_CHILD_SCHEMA.extend(
|
||||||
{
|
{
|
||||||
cv.Optional(key): button.button_schema(
|
cv.Optional(key): button.button_schema(
|
||||||
button.ApplyButton,
|
BQ25186Button,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=icon,
|
icon=icon,
|
||||||
device_class=device_class,
|
device_class=device_class,
|
||||||
@@ -44,6 +46,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
|
|
||||||
|
|
||||||
async def to_code(config: ConfigType) -> None:
|
async def to_code(config: ConfigType) -> None:
|
||||||
buttons = button.sub_buttons(config, parent=config[CONF_BQ25186_ID])
|
|
||||||
for key, mask, value, _, _ in _BUTTONS:
|
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])
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.automation import maybe_simple_id
|
from esphome.automation import maybe_simple_id
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import mqtt, web_server
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_DEVICE_CLASS,
|
CONF_DEVICE_CLASS,
|
||||||
@@ -9,6 +10,7 @@ from esphome.const import (
|
|||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_MQTT_ID,
|
CONF_MQTT_ID,
|
||||||
CONF_ON_PRESS,
|
CONF_ON_PRESS,
|
||||||
|
CONF_WEB_SERVER,
|
||||||
DEVICE_CLASS_EMPTY,
|
DEVICE_CLASS_EMPTY,
|
||||||
DEVICE_CLASS_IDENTIFY,
|
DEVICE_CLASS_IDENTIFY,
|
||||||
DEVICE_CLASS_RESTART,
|
DEVICE_CLASS_RESTART,
|
||||||
@@ -19,12 +21,11 @@ from esphome.const import (
|
|||||||
ICON_RESTART,
|
ICON_RESTART,
|
||||||
ICON_RESTART_ALERT,
|
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 (
|
from esphome.core.entity_helpers import (
|
||||||
ApplyEntity,
|
ApplyEntity,
|
||||||
SubEntities,
|
SubEntities,
|
||||||
entity_duplicate_validator,
|
entity_duplicate_validator,
|
||||||
mqtt_component_class,
|
|
||||||
queue_entity_register,
|
queue_entity_register,
|
||||||
setup_device_class,
|
setup_device_class,
|
||||||
setup_entity,
|
setup_entity,
|
||||||
@@ -52,13 +53,11 @@ validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
|||||||
|
|
||||||
|
|
||||||
_BUTTON_SCHEMA = (
|
_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.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||||
.extend(
|
.extend(
|
||||||
{
|
{
|
||||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
|
||||||
mqtt_component_class("MQTTButtonComponent")
|
|
||||||
),
|
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_DEVICE_CLASS, visibility=cv.Visibility.ADVANCED
|
CONF_DEVICE_CLASS, visibility=cv.Visibility.ADVANCED
|
||||||
): validate_device_class,
|
): validate_device_class,
|
||||||
@@ -136,7 +135,12 @@ async def setup_button_core_(var: MockObj, config: ConfigType) -> None:
|
|||||||
|
|
||||||
setup_device_class(config)
|
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:
|
async def register_button(var: MockObj, config: ConfigType) -> None:
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
#include "cc1101pa.h"
|
#include "cc1101pa.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/progmem.h"
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
|
|
||||||
@@ -15,9 +14,6 @@ namespace esphome::cc1101 {
|
|||||||
ESPHOME_LOG_TAG(TAG, "cc1101");
|
ESPHOME_LOG_TAG(TAG, "cc1101");
|
||||||
static constexpr size_t TX_FIFO_SIZE = 64;
|
static constexpr size_t TX_FIFO_SIZE = 64;
|
||||||
|
|
||||||
// MDMCFG2 MOD_FORMAT values
|
|
||||||
PROGMEM_STRING_TABLE(ModulationStrings, "2-FSK", "GFSK", "UNUSED", "ASK/OOK", "4-FSK", "UNUSED", "UNUSED", "MSK");
|
|
||||||
|
|
||||||
static void split_float(float value, int mbits, uint8_t &e, uint32_t &m) {
|
static void split_float(float value, int mbits, uint8_t &e, uint32_t &m) {
|
||||||
int e_tmp;
|
int e_tmp;
|
||||||
float m_tmp = std::frexp(value, &e_tmp);
|
float m_tmp = std::frexp(value, &e_tmp);
|
||||||
@@ -242,6 +238,8 @@ void CC1101Component::loop() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void CC1101Component::dump_config() {
|
void CC1101Component::dump_config() {
|
||||||
|
static const char *const MODULATION_NAMES[] = {"2-FSK", "GFSK", "UNUSED", "ASK/OOK",
|
||||||
|
"4-FSK", "UNUSED", "UNUSED", "MSK"};
|
||||||
int32_t freq = static_cast<int32_t>(this->state_.FREQ2 << 16 | this->state_.FREQ1 << 8 | this->state_.FREQ0) *
|
int32_t freq = static_cast<int32_t>(this->state_.FREQ2 << 16 | this->state_.FREQ1 << 8 | this->state_.FREQ0) *
|
||||||
XTAL_FREQUENCY / (1 << 16);
|
XTAL_FREQUENCY / (1 << 16);
|
||||||
float symbol_rate = (((256.0f + this->state_.DRATE_M) * (1 << this->state_.DRATE_E)) / (1 << 28)) * XTAL_FREQUENCY;
|
float symbol_rate = (((256.0f + this->state_.DRATE_M) * (1 << this->state_.DRATE_E)) / (1 << 28)) * XTAL_FREQUENCY;
|
||||||
@@ -255,9 +253,8 @@ void CC1101Component::dump_config() {
|
|||||||
" Symbol Rate: %.0f baud\n"
|
" Symbol Rate: %.0f baud\n"
|
||||||
" Filter Bandwidth: %.1f Hz\n"
|
" Filter Bandwidth: %.1f Hz\n"
|
||||||
" Output Power: %.1f dBm",
|
" Output Power: %.1f dBm",
|
||||||
this->chip_id_, freq, this->state_.CHANNR,
|
this->chip_id_, freq, this->state_.CHANNR, MODULATION_NAMES[this->state_.MOD_FORMAT & 0x07],
|
||||||
LOG_STR_ARG(ModulationStrings::get_log_str(this->state_.MOD_FORMAT & 0x07, 0)), symbol_rate, bw,
|
symbol_rate, bw, this->output_power_effective_);
|
||||||
this->output_power_effective_);
|
|
||||||
LOG_PIN(" CS Pin: ", this->cs_);
|
LOG_PIN(" CS Pin: ", this->cs_);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ from typing import Any
|
|||||||
|
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import mqtt, web_server
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_ACTION_STATE_TOPIC,
|
CONF_ACTION_STATE_TOPIC,
|
||||||
@@ -48,15 +49,15 @@ from esphome.const import (
|
|||||||
CONF_TEMPERATURE_STEP,
|
CONF_TEMPERATURE_STEP,
|
||||||
CONF_UNIT_OF_MEASUREMENT,
|
CONF_UNIT_OF_MEASUREMENT,
|
||||||
CONF_VISUAL,
|
CONF_VISUAL,
|
||||||
|
CONF_WEB_SERVER,
|
||||||
UNIT_KELVIN,
|
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 (
|
from esphome.core.entity_helpers import (
|
||||||
TemperatureUnitResolver,
|
TemperatureUnitResolver,
|
||||||
convert_visual_temperatures,
|
convert_visual_temperatures,
|
||||||
declare_fixed_temperature_unit,
|
declare_fixed_temperature_unit,
|
||||||
entity_duplicate_validator,
|
entity_duplicate_validator,
|
||||||
mqtt_component_class,
|
|
||||||
queue_entity_register,
|
queue_entity_register,
|
||||||
setup_entity,
|
setup_entity,
|
||||||
setup_entity_temperature_unit,
|
setup_entity_temperature_unit,
|
||||||
@@ -176,13 +177,11 @@ _visual_temperatures_in_entity_unit = partial(
|
|||||||
|
|
||||||
|
|
||||||
_CLIMATE_SCHEMA = (
|
_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.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||||
.extend(
|
.extend(
|
||||||
{
|
{
|
||||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTClimateComponent),
|
||||||
mqtt_component_class("MQTTClimateComponent")
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_VISUAL, default={}): cv.Schema(
|
cv.Optional(CONF_VISUAL, default={}): cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_MIN_TEMPERATURE): cv.temperature_with_unit,
|
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))
|
cg.add(var.set_visual_max_humidity_override(max_humidity))
|
||||||
setup_entity_temperature_unit(var, config)
|
setup_entity_temperature_unit(var, config)
|
||||||
|
|
||||||
mqtt_ = await entity_helpers.setup_entity_integrations(var, config)
|
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
||||||
if mqtt_ 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:
|
if (action_state_topic := config.get(CONF_ACTION_STATE_TOPIC)) is not None:
|
||||||
cg.add(mqtt_.set_custom_action_state_topic(action_state_topic))
|
cg.add(mqtt_.set_custom_action_state_topic(action_state_topic))
|
||||||
if (away_command_topic := config.get(CONF_AWAY_COMMAND_TOPIC)) is not None:
|
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)
|
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:
|
def set_custom_fan_modes(var: MockObj, modes: list[str]) -> None:
|
||||||
"""Point the climate at a shared flash table of custom fan modes."""
|
"""Point the climate at a shared flash table of custom fan modes."""
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ import logging
|
|||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.automation import maybe_simple_id
|
from esphome.automation import maybe_simple_id
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import mqtt, web_server
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_DEVICE_CLASS,
|
CONF_DEVICE_CLASS,
|
||||||
@@ -22,6 +23,7 @@ from esphome.const import (
|
|||||||
CONF_TILT_COMMAND_TOPIC,
|
CONF_TILT_COMMAND_TOPIC,
|
||||||
CONF_TILT_STATE_TOPIC,
|
CONF_TILT_STATE_TOPIC,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
|
CONF_WEB_SERVER,
|
||||||
DEVICE_CLASS_AWNING,
|
DEVICE_CLASS_AWNING,
|
||||||
DEVICE_CLASS_BLIND,
|
DEVICE_CLASS_BLIND,
|
||||||
DEVICE_CLASS_CURTAIN,
|
DEVICE_CLASS_CURTAIN,
|
||||||
@@ -34,10 +36,9 @@ from esphome.const import (
|
|||||||
DEVICE_CLASS_SHUTTER,
|
DEVICE_CLASS_SHUTTER,
|
||||||
DEVICE_CLASS_WINDOW,
|
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 (
|
from esphome.core.entity_helpers import (
|
||||||
entity_duplicate_validator,
|
entity_duplicate_validator,
|
||||||
mqtt_component_class,
|
|
||||||
queue_entity_register,
|
queue_entity_register,
|
||||||
setup_device_class,
|
setup_device_class,
|
||||||
setup_entity,
|
setup_entity,
|
||||||
@@ -111,13 +112,11 @@ TRIGGERS = {
|
|||||||
|
|
||||||
|
|
||||||
_COVER_SCHEMA = (
|
_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.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||||
.extend(
|
.extend(
|
||||||
{
|
{
|
||||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTCoverComponent),
|
||||||
mqtt_component_class("MQTTCoverComponent")
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_MQTT_JSON_STATE_PAYLOAD): cv.All(
|
cv.Optional(CONF_MQTT_JSON_STATE_PAYLOAD): cv.All(
|
||||||
cv.requires_component("mqtt"), cv.boolean
|
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)
|
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
|
||||||
await automation.build_automation(trigger, [], conf)
|
await automation.build_automation(trigger, [], conf)
|
||||||
|
|
||||||
mqtt_ = await entity_helpers.setup_entity_integrations(var, config)
|
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
||||||
if mqtt_ 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:
|
if (position_state_topic := config.get(CONF_POSITION_STATE_TOPIC)) is not None:
|
||||||
cg.add(mqtt_.set_custom_position_state_topic(position_state_topic))
|
cg.add(mqtt_.set_custom_position_state_topic(position_state_topic))
|
||||||
if (
|
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:
|
if (tilt_command_topic := config.get(CONF_TILT_COMMAND_TOPIC)) is not None:
|
||||||
cg.add(mqtt_.set_custom_tilt_command_topic(tilt_command_topic))
|
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:
|
async def register_cover(var: MockObj, config: ConfigType) -> None:
|
||||||
if not CORE.has_id(config[CONF_ID]):
|
if not CORE.has_id(config[CONF_ID]):
|
||||||
|
|||||||
@@ -28,23 +28,30 @@ void CSE7766Component::loop() {
|
|||||||
|
|
||||||
// Read all available bytes in batches to reduce UART call overhead.
|
// 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.
|
// At 4800 baud (~480 bytes/sec) with ~122 Hz loop rate, typically ~4 bytes per call.
|
||||||
this->read_available<CSE7766_RAW_DATA_SIZE>(
|
uint8_t buf[CSE7766_RAW_DATA_SIZE];
|
||||||
[this](uint8_t byte) {
|
while (avail > 0) {
|
||||||
this->raw_data_[this->raw_data_index_] = byte;
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
if (!this->check_byte_()) {
|
if (!this->read_array(buf, to_read)) {
|
||||||
this->raw_data_index_ = 0;
|
break;
|
||||||
this->status_set_warning();
|
}
|
||||||
return;
|
avail -= to_read;
|
||||||
}
|
|
||||||
|
|
||||||
if (this->raw_data_index_ == CSE7766_RAW_DATA_SIZE - 1) {
|
for (size_t i = 0; i < to_read; i++) {
|
||||||
this->parse_data_();
|
this->raw_data_[this->raw_data_index_] = buf[i];
|
||||||
this->status_clear_warning();
|
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;
|
if (this->raw_data_index_ == CSE7766_RAW_DATA_SIZE - 1) {
|
||||||
},
|
this->parse_data_();
|
||||||
avail);
|
this->status_clear_warning();
|
||||||
|
}
|
||||||
|
|
||||||
|
this->raw_data_index_ = (this->raw_data_index_ + 1) % CSE7766_RAW_DATA_SIZE;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
bool CSE7766Component::check_byte_() {
|
bool CSE7766Component::check_byte_() {
|
||||||
|
|||||||
@@ -2,6 +2,7 @@ from collections.abc import Callable
|
|||||||
|
|
||||||
from esphome import automation
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import mqtt, time, web_server
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_DATE,
|
CONF_DATE,
|
||||||
@@ -19,12 +20,12 @@ from esphome.const import (
|
|||||||
CONF_TIME_ID,
|
CONF_TIME_ID,
|
||||||
CONF_TRIGGER_ID,
|
CONF_TRIGGER_ID,
|
||||||
CONF_TYPE,
|
CONF_TYPE,
|
||||||
|
CONF_WEB_SERVER,
|
||||||
CONF_YEAR,
|
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 (
|
from esphome.core.entity_helpers import (
|
||||||
entity_duplicate_validator,
|
entity_duplicate_validator,
|
||||||
mqtt_component_class,
|
|
||||||
queue_entity_register,
|
queue_entity_register,
|
||||||
setup_entity,
|
setup_entity,
|
||||||
)
|
)
|
||||||
@@ -37,10 +38,6 @@ DOMAIN = "datetime"
|
|||||||
IS_PLATFORM_COMPONENT = True
|
IS_PLATFORM_COMPONENT = True
|
||||||
|
|
||||||
datetime_ns = cg.esphome_ns.namespace("datetime")
|
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)
|
DateTimeBase = datetime_ns.class_("DateTimeBase", cg.EntityBase)
|
||||||
DateEntity = datetime_ns.class_("DateEntity", DateTimeBase)
|
DateEntity = datetime_ns.class_("DateEntity", DateTimeBase)
|
||||||
TimeEntity = datetime_ns.class_("TimeEntity", DateTimeBase)
|
TimeEntity = datetime_ns.class_("TimeEntity", DateTimeBase)
|
||||||
@@ -67,7 +64,7 @@ DATETIME_MODES = [
|
|||||||
def _validate_time_present(config: ConfigType) -> ConfigType:
|
def _validate_time_present(config: ConfigType) -> ConfigType:
|
||||||
config = config.copy()
|
config = config.copy()
|
||||||
if CONF_ON_TIME in config and CONF_TIME_ID not in config:
|
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
|
config[CONF_TIME_ID] = time_id
|
||||||
return config
|
return config
|
||||||
|
|
||||||
@@ -76,10 +73,10 @@ _DATETIME_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
|
|||||||
cv.Schema(
|
cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_ON_VALUE): automation.validate_automation({}),
|
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)
|
.extend(cv.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||||
).add_extra(_validate_time_present)
|
).add_extra(_validate_time_present)
|
||||||
|
|
||||||
@@ -90,9 +87,7 @@ def date_schema(class_: MockObjClass) -> cv.Schema:
|
|||||||
schema = cv.Schema(
|
schema = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(class_),
|
cv.GenerateID(): cv.declare_id(class_),
|
||||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTDateComponent),
|
||||||
mqtt_component_class("MQTTDateComponent")
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_TYPE, default="DATE"): cv.one_of("DATE", upper=True),
|
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(
|
schema = cv.Schema(
|
||||||
{
|
{
|
||||||
cv.GenerateID(): cv.declare_id(class_),
|
cv.GenerateID(): cv.declare_id(class_),
|
||||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTTimeComponent),
|
||||||
mqtt_component_class("MQTTTimeComponent")
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_TYPE, default="TIME"): cv.one_of("TIME", upper=True),
|
cv.Optional(CONF_TYPE, default="TIME"): cv.one_of("TIME", upper=True),
|
||||||
cv.Optional(CONF_ON_TIME): automation.validate_automation(
|
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.GenerateID(): cv.declare_id(class_),
|
||||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
||||||
mqtt_component_class("MQTTDateTimeComponent")
|
mqtt.MQTTDateTimeComponent
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_TYPE, default="DATETIME"): cv.one_of(
|
cv.Optional(CONF_TYPE, default="DATETIME"): cv.one_of(
|
||||||
"DATETIME", upper=True
|
"DATETIME", upper=True
|
||||||
@@ -139,7 +132,11 @@ def datetime_schema(class_: MockObjClass) -> cv.Schema:
|
|||||||
|
|
||||||
@setup_entity("datetime")
|
@setup_entity("datetime")
|
||||||
async def setup_datetime_core_(var: MockObj, config: ConfigType) -> None:
|
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.
|
# The state callback carries nothing; the automation gets the value as an ESPTime.
|
||||||
for conf in config.get(CONF_ON_VALUE, []):
|
for conf in config.get(CONF_ON_VALUE, []):
|
||||||
await automation.build_callback_automation(
|
await automation.build_callback_automation(
|
||||||
|
|||||||
@@ -135,140 +135,153 @@ void DFPlayer::send_cmd_(uint8_t cmd, uint16_t argument) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DFPlayer::loop() {
|
void DFPlayer::loop() {
|
||||||
this->read_available([this](uint8_t byte) {
|
// Read all available bytes in batches to reduce UART call overhead.
|
||||||
if (this->read_pos_ == DFPLAYER_READ_BUFFER_LENGTH)
|
size_t avail = this->available();
|
||||||
this->read_pos_ = 0;
|
uint8_t buf[64];
|
||||||
|
while (avail > 0) {
|
||||||
switch (this->read_pos_) {
|
size_t to_read = std::min(avail, sizeof(buf));
|
||||||
case 0: // Start mark
|
if (!this->read_array(buf, to_read)) {
|
||||||
if (byte != 0x7E)
|
break;
|
||||||
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;
|
|
||||||
}
|
}
|
||||||
this->read_buffer_[this->read_pos_] = byte;
|
avail -= to_read;
|
||||||
this->read_pos_++;
|
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:"); }
|
void DFPlayer::dump_config() { ESP_LOGCONFIG(TAG, "DFPlayer:"); }
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,10 @@
|
|||||||
#include "dfrobot_sen0465_uart.h"
|
#include "dfrobot_sen0465_uart.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
|
|
||||||
namespace esphome::dfrobot_sen0465_uart {
|
namespace esphome::dfrobot_sen0465_uart {
|
||||||
|
|
||||||
|
using dfrobot_sen0465_base::FRAME_HEAD;
|
||||||
using dfrobot_sen0465_base::FRAME_SIZE;
|
using dfrobot_sen0465_base::FRAME_SIZE;
|
||||||
using dfrobot_sen0465_base::RESPONSE_TIMEOUT_ID;
|
using dfrobot_sen0465_base::RESPONSE_TIMEOUT_ID;
|
||||||
|
|
||||||
@@ -15,21 +18,30 @@ void DFRobotSEN0465UARTComponent::setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DFRobotSEN0465UARTComponent::loop() {
|
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.
|
// The tail of a reading the probe sent on its own may precede the answer; start at the next frame head.
|
||||||
const uint8_t *frame = nullptr;
|
uint8_t byte;
|
||||||
this->read_available<FRAME_SIZE>([&](uint8_t c) { return (frame = this->reader_.feed(c)) == nullptr; });
|
while (this->rx_count_ == 0 && this->peek_byte(&byte) && byte != FRAME_HEAD)
|
||||||
if (frame == nullptr)
|
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;
|
return;
|
||||||
|
|
||||||
|
this->rx_count_ = 0;
|
||||||
this->cancel_timeout(RESPONSE_TIMEOUT_ID);
|
this->cancel_timeout(RESPONSE_TIMEOUT_ID);
|
||||||
this->disable_loop();
|
this->disable_loop();
|
||||||
this->handle_response_(frame);
|
this->handle_response_(this->rx_buffer_);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool DFRobotSEN0465UARTComponent::write_frame(const uint8_t *frame) {
|
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
|
// Drop anything left over, such as the answer to the mode change or readings sent before passive mode was set
|
||||||
this->discard_available();
|
this->discard_available();
|
||||||
this->reader_.reset();
|
this->rx_count_ = 0;
|
||||||
this->write_array(frame, FRAME_SIZE);
|
this->write_array(frame, FRAME_SIZE);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -38,7 +50,7 @@ void DFRobotSEN0465UARTComponent::read_response() {
|
|||||||
this->enable_loop();
|
this->enable_loop();
|
||||||
this->set_timeout(RESPONSE_TIMEOUT_ID, RESPONSE_TIMEOUT_MS, [this]() {
|
this->set_timeout(RESPONSE_TIMEOUT_ID, RESPONSE_TIMEOUT_MS, [this]() {
|
||||||
this->disable_loop();
|
this->disable_loop();
|
||||||
this->reader_.reset();
|
this->rx_count_ = 0;
|
||||||
this->handle_no_response_();
|
this->handle_no_response_();
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,6 @@
|
|||||||
|
|
||||||
#include "esphome/components/dfrobot_sen0465_base/dfrobot_sen0465_base.h"
|
#include "esphome/components/dfrobot_sen0465_base/dfrobot_sen0465_base.h"
|
||||||
#include "esphome/components/uart/uart.h"
|
#include "esphome/components/uart/uart.h"
|
||||||
#include "esphome/core/frame_reader.h"
|
|
||||||
|
|
||||||
namespace esphome::dfrobot_sen0465_uart {
|
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;
|
bool write_frame(const uint8_t *frame) override;
|
||||||
void read_response() 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
|
} // 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.const import CONF_FACTORY_RESET, CONF_ID, CONF_RESTART
|
||||||
from esphome.types import ConfigType
|
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 = {
|
CONFIG_SCHEMA = {
|
||||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||||
cv.GenerateID(CONF_DFROBOT_SEN0609_ID): cv.use_id(DFRobotSEN0609Component),
|
cv.GenerateID(CONF_DFROBOT_SEN0609_ID): cv.use_id(DFRobotSEN0609Component),
|
||||||
cv.Optional(CONF_FACTORY_RESET): button.factory_reset_button_schema(
|
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_QUERY_PARAMS): button.query_button_schema(DFRobotSEN0609Button),
|
||||||
cv.Optional(CONF_RESTART): button.restart_button_schema(button.ApplyButton),
|
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])
|
hub = await cg.get_variable(config[CONF_DFROBOT_SEN0609_ID])
|
||||||
buttons = button.sub_buttons(config, parent=hub)
|
buttons = button.sub_buttons(config, parent=hub)
|
||||||
await buttons(
|
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(
|
||||||
await buttons(CONF_RESTART, hub.set_restart_button, apply="restart()")
|
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/log.h"
|
||||||
#include "esphome/core/progmem.h"
|
#include "esphome/core/progmem.h"
|
||||||
|
|
||||||
|
#include <algorithm>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <cstdlib>
|
#include <cstdlib>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
@@ -174,11 +175,21 @@ void DFRobotSEN0609Component::setup() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void DFRobotSEN0609Component::loop() {
|
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;
|
this->read_failed_ = false;
|
||||||
} else if (!this->read_failed_) {
|
avail -= to_read;
|
||||||
ESP_LOGW(TAG, "UART read failed");
|
for (size_t i = 0; i < to_read; i++)
|
||||||
this->read_failed_ = true;
|
this->readline_(buf[i]);
|
||||||
}
|
}
|
||||||
this->run_sequencer_();
|
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])));
|
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) {
|
void DFRobotSEN0609Component::forget_number_changes_(Param param, bool unknown) {
|
||||||
for (uint8_t i = 0; i < NUMBER_COUNT; i++) {
|
for (uint8_t i = 0; i < NUMBER_COUNT; i++) {
|
||||||
if (progmem_read_byte(&NUMBER_PARAMS[i]) != param)
|
if (progmem_read_byte(&NUMBER_PARAMS[i]) != param)
|
||||||
|
|||||||
@@ -62,6 +62,12 @@ enum SwitchSetting : uint8_t {
|
|||||||
SWITCH_COUNT,
|
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.
|
/// Module parameters; a write or read of each is one command. Pending work runs in this order.
|
||||||
enum Param : uint8_t {
|
enum Param : uint8_t {
|
||||||
PARAM_RUN_APP,
|
PARAM_RUN_APP,
|
||||||
@@ -124,8 +130,6 @@ class DFRobotSEN0609Component final : public Component, public uart::UARTDevice
|
|||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
void set_number(NumberSetting setting, number::Number *number) { this->numbers_[setting] = number; }
|
void set_number(NumberSetting setting, number::Number *number) { this->numbers_[setting] = number; }
|
||||||
void control_number(NumberSetting setting, float value);
|
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
|
#endif
|
||||||
#ifdef USE_SWITCH
|
#ifdef USE_SWITCH
|
||||||
void set_switch(SwitchSetting setting, switch_::Switch *sw) { this->switches_[setting] = sw; }
|
void set_switch(SwitchSetting setting, switch_::Switch *sw) { this->switches_[setting] = sw; }
|
||||||
|
|||||||
@@ -31,6 +31,7 @@ from .. import (
|
|||||||
final_validate_mode,
|
final_validate_mode,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
DFRobotSEN0609Number = dfrobot_sen0609_ns.class_("DFRobotSEN0609Number", number.Number)
|
||||||
NumberSetting = dfrobot_sen0609_ns.enum("NumberSetting")
|
NumberSetting = dfrobot_sen0609_ns.enum("NumberSetting")
|
||||||
|
|
||||||
CONF_HOLD_SENSITIVITY = "hold_sensitivity"
|
CONF_HOLD_SENSITIVITY = "hold_sensitivity"
|
||||||
@@ -54,7 +55,7 @@ class Setting(NamedTuple):
|
|||||||
|
|
||||||
def _distance(icon: str = ICON_RULER) -> cv.Schema:
|
def _distance(icon: str = ICON_RULER) -> cv.Schema:
|
||||||
return number.number_schema(
|
return number.number_schema(
|
||||||
number.ApplyNumber,
|
DFRobotSEN0609Number,
|
||||||
device_class=DEVICE_CLASS_DISTANCE,
|
device_class=DEVICE_CLASS_DISTANCE,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=icon,
|
icon=icon,
|
||||||
@@ -64,7 +65,7 @@ def _distance(icon: str = ICON_RULER) -> cv.Schema:
|
|||||||
|
|
||||||
def _duration() -> cv.Schema:
|
def _duration() -> cv.Schema:
|
||||||
return number.number_schema(
|
return number.number_schema(
|
||||||
number.ApplyNumber,
|
DFRobotSEN0609Number,
|
||||||
device_class=DEVICE_CLASS_DURATION,
|
device_class=DEVICE_CLASS_DURATION,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_TIMELAPSE,
|
icon=ICON_TIMELAPSE,
|
||||||
@@ -74,7 +75,7 @@ def _duration() -> cv.Schema:
|
|||||||
|
|
||||||
def _plain() -> cv.Schema:
|
def _plain() -> cv.Schema:
|
||||||
return number.number_schema(
|
return number.number_schema(
|
||||||
number.ApplyNumber,
|
DFRobotSEN0609Number,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_MOTION_SENSOR,
|
icon=ICON_MOTION_SENSOR,
|
||||||
)
|
)
|
||||||
@@ -186,8 +187,8 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
await numbers(
|
await numbers(
|
||||||
s.key,
|
s.key,
|
||||||
lambda n, setting=s.setting: hub.set_number(setting, n),
|
lambda n, setting=s.setting: hub.set_number(setting, n),
|
||||||
|
s.setting,
|
||||||
min_value=s.min_value,
|
min_value=s.min_value,
|
||||||
max_value=max_range if s.max_value is None else s.max_value,
|
max_value=max_range if s.max_value is None else s.max_value,
|
||||||
step=s.step,
|
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
|
||||||
@@ -1,5 +1,7 @@
|
|||||||
#include "ds1603l.h"
|
#include "ds1603l.h"
|
||||||
|
|
||||||
|
#include <cstring>
|
||||||
|
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
@@ -8,28 +10,35 @@ namespace esphome::ds1603l {
|
|||||||
ESPHOME_LOG_TAG(TAG, "ds1603l.sensor");
|
ESPHOME_LOG_TAG(TAG, "ds1603l.sensor");
|
||||||
|
|
||||||
void DS1603L::loop() {
|
void DS1603L::loop() {
|
||||||
this->read_available([this](uint8_t byte) {
|
// Assemble frames one byte at a time so a stream that starts mid-frame can realign
|
||||||
const uint8_t *frame = this->reader_.feed(byte);
|
uint8_t byte;
|
||||||
if (frame == nullptr) {
|
while (this->available() > 0 && this->read_byte(&byte)) {
|
||||||
if (this->reader_.length() == 0) {
|
if (this->rx_count_ == 0 && byte != HEADER_BYTE) {
|
||||||
ESP_LOGV(TAG, "Skipping byte 0x%02X while looking for header", byte);
|
ESP_LOGV(TAG, "Skipping byte 0x%02X while looking for header", byte);
|
||||||
}
|
continue;
|
||||||
return;
|
|
||||||
}
|
}
|
||||||
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
|
// 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); }
|
void DS1603L::dump_config() { LOG_SENSOR("", "DS1603L", this); }
|
||||||
|
|
||||||
bool DS1603L::parse_data_(const uint8_t *frame) {
|
bool DS1603L::parse_data_() {
|
||||||
uint8_t header = frame[0];
|
uint8_t header = this->rx_buffer_[0];
|
||||||
uint8_t data_h = frame[1];
|
uint8_t data_h = this->rx_buffer_[1];
|
||||||
uint8_t data_l = frame[2];
|
uint8_t data_l = this->rx_buffer_[2];
|
||||||
uint8_t checksum = frame[3];
|
uint8_t checksum = this->rx_buffer_[3];
|
||||||
|
|
||||||
uint8_t computed_checksum = (header + data_h + data_l) & 0xFF;
|
uint8_t computed_checksum = (header + data_h + data_l) & 0xFF;
|
||||||
|
|
||||||
@@ -44,4 +53,16 @@ bool DS1603L::parse_data_(const uint8_t *frame) {
|
|||||||
return true;
|
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
|
} // namespace esphome::ds1603l
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
#include "esphome/components/sensor/sensor.h"
|
#include "esphome/components/sensor/sensor.h"
|
||||||
#include "esphome/components/uart/uart.h"
|
#include "esphome/components/uart/uart.h"
|
||||||
#include "esphome/core/component.h"
|
#include "esphome/core/component.h"
|
||||||
#include "esphome/core/frame_reader.h"
|
|
||||||
|
|
||||||
namespace esphome::ds1603l {
|
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 uint8_t HEADER_BYTE = 0xFF;
|
||||||
static constexpr size_t FRAME_SIZE = 4;
|
static constexpr size_t FRAME_SIZE = 4;
|
||||||
|
|
||||||
// Validates the checksum of the frame and publishes it. Returns false if the frame is invalid.
|
// Validates the checksum of the frame in rx_buffer_ and publishes it. Returns false if the frame is invalid.
|
||||||
bool parse_data_(const uint8_t *frame);
|
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
|
} // namespace esphome::ds1603l
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "ds248x_one_wire_bus.h"
|
#include "ds248x_one_wire_bus.h"
|
||||||
#include "ds248x.h"
|
#include "ds248x.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::ds248x {
|
namespace esphome::ds248x {
|
||||||
@@ -151,11 +152,7 @@ uint64_t DS248xOneWireBus::search_int() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Update address based on direction taken
|
// Update address based on direction taken
|
||||||
if (dir_taken) {
|
assign_bits(address, bit_mask, dir_taken);
|
||||||
address |= bit_mask;
|
|
||||||
} else {
|
|
||||||
address &= ~bit_mask;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Search successful
|
// Search successful
|
||||||
|
|||||||
@@ -4,18 +4,21 @@
|
|||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/progmem.h"
|
|
||||||
|
|
||||||
namespace esphome::epaper_spi {
|
namespace esphome::epaper_spi {
|
||||||
|
|
||||||
ESPHOME_LOG_TAG(TAG, "epaper_spi");
|
ESPHOME_LOG_TAG(TAG, "epaper_spi");
|
||||||
static constexpr size_t EPAPER_MAX_CMD_LOG_BYTES = 128;
|
static constexpr size_t EPAPER_MAX_CMD_LOG_BYTES = 128;
|
||||||
|
|
||||||
PROGMEM_STRING_TABLE(EPaperStateStrings, "IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE",
|
static constexpr const char *const EPAPER_STATE_STRINGS[] = {
|
||||||
"TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP", "Unknown");
|
"IDLE", "UPDATE", "RESET", "RESET_END", "SHOULD_WAIT", "INITIALISE",
|
||||||
|
"TRANSFER_DATA", "POWER_ON", "REFRESH_SCREEN", "POWER_OFF", "DEEP_SLEEP",
|
||||||
|
};
|
||||||
|
|
||||||
const LogString *EPaperBase::epaper_state_to_string_() {
|
const char *EPaperBase::epaper_state_to_string_() {
|
||||||
return EPaperStateStrings::get_log_str(static_cast<uint8_t>(this->state_), EPaperStateStrings::LAST_INDEX);
|
if (auto idx = static_cast<unsigned>(this->state_); idx < std::size(EPAPER_STATE_STRINGS))
|
||||||
|
return EPAPER_STATE_STRINGS[idx];
|
||||||
|
return "Unknown";
|
||||||
}
|
}
|
||||||
|
|
||||||
void EPaperBase::setup() {
|
void EPaperBase::setup() {
|
||||||
@@ -156,7 +159,7 @@ void EPaperBase::update_effective_transform_() {
|
|||||||
|
|
||||||
void EPaperBase::update() {
|
void EPaperBase::update() {
|
||||||
if (this->state_ != EPaperState::IDLE) {
|
if (this->state_ != EPaperState::IDLE) {
|
||||||
ESP_LOGE(TAG, "Display already in state %s", LOG_STR_ARG(epaper_state_to_string_()));
|
ESP_LOGE(TAG, "Display already in state %s", epaper_state_to_string_());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
this->set_state_(EPaperState::UPDATE);
|
this->set_state_(EPaperState::UPDATE);
|
||||||
@@ -209,7 +212,7 @@ void EPaperBase::loop() {
|
|||||||
this->high_frequency_loop_.stop();
|
this->high_frequency_loop_.stop();
|
||||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||||
if (now - this->waiting_for_idle_last_print_ >= 1000) {
|
if (now - this->waiting_for_idle_last_print_ >= 1000) {
|
||||||
ESP_LOGV(TAG, "Waiting for idle in state %s", LOG_STR_ARG(this->epaper_state_to_string_()));
|
ESP_LOGV(TAG, "Waiting for idle in state %s", this->epaper_state_to_string_());
|
||||||
this->waiting_for_idle_last_print_ = millis();
|
this->waiting_for_idle_last_print_ = millis();
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
@@ -224,7 +227,7 @@ void EPaperBase::loop() {
|
|||||||
// The busy pin never cleared: give up on this update so later ones are not refused, and make the next one full
|
// The busy pin never cleared: give up on this update so later ones are not refused, and make the next one full
|
||||||
void EPaperBase::abandon_update_() {
|
void EPaperBase::abandon_update_() {
|
||||||
ESP_LOGE(TAG, "Display stayed busy for %u s in state %s; check the busy pin", (unsigned) this->busy_timeout_seconds_,
|
ESP_LOGE(TAG, "Display stayed busy for %u s in state %s; check the busy pin", (unsigned) this->busy_timeout_seconds_,
|
||||||
LOG_STR_ARG(this->epaper_state_to_string_()));
|
this->epaper_state_to_string_());
|
||||||
this->status_set_warning(LOG_STR("Busy pin timeout"));
|
this->status_set_warning(LOG_STR("Busy pin timeout"));
|
||||||
if (this->state_ == EPaperState::TRANSFER_DATA)
|
if (this->state_ == EPaperState::TRANSFER_DATA)
|
||||||
this->abort_transfer();
|
this->abort_transfer();
|
||||||
@@ -244,10 +247,10 @@ void EPaperBase::abandon_update_() {
|
|||||||
* Should a subclassed class need to override this, the method will need to be made virtual.
|
* Should a subclassed class need to override this, the method will need to be made virtual.
|
||||||
*/
|
*/
|
||||||
void EPaperBase::process_state_() {
|
void EPaperBase::process_state_() {
|
||||||
ESP_LOGV(TAG, "Process state entered in state %s", LOG_STR_ARG(epaper_state_to_string_()));
|
ESP_LOGV(TAG, "Process state entered in state %s", epaper_state_to_string_());
|
||||||
switch (this->state_) {
|
switch (this->state_) {
|
||||||
default:
|
default:
|
||||||
ESP_LOGE(TAG, "Display is in unhandled state %s", LOG_STR_ARG(epaper_state_to_string_()));
|
ESP_LOGE(TAG, "Display is in unhandled state %s", epaper_state_to_string_());
|
||||||
this->set_state_(EPaperState::IDLE);
|
this->set_state_(EPaperState::IDLE);
|
||||||
break;
|
break;
|
||||||
case EPaperState::IDLE:
|
case EPaperState::IDLE:
|
||||||
@@ -339,7 +342,7 @@ void EPaperBase::process_state_() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
|
void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
|
||||||
ESP_LOGV(TAG, "Exit state %s", LOG_STR_ARG(this->epaper_state_to_string_()));
|
ESP_LOGV(TAG, "Exit state %s", this->epaper_state_to_string_());
|
||||||
this->state_ = state;
|
this->state_ = state;
|
||||||
// allow subclasses to nominate delays
|
// allow subclasses to nominate delays
|
||||||
if (delay == 0)
|
if (delay == 0)
|
||||||
@@ -347,7 +350,7 @@ void EPaperBase::set_state_(EPaperState state, uint16_t delay) {
|
|||||||
this->next_delay_ = 0;
|
this->next_delay_ = 0;
|
||||||
this->delay_until_ = millis() + delay;
|
this->delay_until_ = millis() + delay;
|
||||||
this->wait_for_idle_(state > EPaperState::SHOULD_WAIT);
|
this->wait_for_idle_(state > EPaperState::SHOULD_WAIT);
|
||||||
ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", LOG_STR_ARG(this->epaper_state_to_string_()), delay,
|
ESP_LOGV(TAG, "Enter state %s, delay %u, wait_for_idle=%s", this->epaper_state_to_string_(), delay,
|
||||||
TRUEFALSE(this->waiting_for_idle_));
|
TRUEFALSE(this->waiting_for_idle_));
|
||||||
// Each step of an update otherwise waits for the next loop iteration; loop() slows down again for long waits
|
// Each step of an update otherwise waits for the next loop iteration; loop() slows down again for long waits
|
||||||
if (state == EPaperState::IDLE) {
|
if (state == EPaperState::IDLE) {
|
||||||
|
|||||||
@@ -125,7 +125,7 @@ class EPaperBase : public Display,
|
|||||||
bool is_using_partial_update_() const { return this->full_update_every_ > 1; }
|
bool is_using_partial_update_() const { return this->full_update_every_ > 1; }
|
||||||
void process_state_();
|
void process_state_();
|
||||||
|
|
||||||
const LogString *epaper_state_to_string_();
|
const char *epaper_state_to_string_();
|
||||||
bool is_idle_() const;
|
bool is_idle_() const;
|
||||||
void setup_pins_() const;
|
void setup_pins_() const;
|
||||||
virtual bool reset();
|
virtual bool reset();
|
||||||
|
|||||||
@@ -1,18 +1,20 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
from esphome.components import button
|
from esphome.components import button
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import ENTITY_CATEGORY_CONFIG
|
from esphome.const import ENTITY_CATEGORY_CONFIG
|
||||||
from esphome.types import ConfigType
|
from esphome.types import ConfigType
|
||||||
|
|
||||||
from .. import CONF_ESPECTRE_ID, ESPectreComponent
|
from .. import CONF_ESPECTRE_ID, ESPectreComponent, espectre_ns
|
||||||
|
|
||||||
DEPENDENCIES = ["espectre"]
|
DEPENDENCIES = ["espectre"]
|
||||||
|
|
||||||
|
RecalibrateButton = espectre_ns.class_("RecalibrateButton", button.Button)
|
||||||
|
|
||||||
CONFIG_SCHEMA = button.button_schema(
|
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)})
|
).extend({cv.GenerateID(CONF_ESPECTRE_ID): cv.use_id(ESPectreComponent)})
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config: ConfigType) -> None:
|
async def to_code(config: ConfigType) -> None:
|
||||||
buttons = button.sub_buttons(config, parent=config[CONF_ESPECTRE_ID])
|
var = await button.new_button(config)
|
||||||
await buttons.create(config, None, apply="recalibrate()")
|
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
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
from esphome import automation
|
from esphome import automation
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import mqtt, web_server
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_DEVICE_CLASS,
|
CONF_DEVICE_CLASS,
|
||||||
@@ -9,15 +10,15 @@ from esphome.const import (
|
|||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_MQTT_ID,
|
CONF_MQTT_ID,
|
||||||
CONF_ON_EVENT,
|
CONF_ON_EVENT,
|
||||||
|
CONF_WEB_SERVER,
|
||||||
DEVICE_CLASS_BUTTON,
|
DEVICE_CLASS_BUTTON,
|
||||||
DEVICE_CLASS_DOORBELL,
|
DEVICE_CLASS_DOORBELL,
|
||||||
DEVICE_CLASS_EMPTY,
|
DEVICE_CLASS_EMPTY,
|
||||||
DEVICE_CLASS_MOTION,
|
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 (
|
from esphome.core.entity_helpers import (
|
||||||
entity_duplicate_validator,
|
entity_duplicate_validator,
|
||||||
mqtt_component_class,
|
|
||||||
queue_entity_register,
|
queue_entity_register,
|
||||||
setup_device_class,
|
setup_device_class,
|
||||||
setup_entity,
|
setup_entity,
|
||||||
@@ -43,13 +44,11 @@ EventPtr = Event.operator("ptr")
|
|||||||
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
|
||||||
|
|
||||||
_EVENT_SCHEMA = (
|
_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.MQTT_COMPONENT_SCHEMA)
|
||||||
.extend(
|
.extend(
|
||||||
{
|
{
|
||||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTEventComponent),
|
||||||
mqtt_component_class("MQTTEventComponent")
|
|
||||||
),
|
|
||||||
cv.GenerateID(): cv.declare_id(Event),
|
cv.GenerateID(): cv.declare_id(Event),
|
||||||
cv.Optional(
|
cv.Optional(
|
||||||
CONF_DEVICE_CLASS, visibility=cv.Visibility.ADVANCED
|
CONF_DEVICE_CLASS, visibility=cv.Visibility.ADVANCED
|
||||||
@@ -105,7 +104,12 @@ async def setup_event_core_(
|
|||||||
|
|
||||||
setup_device_class(config)
|
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(
|
async def register_event(
|
||||||
|
|||||||
@@ -1,14 +1,14 @@
|
|||||||
#include "ezo.h"
|
#include "ezo.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/progmem.h"
|
|
||||||
|
|
||||||
namespace esphome::ezo {
|
namespace esphome::ezo {
|
||||||
|
|
||||||
PROGMEM_STRING_TABLE(EzoCommandTypeStrings, "EZO_READ", "EZO_LED", "EZO_DEVICE_INFORMATION", "EZO_SLOPE",
|
static const char *const EZO_COMMAND_TYPE_STRINGS[] = {"EZO_READ", "EZO_LED", "EZO_DEVICE_INFORMATION",
|
||||||
"EZO_CALIBRATION", "EZO_SLEEP", "EZO_I2C", "EZO_T", "EZO_CUSTOM");
|
"EZO_SLOPE", "EZO_CALIBRATION", "EZO_SLEEP",
|
||||||
|
"EZO_I2C", "EZO_T", "EZO_CUSTOM"};
|
||||||
|
|
||||||
PROGMEM_STRING_TABLE(EzoCalibrationTypeStrings, "LOW", "MID", "HIGH");
|
static const char *const EZO_CALIBRATION_TYPE_STRINGS[] = {"LOW", "MID", "HIGH"};
|
||||||
|
|
||||||
void EZOSensor::dump_config() {
|
void EZOSensor::dump_config() {
|
||||||
LOG_SENSOR("", "EZO", this);
|
LOG_SENSOR("", "EZO", this);
|
||||||
@@ -103,8 +103,7 @@ void EZOSensor::loop() {
|
|||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGV(TAG, "Received buffer \"%s\" for command type %s", &buf[1],
|
ESP_LOGV(TAG, "Received buffer \"%s\" for command type %s", &buf[1], EZO_COMMAND_TYPE_STRINGS[to_run->command_type]);
|
||||||
LOG_STR_ARG(EzoCommandTypeStrings::get_log_str(to_run->command_type, EzoCommandTypeStrings::LAST_INDEX)));
|
|
||||||
|
|
||||||
if (buf[0] == 1) {
|
if (buf[0] == 1) {
|
||||||
std::string payload = reinterpret_cast<char *>(&buf[1]);
|
std::string payload = reinterpret_cast<char *>(&buf[1]);
|
||||||
@@ -169,9 +168,7 @@ void EZOSensor::add_command_(const char *command, EzoCommandType command_type, u
|
|||||||
void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) {
|
void EZOSensor::set_calibration_point_(EzoCalibrationType type, float value) {
|
||||||
// max 21: "Cal,"(4) + type(4) + ","(1) + float(11) + null; use 24 for safety
|
// max 21: "Cal,"(4) + type(4) + ","(1) + float(11) + null; use 24 for safety
|
||||||
char payload[24];
|
char payload[24];
|
||||||
ESPHOME_snprintf_P(payload, sizeof(payload), ESPHOME_PSTR("Cal,%s,%0.2f"),
|
snprintf(payload, sizeof(payload), "Cal,%s,%0.2f", EZO_CALIBRATION_TYPE_STRINGS[type], value);
|
||||||
LOG_STR_ARG(EzoCalibrationTypeStrings::get_log_str(type, EzoCalibrationTypeStrings::LAST_INDEX)),
|
|
||||||
value);
|
|
||||||
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
|
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -179,7 +176,7 @@ void EZOSensor::set_address(uint8_t address) {
|
|||||||
if (address > 0 && address < 128) {
|
if (address > 0 && address < 128) {
|
||||||
// max 8: "I2C,"(4) + uint8(3) + null
|
// max 8: "I2C,"(4) + uint8(3) + null
|
||||||
char payload[8];
|
char payload[8];
|
||||||
ESPHOME_snprintf_P(payload, sizeof(payload), ESPHOME_PSTR("I2C,%u"), address);
|
snprintf(payload, sizeof(payload), "I2C,%u", address);
|
||||||
this->new_address_ = address;
|
this->new_address_ = address;
|
||||||
this->add_command_(payload, EzoCommandType::EZO_I2C);
|
this->add_command_(payload, EzoCommandType::EZO_I2C);
|
||||||
} else {
|
} else {
|
||||||
@@ -200,7 +197,7 @@ void EZOSensor::get_t() { this->add_command_("T,?", EzoCommandType::EZO_T); }
|
|||||||
void EZOSensor::set_t(float value) {
|
void EZOSensor::set_t(float value) {
|
||||||
// max 14 bytes: "T,"(2) + float with "%0.2f" (up to 11 chars) + null(1); use 16 for alignment
|
// max 14 bytes: "T,"(2) + float with "%0.2f" (up to 11 chars) + null(1); use 16 for alignment
|
||||||
char payload[16];
|
char payload[16];
|
||||||
ESPHOME_snprintf_P(payload, sizeof(payload), ESPHOME_PSTR("T,%0.2f"), value);
|
snprintf(payload, sizeof(payload), "T,%0.2f", value);
|
||||||
this->add_command_(payload, EzoCommandType::EZO_T);
|
this->add_command_(payload, EzoCommandType::EZO_T);
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -223,7 +220,7 @@ void EZOSensor::set_calibration_point_high(float value) {
|
|||||||
void EZOSensor::set_calibration_generic(float value) {
|
void EZOSensor::set_calibration_generic(float value) {
|
||||||
// exact 16 bytes: "Cal," (4) + float with "%0.2f" (up to 11 chars, e.g. "-9999999.99") + null (1) = 16
|
// exact 16 bytes: "Cal," (4) + float with "%0.2f" (up to 11 chars, e.g. "-9999999.99") + null (1) = 16
|
||||||
char payload[16];
|
char payload[16];
|
||||||
ESPHOME_snprintf_P(payload, sizeof(payload), ESPHOME_PSTR("Cal,%0.2f"), value);
|
snprintf(payload, sizeof(payload), "Cal,%0.2f", value);
|
||||||
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
|
this->add_command_(payload, EzoCommandType::EZO_CALIBRATION, 900);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,7 +1,6 @@
|
|||||||
#include "ezo_pmp.h"
|
#include "ezo_pmp.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
#include "esphome/core/progmem.h"
|
|
||||||
|
|
||||||
namespace esphome::ezo_pmp {
|
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_CALIBRATION_STATUS = 129;
|
||||||
static const uint16_t EZO_PMP_COMMAND_READ_PUMP_VOLTAGE = 257;
|
static const uint16_t EZO_PMP_COMMAND_READ_PUMP_VOLTAGE = 257;
|
||||||
|
|
||||||
#ifdef USE_TEXT_SENSOR
|
static const std::string DOSING_MODE_NONE = "None";
|
||||||
enum DosingMode : uint8_t {
|
static const std::string DOSING_MODE_VOLUME = "Volume";
|
||||||
DOSING_MODE_NONE,
|
static const std::string DOSING_MODE_VOLUME_OVER_TIME = "Volume/Time";
|
||||||
DOSING_MODE_VOLUME,
|
static const std::string DOSING_MODE_CONSTANT_FLOW_RATE = "Constant Flow Rate";
|
||||||
DOSING_MODE_VOLUME_OVER_TIME,
|
static const std::string DOSING_MODE_CONTINUOUS = "Continuous";
|
||||||
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
|
|
||||||
|
|
||||||
void EzoPMP::dump_config() {
|
void EzoPMP::dump_config() {
|
||||||
LOG_I2C_DEVICE(this);
|
LOG_I2C_DEVICE(this);
|
||||||
@@ -226,7 +212,8 @@ void EzoPMP::read_command_result_() {
|
|||||||
#ifdef USE_TEXT_SENSOR
|
#ifdef USE_TEXT_SENSOR
|
||||||
if (!this->is_dosing_flag_ && !this->is_paused_flag_) {
|
if (!this->is_dosing_flag_ && !this->is_paused_flag_) {
|
||||||
// If pump is not paused and not dispensing
|
// 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
|
#endif
|
||||||
|
|
||||||
@@ -298,25 +285,29 @@ void EzoPMP::read_command_result_() {
|
|||||||
|
|
||||||
case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55)
|
case EZO_PMP_COMMAND_DOSE_VOLUME: // Volume Dispensing (page 55)
|
||||||
#ifdef USE_TEXT_SENSOR
|
#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
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56)
|
case EZO_PMP_COMMAND_DOSE_VOLUME_OVER_TIME: // Dose over time (page 56)
|
||||||
#ifdef USE_TEXT_SENSOR
|
#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
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57)
|
case EZO_PMP_COMMAND_DOSE_WITH_CONSTANT_FLOW_RATE: // Constant Flow Rate (page 57)
|
||||||
#ifdef USE_TEXT_SENSOR
|
#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
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54)
|
case EZO_PMP_COMMAND_DOSE_CONTINUOUSLY: // Continuous Dispensing (page 54)
|
||||||
#ifdef USE_TEXT_SENSOR
|
#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
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
@@ -327,7 +318,8 @@ void EzoPMP::read_command_result_() {
|
|||||||
this->is_paused_->publish_state(this->is_paused_flag_);
|
this->is_paused_->publish_state(this->is_paused_flag_);
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_TEXT_SENSOR
|
#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
|
#endif
|
||||||
break;
|
break;
|
||||||
|
|
||||||
|
|||||||
@@ -1,6 +1,7 @@
|
|||||||
from esphome import automation
|
from esphome import automation
|
||||||
from esphome.automation import maybe_simple_id
|
from esphome.automation import maybe_simple_id
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
|
from esphome.components import mqtt, web_server
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import (
|
from esphome.const import (
|
||||||
CONF_DIRECTION,
|
CONF_DIRECTION,
|
||||||
@@ -28,11 +29,11 @@ from esphome.const import (
|
|||||||
CONF_SPEED_LEVEL_STATE_TOPIC,
|
CONF_SPEED_LEVEL_STATE_TOPIC,
|
||||||
CONF_SPEED_STATE_TOPIC,
|
CONF_SPEED_STATE_TOPIC,
|
||||||
CONF_TRIGGER_ID,
|
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 (
|
from esphome.core.entity_helpers import (
|
||||||
entity_duplicate_validator,
|
entity_duplicate_validator,
|
||||||
mqtt_component_class,
|
|
||||||
queue_entity_register,
|
queue_entity_register,
|
||||||
setup_entity,
|
setup_entity,
|
||||||
)
|
)
|
||||||
@@ -77,16 +78,14 @@ FanPresetSetTrigger = fan_ns.class_(
|
|||||||
)
|
)
|
||||||
|
|
||||||
_FAN_SCHEMA = (
|
_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.MQTT_COMMAND_COMPONENT_SCHEMA)
|
||||||
.extend(
|
.extend(
|
||||||
{
|
{
|
||||||
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
|
cv.Optional(CONF_RESTORE_MODE, default="ALWAYS_OFF"): cv.enum(
|
||||||
RESTORE_MODES, upper=True, space="_"
|
RESTORE_MODES, upper=True, space="_"
|
||||||
),
|
),
|
||||||
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
|
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTFanComponent),
|
||||||
mqtt_component_class("MQTTFanComponent")
|
|
||||||
),
|
|
||||||
cv.Optional(CONF_DIRECTION_STATE_TOPIC): cv.All(
|
cv.Optional(CONF_DIRECTION_STATE_TOPIC): cv.All(
|
||||||
cv.requires_component("mqtt"), cv.publish_topic
|
cv.requires_component("mqtt"), cv.publish_topic
|
||||||
),
|
),
|
||||||
@@ -241,8 +240,10 @@ _TRIGGER_AUTOMATIONS = (
|
|||||||
async def setup_fan_core_(var, config):
|
async def setup_fan_core_(var, config):
|
||||||
cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))
|
cg.add(var.set_restore_mode(config[CONF_RESTORE_MODE]))
|
||||||
|
|
||||||
mqtt_ = await entity_helpers.setup_entity_integrations(var, config)
|
if (mqtt_id := config.get(CONF_MQTT_ID)) is not None:
|
||||||
if mqtt_ is not None:
|
mqtt_ = cg.new_Pvariable(mqtt_id, var)
|
||||||
|
await mqtt.register_mqtt_component(mqtt_, config)
|
||||||
|
|
||||||
if (
|
if (
|
||||||
direction_state_topic := config.get(CONF_DIRECTION_STATE_TOPIC)
|
direction_state_topic := config.get(CONF_DIRECTION_STATE_TOPIC)
|
||||||
) is not None:
|
) 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:
|
if (speed_command_topic := config.get(CONF_SPEED_COMMAND_TOPIC)) is not None:
|
||||||
cg.add(mqtt_.set_custom_speed_command_topic(speed_command_topic))
|
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, []):
|
for conf in config.get(CONF_ON_STATE, []):
|
||||||
await automation.build_parent_callback_automation(
|
await automation.build_parent_callback_automation(
|
||||||
var, "add_on_state_callback", (Fan.operator("ptr"), "x"), conf
|
var, "add_on_state_callback", (Fan.operator("ptr"), "x"), conf
|
||||||
|
|||||||
@@ -1,10 +1,12 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
from esphome.components import button
|
from esphome.components import button
|
||||||
import esphome.config_validation as cv
|
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"]
|
CODEOWNERS = ["@paveldn"]
|
||||||
|
SelfCleaningButton = haier_ns.class_("SelfCleaningButton", button.Button)
|
||||||
|
SteriCleaningButton = haier_ns.class_("SteriCleaningButton", button.Button)
|
||||||
|
|
||||||
|
|
||||||
# Haier buttons
|
# Haier buttons
|
||||||
@@ -18,18 +20,19 @@ CONFIG_SCHEMA = cv.Schema(
|
|||||||
{
|
{
|
||||||
cv.GenerateID(CONF_HAIER_ID): cv.use_id(HonClimate),
|
cv.GenerateID(CONF_HAIER_ID): cv.use_id(HonClimate),
|
||||||
cv.Optional(CONF_SELF_CLEANING): button.button_schema(
|
cv.Optional(CONF_SELF_CLEANING): button.button_schema(
|
||||||
button.ApplyButton,
|
SelfCleaningButton,
|
||||||
icon=ICON_SPRAY_BOTTLE,
|
icon=ICON_SPRAY_BOTTLE,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_STERI_CLEANING): button.button_schema(
|
cv.Optional(CONF_STERI_CLEANING): button.button_schema(
|
||||||
button.ApplyButton,
|
SteriCleaningButton,
|
||||||
icon=ICON_SPRAY_BOTTLE,
|
icon=ICON_SPRAY_BOTTLE,
|
||||||
),
|
),
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config: ConfigType) -> None:
|
async def to_code(config):
|
||||||
buttons = button.sub_buttons(config, parent=config[CONF_HAIER_ID])
|
for button_type in [CONF_SELF_CLEANING, CONF_STERI_CLEANING]:
|
||||||
await buttons(CONF_SELF_CLEANING, None, apply="start_self_cleaning()")
|
if conf := config.get(button_type):
|
||||||
await buttons(CONF_STERI_CLEANING, None, apply="start_steri_cleaning()")
|
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
|
||||||
@@ -6,7 +6,6 @@
|
|||||||
#include "esphome/components/wifi/wifi_component.h"
|
#include "esphome/components/wifi/wifi_component.h"
|
||||||
#endif
|
#endif
|
||||||
#include "haier_base.h"
|
#include "haier_base.h"
|
||||||
#include "esphome/core/progmem.h"
|
|
||||||
|
|
||||||
using namespace esphome::climate;
|
using namespace esphome::climate;
|
||||||
using namespace esphome::uart;
|
using namespace esphome::uart;
|
||||||
@@ -20,16 +19,28 @@ constexpr size_t PROTOCOL_INITIALIZATION_INTERVAL = 10000;
|
|||||||
constexpr size_t DEFAULT_MESSAGES_INTERVAL_MS = 2000;
|
constexpr size_t DEFAULT_MESSAGES_INTERVAL_MS = 2000;
|
||||||
constexpr size_t CONTROL_MESSAGES_INTERVAL_MS = 400;
|
constexpr size_t CONTROL_MESSAGES_INTERVAL_MS = 400;
|
||||||
|
|
||||||
PROGMEM_STRING_TABLE(ProtocolPhaseStrings, "SENDING_INIT_1", "SENDING_INIT_2", "SENDING_FIRST_STATUS_REQUEST",
|
const char *HaierClimateBase::phase_to_string_(ProtocolPhases phase) {
|
||||||
"SENDING_FIRST_ALARM_STATUS_REQUEST", "IDLE", "SENDING_STATUS_REQUEST",
|
static const char *phase_names[] = {
|
||||||
"SENDING_UPDATE_SIGNAL_REQUEST", "SENDING_SIGNAL_LEVEL", "SENDING_CONTROL",
|
"SENDING_INIT_1",
|
||||||
"SENDING_ACTION_COMMAND", "SENDING_ALARM_STATUS_REQUEST", "UNKNOWN");
|
"SENDING_INIT_2",
|
||||||
|
"SENDING_FIRST_STATUS_REQUEST",
|
||||||
const LogString *HaierClimateBase::phase_to_string_(ProtocolPhases phase) {
|
"SENDING_FIRST_ALARM_STATUS_REQUEST",
|
||||||
static_assert(ProtocolPhaseStrings::COUNT == ((int) ProtocolPhases::NUM_PROTOCOL_PHASES) + 1,
|
"IDLE",
|
||||||
"ProtocolPhaseStrings must match the enum ProtocolPhases, with UNKNOWN last");
|
"SENDING_STATUS_REQUEST",
|
||||||
// UNKNOWN (-1) wraps past the table and reads as the last entry, "UNKNOWN"
|
"SENDING_UPDATE_SIGNAL_REQUEST",
|
||||||
return ProtocolPhaseStrings::get_log_str(static_cast<uint8_t>(phase), ProtocolPhaseStrings::LAST_INDEX);
|
"SENDING_SIGNAL_LEVEL",
|
||||||
|
"SENDING_CONTROL",
|
||||||
|
"SENDING_ACTION_COMMAND",
|
||||||
|
"SENDING_ALARM_STATUS_REQUEST",
|
||||||
|
"UNKNOWN" // Should be the last!
|
||||||
|
};
|
||||||
|
static_assert(
|
||||||
|
(sizeof(phase_names) / sizeof(char *)) == (((int) ProtocolPhases::NUM_PROTOCOL_PHASES) + 1),
|
||||||
|
"Wrong phase_names array size. Please, make sure that this array is aligned with the enum ProtocolPhases");
|
||||||
|
int phase_index = (int) phase;
|
||||||
|
if ((phase_index > (int) ProtocolPhases::NUM_PROTOCOL_PHASES) || (phase_index < 0))
|
||||||
|
phase_index = (int) ProtocolPhases::NUM_PROTOCOL_PHASES;
|
||||||
|
return phase_names[phase_index];
|
||||||
}
|
}
|
||||||
|
|
||||||
bool check_timeout(std::chrono::steady_clock::time_point now, std::chrono::steady_clock::time_point tpoint,
|
bool check_timeout(std::chrono::steady_clock::time_point now, std::chrono::steady_clock::time_point tpoint,
|
||||||
@@ -60,8 +71,7 @@ HaierClimateBase::~HaierClimateBase() {}
|
|||||||
|
|
||||||
void HaierClimateBase::set_phase(ProtocolPhases phase) {
|
void HaierClimateBase::set_phase(ProtocolPhases phase) {
|
||||||
if (this->protocol_phase_ != phase) {
|
if (this->protocol_phase_ != phase) {
|
||||||
ESP_LOGV(TAG, "Phase transition: %s => %s", LOG_STR_ARG(phase_to_string_(this->protocol_phase_)),
|
ESP_LOGV(TAG, "Phase transition: %s => %s", phase_to_string_(this->protocol_phase_), phase_to_string_(phase));
|
||||||
LOG_STR_ARG(phase_to_string_(phase)));
|
|
||||||
this->protocol_phase_ = phase;
|
this->protocol_phase_ = phase;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -215,7 +225,7 @@ haier_protocol::HandlerError HaierClimateBase::report_network_status_answer_hand
|
|||||||
|
|
||||||
haier_protocol::HandlerError HaierClimateBase::timeout_default_handler_(haier_protocol::FrameType request_type) {
|
haier_protocol::HandlerError HaierClimateBase::timeout_default_handler_(haier_protocol::FrameType request_type) {
|
||||||
ESP_LOGW(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) request_type,
|
ESP_LOGW(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) request_type,
|
||||||
LOG_STR_ARG(phase_to_string_(this->protocol_phase_)));
|
phase_to_string_(this->protocol_phase_));
|
||||||
if (this->protocol_phase_ > ProtocolPhases::IDLE) {
|
if (this->protocol_phase_ > ProtocolPhases::IDLE) {
|
||||||
this->set_phase(ProtocolPhases::IDLE);
|
this->set_phase(ProtocolPhases::IDLE);
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -95,7 +95,7 @@ class HaierClimateBase : public esphome::Component,
|
|||||||
SENDING_ALARM_STATUS_REQUEST,
|
SENDING_ALARM_STATUS_REQUEST,
|
||||||
NUM_PROTOCOL_PHASES
|
NUM_PROTOCOL_PHASES
|
||||||
};
|
};
|
||||||
const LogString *phase_to_string_(ProtocolPhases phase);
|
const char *phase_to_string_(ProtocolPhases phase);
|
||||||
virtual void set_handlers() = 0;
|
virtual void set_handlers() = 0;
|
||||||
virtual void process_phase(std::chrono::steady_clock::time_point now) = 0;
|
virtual void process_phase(std::chrono::steady_clock::time_point now) = 0;
|
||||||
virtual haier_protocol::HaierMessage get_control_message() = 0; // NOLINT(readability-identifier-naming)
|
virtual haier_protocol::HaierMessage get_control_message() = 0; // NOLINT(readability-identifier-naming)
|
||||||
|
|||||||
@@ -564,7 +564,7 @@ void HonClimate::process_phase(std::chrono::steady_clock::time_point now) {
|
|||||||
default:
|
default:
|
||||||
// Shouldn't get here
|
// Shouldn't get here
|
||||||
ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication",
|
ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication",
|
||||||
LOG_STR_ARG(phase_to_string_(this->protocol_phase_)), (int) this->protocol_phase_);
|
phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_);
|
||||||
this->set_phase(ProtocolPhases::SENDING_INIT_1);
|
this->set_phase(ProtocolPhases::SENDING_INIT_1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -93,7 +93,7 @@ haier_protocol::HandlerError Smartair2Climate::messages_timeout_handler_with_cyc
|
|||||||
if (this->protocol_phase_ >= ProtocolPhases::IDLE)
|
if (this->protocol_phase_ >= ProtocolPhases::IDLE)
|
||||||
return HaierClimateBase::timeout_default_handler_(message_type);
|
return HaierClimateBase::timeout_default_handler_(message_type);
|
||||||
ESP_LOGI(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) message_type,
|
ESP_LOGI(TAG, "Answer timeout for command %02X, phase %s", (uint8_t) message_type,
|
||||||
LOG_STR_ARG(phase_to_string_(this->protocol_phase_)));
|
phase_to_string_(this->protocol_phase_));
|
||||||
ProtocolPhases new_phase = (ProtocolPhases) ((int) this->protocol_phase_ + 1);
|
ProtocolPhases new_phase = (ProtocolPhases) ((int) this->protocol_phase_ + 1);
|
||||||
if (new_phase >= ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST)
|
if (new_phase >= ProtocolPhases::SENDING_FIRST_ALARM_STATUS_REQUEST)
|
||||||
new_phase = ProtocolPhases::SENDING_INIT_1;
|
new_phase = ProtocolPhases::SENDING_INIT_1;
|
||||||
@@ -217,7 +217,7 @@ void Smartair2Climate::process_phase(std::chrono::steady_clock::time_point now)
|
|||||||
default:
|
default:
|
||||||
// Shouldn't get here
|
// Shouldn't get here
|
||||||
ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication",
|
ESP_LOGE(TAG, "Wrong protocol handler state: %s (%d), resetting communication",
|
||||||
LOG_STR_ARG(phase_to_string_(this->protocol_phase_)), (int) this->protocol_phase_);
|
phase_to_string_(this->protocol_phase_), (int) this->protocol_phase_);
|
||||||
this->set_phase(ProtocolPhases::SENDING_INIT_1);
|
this->set_phase(ProtocolPhases::SENDING_INIT_1);
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,9 +1,10 @@
|
|||||||
|
import esphome.codegen as cg
|
||||||
from esphome.components import button
|
from esphome.components import button
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
from esphome.const import ICON_AIR_FILTER
|
from esphome.const import ICON_AIR_FILTER
|
||||||
from esphome.types import ConfigType
|
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"]
|
DEPENDENCIES = ["hoermann_hcp"]
|
||||||
|
|
||||||
@@ -12,6 +13,10 @@ CONF_VENT = "vent"
|
|||||||
|
|
||||||
ICON_GARAGE_OPEN_VARIANT = "mdi:garage-open-variant"
|
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)
|
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.GenerateID(CONF_HOERMANN_HCP_ID): cv.use_id(HoermannHcp),
|
||||||
cv.Optional(CONF_VENT): button.button_schema(
|
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(
|
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:
|
async def to_code(config: ConfigType) -> None:
|
||||||
buttons = button.sub_buttons(config, parent=config[CONF_HOERMANN_HCP_ID])
|
parent = await cg.get_variable(config[CONF_HOERMANN_HCP_ID])
|
||||||
await buttons(CONF_VENT, None, apply="vent_door()")
|
for key in BUTTON_KEYS:
|
||||||
await buttons(CONF_HALF_OPEN, None, apply="half_open_door()")
|
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
|
||||||
@@ -1,6 +1,7 @@
|
|||||||
#include "htu21d.h"
|
#include "htu21d.h"
|
||||||
#include "esphome/core/log.h"
|
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
|
#include "esphome/core/helpers.h"
|
||||||
|
#include "esphome/core/log.h"
|
||||||
|
|
||||||
namespace esphome::htu21d {
|
namespace esphome::htu21d {
|
||||||
|
|
||||||
@@ -112,11 +113,7 @@ void HTU21DComponent::set_heater(bool status) {
|
|||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (status) {
|
assign_bits(raw_heater, 1 << HTU21D_REG_HTRE_BIT, status);
|
||||||
raw_heater |= (1 << HTU21D_REG_HTRE_BIT);
|
|
||||||
} else {
|
|
||||||
raw_heater &= ~(1 << HTU21D_REG_HTRE_BIT);
|
|
||||||
}
|
|
||||||
if (this->write_register(HTU21D_WRITERHT_REG_CMD, &raw_heater, 1) != i2c::ERROR_OK) {
|
if (this->write_register(HTU21D_WRITERHT_REG_CMD, &raw_heater, 1) != i2c::ERROR_OK) {
|
||||||
this->status_set_warning();
|
this->status_set_warning();
|
||||||
return;
|
return;
|
||||||
|
|||||||
@@ -41,13 +41,11 @@ from esphome.const import (
|
|||||||
CONF_FREQUENCY,
|
CONF_FREQUENCY,
|
||||||
CONF_I2C_ID,
|
CONF_I2C_ID,
|
||||||
CONF_ID,
|
CONF_ID,
|
||||||
CONF_INVERTED,
|
|
||||||
CONF_LOW_POWER_MODE,
|
CONF_LOW_POWER_MODE,
|
||||||
CONF_SCAN,
|
CONF_SCAN,
|
||||||
CONF_SCL,
|
CONF_SCL,
|
||||||
CONF_SDA,
|
CONF_SDA,
|
||||||
CONF_TIMEOUT,
|
CONF_TIMEOUT,
|
||||||
PLATFORM_BK72XX,
|
|
||||||
PLATFORM_ESP32,
|
PLATFORM_ESP32,
|
||||||
PLATFORM_ESP8266,
|
PLATFORM_ESP8266,
|
||||||
PLATFORM_HOST,
|
PLATFORM_HOST,
|
||||||
@@ -70,7 +68,6 @@ ArduinoI2CBus = i2c_ns.class_("ArduinoI2CBus", InternalI2CBus, cg.Component)
|
|||||||
IDFI2CBus = i2c_ns.class_("IDFI2CBus", InternalI2CBus, cg.Component)
|
IDFI2CBus = i2c_ns.class_("IDFI2CBus", InternalI2CBus, cg.Component)
|
||||||
ZephyrI2CBus = i2c_ns.class_("ZephyrI2CBus", I2CBus, cg.Component)
|
ZephyrI2CBus = i2c_ns.class_("ZephyrI2CBus", I2CBus, cg.Component)
|
||||||
HostI2CBus = i2c_ns.class_("HostI2CBus", I2CBus, cg.Component)
|
HostI2CBus = i2c_ns.class_("HostI2CBus", I2CBus, cg.Component)
|
||||||
SoftwareI2CBus = i2c_ns.class_("SoftwareI2CBus", I2CBus, cg.Component)
|
|
||||||
I2CDevice = i2c_ns.class_("I2CDevice")
|
I2CDevice = i2c_ns.class_("I2CDevice")
|
||||||
|
|
||||||
ESP32_I2C_CAPABILITIES = {
|
ESP32_I2C_CAPABILITIES = {
|
||||||
@@ -111,8 +108,6 @@ def validate_device(value: str) -> str:
|
|||||||
def _bus_declare_type(value: Any) -> ID:
|
def _bus_declare_type(value: Any) -> ID:
|
||||||
if CORE.is_esp32:
|
if CORE.is_esp32:
|
||||||
return cv.declare_id(IDFI2CBus)(value)
|
return cv.declare_id(IDFI2CBus)(value)
|
||||||
if CORE.is_libretiny:
|
|
||||||
return cv.declare_id(SoftwareI2CBus)(value)
|
|
||||||
if CORE.using_arduino:
|
if CORE.using_arduino:
|
||||||
return cv.declare_id(ArduinoI2CBus)(value)
|
return cv.declare_id(ArduinoI2CBus)(value)
|
||||||
if CORE.using_zephyr:
|
if CORE.using_zephyr:
|
||||||
@@ -133,28 +128,7 @@ def _rp2040_i2c_controller(pin: int) -> int:
|
|||||||
return (pin // 2) % 2
|
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:
|
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:
|
if CORE.is_esp32:
|
||||||
return cv.require_framework_version(
|
return cv.require_framework_version(
|
||||||
esp_idf=cv.Version(5, 4, 2), esp32_arduino=cv.Version(3, 2, 1)
|
esp_idf=cv.Version(5, 4, 2), esp32_arduino=cv.Version(3, 2, 1)
|
||||||
@@ -207,7 +181,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
esp8266="SDA",
|
esp8266="SDA",
|
||||||
rp2="SDA",
|
rp2="SDA",
|
||||||
nrf52="SDA",
|
nrf52="SDA",
|
||||||
): _i2c_pin_number,
|
): pins.internal_gpio_pin_number,
|
||||||
cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32=True): cv.All(
|
cv.SplitDefault(CONF_SDA_PULLUP_ENABLED, esp32=True): cv.All(
|
||||||
cv.only_on_esp32, cv.boolean
|
cv.only_on_esp32, cv.boolean
|
||||||
),
|
),
|
||||||
@@ -217,7 +191,7 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
esp8266="SCL",
|
esp8266="SCL",
|
||||||
rp2="SCL",
|
rp2="SCL",
|
||||||
nrf52="SCL",
|
nrf52="SCL",
|
||||||
): _i2c_pin_number,
|
): pins.internal_gpio_pin_number,
|
||||||
cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32=True): cv.All(
|
cv.SplitDefault(CONF_SCL_PULLUP_ENABLED, esp32=True): cv.All(
|
||||||
cv.only_on_esp32, cv.boolean
|
cv.only_on_esp32, cv.boolean
|
||||||
),
|
),
|
||||||
@@ -228,7 +202,6 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
rp2="50kHz",
|
rp2="50kHz",
|
||||||
nrf52="100kHz",
|
nrf52="100kHz",
|
||||||
host="50kHz",
|
host="50kHz",
|
||||||
bk72xx="100kHz",
|
|
||||||
): cv.All(
|
): cv.All(
|
||||||
cv.frequency,
|
cv.frequency,
|
||||||
cv.float_range(min=0, min_included=False),
|
cv.float_range(min=0, min_included=False),
|
||||||
@@ -252,7 +225,6 @@ CONFIG_SCHEMA = cv.All(
|
|||||||
).extend(cv.COMPONENT_SCHEMA),
|
).extend(cv.COMPONENT_SCHEMA),
|
||||||
cv.only_on(
|
cv.only_on(
|
||||||
[
|
[
|
||||||
PLATFORM_BK72XX,
|
|
||||||
PLATFORM_ESP32,
|
PLATFORM_ESP32,
|
||||||
PLATFORM_ESP8266,
|
PLATFORM_ESP8266,
|
||||||
PLATFORM_RP2,
|
PLATFORM_RP2,
|
||||||
@@ -371,16 +343,6 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
cg.add(var.set_timeout(int(config[CONF_TIMEOUT].total_microseconds)))
|
cg.add(var.set_timeout(int(config[CONF_TIMEOUT].total_microseconds)))
|
||||||
if CONF_LOW_POWER_MODE in config:
|
if CONF_LOW_POWER_MODE in config:
|
||||||
cg.add(var.set_lp_mode(bool(config[CONF_LOW_POWER_MODE])))
|
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:
|
else:
|
||||||
var = cg.new_Pvariable(config[CONF_ID])
|
var = cg.new_Pvariable(config[CONF_ID])
|
||||||
await cg.register_component(var, config)
|
await cg.register_component(var, config)
|
||||||
@@ -500,6 +462,7 @@ _platform_filter = filter_source_files_from_platform(
|
|||||||
"i2c_bus_arduino.cpp": {
|
"i2c_bus_arduino.cpp": {
|
||||||
PlatformFramework.ESP8266_ARDUINO,
|
PlatformFramework.ESP8266_ARDUINO,
|
||||||
PlatformFramework.RP2_ARDUINO,
|
PlatformFramework.RP2_ARDUINO,
|
||||||
|
PlatformFramework.BK72XX_ARDUINO,
|
||||||
PlatformFramework.RTL87XX_ARDUINO,
|
PlatformFramework.RTL87XX_ARDUINO,
|
||||||
PlatformFramework.LN882X_ARDUINO,
|
PlatformFramework.LN882X_ARDUINO,
|
||||||
},
|
},
|
||||||
@@ -507,7 +470,6 @@ _platform_filter = filter_source_files_from_platform(
|
|||||||
PlatformFramework.ESP32_ARDUINO,
|
PlatformFramework.ESP32_ARDUINO,
|
||||||
PlatformFramework.ESP32_IDF,
|
PlatformFramework.ESP32_IDF,
|
||||||
},
|
},
|
||||||
"i2c_bus_software.cpp": {PlatformFramework.BK72XX_ARDUINO},
|
|
||||||
"i2c_bus_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
|
"i2c_bus_zephyr.cpp": {PlatformFramework.NRF52_ZEPHYR},
|
||||||
"i2c_bus_host.cpp": {PlatformFramework.HOST_NATIVE},
|
"i2c_bus_host.cpp": {PlatformFramework.HOST_NATIVE},
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -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
|
|
||||||
@@ -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
|
|
||||||
@@ -2,20 +2,11 @@
|
|||||||
|
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/log.h"
|
#include "esphome/core/log.h"
|
||||||
#include "esphome/core/progmem.h"
|
|
||||||
|
|
||||||
namespace esphome::kamstrup_kmp {
|
namespace esphome::kamstrup_kmp {
|
||||||
|
|
||||||
ESPHOME_LOG_TAG(TAG, "kamstrup_kmp");
|
ESPHOME_LOG_TAG(TAG, "kamstrup_kmp");
|
||||||
|
|
||||||
// KMP units indexed by unit code; the empty first entry doubles as the fallback for unknown codes
|
|
||||||
PROGMEM_STRING_TABLE(KmpUnits, "", "Wh", "kWh", "MWh", "GWh", "J", "kJ", "MJ", "GJ", "Cal", "kCal", "Mcal", "Gcal",
|
|
||||||
"varh", "kvarh", "Mvarh", "Gvarh", "VAh", "kVAh", "MVAh", "GVAh", "kW", "kW", "MW", "GW", "kvar",
|
|
||||||
"kvar", "Mvar", "Gvar", "VA", "kVA", "MVA", "GVA", "V", "A", "kV", "kA", "C", "K", "l", "m3",
|
|
||||||
"l/h", "m3/h", "m3xC", "ton", "ton/h", "h", "hh:mm:ss", "yy:mm:dd", "yyyy:mm:dd", "mm:dd", "",
|
|
||||||
"bar", "RTC", "ASCII", "m3 x 10", "ton x 10", "GJ x 10", "minutes", "Bitfield", "s", "ms", "days",
|
|
||||||
"RTC-Q", "Datetime");
|
|
||||||
|
|
||||||
void KamstrupKMPComponent::dump_config() {
|
void KamstrupKMPComponent::dump_config() {
|
||||||
ESP_LOGCONFIG(TAG, "kamstrup_kmp:");
|
ESP_LOGCONFIG(TAG, "kamstrup_kmp:");
|
||||||
LOG_UPDATE_INTERVAL(this);
|
LOG_UPDATE_INTERVAL(this);
|
||||||
@@ -246,6 +237,8 @@ void KamstrupKMPComponent::parse_command_message_(uint16_t command, const uint8_
|
|||||||
}
|
}
|
||||||
|
|
||||||
void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint8_t unit_idx) {
|
void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint8_t unit_idx) {
|
||||||
|
const char *unit = unit_idx < sizeof(UNITS) / sizeof(UNITS[0]) ? UNITS[unit_idx] : "";
|
||||||
|
|
||||||
// Standard sensors
|
// Standard sensors
|
||||||
if (command == CMD_HEAT_ENERGY && this->heat_energy_sensor_ != nullptr) {
|
if (command == CMD_HEAT_ENERGY && this->heat_energy_sensor_ != nullptr) {
|
||||||
this->heat_energy_sensor_->publish_state(value);
|
this->heat_energy_sensor_->publish_state(value);
|
||||||
@@ -270,8 +263,7 @@ void KamstrupKMPComponent::set_sensor_value_(uint16_t command, float value, uint
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
ESP_LOGD(TAG, "Received value for command 0x%04X: %.3f [%s]", command, value,
|
ESP_LOGD(TAG, "Received value for command 0x%04X: %.3f [%s]", command, value, unit);
|
||||||
LOG_STR_ARG(KmpUnits::get_log_str(unit_idx, 0)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::kamstrup_kmp
|
} // namespace esphome::kamstrup_kmp
|
||||||
@@ -63,6 +63,16 @@ static const uint16_t CMD_TEMP_DIFF = 0x0059;
|
|||||||
static const uint16_t CMD_FLOW = 0x004A;
|
static const uint16_t CMD_FLOW = 0x004A;
|
||||||
static const uint16_t CMD_VOLUME = 0x0044;
|
static const uint16_t CMD_VOLUME = 0x0044;
|
||||||
|
|
||||||
|
// KMP units
|
||||||
|
static const char *const UNITS[] = {
|
||||||
|
"", "Wh", "kWh", "MWh", "GWh", "J", "kJ", "MJ", "GJ", "Cal",
|
||||||
|
"kCal", "Mcal", "Gcal", "varh", "kvarh", "Mvarh", "Gvarh", "VAh", "kVAh", "MVAh",
|
||||||
|
"GVAh", "kW", "kW", "MW", "GW", "kvar", "kvar", "Mvar", "Gvar", "VA",
|
||||||
|
"kVA", "MVA", "GVA", "V", "A", "kV", "kA", "C", "K", "l",
|
||||||
|
"m3", "l/h", "m3/h", "m3xC", "ton", "ton/h", "h", "hh:mm:ss", "yy:mm:dd", "yyyy:mm:dd",
|
||||||
|
"mm:dd", "", "bar", "RTC", "ASCII", "m3 x 10", "ton x 10", "GJ x 10", "minutes", "Bitfield",
|
||||||
|
"s", "ms", "days", "RTC-Q", "Datetime"};
|
||||||
|
|
||||||
class KamstrupKMPComponent final : public PollingComponent, public uart::UARTDevice {
|
class KamstrupKMPComponent final : public PollingComponent, public uart::UARTDevice {
|
||||||
public:
|
public:
|
||||||
void set_heat_energy_sensor(sensor::Sensor *sensor) { this->heat_energy_sensor_ = sensor; }
|
void set_heat_energy_sensor(sensor::Sensor *sensor) { this->heat_energy_sensor_ = sensor; }
|
||||||
|
|||||||
@@ -5,26 +5,26 @@ import esphome.config_validation as cv
|
|||||||
from esphome.const import CONF_FACTORY_RESET, CONF_ID, CONF_RESTART
|
from esphome.const import CONF_FACTORY_RESET, CONF_ID, CONF_RESTART
|
||||||
from esphome.types import ConfigType
|
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 = {
|
CONFIG_SCHEMA = {
|
||||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||||
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
||||||
cv.Optional(CONF_FACTORY_RESET): button.factory_reset_button_schema(
|
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),
|
||||||
cv.Optional(CONF_QUERY_PARAMS): button.query_button_schema(button.ApplyButton),
|
cv.Optional(CONF_QUERY_PARAMS): button.query_button_schema(QueryButton),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
async def to_code(config: ConfigType) -> None:
|
async def to_code(config: ConfigType) -> None:
|
||||||
hub = await cg.get_variable(config[CONF_LD2410_ID])
|
hub = await cg.get_variable(config[CONF_LD2410_ID])
|
||||||
buttons = button.sub_buttons(config, parent=hub)
|
buttons = button.sub_buttons(config, parent=hub)
|
||||||
await buttons(
|
await buttons(CONF_FACTORY_RESET, hub.set_factory_reset_button)
|
||||||
CONF_FACTORY_RESET, hub.set_factory_reset_button, apply="factory_reset()"
|
await buttons(CONF_RESTART, hub.set_restart_button)
|
||||||
)
|
await buttons(CONF_QUERY_PARAMS, hub.set_query_button)
|
||||||
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()")
|
|
||||||
@@ -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
|
||||||
@@ -9,12 +9,27 @@
|
|||||||
|
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/helpers.h"
|
#include "esphome/core/helpers.h"
|
||||||
#include "esphome/core/progmem.h"
|
|
||||||
|
|
||||||
namespace esphome::ld2410 {
|
namespace esphome::ld2410 {
|
||||||
|
|
||||||
ESPHOME_LOG_TAG(TAG, "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 {
|
enum LightFunction : uint8_t {
|
||||||
LIGHT_FUNCTION_OFF = 0x00,
|
LIGHT_FUNCTION_OFF = 0x00,
|
||||||
LIGHT_FUNCTION_BELOW = 0x01,
|
LIGHT_FUNCTION_BELOW = 0x01,
|
||||||
@@ -54,17 +69,73 @@ enum AckData : uint8_t {
|
|||||||
COMMAND_STATUS = 7,
|
COMMAND_STATUS = 7,
|
||||||
};
|
};
|
||||||
|
|
||||||
// Index + 1 is the BaudRate value; the select options use the same order
|
// Memory-efficient lookup tables
|
||||||
PROGMEM_STRING_TABLE(BaudRateStrings, "9600", "19200", "38400", "57600", "115200", "230400", "256000", "460800");
|
struct StringToUint8 {
|
||||||
static_assert(BAUD_RATE_9600 == 1 && BAUD_RATE_460800 == BaudRateStrings::COUNT, "BaudRate is index + 1");
|
const char *str;
|
||||||
// Same order as DISTANCE_RESOLUTIONS
|
const uint8_t value;
|
||||||
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");
|
|
||||||
|
|
||||||
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
|
// Commands
|
||||||
static constexpr uint8_t CMD_ENABLE_CONF = 0xFF;
|
static constexpr uint8_t CMD_ENABLE_CONF = 0xFF;
|
||||||
@@ -201,7 +272,20 @@ void LD2410Component::restart_and_read_all_info() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LD2410Component::loop() {
|
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) {
|
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) {
|
if (this->buffer_pos_ < 11 + HEADER_FOOTER_SIZE) {
|
||||||
break; // too short to hold the resolution
|
break; // too short to hold the resolution
|
||||||
}
|
}
|
||||||
const auto index = ld24xx::find_index(DISTANCE_RESOLUTIONS, this->buffer_data_[10]);
|
const auto *distance_resolution = find_str(DISTANCE_RESOLUTIONS_BY_UINT, this->buffer_data_[10]);
|
||||||
if (!index) {
|
ESP_LOGV(TAG, "Distance resolution: %s", distance_resolution);
|
||||||
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)));
|
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
if (this->distance_resolution_select_ != nullptr) {
|
if (this->distance_resolution_select_ != nullptr) {
|
||||||
this->distance_resolution_select_->publish_state(*index);
|
this->distance_resolution_select_->publish_state(distance_resolution);
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
break;
|
break;
|
||||||
@@ -403,14 +482,16 @@ void LD2410Component::handle_ack_data_() {
|
|||||||
this->light_function_ = this->buffer_data_[10];
|
this->light_function_ = this->buffer_data_[10];
|
||||||
this->light_threshold_ = this->buffer_data_[11];
|
this->light_threshold_ = this->buffer_data_[11];
|
||||||
this->out_pin_level_ = this->buffer_data_[12];
|
this->out_pin_level_ = this->buffer_data_[12];
|
||||||
ESP_LOGV(TAG, "Light function: %u, threshold: %u, out pin level: %u", this->light_function_,
|
const auto *light_function_str = find_str(LIGHT_FUNCTIONS_BY_UINT, this->light_function_);
|
||||||
this->light_threshold_, this->out_pin_level_);
|
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
|
#ifdef USE_SELECT
|
||||||
if (this->light_function_select_ != nullptr) {
|
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) {
|
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
|
#endif
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
@@ -547,29 +628,15 @@ void LD2410Component::set_bluetooth(bool enable) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LD2410Component::set_distance_resolution(const char *state) {
|
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);
|
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->send_command_(CMD_SET_DISTANCE_RESOLUTION, cmd_value, sizeof(cmd_value));
|
||||||
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
|
this->set_timeout(200, [this]() { this->restart_and_read_all_info(); });
|
||||||
}
|
}
|
||||||
|
|
||||||
void LD2410Component::set_baud_rate(const char *state) {
|
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);
|
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->send_command_(CMD_SET_BAUD_RATE, cmd_value, sizeof(cmd_value));
|
||||||
this->set_timeout(200, [this]() { this->restart_(); });
|
this->set_timeout(200, [this]() { this->restart_(); });
|
||||||
}
|
}
|
||||||
@@ -648,16 +715,7 @@ void LD2410Component::set_max_distances_timeout() {
|
|||||||
this->set_config_mode_(false);
|
this->set_config_mode_(false);
|
||||||
}
|
}
|
||||||
|
|
||||||
void LD2410Component::set_gate_threshold(number::Number *n) {
|
void LD2410Component::set_gate_threshold(uint8_t gate) {
|
||||||
// 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;
|
|
||||||
}
|
|
||||||
number::Number *motionsens = this->gate_move_threshold_numbers_[gate];
|
number::Number *motionsens = this->gate_move_threshold_numbers_[gate];
|
||||||
number::Number *stillsens = this->gate_still_threshold_numbers_[gate];
|
number::Number *stillsens = this->gate_still_threshold_numbers_[gate];
|
||||||
|
|
||||||
@@ -701,15 +759,11 @@ void LD2410Component::set_light_out_control() {
|
|||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SELECT
|
#ifdef USE_SELECT
|
||||||
if (this->light_function_select_ != nullptr) {
|
if (this->light_function_select_ != nullptr && this->light_function_select_->has_state()) {
|
||||||
if (const auto index = this->light_function_select_->active_index()) {
|
this->light_function_ = find_uint8(LIGHT_FUNCTIONS_BY_STR, this->light_function_select_->current_option().c_str());
|
||||||
this->light_function_ = *index;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
if (this->out_pin_level_select_ != nullptr) {
|
if (this->out_pin_level_select_ != nullptr && this->out_pin_level_select_->has_state()) {
|
||||||
if (const auto index = this->out_pin_level_select_->active_index()) {
|
this->out_pin_level_ = find_uint8(OUT_PIN_LEVELS_BY_STR, this->out_pin_level_select_->current_option().c_str());
|
||||||
this->out_pin_level_ = *index;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
this->set_config_mode_(true);
|
this->set_config_mode_(true);
|
||||||
|
|||||||
@@ -37,25 +37,6 @@ using namespace ld24xx;
|
|||||||
static constexpr uint8_t MAX_LINE_LENGTH = 50;
|
static constexpr uint8_t MAX_LINE_LENGTH = 50;
|
||||||
static constexpr uint8_t TOTAL_GATES = 9; // Total number of gates supported by the LD2410
|
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 {
|
class LD2410Component final : public Component, public uart::UARTDevice {
|
||||||
#ifdef USE_BINARY_SENSOR
|
#ifdef USE_BINARY_SENSOR
|
||||||
SUB_BINARY_SENSOR(out_pin_presence_status)
|
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_still_threshold_number(uint8_t gate, number::Number *n);
|
||||||
void set_gate_move_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_max_distances_timeout();
|
||||||
void set_gate_threshold(number::Number *n);
|
void set_gate_threshold(uint8_t gate);
|
||||||
#endif
|
#endif
|
||||||
#ifdef USE_SENSOR
|
#ifdef USE_SENSOR
|
||||||
void set_gate_move_sensor(uint8_t gate, sensor::Sensor *s);
|
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 restart_and_read_all_info();
|
||||||
void set_bluetooth(bool enable);
|
void set_bluetooth(bool enable);
|
||||||
void set_distance_resolution(const char *state);
|
void set_distance_resolution(const char *state);
|
||||||
void set_distance_resolution(DistanceResolution resolution);
|
|
||||||
void set_baud_rate(const char *state);
|
void set_baud_rate(const char *state);
|
||||||
void set_baud_rate(BaudRate rate);
|
|
||||||
void factory_reset();
|
void factory_reset();
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
from functools import partial
|
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import number
|
from esphome.components import number
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
@@ -20,7 +18,11 @@ from esphome.const import (
|
|||||||
)
|
)
|
||||||
from esphome.types import ConfigType
|
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_MOVE_DISTANCE_GATE = "max_move_distance_gate"
|
||||||
CONF_MAX_STILL_DISTANCE_GATE = "max_still_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_ID): cv.declare_id(cg.EntityBase),
|
||||||
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
||||||
cv.Inclusive(CONF_TIMEOUT, TIMEOUT_GROUP): number.number_schema(
|
cv.Inclusive(CONF_TIMEOUT, TIMEOUT_GROUP): number.number_schema(
|
||||||
number.ApplyNumber,
|
MaxDistanceTimeoutNumber,
|
||||||
unit_of_measurement=UNIT_SECOND,
|
unit_of_measurement=UNIT_SECOND,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_TIMELAPSE,
|
icon=ICON_TIMELAPSE,
|
||||||
),
|
),
|
||||||
cv.Inclusive(CONF_MAX_MOVE_DISTANCE_GATE, TIMEOUT_GROUP): number.number_schema(
|
cv.Inclusive(CONF_MAX_MOVE_DISTANCE_GATE, TIMEOUT_GROUP): number.number_schema(
|
||||||
number.ApplyNumber,
|
MaxDistanceTimeoutNumber,
|
||||||
device_class=DEVICE_CLASS_DISTANCE,
|
device_class=DEVICE_CLASS_DISTANCE,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_MOTION_SENSOR,
|
icon=ICON_MOTION_SENSOR,
|
||||||
),
|
),
|
||||||
cv.Inclusive(CONF_MAX_STILL_DISTANCE_GATE, TIMEOUT_GROUP): number.number_schema(
|
cv.Inclusive(CONF_MAX_STILL_DISTANCE_GATE, TIMEOUT_GROUP): number.number_schema(
|
||||||
number.ApplyNumber,
|
MaxDistanceTimeoutNumber,
|
||||||
device_class=DEVICE_CLASS_DISTANCE,
|
device_class=DEVICE_CLASS_DISTANCE,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_MOTION_SENSOR,
|
icon=ICON_MOTION_SENSOR,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_LIGHT_THRESHOLD): number.number_schema(
|
cv.Optional(CONF_LIGHT_THRESHOLD): number.number_schema(
|
||||||
number.ApplyNumber,
|
LightThresholdNumber,
|
||||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_LIGHTBULB,
|
icon=ICON_LIGHTBULB,
|
||||||
@@ -64,14 +66,14 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
|||||||
cv.Optional(f"g{x}"): cv.Schema(
|
cv.Optional(f"g{x}"): cv.Schema(
|
||||||
{
|
{
|
||||||
cv.Required(CONF_MOVE_THRESHOLD): number.number_schema(
|
cv.Required(CONF_MOVE_THRESHOLD): number.number_schema(
|
||||||
number.ApplyNumber,
|
GateThresholdNumber,
|
||||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||||
unit_of_measurement=UNIT_PERCENT,
|
unit_of_measurement=UNIT_PERCENT,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_MOTION_SENSOR,
|
icon=ICON_MOTION_SENSOR,
|
||||||
),
|
),
|
||||||
cv.Required(CONF_STILL_THRESHOLD): number.number_schema(
|
cv.Required(CONF_STILL_THRESHOLD): number.number_schema(
|
||||||
number.ApplyNumber,
|
GateThresholdNumber,
|
||||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||||
unit_of_measurement=UNIT_PERCENT,
|
unit_of_measurement=UNIT_PERCENT,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
@@ -87,14 +89,8 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
|||||||
async def to_code(config: ConfigType) -> None:
|
async def to_code(config: ConfigType) -> None:
|
||||||
hub = await cg.get_variable(config[CONF_LD2410_ID])
|
hub = await cg.get_variable(config[CONF_LD2410_ID])
|
||||||
numbers = number.sub_numbers(config, parent=hub)
|
numbers = number.sub_numbers(config, parent=hub)
|
||||||
timeouts = {"apply": "set_max_distances_timeout()", "publish": "before"}
|
|
||||||
await numbers(
|
await numbers(
|
||||||
CONF_TIMEOUT,
|
CONF_TIMEOUT, hub.set_timeout_number, min_value=0, max_value=65535, step=1
|
||||||
hub.set_timeout_number,
|
|
||||||
min_value=0,
|
|
||||||
max_value=65535,
|
|
||||||
step=1,
|
|
||||||
**timeouts,
|
|
||||||
)
|
)
|
||||||
await numbers(
|
await numbers(
|
||||||
CONF_MAX_MOVE_DISTANCE_GATE,
|
CONF_MAX_MOVE_DISTANCE_GATE,
|
||||||
@@ -102,7 +98,6 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
min_value=2,
|
min_value=2,
|
||||||
max_value=8,
|
max_value=8,
|
||||||
step=1,
|
step=1,
|
||||||
**timeouts,
|
|
||||||
)
|
)
|
||||||
await numbers(
|
await numbers(
|
||||||
CONF_MAX_STILL_DISTANCE_GATE,
|
CONF_MAX_STILL_DISTANCE_GATE,
|
||||||
@@ -110,7 +105,6 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
min_value=2,
|
min_value=2,
|
||||||
max_value=8,
|
max_value=8,
|
||||||
step=1,
|
step=1,
|
||||||
**timeouts,
|
|
||||||
)
|
)
|
||||||
await numbers(
|
await numbers(
|
||||||
CONF_LIGHT_THRESHOLD,
|
CONF_LIGHT_THRESHOLD,
|
||||||
@@ -118,25 +112,21 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
min_value=0,
|
min_value=0,
|
||||||
max_value=255,
|
max_value=255,
|
||||||
step=1,
|
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):
|
for x in range(9):
|
||||||
if gate_conf := config.get(f"g{x}"):
|
if gate_conf := config.get(f"g{x}"):
|
||||||
await numbers.create(
|
move_config = gate_conf[CONF_MOVE_THRESHOLD]
|
||||||
gate_conf[CONF_MOVE_THRESHOLD],
|
n = cg.new_Pvariable(move_config[CONF_ID], x)
|
||||||
partial(hub.set_gate_move_threshold_number, x),
|
await number.register_number(
|
||||||
**threshold,
|
n, move_config, min_value=0, max_value=100, step=1
|
||||||
)
|
)
|
||||||
await numbers.create(
|
await cg.register_parented(n, hub)
|
||||||
gate_conf[CONF_STILL_THRESHOLD],
|
cg.add(hub.set_gate_move_threshold_number(x, n))
|
||||||
partial(hub.set_gate_still_threshold_number, x),
|
|
||||||
**threshold,
|
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:
|
async def to_code(config: ConfigType) -> None:
|
||||||
hub = await cg.get_variable(config[CONF_LD2410_ID])
|
hub = await cg.get_variable(config[CONF_LD2410_ID])
|
||||||
selects = select.sub_selects(config, parent=hub)
|
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(
|
await selects(
|
||||||
CONF_DISTANCE_RESOLUTION,
|
CONF_DISTANCE_RESOLUTION,
|
||||||
hub.set_distance_resolution_select,
|
hub.set_distance_resolution_select,
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ namespace esphome::ld2410 {
|
|||||||
|
|
||||||
void BaudRateSelect::control(size_t index) {
|
void BaudRateSelect::control(size_t index) {
|
||||||
this->publish_state(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
|
} // namespace esphome::ld2410
|
||||||
@@ -4,8 +4,7 @@ namespace esphome::ld2410 {
|
|||||||
|
|
||||||
void DistanceResolutionSelect::control(size_t index) {
|
void DistanceResolutionSelect::control(size_t index) {
|
||||||
this->publish_state(index);
|
this->publish_state(index);
|
||||||
this->parent_->set_distance_resolution(
|
this->parent_->set_distance_resolution(this->option_at(index));
|
||||||
static_cast<DistanceResolution>(progmem_read_byte(&DISTANCE_RESOLUTIONS[index])));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
} // namespace esphome::ld2410
|
} // namespace esphome::ld2410
|
||||||
@@ -11,7 +11,10 @@ from esphome.const import (
|
|||||||
)
|
)
|
||||||
from esphome.types import ConfigType
|
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"
|
CONF_ENGINEERING_MODE = "engineering_mode"
|
||||||
|
|
||||||
@@ -19,13 +22,13 @@ CONFIG_SCHEMA = {
|
|||||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||||
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
cv.GenerateID(CONF_LD2410_ID): cv.use_id(LD2410Component),
|
||||||
cv.Optional(CONF_ENGINEERING_MODE): switch.switch_schema(
|
cv.Optional(CONF_ENGINEERING_MODE): switch.switch_schema(
|
||||||
switch.ApplySwitch,
|
EngineeringModeSwitch,
|
||||||
device_class=DEVICE_CLASS_SWITCH,
|
device_class=DEVICE_CLASS_SWITCH,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_PULSE,
|
icon=ICON_PULSE,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_BLUETOOTH): switch.switch_schema(
|
cv.Optional(CONF_BLUETOOTH): switch.switch_schema(
|
||||||
switch.ApplySwitch,
|
BluetoothSwitch,
|
||||||
device_class=DEVICE_CLASS_SWITCH,
|
device_class=DEVICE_CLASS_SWITCH,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_BLUETOOTH,
|
icon=ICON_BLUETOOTH,
|
||||||
@@ -36,15 +39,5 @@ CONFIG_SCHEMA = {
|
|||||||
async def to_code(config: ConfigType) -> None:
|
async def to_code(config: ConfigType) -> None:
|
||||||
hub = await cg.get_variable(config[CONF_LD2410_ID])
|
hub = await cg.get_variable(config[CONF_LD2410_ID])
|
||||||
switches = switch.sub_switches(config, parent=hub)
|
switches = switch.sub_switches(config, parent=hub)
|
||||||
await switches(
|
await switches(CONF_ENGINEERING_MODE, hub.set_engineering_mode_switch)
|
||||||
CONF_ENGINEERING_MODE,
|
await switches(CONF_BLUETOOTH, hub.set_bluetooth_switch)
|
||||||
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",
|
|
||||||
)
|
|
||||||
@@ -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
|
||||||
@@ -1,5 +1,6 @@
|
|||||||
#include "ld2410s.h"
|
#include "ld2410s.h"
|
||||||
#include "ld2410s_number.h"
|
#include "ld2410s_number.h"
|
||||||
|
#include <algorithm>
|
||||||
#include <cstring>
|
#include <cstring>
|
||||||
#include "esphome/core/application.h"
|
#include "esphome/core/application.h"
|
||||||
#include "esphome/core/hal.h"
|
#include "esphome/core/hal.h"
|
||||||
@@ -94,8 +95,19 @@ void LD2410SComponent::dump_config() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LD2410SComponent::loop() {
|
void LD2410SComponent::loop() {
|
||||||
if (!this->read_available<16>([this](uint8_t b) { this->receive_byte_(b); })) {
|
uint8_t chunk[16];
|
||||||
ESP_LOGV(TAG, "UART read failed");
|
// 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();
|
const uint32_t now = App.get_loop_component_start_time();
|
||||||
if (this->sequence_ == nullptr) {
|
if (this->sequence_ == nullptr) {
|
||||||
|
|||||||
@@ -11,20 +11,26 @@ from esphome.const import (
|
|||||||
)
|
)
|
||||||
from esphome.types import ConfigType
|
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"
|
CONF_START_DYNAMIC_BACKGROUND_CORRECTION = "start_dynamic_background_correction"
|
||||||
|
|
||||||
CONFIG_SCHEMA = {
|
CONFIG_SCHEMA = {
|
||||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||||
cv.Optional(CONF_FACTORY_RESET): button.factory_reset_button_schema(
|
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_QUERY_PARAMS): button.query_button_schema(QueryButton),
|
||||||
cv.Optional(CONF_RESTART): button.restart_button_schema(button.ApplyButton),
|
cv.Optional(CONF_RESTART): button.restart_button_schema(RestartButton),
|
||||||
cv.Optional(CONF_START_DYNAMIC_BACKGROUND_CORRECTION): button.button_schema(
|
cv.Optional(CONF_START_DYNAMIC_BACKGROUND_CORRECTION): button.button_schema(
|
||||||
button.ApplyButton,
|
StartDynamicBackgroundCorrectionButton,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_PULSE,
|
icon=ICON_PULSE,
|
||||||
),
|
),
|
||||||
@@ -34,15 +40,10 @@ CONFIG_SCHEMA = {
|
|||||||
async def to_code(config: ConfigType) -> None:
|
async def to_code(config: ConfigType) -> None:
|
||||||
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
||||||
buttons = button.sub_buttons(config, parent=hub)
|
buttons = button.sub_buttons(config, parent=hub)
|
||||||
await buttons(
|
await buttons(CONF_FACTORY_RESET, hub.set_factory_reset_button)
|
||||||
CONF_FACTORY_RESET, hub.set_factory_reset_button, apply="factory_reset()"
|
await buttons(CONF_QUERY_PARAMS, hub.set_query_button)
|
||||||
)
|
await buttons(CONF_RESTART, hub.set_restart_button)
|
||||||
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(
|
await buttons(
|
||||||
CONF_START_DYNAMIC_BACKGROUND_CORRECTION,
|
CONF_START_DYNAMIC_BACKGROUND_CORRECTION,
|
||||||
hub.set_start_dynamic_background_correction_button,
|
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
|
||||||
@@ -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 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
|
// Commands
|
||||||
static constexpr uint8_t CMD_ENABLE_CONF = 0xFF;
|
static constexpr uint8_t CMD_ENABLE_CONF = 0xFF;
|
||||||
static constexpr uint8_t CMD_DISABLE_CONF = 0xFE;
|
static constexpr uint8_t CMD_DISABLE_CONF = 0xFE;
|
||||||
@@ -236,7 +237,20 @@ void LD2412Component::restart_and_read_all_info() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LD2412Component::loop() {
|
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) {
|
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
|
// 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_ID = 0;
|
||||||
|
static constexpr uint32_t GATE_QUERY_TIMEOUT_MS = 2000;
|
||||||
#endif
|
#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_() {
|
void LD2412Component::handle_ack_data_() {
|
||||||
ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", this->buffer_data_[COMMAND]);
|
ESP_LOGV(TAG, "Handling ACK DATA for COMMAND %02X", this->buffer_data_[COMMAND]);
|
||||||
@@ -554,44 +566,38 @@ void LD2412Component::handle_ack_data_() {
|
|||||||
break;
|
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) {
|
if (this->buffer_pos_ < 15 + HEADER_FOOTER_SIZE) {
|
||||||
break; // too short to hold the configuration
|
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);
|
Moving distance range: 9th byte
|
||||||
this->basic_conf_read_ = true;
|
Still distance range: 10th byte
|
||||||
// Min gate: 9th byte, max gate: 10th byte, timeout: 11~12th bytes, output pin level: 13th byte
|
*/
|
||||||
this->min_gate_ = this->buffer_data_[10];
|
set_number_value(this->min_distance_gate_number_, this->buffer_data_[10]);
|
||||||
this->max_gate_ = this->buffer_data_[11];
|
set_number_value(this->max_distance_gate_number_, this->buffer_data_[11] - 1);
|
||||||
this->timeout_ = encode_uint16(this->buffer_data_[13], this->buffer_data_[12]);
|
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];
|
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
|
#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_)) {
|
if (const auto index = ld24xx::find_index(OUT_PIN_LEVELS, this->out_pin_level_)) {
|
||||||
this->out_pin_level_select_->publish_state(*index);
|
this->out_pin_level_select_->publish_state(*index);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
if (pending) {
|
#endif
|
||||||
this->set_basic_config();
|
} break;
|
||||||
}
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
default:
|
default:
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
@@ -697,9 +703,8 @@ void LD2412Component::set_engineering_mode(bool enable) {
|
|||||||
void LD2412Component::factory_reset() {
|
void LD2412Component::factory_reset() {
|
||||||
this->set_config_mode_(true);
|
this->set_config_mode_(true);
|
||||||
this->send_command_(CMD_FACTORY_RESET, nullptr, 0);
|
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
|
#ifdef USE_NUMBER
|
||||||
|
// Reset discards the thresholds; the next write must wait for the re-read
|
||||||
this->gate_move_thresholds_.read = false;
|
this->gate_move_thresholds_.read = false;
|
||||||
this->gate_still_thresholds_.read = false;
|
this->gate_still_thresholds_.read = false;
|
||||||
#endif
|
#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::query_light_control_() { this->send_command_(CMD_QUERY_LIGHT_CONTROL, nullptr, 0); }
|
||||||
|
|
||||||
void LD2412Component::set_basic_config() {
|
void LD2412Component::set_basic_config() {
|
||||||
// Entities that are not configured keep what the module last reported
|
uint8_t min_gate = 1;
|
||||||
uint8_t min_gate = this->min_gate_;
|
uint8_t max_gate = TOTAL_GATES;
|
||||||
uint8_t max_gate = this->max_gate_;
|
uint16_t timeout = DEFAULT_PRESENCE_TIMEOUT;
|
||||||
uint16_t timeout = this->timeout_;
|
uint8_t out_pin_level = 0x01;
|
||||||
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
|
|
||||||
|
|
||||||
#ifdef USE_NUMBER
|
#ifdef USE_NUMBER
|
||||||
if (this->min_distance_gate_number_ != nullptr) {
|
if (this->min_distance_gate_number_ != nullptr) {
|
||||||
@@ -762,19 +755,8 @@ void LD2412Component::set_basic_config() {
|
|||||||
if (!index)
|
if (!index)
|
||||||
return;
|
return;
|
||||||
out_pin_level = progmem_read_byte(&OUT_PIN_LEVELS[*index]);
|
out_pin_level = progmem_read_byte(&OUT_PIN_LEVELS[*index]);
|
||||||
} else {
|
|
||||||
from_module = true;
|
|
||||||
}
|
}
|
||||||
#endif
|
#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] = {
|
uint8_t value[5] = {
|
||||||
lowbyte(min_gate), lowbyte(max_gate), lowbyte(timeout), highbyte(timeout), out_pin_level,
|
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);
|
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
|
#ifdef USE_NUMBER
|
||||||
void LD2412Component::send_gate_thresholds_(bool motion) {
|
void LD2412Component::send_gate_thresholds_(bool motion) {
|
||||||
GateThresholds &group = this->gate_thresholds_(motion);
|
GateThresholds &group = this->gate_thresholds_(motion);
|
||||||
@@ -821,9 +783,9 @@ void LD2412Component::send_gate_thresholds_(bool motion) {
|
|||||||
}
|
}
|
||||||
if (configured < TOTAL_GATES && !group.read) {
|
if (configured < TOTAL_GATES && !group.read) {
|
||||||
// Unread gates would go out as zero, which is maximum sensitivity
|
// 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->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); });
|
[this, motion]() { this->gate_query_timeout_(motion); });
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -831,7 +793,7 @@ void LD2412Component::send_gate_thresholds_(bool motion) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void LD2412Component::gate_query_timeout_(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);
|
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
|
// 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) {
|
for (number::Number *n : this->gate_thresholds_(motion).numbers) {
|
||||||
|
|||||||
@@ -35,7 +35,6 @@ using namespace ld24xx;
|
|||||||
|
|
||||||
static constexpr uint8_t MAX_LINE_LENGTH = 54; // Max characters for serial buffer
|
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 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 {
|
enum BaudRate : uint8_t {
|
||||||
BAUD_RATE_9600 = 1,
|
BAUD_RATE_9600 = 1,
|
||||||
@@ -137,7 +136,6 @@ class LD2412Component final : public Component, public uart::UARTDevice {
|
|||||||
void handle_periodic_data_();
|
void handle_periodic_data_();
|
||||||
void handle_ack_data_();
|
void handle_ack_data_();
|
||||||
bool handle_frame_(uint8_t frame_len);
|
bool handle_frame_(uint8_t frame_len);
|
||||||
void basic_conf_timeout_();
|
|
||||||
void readline_(int readch);
|
void readline_(int readch);
|
||||||
void query_parameters_();
|
void query_parameters_();
|
||||||
void get_version_();
|
void get_version_();
|
||||||
@@ -162,12 +160,7 @@ class LD2412Component final : public Component, public uart::UARTDevice {
|
|||||||
|
|
||||||
uint8_t light_function_ = 0;
|
uint8_t light_function_ = 0;
|
||||||
uint8_t light_threshold_ = 0;
|
uint8_t light_threshold_ = 0;
|
||||||
// Basic configuration as last reported by the module; resent for entities that are not configured
|
uint8_t out_pin_level_ = 0;
|
||||||
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 buffer_pos_ = 0; // where to resume processing/populating buffer
|
uint8_t buffer_pos_ = 0; // where to resume processing/populating buffer
|
||||||
uint8_t buffer_data_[MAX_LINE_LENGTH];
|
uint8_t buffer_data_[MAX_LINE_LENGTH];
|
||||||
uint8_t mac_address_[MAC_ADDRESS_SIZE] = {0, 0, 0, 0, 0, 0};
|
uint8_t mac_address_[MAC_ADDRESS_SIZE] = {0, 0, 0, 0, 0, 0};
|
||||||
|
|||||||
@@ -1,5 +1,3 @@
|
|||||||
from functools import partial
|
|
||||||
|
|
||||||
import esphome.codegen as cg
|
import esphome.codegen as cg
|
||||||
from esphome.components import number
|
from esphome.components import number
|
||||||
import esphome.config_validation as cv
|
import esphome.config_validation as cv
|
||||||
@@ -20,7 +18,11 @@ from esphome.const import (
|
|||||||
)
|
)
|
||||||
from esphome.types import ConfigType
|
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_LIGHT_THRESHOLD = "light_threshold"
|
||||||
CONF_MAX_DISTANCE_GATE = "max_distance_gate"
|
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_ID): cv.declare_id(cg.EntityBase),
|
||||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||||
cv.Optional(CONF_LIGHT_THRESHOLD): number.number_schema(
|
cv.Optional(CONF_LIGHT_THRESHOLD): number.number_schema(
|
||||||
number.ApplyNumber,
|
LightThresholdNumber,
|
||||||
device_class=DEVICE_CLASS_ILLUMINANCE,
|
device_class=DEVICE_CLASS_ILLUMINANCE,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_LIGHTBULB,
|
icon=ICON_LIGHTBULB,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_MAX_DISTANCE_GATE): number.number_schema(
|
cv.Optional(CONF_MAX_DISTANCE_GATE): number.number_schema(
|
||||||
number.ApplyNumber,
|
MaxDistanceTimeoutNumber,
|
||||||
device_class=DEVICE_CLASS_DISTANCE,
|
device_class=DEVICE_CLASS_DISTANCE,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_MOTION_SENSOR,
|
icon=ICON_MOTION_SENSOR,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_MIN_DISTANCE_GATE): number.number_schema(
|
cv.Optional(CONF_MIN_DISTANCE_GATE): number.number_schema(
|
||||||
number.ApplyNumber,
|
MaxDistanceTimeoutNumber,
|
||||||
device_class=DEVICE_CLASS_DISTANCE,
|
device_class=DEVICE_CLASS_DISTANCE,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_MOTION_SENSOR,
|
icon=ICON_MOTION_SENSOR,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_TIMEOUT): number.number_schema(
|
cv.Optional(CONF_TIMEOUT): number.number_schema(
|
||||||
number.ApplyNumber,
|
MaxDistanceTimeoutNumber,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_TIMELAPSE,
|
icon=ICON_TIMELAPSE,
|
||||||
unit_of_measurement=UNIT_SECOND,
|
unit_of_measurement=UNIT_SECOND,
|
||||||
@@ -64,14 +66,14 @@ CONFIG_SCHEMA = CONFIG_SCHEMA.extend(
|
|||||||
cv.Optional(f"gate_{x}"): (
|
cv.Optional(f"gate_{x}"): (
|
||||||
{
|
{
|
||||||
cv.Required(CONF_MOVE_THRESHOLD): number.number_schema(
|
cv.Required(CONF_MOVE_THRESHOLD): number.number_schema(
|
||||||
number.ApplyNumber,
|
GateThresholdNumber,
|
||||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_MOTION_SENSOR,
|
icon=ICON_MOTION_SENSOR,
|
||||||
unit_of_measurement=UNIT_PERCENT,
|
unit_of_measurement=UNIT_PERCENT,
|
||||||
),
|
),
|
||||||
cv.Required(CONF_STILL_THRESHOLD): number.number_schema(
|
cv.Required(CONF_STILL_THRESHOLD): number.number_schema(
|
||||||
number.ApplyNumber,
|
GateThresholdNumber,
|
||||||
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
device_class=DEVICE_CLASS_SIGNAL_STRENGTH,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_MOTION_SENSOR,
|
icon=ICON_MOTION_SENSOR,
|
||||||
@@ -93,17 +95,13 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
min_value=0,
|
min_value=0,
|
||||||
max_value=255,
|
max_value=255,
|
||||||
step=1,
|
step=1,
|
||||||
apply="set_light_out_control()",
|
|
||||||
publish="before",
|
|
||||||
)
|
)
|
||||||
basic = {"apply": "set_basic_config()", "publish": "before"}
|
|
||||||
await numbers(
|
await numbers(
|
||||||
CONF_MAX_DISTANCE_GATE,
|
CONF_MAX_DISTANCE_GATE,
|
||||||
hub.set_max_distance_gate_number,
|
hub.set_max_distance_gate_number,
|
||||||
min_value=2,
|
min_value=2,
|
||||||
max_value=13,
|
max_value=13,
|
||||||
step=1,
|
step=1,
|
||||||
**basic,
|
|
||||||
)
|
)
|
||||||
await numbers(
|
await numbers(
|
||||||
CONF_MIN_DISTANCE_GATE,
|
CONF_MIN_DISTANCE_GATE,
|
||||||
@@ -111,32 +109,23 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
min_value=1,
|
min_value=1,
|
||||||
max_value=12,
|
max_value=12,
|
||||||
step=1,
|
step=1,
|
||||||
**basic,
|
|
||||||
)
|
)
|
||||||
threshold = {
|
|
||||||
"min_value": 0,
|
|
||||||
"max_value": 100,
|
|
||||||
"step": 1,
|
|
||||||
"apply": "set_gate_threshold(entity)",
|
|
||||||
"publish": "before",
|
|
||||||
}
|
|
||||||
for x in range(14):
|
for x in range(14):
|
||||||
if gate_conf := config.get(f"gate_{x}"):
|
if gate_conf := config.get(f"gate_{x}"):
|
||||||
await numbers.create(
|
move_config = gate_conf[CONF_MOVE_THRESHOLD]
|
||||||
gate_conf[CONF_MOVE_THRESHOLD],
|
n = await number.new_number(move_config, min_value=0, max_value=100, step=1)
|
||||||
partial(hub.set_gate_move_threshold_number, x),
|
await cg.register_parented(n, hub)
|
||||||
**threshold,
|
cg.add(hub.set_gate_move_threshold_number(x, n))
|
||||||
)
|
still_config = gate_conf[CONF_STILL_THRESHOLD]
|
||||||
await numbers.create(
|
n = await number.new_number(
|
||||||
gate_conf[CONF_STILL_THRESHOLD],
|
still_config, min_value=0, max_value=100, step=1
|
||||||
partial(hub.set_gate_still_threshold_number, x),
|
|
||||||
**threshold,
|
|
||||||
)
|
)
|
||||||
|
await cg.register_parented(n, hub)
|
||||||
|
cg.add(hub.set_gate_still_threshold_number(x, n))
|
||||||
await numbers(
|
await numbers(
|
||||||
CONF_TIMEOUT,
|
CONF_TIMEOUT,
|
||||||
hub.set_timeout_number,
|
hub.set_timeout_number,
|
||||||
min_value=0,
|
min_value=0,
|
||||||
max_value=900,
|
max_value=900,
|
||||||
step=1,
|
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
|
||||||
@@ -42,7 +42,7 @@ CONFIG_SCHEMA = {
|
|||||||
icon=ICON_LIGHTBULB,
|
icon=ICON_LIGHTBULB,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_OUT_PIN_LEVEL): select.select_schema(
|
cv.Optional(CONF_OUT_PIN_LEVEL): select.select_schema(
|
||||||
select.ApplySelect,
|
LightOutControlSelect,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_SCALE,
|
icon=ICON_SCALE,
|
||||||
),
|
),
|
||||||
@@ -77,11 +77,8 @@ async def to_code(config: ConfigType) -> None:
|
|||||||
hub.set_light_function_select,
|
hub.set_light_function_select,
|
||||||
options=["off", "below", "above"],
|
options=["off", "below", "above"],
|
||||||
)
|
)
|
||||||
# The pin level travels in the basic configuration command, not the light control one
|
|
||||||
await selects(
|
await selects(
|
||||||
CONF_OUT_PIN_LEVEL,
|
CONF_OUT_PIN_LEVEL,
|
||||||
hub.set_out_pin_level_select,
|
hub.set_out_pin_level_select,
|
||||||
options=["low", "high"],
|
options=["low", "high"],
|
||||||
apply="set_basic_config()",
|
|
||||||
publish="before",
|
|
||||||
)
|
)
|
||||||
@@ -11,7 +11,10 @@ from esphome.const import (
|
|||||||
)
|
)
|
||||||
from esphome.types import ConfigType
|
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"
|
CONF_ENGINEERING_MODE = "engineering_mode"
|
||||||
|
|
||||||
@@ -19,13 +22,13 @@ CONFIG_SCHEMA = {
|
|||||||
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
cv.GenerateID(CONF_ID): cv.declare_id(cg.EntityBase),
|
||||||
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
cv.GenerateID(CONF_LD2412_ID): cv.use_id(LD2412Component),
|
||||||
cv.Optional(CONF_BLUETOOTH): switch.switch_schema(
|
cv.Optional(CONF_BLUETOOTH): switch.switch_schema(
|
||||||
switch.ApplySwitch,
|
BluetoothSwitch,
|
||||||
device_class=DEVICE_CLASS_SWITCH,
|
device_class=DEVICE_CLASS_SWITCH,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_BLUETOOTH,
|
icon=ICON_BLUETOOTH,
|
||||||
),
|
),
|
||||||
cv.Optional(CONF_ENGINEERING_MODE): switch.switch_schema(
|
cv.Optional(CONF_ENGINEERING_MODE): switch.switch_schema(
|
||||||
switch.ApplySwitch,
|
EngineeringModeSwitch,
|
||||||
device_class=DEVICE_CLASS_SWITCH,
|
device_class=DEVICE_CLASS_SWITCH,
|
||||||
entity_category=ENTITY_CATEGORY_CONFIG,
|
entity_category=ENTITY_CATEGORY_CONFIG,
|
||||||
icon=ICON_PULSE,
|
icon=ICON_PULSE,
|
||||||
@@ -36,15 +39,5 @@ CONFIG_SCHEMA = {
|
|||||||
async def to_code(config: ConfigType) -> None:
|
async def to_code(config: ConfigType) -> None:
|
||||||
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
hub = await cg.get_variable(config[CONF_LD2412_ID])
|
||||||
switches = switch.sub_switches(config, parent=hub)
|
switches = switch.sub_switches(config, parent=hub)
|
||||||
await switches(
|
await switches(CONF_BLUETOOTH, hub.set_bluetooth_switch)
|
||||||
CONF_BLUETOOTH,
|
await switches(CONF_ENGINEERING_MODE, hub.set_engineering_mode_switch)
|
||||||
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",
|
|
||||||
)
|
|
||||||
@@ -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
|
||||||
Loaded 100 of 237 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user