Merge branch 'dev' into binary_sensor_multiclick_static_array

This commit is contained in:
J. Nick Koston
2026-03-27 13:42:31 -10:00
committed by GitHub
156 changed files with 2526 additions and 1923 deletions
+1 -1
View File
@@ -154,7 +154,7 @@ jobs:
. venv/bin/activate
pytest -vv --cov-report=xml --tb=native -n auto tests --ignore=tests/integration/
- name: Upload coverage to Codecov
uses: codecov/codecov-action@1af58845a975a7985b0beb0cbe6fbbb71a41dbad # v5.5.3
uses: codecov/codecov-action@57e3a136b779b570ffcdbf80b3bdc90e7fab3de2 # v6.0.0
with:
token: ${{ secrets.CODECOV_TOKEN }}
- name: Save Python virtual environment cache
+2 -2
View File
@@ -58,7 +58,7 @@ jobs:
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
uses: github/codeql-action/init@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
with:
languages: ${{ matrix.language }}
build-mode: ${{ matrix.build-mode }}
@@ -86,6 +86,6 @@ jobs:
exit 1
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@38697555549f1db7851b81482ff19f1fa5c4fedc # v4.34.1
uses: github/codeql-action/analyze@c10b8064de6f491fea524254123dbe5e09572f13 # v4.35.1
with:
category: "/language:${{matrix.language}}"
+1 -1
View File
@@ -11,7 +11,7 @@ ci:
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
# Ruff version.
rev: v0.15.6
rev: v0.15.8
hooks:
# Run the linter.
- id: ruff
+1
View File
@@ -92,6 +92,7 @@ esphome/components/bmp3xx_i2c/* @latonita
esphome/components/bmp3xx_spi/* @latonita
esphome/components/bmp581_base/* @danielkent-net @kahrendt
esphome/components/bmp581_i2c/* @danielkent-net @kahrendt
esphome/components/bmp581_spi/* @danielkent-net @kahrendt
esphome/components/bp1658cj/* @Cossid
esphome/components/bp5758d/* @Cossid
esphome/components/bthome_mithermometer/* @nagyrobi
+44
View File
@@ -137,6 +137,9 @@ UpdateComponentAction = cg.esphome_ns.class_("UpdateComponentAction", Action)
SuspendComponentAction = cg.esphome_ns.class_("SuspendComponentAction", Action)
ResumeComponentAction = cg.esphome_ns.class_("ResumeComponentAction", Action)
Automation = cg.esphome_ns.class_("Automation")
TriggerForwarder = cg.esphome_ns.class_("TriggerForwarder")
TriggerOnTrueForwarder = cg.esphome_ns.class_("TriggerOnTrueForwarder")
TriggerOnFalseForwarder = cg.esphome_ns.class_("TriggerOnFalseForwarder")
LambdaCondition = cg.esphome_ns.class_("LambdaCondition", Condition)
StatelessLambdaCondition = cg.esphome_ns.class_("StatelessLambdaCondition", Condition)
@@ -661,3 +664,44 @@ async def build_automation(
actions = await build_action_list(config[CONF_THEN], templ, args)
cg.add(obj.add_actions(actions))
return obj
async def build_callback_automation(
parent: MockObj,
callback_method: str,
args: TemplateArgsType,
config: ConfigType,
forwarder: MockObj | MockObjClass | None = None,
) -> None:
"""Build an Automation and register it as a callback on the parent.
Eliminates the need for a Trigger wrapper object by registering the
automation's trigger() directly as a callback on the parent component.
Uses template forwarder structs so the compiler deduplicates the operator()
body across all call sites with the same signature. The forwarder must be
pointer-sized (single Automation* field) to fit inline in Callback::ctx_
and avoid heap allocation.
:param parent: The component object (e.g., button, sensor).
:param callback_method: Name of the callback method (e.g., "add_on_press_callback").
:param args: Automation template args as list of (type, name) tuples.
:param config: The automation config dict.
:param forwarder: Optional forwarder type to use instead of the default
TriggerForwarder<Ts...>. Pass any struct type whose aggregate init takes
a single Automation pointer (e.g., TriggerOnTrueForwarder).
"""
arg_types = [arg[0] for arg in args]
templ = cg.TemplateArguments(*arg_types)
obj = cg.new_Pvariable(config[CONF_AUTOMATION_ID], templ)
actions = await build_action_list(config[CONF_THEN], templ, args)
cg.add(obj.add_actions(actions))
# Use template forwarder structs for deduplication. The compiler generates
# one operator() per forwarder type; different automation pointers are just
# data in the struct.
if forwarder is None:
forwarder = TriggerForwarder.template(templ)
# RawExpression for aggregate init — both forwarder and obj are codegen
# MockObjs (not user input), and there's no Expression type for positional
# aggregate initialization (StructInitializer uses named fields).
cg.add(getattr(parent, callback_method)(cg.RawExpression(f"{forwarder}{{{obj}}}")))
@@ -10,7 +10,6 @@ from esphome.const import (
CONF_ID,
CONF_MQTT_ID,
CONF_ON_STATE,
CONF_TRIGGER_ID,
CONF_WEB_SERVER,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
@@ -34,39 +33,9 @@ CONF_ON_READY = "on_ready"
alarm_control_panel_ns = cg.esphome_ns.namespace("alarm_control_panel")
AlarmControlPanel = alarm_control_panel_ns.class_("AlarmControlPanel", cg.EntityBase)
StateTrigger = alarm_control_panel_ns.class_(
"StateTrigger", automation.Trigger.template()
)
TriggeredTrigger = alarm_control_panel_ns.class_(
"TriggeredTrigger", automation.Trigger.template()
)
ClearedTrigger = alarm_control_panel_ns.class_(
"ClearedTrigger", automation.Trigger.template()
)
ArmingTrigger = alarm_control_panel_ns.class_(
"ArmingTrigger", automation.Trigger.template()
)
PendingTrigger = alarm_control_panel_ns.class_(
"PendingTrigger", automation.Trigger.template()
)
ArmedHomeTrigger = alarm_control_panel_ns.class_(
"ArmedHomeTrigger", automation.Trigger.template()
)
ArmedNightTrigger = alarm_control_panel_ns.class_(
"ArmedNightTrigger", automation.Trigger.template()
)
ArmedAwayTrigger = alarm_control_panel_ns.class_(
"ArmedAwayTrigger", automation.Trigger.template()
)
DisarmedTrigger = alarm_control_panel_ns.class_(
"DisarmedTrigger", automation.Trigger.template()
)
ChimeTrigger = alarm_control_panel_ns.class_(
"ChimeTrigger", automation.Trigger.template()
)
ReadyTrigger = alarm_control_panel_ns.class_(
"ReadyTrigger", automation.Trigger.template()
)
StateAnyForwarder = alarm_control_panel_ns.class_("StateAnyForwarder")
StateEnterForwarder = alarm_control_panel_ns.class_("StateEnterForwarder")
AlarmControlPanelState = alarm_control_panel_ns.enum("AlarmControlPanelState")
ArmAwayAction = alarm_control_panel_ns.class_("ArmAwayAction", automation.Action)
ArmHomeAction = alarm_control_panel_ns.class_("ArmHomeAction", automation.Action)
@@ -89,61 +58,17 @@ _ALARM_CONTROL_PANEL_SCHEMA = (
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(
mqtt.MQTTAlarmControlPanelComponent
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TriggeredTrigger),
}
),
cv.Optional(CONF_ON_ARMING): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmingTrigger),
}
),
cv.Optional(CONF_ON_PENDING): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PendingTrigger),
}
),
cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedHomeTrigger),
}
),
cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedNightTrigger),
}
),
cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ArmedAwayTrigger),
}
),
cv.Optional(CONF_ON_DISARMED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DisarmedTrigger),
}
),
cv.Optional(CONF_ON_CLEARED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ClearedTrigger),
}
),
cv.Optional(CONF_ON_CHIME): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ChimeTrigger),
}
),
cv.Optional(CONF_ON_READY): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReadyTrigger),
}
),
cv.Optional(CONF_ON_STATE): automation.validate_automation({}),
cv.Optional(CONF_ON_TRIGGERED): automation.validate_automation({}),
cv.Optional(CONF_ON_ARMING): automation.validate_automation({}),
cv.Optional(CONF_ON_PENDING): automation.validate_automation({}),
cv.Optional(CONF_ON_ARMED_HOME): automation.validate_automation({}),
cv.Optional(CONF_ON_ARMED_NIGHT): automation.validate_automation({}),
cv.Optional(CONF_ON_ARMED_AWAY): automation.validate_automation({}),
cv.Optional(CONF_ON_DISARMED): automation.validate_automation({}),
cv.Optional(CONF_ON_CLEARED): automation.validate_automation({}),
cv.Optional(CONF_ON_CHIME): automation.validate_automation({}),
cv.Optional(CONF_ON_READY): automation.validate_automation({}),
}
)
)
@@ -189,38 +114,39 @@ ALARM_CONTROL_PANEL_CONDITION_SCHEMA = maybe_simple_id(
@setup_entity("alarm_control_panel")
async def setup_alarm_control_panel_core_(var, config):
for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_TRIGGERED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ARMING, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_PENDING, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ARMED_HOME, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ARMED_NIGHT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_ARMED_AWAY, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_DISARMED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_state_callback", [], conf, forwarder=StateAnyForwarder
)
_STATE_ENTER_MAP = {
CONF_ON_TRIGGERED: AlarmControlPanelState.ACP_STATE_TRIGGERED,
CONF_ON_ARMING: AlarmControlPanelState.ACP_STATE_ARMING,
CONF_ON_PENDING: AlarmControlPanelState.ACP_STATE_PENDING,
CONF_ON_ARMED_HOME: AlarmControlPanelState.ACP_STATE_ARMED_HOME,
CONF_ON_ARMED_NIGHT: AlarmControlPanelState.ACP_STATE_ARMED_NIGHT,
CONF_ON_ARMED_AWAY: AlarmControlPanelState.ACP_STATE_ARMED_AWAY,
CONF_ON_DISARMED: AlarmControlPanelState.ACP_STATE_DISARMED,
}
for conf_key, state_enum in _STATE_ENTER_MAP.items():
for conf in config.get(conf_key, []):
await automation.build_callback_automation(
var,
"add_on_state_callback",
[],
conf,
forwarder=StateEnterForwarder.template(state_enum),
)
for conf in config.get(CONF_ON_CLEARED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_cleared_callback", [], conf
)
for conf in config.get(CONF_ON_CHIME, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_chime_callback", [], conf
)
for conf in config.get(CONF_ON_READY, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_ready_callback", [], conf
)
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):
@@ -35,8 +35,8 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
LOG_STR_ARG(alarm_control_panel_state_to_string(state)),
LOG_STR_ARG(alarm_control_panel_state_to_string(prev_state)));
this->current_state_ = state;
// Single state callback - triggers check get_state() for specific states
this->state_callback_.call();
// Single state callback - listeners receive the new state as an argument
this->state_callback_.call(state);
#if defined(USE_ALARM_CONTROL_PANEL) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_alarm_control_panel_update(this);
#endif
@@ -145,8 +145,8 @@ class AlarmControlPanel : public EntityBase {
uint32_t last_update_;
// the call control function
virtual void control(const AlarmControlPanelCall &call) = 0;
// state callback - triggers check get_state() for specific state
LazyCallbackManager<void()> state_callback_{};
// state callback - passes the new state to listeners
LazyCallbackManager<void(AlarmControlPanelState)> state_callback_{};
// clear callback - fires when leaving TRIGGERED state
LazyCallbackManager<void()> cleared_callback_{};
// chime callback
@@ -5,60 +5,27 @@
namespace esphome::alarm_control_panel {
/// Trigger on any state change
class StateTrigger : public Trigger<> {
public:
explicit StateTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_state_callback([this]() { this->trigger(); });
/// Callback forwarder that triggers an Automation<> on any state change.
/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_.
struct StateAnyForwarder {
Automation<> *automation;
void operator()(AlarmControlPanelState /*state*/) const { this->automation->trigger(); }
};
/// Callback forwarder that triggers an Automation<> only when the alarm enters a specific state.
/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_.
template<AlarmControlPanelState State> struct StateEnterForwarder {
Automation<> *automation;
void operator()(AlarmControlPanelState state) const {
if (state == State)
this->automation->trigger();
}
};
/// Template trigger that fires when entering a specific state
template<AlarmControlPanelState State> class StateEnterTrigger : public Trigger<> {
public:
explicit StateEnterTrigger(AlarmControlPanel *alarm_control_panel) : alarm_control_panel_(alarm_control_panel) {
alarm_control_panel->add_on_state_callback([this]() {
if (this->alarm_control_panel_->get_state() == State)
this->trigger();
});
}
protected:
AlarmControlPanel *alarm_control_panel_;
};
// Type aliases for state-specific triggers
using TriggeredTrigger = StateEnterTrigger<ACP_STATE_TRIGGERED>;
using ArmingTrigger = StateEnterTrigger<ACP_STATE_ARMING>;
using PendingTrigger = StateEnterTrigger<ACP_STATE_PENDING>;
using ArmedHomeTrigger = StateEnterTrigger<ACP_STATE_ARMED_HOME>;
using ArmedNightTrigger = StateEnterTrigger<ACP_STATE_ARMED_NIGHT>;
using ArmedAwayTrigger = StateEnterTrigger<ACP_STATE_ARMED_AWAY>;
using DisarmedTrigger = StateEnterTrigger<ACP_STATE_DISARMED>;
/// Trigger when leaving TRIGGERED state (alarm cleared)
class ClearedTrigger : public Trigger<> {
public:
explicit ClearedTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_cleared_callback([this]() { this->trigger(); });
}
};
/// Trigger on chime event (zone opened while disarmed)
class ChimeTrigger : public Trigger<> {
public:
explicit ChimeTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_chime_callback([this]() { this->trigger(); });
}
};
/// Trigger on ready state change
class ReadyTrigger : public Trigger<> {
public:
explicit ReadyTrigger(AlarmControlPanel *alarm_control_panel) {
alarm_control_panel->add_on_ready_callback([this]() { this->trigger(); });
}
};
static_assert(sizeof(StateAnyForwarder) <= sizeof(void *));
static_assert(std::is_trivially_copyable_v<StateAnyForwarder>);
static_assert(sizeof(StateEnterForwarder<ACP_STATE_TRIGGERED>) <= sizeof(void *));
static_assert(std::is_trivially_copyable_v<StateEnterForwarder<ACP_STATE_TRIGGERED>>);
template<typename... Ts> class ArmAwayAction : public Action<Ts...> {
public:
+1
View File
@@ -2512,6 +2512,7 @@ message ListEntitiesInfraredResponse {
EntityCategory entity_category = 6;
uint32 device_id = 7 [(field_ifdef) = "USE_DEVICES"];
uint32 capabilities = 8; // Bitfield of InfraredCapabilityFlags
uint32 receiver_frequency = 9; // Demodulation frequency of the IR receiver in Hz (0 = unspecified)
}
// Command to transmit infrared/RF data using raw timings
@@ -1549,6 +1549,7 @@ uint16_t APIConnection::try_send_infrared_info(EntityBase *entity, APIConnection
auto *infrared = static_cast<infrared::Infrared *>(entity);
ListEntitiesInfraredResponse msg;
msg.capabilities = infrared->get_capability_flags();
msg.receiver_frequency = infrared->get_traits().get_receiver_frequency_hz();
return fill_and_encode_entity_info(infrared, msg, conn, remaining_size);
}
#endif
+2
View File
@@ -3657,6 +3657,7 @@ void ListEntitiesInfraredResponse::encode(ProtoWriteBuffer &buffer) const {
buffer.encode_uint32(7, this->device_id);
#endif
buffer.encode_uint32(8, this->capabilities);
buffer.encode_uint32(9, this->receiver_frequency);
}
uint32_t ListEntitiesInfraredResponse::calculate_size() const {
uint32_t size = 0;
@@ -3672,6 +3673,7 @@ uint32_t ListEntitiesInfraredResponse::calculate_size() const {
size += ProtoSize::calc_uint32(1, this->device_id);
#endif
size += ProtoSize::calc_uint32(1, this->capabilities);
size += ProtoSize::calc_uint32(1, this->receiver_frequency);
return size;
}
#endif
+2 -1
View File
@@ -3041,11 +3041,12 @@ class ZWaveProxyRequest final : public ProtoDecodableMessage {
class ListEntitiesInfraredResponse final : public InfoResponseProtoMessage {
public:
static constexpr uint8_t MESSAGE_TYPE = 135;
static constexpr uint8_t ESTIMATED_SIZE = 44;
static constexpr uint8_t ESTIMATED_SIZE = 48;
#ifdef HAS_PROTO_MESSAGE_DUMP
const LogString *message_name() const override { return LOG_STR("list_entities_infrared_response"); }
#endif
uint32_t capabilities{0};
uint32_t receiver_frequency{0};
void encode(ProtoWriteBuffer &buffer) const;
uint32_t calculate_size() const;
#ifdef HAS_PROTO_MESSAGE_DUMP
+1
View File
@@ -2572,6 +2572,7 @@ const char *ListEntitiesInfraredResponse::dump_to(DumpBuffer &out) const {
dump_field(out, ESPHOME_PSTR("device_id"), this->device_id);
#endif
dump_field(out, ESPHOME_PSTR("capabilities"), this->capabilities);
dump_field(out, ESPHOME_PSTR("receiver_frequency"), this->receiver_frequency);
return out.c_str();
}
#endif
+2 -2
View File
@@ -550,8 +550,8 @@ float ATM90E32Component::get_phase_harmonic_active_power_(uint8_t phase) {
}
float ATM90E32Component::get_phase_angle_(uint8_t phase) {
uint16_t val = this->read16_(ATM90E32_REGISTER_PANGLE + phase) / 10.0;
return (val > 180) ? (float) (val - 360.0f) : (float) val;
float val = this->read16_(ATM90E32_REGISTER_PANGLE + phase) / 10.0f;
return (val > 180.0f) ? val - 360.0f : val;
}
float ATM90E32Component::get_phase_peak_current_(uint8_t phase) {
-1
View File
@@ -134,7 +134,6 @@ class ATM90E32Component : public PollingComponent,
void set_freq_status_text_sensor(text_sensor::TextSensor *sensor) { this->freq_status_text_sensor_ = sensor; }
#endif
uint16_t calculate_voltage_threshold(int line_freq, uint16_t ugain, float multiplier);
int32_t last_periodic_millis = millis();
protected:
#ifdef USE_NUMBER
+18 -43
View File
@@ -120,10 +120,6 @@ BinarySensorInitiallyOff = binary_sensor_ns.class_(
BinarySensorPtr = BinarySensor.operator("ptr")
# Triggers
PressTrigger = binary_sensor_ns.class_("PressTrigger", automation.Trigger.template())
ReleaseTrigger = binary_sensor_ns.class_(
"ReleaseTrigger", automation.Trigger.template()
)
ClickTrigger = binary_sensor_ns.class_("ClickTrigger", automation.Trigger.template())
DoubleClickTrigger = binary_sensor_ns.class_(
"DoubleClickTrigger", automation.Trigger.template()
@@ -133,13 +129,6 @@ MultiClickTriggerBase = binary_sensor_ns.class_(
)
MultiClickTrigger = binary_sensor_ns.class_("MultiClickTrigger", MultiClickTriggerBase)
MultiClickTriggerEvent = binary_sensor_ns.struct("MultiClickTriggerEvent")
StateTrigger = binary_sensor_ns.class_(
"StateTrigger", automation.Trigger.template(bool)
)
StateChangeTrigger = binary_sensor_ns.class_(
"StateChangeTrigger",
automation.Trigger.template(cg.optional.template(bool), cg.optional.template(bool)),
)
BinarySensorPublishAction = binary_sensor_ns.class_(
"BinarySensorPublishAction", automation.Action
@@ -459,16 +448,8 @@ _BINARY_SENSOR_SCHEMA = (
): cv.boolean,
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_FILTERS): validate_filters,
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PressTrigger),
}
),
cv.Optional(CONF_ON_RELEASE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ReleaseTrigger),
}
),
cv.Optional(CONF_ON_PRESS): automation.validate_automation({}),
cv.Optional(CONF_ON_RELEASE): automation.validate_automation({}),
cv.Optional(CONF_ON_CLICK): cv.All(
automation.validate_automation(
{
@@ -512,16 +493,8 @@ _BINARY_SENSOR_SCHEMA = (
): cv.positive_time_period_milliseconds,
}
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateTrigger),
}
),
cv.Optional(CONF_ON_STATE_CHANGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(StateChangeTrigger),
}
),
cv.Optional(CONF_ON_STATE): automation.validate_automation({}),
cv.Optional(CONF_ON_STATE_CHANGE): automation.validate_automation({}),
}
)
)
@@ -559,13 +532,14 @@ def binary_sensor_schema(
@coroutine_with_priority(CoroPriority.AUTOMATION)
async def _build_binary_sensor_automations(var, config):
for conf in config.get(CONF_ON_PRESS, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_RELEASE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf_key, forwarder in (
(CONF_ON_PRESS, automation.TriggerOnTrueForwarder),
(CONF_ON_RELEASE, automation.TriggerOnFalseForwarder),
):
for conf in config.get(conf_key, []):
await automation.build_callback_automation(
var, "add_on_state_callback", [], conf, forwarder=forwarder
)
for conf in config.get(CONF_ON_CLICK, []):
trigger = cg.new_Pvariable(
@@ -598,13 +572,14 @@ async def _build_binary_sensor_automations(var, config):
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(bool, "x")], conf)
await automation.build_callback_automation(
var, "add_on_state_callback", [(bool, "x")], conf
)
for conf in config.get(CONF_ON_STATE_CHANGE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger,
await automation.build_callback_automation(
var,
"add_full_state_callback",
[
(cg.optional.template(bool), "x_previous"),
(cg.optional.template(bool), "x"),
+1 -1
View File
@@ -124,7 +124,7 @@ def set_reference_values(config):
config.setdefault(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_LEGACY_UREF)
config.setdefault(CONF_CURRENT_REFERENCE, DEFAULT_BL0940_LEGACY_IREF)
config.setdefault(CONF_POWER_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
config.setdefault(CONF_ENERGY_REFERENCE, DEFAULT_BL0940_LEGACY_PREF)
config.setdefault(CONF_ENERGY_REFERENCE, DEFAULT_BL0940_LEGACY_EREF)
else:
vref = config.get(CONF_VOLTAGE_REFERENCE, DEFAULT_BL0940_VREF)
r_one = config.get(CONF_RESISTOR_ONE, DEFAULT_BL0940_R1)
@@ -469,14 +469,18 @@ bool BMP581Component::read_temperature_and_pressure_(float &temperature, float &
}
bool BMP581Component::reset_() {
// - activates interface (only relevant for SPI mode)
// - writes reset command to the command register
// - waits for sensor to complete reset
// - activates interface (only relevant for SPI mode)
// - returns the Power-On-Reboot interrupt status, which is asserted if successful
// activates communication interface (SPI only)
this->activate_interface();
// writes reset command to BMP's command register
if (!this->bmp_write_byte(BMP581_COMMAND, RESET_COMMAND)) {
ESP_LOGE(TAG, "Failed to write reset command");
return false;
}
@@ -484,6 +488,9 @@ bool BMP581Component::reset_() {
// - round up to 3 ms
delay(3);
// reactivates communication interface after reset (SPI only)
this->activate_interface();
// read interrupt status register
if (!this->bmp_read_byte(BMP581_INT_STATUS, &this->int_status_.reg)) {
ESP_LOGE(TAG, "Failed to read interrupt status register");
@@ -491,7 +498,7 @@ bool BMP581Component::reset_() {
return false;
}
// Power-On-Reboot bit is asserted if sensor successfully reset
// power-On-Reboot bit is asserted if sensor successfully reset
return this->int_status_.bit.por;
}
@@ -87,6 +87,9 @@ class BMP581Component : public PollingComponent {
virtual bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
virtual bool bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len) = 0;
// Interface activation function. Only used for SPI interface; no-op for I2C.
virtual void activate_interface() {}
sensor::Sensor *temperature_sensor_{nullptr};
sensor::Sensor *pressure_sensor_{nullptr};
@@ -0,0 +1,73 @@
#include <cstdint>
#include <cstddef>
#include "bmp581_spi.h"
#include "esphome/components/bmp581_base/bmp581_base.h"
#include "esphome/components/spi/spi.h"
namespace esphome::bmp581_spi {
static const char *const TAG = "bmp581_spi";
// OR (|) register with BMP_SPI_READ for read
inline constexpr uint8_t BMP_SPI_READ = 0x80;
// AND (&) register with BMP_SPI_WRITE for write
inline constexpr uint8_t BMP_SPI_WRITE = 0x7F;
void BMP581SPIComponent::dump_config() {
BMP581Component::dump_config();
LOG_SPI_DEVICE(this);
}
void BMP581SPIComponent::setup() {
this->spi_setup();
BMP581Component::setup();
}
void BMP581SPIComponent::activate_interface() {
// - forces the device into SPI mode using a dummy read
uint8_t dummy_read = 0;
this->bmp_read_byte(bmp581_base::BMP581_CHIP_ID, &dummy_read);
}
// In SPI mode, only 7 bits of the register addresses are used; the MSB of register address is not used
// and replaced by a read/write bit (RW = 0 for write and RW = 1 for read).
// Example: address 0xF7 is accessed by using SPI register address 0x77. For write access, the byte
// 0x77 is transferred, for read access, the byte 0xF7 is transferred.
// The expressions BMP_SPI_READ (| with register) and BMP_SPI_WRITE (& with register)
// are defined for readability.
// https://www.bosch-sensortec.com/media/boschsensortec/downloads/datasheets/bst-bmp581-ds004.pdf
bool BMP581SPIComponent::bmp_read_byte(uint8_t a_register, uint8_t *data) {
this->enable();
this->transfer_byte(a_register | BMP_SPI_READ);
*data = this->transfer_byte(0);
this->disable();
return true;
}
bool BMP581SPIComponent::bmp_write_byte(uint8_t a_register, uint8_t data) {
this->enable();
this->transfer_byte(a_register & BMP_SPI_WRITE);
this->transfer_byte(data);
this->disable();
return true;
}
bool BMP581SPIComponent::bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) {
this->enable();
this->transfer_byte(a_register | BMP_SPI_READ);
this->read_array(data, len);
this->disable();
return true;
}
bool BMP581SPIComponent::bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len) {
this->enable();
this->transfer_byte(a_register & BMP_SPI_WRITE);
this->write_array(data, len);
this->disable();
return true;
}
} // namespace esphome::bmp581_spi
@@ -0,0 +1,24 @@
#pragma once
#include "esphome/components/bmp581_base/bmp581_base.h"
#include "esphome/components/spi/spi.h"
namespace esphome::bmp581_spi {
// BMP581 is technically compatible with SPI Mode0 and Mode3. Default to Mode3.
class BMP581SPIComponent : public esphome::bmp581_base::BMP581Component,
public spi::SPIDevice<spi::BIT_ORDER_MSB_FIRST, spi::CLOCK_POLARITY_HIGH,
spi::CLOCK_PHASE_TRAILING, spi::DATA_RATE_200KHZ> {
public:
void setup() override;
bool bmp_read_byte(uint8_t a_register, uint8_t *data) override;
bool bmp_write_byte(uint8_t a_register, uint8_t data) override;
bool bmp_read_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
bool bmp_write_bytes(uint8_t a_register, uint8_t *data, size_t len) override;
void dump_config() override;
protected:
void activate_interface() override;
};
} // namespace esphome::bmp581_spi
+48
View File
@@ -0,0 +1,48 @@
import logging
import esphome.codegen as cg
from esphome.components import spi
from esphome.components.spi import CONF_SPI_MODE
import esphome.config_validation as cv
from ..bmp581_base import CONFIG_SCHEMA_BASE, to_code_base
AUTO_LOAD = ["bmp581_base"]
CODEOWNERS = ["@kahrendt", "@danielkent-net"]
DEPENDENCIES = ["spi"]
_LOGGER = logging.getLogger(__name__)
VALID_SPI_MODES = {
0: "MODE0",
"0": "MODE0",
"MODE0": "MODE0",
3: "MODE3",
"3": "MODE3",
"MODE3": "MODE3",
}
bmp581_ns = cg.esphome_ns.namespace("bmp581_spi")
BMP581SPIComponent = bmp581_ns.class_(
"BMP581SPIComponent", cg.PollingComponent, spi.SPIDevice
)
def check_spi_mode(config):
spi_mode = config.get(CONF_SPI_MODE)
if spi_mode not in VALID_SPI_MODES:
raise cv.Invalid("BMP581 only supports SPI mode 3")
return config
CONFIG_SCHEMA = cv.All(
CONFIG_SCHEMA_BASE.extend(spi.spi_device_schema(default_mode="mode3")).extend(
{cv.GenerateID(): cv.declare_id(BMP581SPIComponent)}
),
check_spi_mode,
)
async def to_code(config):
var = await to_code_base(config)
await spi.register_spi_device(var, config)
+4 -12
View File
@@ -10,7 +10,6 @@ from esphome.const import (
CONF_ID,
CONF_MQTT_ID,
CONF_ON_PRESS,
CONF_TRIGGER_ID,
CONF_WEB_SERVER,
DEVICE_CLASS_EMPTY,
DEVICE_CLASS_IDENTIFY,
@@ -41,10 +40,6 @@ ButtonPtr = Button.operator("ptr")
PressAction = button_ns.class_("PressAction", automation.Action)
ButtonPressTrigger = button_ns.class_(
"ButtonPressTrigger", automation.Trigger.template()
)
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
@@ -55,11 +50,7 @@ _BUTTON_SCHEMA = (
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTButtonComponent),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_ON_PRESS): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ButtonPressTrigger),
}
),
cv.Optional(CONF_ON_PRESS): automation.validate_automation({}),
}
)
)
@@ -91,8 +82,9 @@ def button_schema(
@setup_entity("button")
async def setup_button_core_(var, config):
for conf in config.get(CONF_ON_PRESS, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_press_callback", [], conf
)
setup_device_class(config)
+2 -3
View File
@@ -367,7 +367,7 @@ optional<ClimateDeviceRestoreState> Climate::restore_state_() {
return recovered;
}
void Climate::save_state_() {
void Climate::save_state_(const ClimateTraits &traits) {
#if (defined(USE_ESP32) || (defined(USE_ESP8266) && USE_ARDUINO_VERSION_CODE >= VERSION_CODE(3, 0, 0))) && \
!defined(CLANG_TIDY)
#pragma GCC diagnostic ignored "-Wclass-memaccess"
@@ -382,7 +382,6 @@ void Climate::save_state_() {
#endif
state.mode = this->mode;
auto traits = this->get_traits();
if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
state.target_temperature_low = this->target_temperature_low;
@@ -480,7 +479,7 @@ void Climate::publish_state() {
ControllerRegistry::notify_climate_update(this);
#endif
// Save state
this->save_state_();
this->save_state_(traits);
}
ClimateTraits Climate::get_traits() {
+2 -1
View File
@@ -335,7 +335,8 @@ class Climate : public EntityBase {
/** Internal method to save the state of the climate device to recover memory. This is automatically
* called from publish_state()
*/
void save_state_();
void save_state_(const ClimateTraits &traits);
void save_state_() { this->save_state_(this->traits()); }
void dump_traits_(const char *tag);
+1
View File
@@ -18,6 +18,7 @@ CONF_ON_PACKET = "on_packet"
CONF_ON_RECEIVE = "on_receive"
CONF_ON_STATE_CHANGE = "on_state_change"
CONF_PARITY = "parity"
CONF_RECEIVER_FREQUENCY = "receiver_frequency"
CONF_REQUEST_HEADERS = "request_headers"
CONF_ROWS = "rows"
CONF_STOP_BITS = "stop_bits"
+1 -1
View File
@@ -7,7 +7,7 @@ namespace copy {
static const char *const TAG = "copy.lock";
void CopyLock::setup() {
source_->add_on_state_callback([this]() { this->publish_state(source_->state); });
source_->add_on_state_callback([this](lock::LockState state) { this->publish_state(state); });
traits.set_assumed_state(source_->traits.get_assumed_state());
traits.set_requires_code(source_->traits.get_requires_code());
+5 -13
View File
@@ -2,16 +2,13 @@ from esphome import automation
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_DEVICE, CONF_FILE, CONF_ID, CONF_TRIGGER_ID, CONF_VOLUME
from esphome.const import CONF_DEVICE, CONF_FILE, CONF_ID, CONF_VOLUME
DEPENDENCIES = ["uart"]
CODEOWNERS = ["@glmnet"]
dfplayer_ns = cg.esphome_ns.namespace("dfplayer")
DFPlayer = dfplayer_ns.class_("DFPlayer", cg.Component)
DFPlayerFinishedPlaybackTrigger = dfplayer_ns.class_(
"DFPlayerFinishedPlaybackTrigger", automation.Trigger.template()
)
DFPlayerIsPlayingCondition = dfplayer_ns.class_(
"DFPlayerIsPlayingCondition", automation.Condition
)
@@ -58,13 +55,7 @@ CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(DFPlayer),
cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
DFPlayerFinishedPlaybackTrigger
),
}
),
cv.Optional(CONF_ON_FINISHED_PLAYBACK): automation.validate_automation({}),
}
).extend(uart.UART_DEVICE_SCHEMA)
)
@@ -79,8 +70,9 @@ async def to_code(config):
await uart.register_uart_device(var, config)
for conf in config.get(CONF_ON_FINISHED_PLAYBACK, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_finished_playback_callback", [], conf
)
@automation.register_action(
-7
View File
@@ -171,12 +171,5 @@ template<typename... Ts> class DFPlayerIsPlayingCondition : public Condition<Ts.
bool check(const Ts &...x) override { return this->parent_->is_playing(); }
};
class DFPlayerFinishedPlaybackTrigger : public Trigger<> {
public:
explicit DFPlayerFinishedPlaybackTrigger(DFPlayer *parent) {
parent->add_on_finished_playback_callback([this]() { this->trigger(); });
}
};
} // namespace dfplayer
} // namespace esphome
+8 -2
View File
@@ -690,9 +690,15 @@ ARDUINO_IDF_VERSION_LOOKUP = {
ESP_IDF_FRAMEWORK_VERSION_LOOKUP = {
"recommended": cv.Version(5, 5, 3, "1"),
"latest": cv.Version(5, 5, 3, "1"),
"dev": cv.Version(5, 5, 3, "1"),
"dev": cv.Version(5, 5, 4),
}
ESP_IDF_PLATFORM_VERSION_LOOKUP = {
cv.Version(
6, 0, 0
): "https://github.com/pioarduino/platform-espressif32.git#prep_IDF6",
cv.Version(
5, 5, 4
): "https://github.com/pioarduino/platform-espressif32.git#develop",
cv.Version(5, 5, 3, "1"): cv.Version(55, 3, 37),
cv.Version(5, 5, 3): cv.Version(55, 3, 37),
cv.Version(5, 5, 2): cv.Version(55, 3, 37),
@@ -1587,7 +1593,7 @@ async def to_code(config):
if conf[CONF_ADVANCED][CONF_ENABLE_FULL_PRINTF]:
cg.add_define("USE_FULL_PRINTF")
else:
for symbol in ("vprintf", "printf", "fprintf"):
for symbol in ("vprintf", "printf", "fprintf", "vfprintf"):
cg.add_build_flag(f"-Wl,--wrap={symbol}")
else:
cg.add_build_flag("-DUSE_ARDUINO")
+11 -6
View File
@@ -2,10 +2,11 @@
* Linker wrap stubs for FILE*-based printf functions.
*
* ESP-IDF SDK components (gpio driver, ringbuf, log_write) reference
* fprintf(), printf(), and vprintf() which pull in newlib's _vfprintf_r
* (~11 KB). This is a separate implementation from _svfprintf_r (used by
* snprintf/vsnprintf) that handles FILE* stream I/O with buffering and
* locking.
* fprintf(), printf(), vprintf(), and vfprintf() which pull in the full
* printf implementation (~11 KB on newlib's _vfprintf_r, ~2.8 KB on
* picolibc's vfprintf). This is a separate implementation from the one
* used by snprintf/vsnprintf that handles FILE* stream I/O with buffering
* and locking.
*
* ESPHome replaces the ESP-IDF log handler via esp_log_set_vprintf_(),
* so the SDK's vprintf() path is dead code at runtime. The fprintf()
@@ -70,11 +71,15 @@ int __wrap_printf(const char *fmt, ...) {
return len;
}
int __wrap_vfprintf(FILE *stream, const char *fmt, va_list ap) {
char buf[PRINTF_BUFFER_SIZE];
return write_printf_buffer(stream, buf, vsnprintf(buf, sizeof(buf), fmt, ap));
}
int __wrap_fprintf(FILE *stream, const char *fmt, ...) {
va_list ap;
va_start(ap, fmt);
char buf[PRINTF_BUFFER_SIZE];
int len = write_printf_buffer(stream, buf, vsnprintf(buf, sizeof(buf), fmt, ap));
int len = __wrap_vfprintf(stream, fmt, ap);
va_end(ap);
return len;
}
@@ -350,7 +350,7 @@ bool BLEClientBase::gattc_event_handler(esp_gattc_cb_event_t event, esp_gatt_if_
// For V3_WITHOUT_CACHE, we already set fast params before connecting
// No need to update them again here
this->log_event_("Searching for services");
esp_ble_gattc_search_service(esp_gattc_if, param->cfg_mtu.conn_id, nullptr);
esp_ble_gattc_search_service(esp_gattc_if, param->open.conn_id, nullptr);
break;
}
case ESP_GATTC_CONNECT_EVT: {
@@ -159,7 +159,7 @@ void BLEService::gatts_event_handler(esp_gatts_cb_event_t event, esp_gatt_if_t g
break;
}
case ESP_GATTS_STOP_EVT: {
if (param->start.service_handle == this->handle_) {
if (param->stop.service_handle == this->handle_) {
this->state_ = STOPPED;
}
break;
+4 -10
View File
@@ -10,7 +10,6 @@ from esphome.const import (
CONF_ID,
CONF_MQTT_ID,
CONF_ON_EVENT,
CONF_TRIGGER_ID,
CONF_WEB_SERVER,
DEVICE_CLASS_BUTTON,
DEVICE_CLASS_DOORBELL,
@@ -41,8 +40,6 @@ EventPtr = Event.operator("ptr")
TriggerEventAction = event_ns.class_("TriggerEventAction", automation.Action)
EventTrigger = event_ns.class_("EventTrigger", automation.Trigger.template())
validate_device_class = cv.one_of(*DEVICE_CLASSES, lower=True, space="_")
_EVENT_SCHEMA = (
@@ -53,11 +50,7 @@ _EVENT_SCHEMA = (
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTEventComponent),
cv.GenerateID(): cv.declare_id(Event),
cv.Optional(CONF_DEVICE_CLASS): validate_device_class,
cv.Optional(CONF_ON_EVENT): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(EventTrigger),
}
),
cv.Optional(CONF_ON_EVENT): automation.validate_automation({}),
}
)
)
@@ -92,8 +85,9 @@ def event_schema(
@setup_entity("event")
async def setup_event_core_(var, config, *, event_types: list[str]):
for conf in config.get(CONF_ON_EVENT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.StringRef, "event_type")], conf)
await automation.build_callback_automation(
var, "add_on_event_callback", [(cg.StringRef, "event_type")], conf
)
cg.add(var.set_event_types(event_types))
-53
View File
@@ -1,53 +0,0 @@
#pragma once
#include <utility>
#include "esphome/core/automation.h"
#include "ezo.h"
namespace esphome {
namespace ezo {
class LedTrigger : public Trigger<bool> {
public:
explicit LedTrigger(EZOSensor *ezo) {
ezo->add_led_state_callback([this](bool value) { this->trigger(value); });
}
};
class CustomTrigger : public Trigger<std::string> {
public:
explicit CustomTrigger(EZOSensor *ezo) {
ezo->add_custom_callback([this](const std::string &value) { this->trigger(value); });
}
};
class TTrigger : public Trigger<std::string> {
public:
explicit TTrigger(EZOSensor *ezo) {
ezo->add_t_callback([this](const std::string &value) { this->trigger(value); });
}
};
class CalibrationTrigger : public Trigger<std::string> {
public:
explicit CalibrationTrigger(EZOSensor *ezo) {
ezo->add_calibration_callback([this](const std::string &value) { this->trigger(value); });
}
};
class SlopeTrigger : public Trigger<std::string> {
public:
explicit SlopeTrigger(EZOSensor *ezo) {
ezo->add_slope_callback([this](const std::string &value) { this->trigger(value); });
}
};
class DeviceInformationTrigger : public Trigger<std::string> {
public:
explicit DeviceInformationTrigger(EZOSensor *ezo) {
ezo->add_device_infomation_callback([this](const std::string &value) { this->trigger(value); });
}
};
} // namespace ezo
} // namespace esphome
+25 -69
View File
@@ -2,7 +2,7 @@ from esphome import automation
import esphome.codegen as cg
from esphome.components import i2c, sensor
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_TRIGGER_ID
from esphome.const import CONF_ID
CODEOWNERS = ["@ssieb"]
@@ -21,61 +21,16 @@ EZOSensor = ezo_ns.class_(
"EZOSensor", sensor.Sensor, cg.PollingComponent, i2c.I2CDevice
)
CustomTrigger = ezo_ns.class_(
"CustomTrigger", automation.Trigger.template(cg.std_string)
)
TTrigger = ezo_ns.class_("TTrigger", automation.Trigger.template(cg.std_string))
SlopeTrigger = ezo_ns.class_("SlopeTrigger", automation.Trigger.template(cg.std_string))
CalibrationTrigger = ezo_ns.class_(
"CalibrationTrigger", automation.Trigger.template(cg.std_string)
)
DeviceInformationTrigger = ezo_ns.class_(
"DeviceInformationTrigger", automation.Trigger.template(cg.std_string)
)
LedTrigger = ezo_ns.class_("LedTrigger", automation.Trigger.template(cg.bool_))
CONFIG_SCHEMA = (
sensor.sensor_schema(EZOSensor)
.extend(
{
cv.Optional(CONF_ON_CUSTOM): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CustomTrigger),
}
),
cv.Optional(CONF_ON_CALIBRATION): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(CalibrationTrigger),
}
),
cv.Optional(CONF_ON_SLOPE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SlopeTrigger),
}
),
cv.Optional(CONF_ON_T): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TTrigger),
}
),
cv.Optional(CONF_ON_DEVICE_INFORMATION): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
DeviceInformationTrigger
),
}
),
cv.Optional(CONF_ON_LED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LedTrigger),
}
),
cv.Optional(CONF_ON_CUSTOM): automation.validate_automation({}),
cv.Optional(CONF_ON_CALIBRATION): automation.validate_automation({}),
cv.Optional(CONF_ON_SLOPE): automation.validate_automation({}),
cv.Optional(CONF_ON_T): automation.validate_automation({}),
cv.Optional(CONF_ON_DEVICE_INFORMATION): automation.validate_automation({}),
cv.Optional(CONF_ON_LED): automation.validate_automation({}),
}
)
.extend(cv.polling_component_schema("60s"))
@@ -90,25 +45,26 @@ async def to_code(config):
await i2c.register_i2c_device(var, config)
for conf in config.get(CONF_ON_CUSTOM, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
await automation.build_callback_automation(
var, "add_custom_callback", [(cg.std_string, "x")], conf
)
for conf in config.get(CONF_ON_LED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(bool, "x")], conf)
await automation.build_callback_automation(
var, "add_led_state_callback", [(bool, "x")], conf
)
for conf in config.get(CONF_ON_DEVICE_INFORMATION, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
await automation.build_callback_automation(
var, "add_device_infomation_callback", [(cg.std_string, "x")], conf
)
for conf in config.get(CONF_ON_SLOPE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
await automation.build_callback_automation(
var, "add_slope_callback", [(cg.std_string, "x")], conf
)
for conf in config.get(CONF_ON_CALIBRATION, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
await automation.build_callback_automation(
var, "add_calibration_callback", [(cg.std_string, "x")], conf
)
for conf in config.get(CONF_ON_T, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.std_string, "x")], conf)
await automation.build_callback_automation(
var, "add_t_callback", [(cg.std_string, "x")], conf
)
+6 -15
View File
@@ -1,10 +1,9 @@
from esphome.automation import Trigger, build_automation, validate_automation
from esphome import automation
import esphome.codegen as cg
from esphome.components.esp8266 import CONF_RESTORE_FROM_FLASH, KEY_ESP8266
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_TRIGGER_ID,
PLATFORM_BK72XX,
PLATFORM_ESP32,
PLATFORM_ESP8266,
@@ -18,7 +17,6 @@ CODEOWNERS = ["@anatoly-savchenkov"]
factory_reset_ns = cg.esphome_ns.namespace("factory_reset")
FactoryResetComponent = factory_reset_ns.class_("FactoryResetComponent", cg.Component)
FastBootTrigger = factory_reset_ns.class_("FastBootTrigger", Trigger, cg.Component)
CONF_MAX_DELAY = "max_delay"
CONF_RESETS_REQUIRED = "resets_required"
@@ -55,11 +53,7 @@ CONFIG_SCHEMA = cv.All(
),
),
cv.Optional(CONF_RESETS_REQUIRED): cv.positive_not_null_int,
cv.Optional(CONF_ON_INCREMENT): validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FastBootTrigger),
}
),
cv.Optional(CONF_ON_INCREMENT): automation.validate_automation({}),
}
).extend(cv.COMPONENT_SCHEMA),
_validate,
@@ -88,12 +82,9 @@ async def to_code(config):
)
await cg.register_component(var, config)
for conf in config.get(CONF_ON_INCREMENT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await build_automation(
trigger,
[
(cg.uint8, "x"),
(cg.uint8, "target"),
],
await automation.build_callback_automation(
var,
"add_increment_callback",
[(cg.uint8, "x"), (cg.uint8, "target")],
conf,
)
@@ -30,12 +30,6 @@ class FactoryResetComponent : public Component {
uint8_t required_count_; // The number of boot attempts before fast boot is enabled
};
class FastBootTrigger : public Trigger<uint8_t, uint8_t> {
public:
explicit FastBootTrigger(FactoryResetComponent *parent) {
parent->add_increment_callback([this](uint8_t current, uint8_t target) { this->trigger(current, target); });
}
};
} // namespace esphome::factory_reset
#endif // !defined(USE_RP2040) && !defined(USE_HOST)
+36 -106
View File
@@ -21,7 +21,6 @@ from esphome.const import (
CONF_SENSING_PIN,
CONF_SPEED,
CONF_STATE,
CONF_TRIGGER_ID,
)
CODEOWNERS = ["@OnFreund", "@loongyh", "@alexborro"]
@@ -38,38 +37,6 @@ FingerprintGrowComponent = fingerprint_grow_ns.class_(
"FingerprintGrowComponent", cg.PollingComponent, uart.UARTDevice
)
FingerScanStartTrigger = fingerprint_grow_ns.class_(
"FingerScanStartTrigger", automation.Trigger.template()
)
FingerScanMatchedTrigger = fingerprint_grow_ns.class_(
"FingerScanMatchedTrigger", automation.Trigger.template(cg.uint16, cg.uint16)
)
FingerScanUnmatchedTrigger = fingerprint_grow_ns.class_(
"FingerScanUnmatchedTrigger", automation.Trigger.template()
)
FingerScanMisplacedTrigger = fingerprint_grow_ns.class_(
"FingerScanMisplacedTrigger", automation.Trigger.template()
)
FingerScanInvalidTrigger = fingerprint_grow_ns.class_(
"FingerScanInvalidTrigger", automation.Trigger.template()
)
EnrollmentScanTrigger = fingerprint_grow_ns.class_(
"EnrollmentScanTrigger", automation.Trigger.template(cg.uint8, cg.uint16)
)
EnrollmentDoneTrigger = fingerprint_grow_ns.class_(
"EnrollmentDoneTrigger", automation.Trigger.template(cg.uint16)
)
EnrollmentFailedTrigger = fingerprint_grow_ns.class_(
"EnrollmentFailedTrigger", automation.Trigger.template(cg.uint16)
)
EnrollmentAction = fingerprint_grow_ns.class_("EnrollmentAction", automation.Action)
CancelEnrollmentAction = fingerprint_grow_ns.class_(
"CancelEnrollmentAction", automation.Action
@@ -125,62 +92,22 @@ CONFIG_SCHEMA = cv.All(
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_PASSWORD): cv.uint32_t,
cv.Optional(CONF_NEW_PASSWORD): cv.uint32_t,
cv.Optional(CONF_ON_FINGER_SCAN_START): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FingerScanStartTrigger
),
}
),
cv.Optional(CONF_ON_FINGER_SCAN_START): automation.validate_automation({}),
cv.Optional(CONF_ON_FINGER_SCAN_MATCHED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FingerScanMatchedTrigger
),
}
{}
),
cv.Optional(CONF_ON_FINGER_SCAN_UNMATCHED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FingerScanUnmatchedTrigger
),
}
{}
),
cv.Optional(CONF_ON_FINGER_SCAN_MISPLACED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FingerScanMisplacedTrigger
),
}
{}
),
cv.Optional(CONF_ON_FINGER_SCAN_INVALID): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FingerScanInvalidTrigger
),
}
),
cv.Optional(CONF_ON_ENROLLMENT_SCAN): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
EnrollmentScanTrigger
),
}
),
cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
EnrollmentDoneTrigger
),
}
),
cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
EnrollmentFailedTrigger
),
}
{}
),
cv.Optional(CONF_ON_ENROLLMENT_SCAN): automation.validate_automation({}),
cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation({}),
cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation({}),
}
)
.extend(cv.polling_component_schema("500ms"))
@@ -214,40 +141,43 @@ async def to_code(config):
cg.add(var.set_idle_period_to_sleep_ms(idle_period_to_sleep_ms))
for conf in config.get(CONF_ON_FINGER_SCAN_START, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_finger_scan_start_callback", [], conf
)
for conf in config.get(CONF_ON_FINGER_SCAN_MATCHED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.uint16, "finger_id"), (cg.uint16, "confidence")], conf
await automation.build_callback_automation(
var,
"add_on_finger_scan_matched_callback",
[(cg.uint16, "finger_id"), (cg.uint16, "confidence")],
conf,
)
for conf in config.get(CONF_ON_FINGER_SCAN_UNMATCHED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_finger_scan_unmatched_callback", [], conf
)
for conf in config.get(CONF_ON_FINGER_SCAN_MISPLACED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_finger_scan_misplaced_callback", [], conf
)
for conf in config.get(CONF_ON_FINGER_SCAN_INVALID, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_finger_scan_invalid_callback", [], conf
)
for conf in config.get(CONF_ON_ENROLLMENT_SCAN, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.uint8, "scan_num"), (cg.uint16, "finger_id")], conf
await automation.build_callback_automation(
var,
"add_on_enrollment_scan_callback",
[(cg.uint8, "scan_num"), (cg.uint16, "finger_id")],
conf,
)
for conf in config.get(CONF_ON_ENROLLMENT_DONE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.uint16, "finger_id")], conf)
await automation.build_callback_automation(
var, "add_on_enrollment_done_callback", [(cg.uint16, "finger_id")], conf
)
for conf in config.get(CONF_ON_ENROLLMENT_FAILED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.uint16, "finger_id")], conf)
await automation.build_callback_automation(
var, "add_on_enrollment_failed_callback", [(cg.uint16, "finger_id")], conf
)
@automation.register_action(
@@ -210,64 +210,6 @@ class FingerprintGrowComponent : public PollingComponent, public uart::UARTDevic
CallbackManager<void(uint16_t)> enrollment_failed_callback_;
};
class FingerScanStartTrigger : public Trigger<> {
public:
explicit FingerScanStartTrigger(FingerprintGrowComponent *parent) {
parent->add_on_finger_scan_start_callback([this]() { this->trigger(); });
}
};
class FingerScanMatchedTrigger : public Trigger<uint16_t, uint16_t> {
public:
explicit FingerScanMatchedTrigger(FingerprintGrowComponent *parent) {
parent->add_on_finger_scan_matched_callback(
[this](uint16_t finger_id, uint16_t confidence) { this->trigger(finger_id, confidence); });
}
};
class FingerScanUnmatchedTrigger : public Trigger<> {
public:
explicit FingerScanUnmatchedTrigger(FingerprintGrowComponent *parent) {
parent->add_on_finger_scan_unmatched_callback([this]() { this->trigger(); });
}
};
class FingerScanMisplacedTrigger : public Trigger<> {
public:
explicit FingerScanMisplacedTrigger(FingerprintGrowComponent *parent) {
parent->add_on_finger_scan_misplaced_callback([this]() { this->trigger(); });
}
};
class FingerScanInvalidTrigger : public Trigger<> {
public:
explicit FingerScanInvalidTrigger(FingerprintGrowComponent *parent) {
parent->add_on_finger_scan_invalid_callback([this]() { this->trigger(); });
}
};
class EnrollmentScanTrigger : public Trigger<uint8_t, uint16_t> {
public:
explicit EnrollmentScanTrigger(FingerprintGrowComponent *parent) {
parent->add_on_enrollment_scan_callback(
[this](uint8_t scan_num, uint16_t finger_id) { this->trigger(scan_num, finger_id); });
}
};
class EnrollmentDoneTrigger : public Trigger<uint16_t> {
public:
explicit EnrollmentDoneTrigger(FingerprintGrowComponent *parent) {
parent->add_on_enrollment_done_callback([this](uint16_t finger_id) { this->trigger(finger_id); });
}
};
class EnrollmentFailedTrigger : public Trigger<uint16_t> {
public:
explicit EnrollmentFailedTrigger(FingerprintGrowComponent *parent) {
parent->add_on_enrollment_failed_callback([this](uint16_t finger_id) { this->trigger(finger_id); });
}
};
template<typename... Ts> class EnrollmentAction : public Action<Ts...>, public Parented<FingerprintGrowComponent> {
public:
TEMPLATABLE_VALUE(uint16_t, finger_id)
+18 -44
View File
@@ -22,7 +22,6 @@ from esphome.const import (
CONF_SUPPORTED_SWING_MODES,
CONF_TARGET_TEMPERATURE,
CONF_TEMPERATURE_STEP,
CONF_TRIGGER_ID,
CONF_VISUAL,
CONF_WIFI,
)
@@ -122,21 +121,6 @@ SUPPORTED_HON_CONTROL_METHODS = {
"SET_SINGLE_PARAMETER": HonControlMethod.SET_SINGLE_PARAMETER,
}
HaierAlarmStartTrigger = haier_ns.class_(
"HaierAlarmStartTrigger",
automation.Trigger.template(cg.uint8, cg.const_char_ptr),
)
HaierAlarmEndTrigger = haier_ns.class_(
"HaierAlarmEndTrigger",
automation.Trigger.template(cg.uint8, cg.const_char_ptr),
)
StatusMessageTrigger = haier_ns.class_(
"StatusMessageTrigger",
automation.Trigger.template(cg.const_char_ptr, cg.size_t),
)
def validate_visual(config):
if CONF_VISUAL in config:
@@ -203,13 +187,7 @@ def _base_config_schema(class_: MockObjClass) -> cv.Schema:
cv.Optional(
CONF_ANSWER_TIMEOUT,
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_ON_STATUS_MESSAGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
StatusMessageTrigger
),
}
),
cv.Optional(CONF_ON_STATUS_MESSAGE): automation.validate_automation({}),
}
)
.extend(uart.UART_DEVICE_SCHEMA)
@@ -264,19 +242,9 @@ CONFIG_SCHEMA = cv.All(
f"The {CONF_OUTDOOR_TEMPERATURE} option is deprecated, use a sensor for a haier platform instead"
),
cv.Optional(CONF_ON_ALARM_START): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
HaierAlarmStartTrigger
),
}
),
cv.Optional(CONF_ON_ALARM_END): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
HaierAlarmEndTrigger
),
}
{}
),
cv.Optional(CONF_ON_ALARM_END): automation.validate_automation({}),
}
),
},
@@ -530,19 +498,25 @@ async def to_code(config):
var.set_status_message_header_size(config[CONF_STATUS_MESSAGE_HEADER_SIZE])
)
for conf in config.get(CONF_ON_ALARM_START, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf
await automation.build_callback_automation(
var,
"add_alarm_start_callback",
[(cg.uint8, "code"), (cg.const_char_ptr, "message")],
conf,
)
for conf in config.get(CONF_ON_ALARM_END, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.uint8, "code"), (cg.const_char_ptr, "message")], conf
await automation.build_callback_automation(
var,
"add_alarm_end_callback",
[(cg.uint8, "code"), (cg.const_char_ptr, "message")],
conf,
)
for conf in config.get(CONF_ON_STATUS_MESSAGE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.const_char_ptr, "data"), (cg.size_t, "data_size")], conf
await automation.build_callback_automation(
var,
"add_status_message_callback",
[(cg.const_char_ptr, "data"), (cg.size_t, "data_size")],
conf,
)
# https://github.com/paveldn/HaierProtocol
cg.add_library("pavlodn/HaierProtocol", "0.9.31")
-7
View File
@@ -177,12 +177,5 @@ class HaierClimateBase : public esphome::Component,
ESPPreferenceObject base_rtc_;
};
class StatusMessageTrigger : public Trigger<const char *, size_t> {
public:
explicit StatusMessageTrigger(HaierClimateBase *parent) {
parent->add_status_message_callback([this](const char *data, size_t data_size) { this->trigger(data, data_size); });
}
};
} // namespace haier
} // namespace esphome
-16
View File
@@ -200,21 +200,5 @@ class HonClimate : public HaierClimateBase {
SwitchState quiet_mode_state_{SwitchState::OFF};
};
class HaierAlarmStartTrigger : public Trigger<uint8_t, const char *> {
public:
explicit HaierAlarmStartTrigger(HonClimate *parent) {
parent->add_alarm_start_callback(
[this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); });
}
};
class HaierAlarmEndTrigger : public Trigger<uint8_t, const char *> {
public:
explicit HaierAlarmEndTrigger(HonClimate *parent) {
parent->add_alarm_end_callback(
[this](uint8_t alarm_code, const char *alarm_message) { this->trigger(alarm_code, alarm_message); });
}
};
} // namespace haier
} // namespace esphome
+28 -81
View File
@@ -8,7 +8,6 @@ from esphome.const import (
CONF_NAME,
CONF_ON_ENROLLMENT_DONE,
CONF_ON_ENROLLMENT_FAILED,
CONF_TRIGGER_ID,
)
CODEOWNERS = ["@OnFreund"]
@@ -28,33 +27,6 @@ HlkFm22xComponent = hlk_fm22x_ns.class_(
"HlkFm22xComponent", cg.PollingComponent, uart.UARTDevice
)
FaceScanMatchedTrigger = hlk_fm22x_ns.class_(
"FaceScanMatchedTrigger", automation.Trigger.template(cg.int16, cg.std_string)
)
FaceScanUnmatchedTrigger = hlk_fm22x_ns.class_(
"FaceScanUnmatchedTrigger", automation.Trigger.template()
)
FaceScanInvalidTrigger = hlk_fm22x_ns.class_(
"FaceScanInvalidTrigger", automation.Trigger.template(cg.uint8)
)
FaceInfoTrigger = hlk_fm22x_ns.class_(
"FaceInfoTrigger",
automation.Trigger.template(
cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16, cg.int16
),
)
EnrollmentDoneTrigger = hlk_fm22x_ns.class_(
"EnrollmentDoneTrigger", automation.Trigger.template(cg.int16, cg.uint8)
)
EnrollmentFailedTrigger = hlk_fm22x_ns.class_(
"EnrollmentFailedTrigger", automation.Trigger.template(cg.uint8)
)
EnrollmentAction = hlk_fm22x_ns.class_("EnrollmentAction", automation.Action)
DeleteAction = hlk_fm22x_ns.class_("DeleteAction", automation.Action)
DeleteAllAction = hlk_fm22x_ns.class_("DeleteAllAction", automation.Action)
@@ -65,46 +37,14 @@ CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(HlkFm22xComponent),
cv.Optional(CONF_ON_FACE_SCAN_MATCHED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FaceScanMatchedTrigger
),
}
),
cv.Optional(CONF_ON_FACE_SCAN_MATCHED): automation.validate_automation({}),
cv.Optional(CONF_ON_FACE_SCAN_UNMATCHED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FaceScanUnmatchedTrigger
),
}
),
cv.Optional(CONF_ON_FACE_SCAN_INVALID): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
FaceScanInvalidTrigger
),
}
),
cv.Optional(CONF_ON_FACE_INFO): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(FaceInfoTrigger),
}
),
cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
EnrollmentDoneTrigger
),
}
),
cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
EnrollmentFailedTrigger
),
}
{}
),
cv.Optional(CONF_ON_FACE_SCAN_INVALID): automation.validate_automation({}),
cv.Optional(CONF_ON_FACE_INFO): automation.validate_automation({}),
cv.Optional(CONF_ON_ENROLLMENT_DONE): automation.validate_automation({}),
cv.Optional(CONF_ON_ENROLLMENT_FAILED): automation.validate_automation({}),
}
)
.extend(cv.polling_component_schema("50ms"))
@@ -118,23 +58,27 @@ async def to_code(config):
await uart.register_uart_device(var, config)
for conf in config.get(CONF_ON_FACE_SCAN_MATCHED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.int16, "face_id"), (cg.std_string, "name")], conf
await automation.build_callback_automation(
var,
"add_on_face_scan_matched_callback",
[(cg.int16, "face_id"), (cg.std_string, "name")],
conf,
)
for conf in config.get(CONF_ON_FACE_SCAN_UNMATCHED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_face_scan_unmatched_callback", [], conf
)
for conf in config.get(CONF_ON_FACE_SCAN_INVALID, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.uint8, "error")], conf)
await automation.build_callback_automation(
var, "add_on_face_scan_invalid_callback", [(cg.uint8, "error")], conf
)
for conf in config.get(CONF_ON_FACE_INFO, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger,
await automation.build_callback_automation(
var,
"add_on_face_info_callback",
[
(cg.int16, "status"),
(cg.int16, "left"),
@@ -149,14 +93,17 @@ async def to_code(config):
)
for conf in config.get(CONF_ON_ENROLLMENT_DONE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.int16, "face_id"), (cg.uint8, "direction")], conf
await automation.build_callback_automation(
var,
"add_on_enrollment_done_callback",
[(cg.int16, "face_id"), (cg.uint8, "direction")],
conf,
)
for conf in config.get(CONF_ON_ENROLLMENT_FAILED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.uint8, "error")], conf)
await automation.build_callback_automation(
var, "add_on_enrollment_failed_callback", [(cg.uint8, "error")], conf
)
@automation.register_action(
-46
View File
@@ -141,52 +141,6 @@ class HlkFm22xComponent : public PollingComponent, public uart::UARTDevice {
CallbackManager<void(uint8_t)> enrollment_failed_callback_;
};
class FaceScanMatchedTrigger : public Trigger<int16_t, std::string> {
public:
explicit FaceScanMatchedTrigger(HlkFm22xComponent *parent) {
parent->add_on_face_scan_matched_callback(
[this](int16_t face_id, const std::string &name) { this->trigger(face_id, name); });
}
};
class FaceScanUnmatchedTrigger : public Trigger<> {
public:
explicit FaceScanUnmatchedTrigger(HlkFm22xComponent *parent) {
parent->add_on_face_scan_unmatched_callback([this]() { this->trigger(); });
}
};
class FaceScanInvalidTrigger : public Trigger<uint8_t> {
public:
explicit FaceScanInvalidTrigger(HlkFm22xComponent *parent) {
parent->add_on_face_scan_invalid_callback([this](uint8_t error) { this->trigger(error); });
}
};
class FaceInfoTrigger : public Trigger<int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t, int16_t> {
public:
explicit FaceInfoTrigger(HlkFm22xComponent *parent) {
parent->add_on_face_info_callback(
[this](int16_t status, int16_t left, int16_t top, int16_t right, int16_t bottom, int16_t yaw, int16_t pitch,
int16_t roll) { this->trigger(status, left, top, right, bottom, yaw, pitch, roll); });
}
};
class EnrollmentDoneTrigger : public Trigger<int16_t, uint8_t> {
public:
explicit EnrollmentDoneTrigger(HlkFm22xComponent *parent) {
parent->add_on_enrollment_done_callback(
[this](int16_t face_id, uint8_t direction) { this->trigger(face_id, direction); });
}
};
class EnrollmentFailedTrigger : public Trigger<uint8_t> {
public:
explicit EnrollmentFailedTrigger(HlkFm22xComponent *parent) {
parent->add_on_enrollment_failed_callback([this](uint8_t error) { this->trigger(error); });
}
};
template<typename... Ts> class EnrollmentAction : public Action<Ts...>, public Parented<HlkFm22xComponent> {
public:
TEMPLATABLE_VALUE(std::string, name)
+4
View File
@@ -101,9 +101,13 @@ class InfraredTraits {
bool get_supports_receiver() const { return this->supports_receiver_; }
void set_supports_receiver(bool supports) { this->supports_receiver_ = supports; }
uint32_t get_receiver_frequency_hz() const { return this->receiver_frequency_hz_; }
void set_receiver_frequency_hz(uint32_t freq) { this->receiver_frequency_hz_ = freq; }
protected:
bool supports_transmitter_{false};
bool supports_receiver_{false};
uint32_t receiver_frequency_hz_{0}; // Demodulation frequency of the IR receiver in Hz (0 = unspecified)
};
/// Infrared - Base class for infrared remote control implementations
+14 -1
View File
@@ -4,6 +4,7 @@ from typing import Any
import esphome.codegen as cg
from esphome.components import infrared, remote_receiver, remote_transmitter
from esphome.components.const import CONF_RECEIVER_FREQUENCY
import esphome.config_validation as cv
from esphome.const import CONF_CARRIER_DUTY_PERCENT, CONF_FREQUENCY
import esphome.final_validate as fv
@@ -19,6 +20,7 @@ CONFIG_SCHEMA = cv.All(
infrared.infrared_schema(IrRfProxy).extend(
{
cv.Optional(CONF_FREQUENCY, default=0): cv.frequency,
cv.Optional(CONF_RECEIVER_FREQUENCY): cv.frequency,
cv.Optional(CONF_REMOTE_RECEIVER_ID): cv.use_id(
remote_receiver.RemoteReceiverComponent
),
@@ -33,7 +35,14 @@ CONFIG_SCHEMA = cv.All(
def _final_validate(config: dict[str, Any]) -> None:
"""Validate that transmitters have a proper carrier duty cycle."""
# Only validate if this is an infrared (not RF) configuration with a transmitter
# receiver_frequency is only meaningful for receiver configurations
if CONF_RECEIVER_FREQUENCY in config and CONF_REMOTE_RECEIVER_ID not in config:
raise cv.Invalid(
f"'{CONF_RECEIVER_FREQUENCY}' can only be used with '{CONF_REMOTE_RECEIVER_ID}', "
"not with a transmitter"
)
# Only validate duty cycle if this is an infrared (not RF) configuration with a transmitter
if config.get(CONF_FREQUENCY, 0) != 0 or CONF_REMOTE_TRANSMITTER_ID not in config:
return
@@ -75,3 +84,7 @@ async def to_code(config: dict[str, Any]) -> None:
if CONF_REMOTE_RECEIVER_ID in config:
receiver = await cg.get_variable(config[CONF_REMOTE_RECEIVER_ID])
cg.add(var.set_receiver(receiver))
# Set receiver demodulation frequency if specified (metadata only, no hardware effect)
if CONF_RECEIVER_FREQUENCY in config:
cg.add(var.set_receiver_frequency(config[CONF_RECEIVER_FREQUENCY]))
@@ -22,6 +22,9 @@ class IrRfProxy : public infrared::Infrared {
/// Check if this is RF mode (non-zero frequency)
bool is_rf() const { return this->frequency_khz_ > 0; }
/// Set the receiver's hardware demodulation frequency in Hz (metadata only, does not affect hardware)
void set_receiver_frequency(uint32_t frequency_hz) { this->get_traits().set_receiver_frequency_hz(frequency_hz); }
protected:
// RF frequency in kHz (Hz / 1000); 0 = infrared, non-zero = RF
uint32_t frequency_khz_{0};
+5 -9
View File
@@ -2,7 +2,7 @@ from esphome import automation
import esphome.codegen as cg
from esphome.components import uart
import esphome.config_validation as cv
from esphome.const import CONF_ID, CONF_ON_DATA, CONF_THROTTLE, CONF_TRIGGER_ID
from esphome.const import CONF_ID, CONF_ON_DATA, CONF_THROTTLE
AUTO_LOAD = ["ld24xx"]
DEPENDENCIES = ["uart"]
@@ -12,7 +12,6 @@ MULTI_CONF = True
ld2450_ns = cg.esphome_ns.namespace("ld2450")
LD2450Component = ld2450_ns.class_("LD2450Component", cg.Component, uart.UARTDevice)
LD2450DataTrigger = ld2450_ns.class_("LD2450DataTrigger", automation.Trigger.template())
CONF_LD2450_ID = "ld2450_id"
@@ -23,11 +22,7 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_THROTTLE): cv.invalid(
f"{CONF_THROTTLE} has been removed; use per-sensor filters, instead"
),
cv.Optional(CONF_ON_DATA): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LD2450DataTrigger),
}
),
cv.Optional(CONF_ON_DATA): automation.validate_automation({}),
}
)
.extend(uart.UART_DEVICE_SCHEMA)
@@ -54,5 +49,6 @@ async def to_code(config):
await cg.register_component(var, config)
await uart.register_uart_device(var, config)
for conf in config.get(CONF_ON_DATA, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_data_callback", [], conf
)
-8
View File
@@ -1,6 +1,5 @@
#pragma once
#include "esphome/core/automation.h"
#include "esphome/core/defines.h"
#include "esphome/core/component.h"
#ifdef USE_SENSOR
@@ -201,11 +200,4 @@ class LD2450Component : public Component, public uart::UARTDevice {
LazyCallbackManager<void()> data_callback_;
};
class LD2450DataTrigger : public Trigger<> {
public:
explicit LD2450DataTrigger(LD2450Component *parent) {
parent->add_on_data_callback([this]() { this->trigger(); });
}
};
} // namespace esphome::ld2450
+75
View File
@@ -24,6 +24,7 @@ from esphome.const import (
CONF_ID,
CONF_INITIAL_STATE,
CONF_MQTT_ID,
CONF_NAME,
CONF_ON_STATE,
CONF_ON_TURN_OFF,
CONF_ON_TURN_ON,
@@ -41,6 +42,8 @@ from esphome.const import (
from esphome.core import CORE, ID, CoroPriority, HexInt, Lambda, coroutine_with_priority
from esphome.core.entity_helpers import entity_duplicate_validator, setup_entity
from esphome.cpp_generator import MockObjClass
import esphome.final_validate as fv
from esphome.types import ConfigType
from .automation import LIGHT_STATE_SCHEMA
from .effects import (
@@ -70,9 +73,19 @@ IS_PLATFORM_COMPONENT = True
DOMAIN = "light"
@dataclass
class EffectRef:
"""A pending effect name reference from a light action to validate."""
light_id: ID
effect_name: str
component_path: list[str | int] # path_context when the action was validated
@dataclass
class LightData:
gamma_tables: dict = field(default_factory=dict) # gamma_value -> fwd_arr
effect_refs: list[EffectRef] = field(default_factory=list)
def _get_data() -> LightData:
@@ -115,6 +128,68 @@ def _get_or_create_gamma_table(gamma_correct):
return fwd_arr
def find_effect_index(effects: list, effect_name: str) -> int | None:
"""Find the 1-based index of an effect by name (case-insensitive).
Returns the 1-based index if found, or None if not found.
"""
effect_name_lower = effect_name.lower()
for i, effect_conf in enumerate(effects):
key = next(iter(effect_conf))
if effect_conf[key][CONF_NAME].lower() == effect_name_lower:
return i + 1
return None
def available_effects_str(effects: list) -> str:
"""Return a comma-separated string of available effect names."""
available = [
effect_conf[next(iter(effect_conf))][CONF_NAME] for effect_conf in effects
]
return ", ".join(f"'{name}'" for name in available) if available else "none"
def _final_validate(config: ConfigType) -> ConfigType:
"""Validate all recorded effect name references against their target lights.
This runs once per light platform instance. If no light platform is configured,
this never runs but the ID validator will catch the missing light ID separately.
"""
data = _get_data()
if not data.effect_refs:
return config
# Drain the list so we only validate once even though
# FINAL_VALIDATE_SCHEMA runs for each light platform instance.
refs = data.effect_refs
data.effect_refs = []
fconf = fv.full_config.get()
for ref in refs:
try:
light_path = fconf.get_path_for_id(ref.light_id)[:-1]
light_config = fconf.get_config_for_path(light_path)
except KeyError:
# Light ID not found — ID validation will have already reported this
continue
effects = light_config.get(CONF_EFFECTS, [])
if find_effect_index(effects, ref.effect_name) is None:
raise cv.FinalExternalInvalid(
f"Effect '{ref.effect_name}' not found for light "
f"'{ref.light_id}'. "
f"Available effects: {available_effects_str(effects)}",
path=[cv.ROOT_CONFIG_PATH] + ref.component_path,
)
return config
FINAL_VALIDATE_SCHEMA = _final_validate
LightRestoreMode = light_ns.enum("LightRestoreMode")
RESTORE_MODES = {
"RESTORE_DEFAULT_OFF": LightRestoreMode.LIGHT_RESTORE_DEFAULT_OFF,
+40 -9
View File
@@ -1,5 +1,6 @@
from esphome import automation
import esphome.codegen as cg
from esphome.config import path_context
import esphome.config_validation as cv
from esphome.const import (
CONF_BLUE,
@@ -17,7 +18,6 @@ from esphome.const import (
CONF_LIMIT_MODE,
CONF_MAX_BRIGHTNESS,
CONF_MIN_BRIGHTNESS,
CONF_NAME,
CONF_RANGE_FROM,
CONF_RANGE_TO,
CONF_RED,
@@ -26,7 +26,7 @@ from esphome.const import (
CONF_WARM_WHITE,
CONF_WHITE,
)
from esphome.core import CORE, Lambda
from esphome.core import CORE, EsphomeError, Lambda
from esphome.cpp_generator import LambdaExpression
from esphome.types import ConfigType
@@ -98,6 +98,31 @@ LIGHT_CONTROL_ACTION_SCHEMA = LIGHT_STATE_SCHEMA.extend(
}
)
def _record_effect_ref(config: ConfigType) -> ConfigType:
"""Record a static effect name reference for later cross-component validation."""
if CONF_EFFECT not in config:
return config
effect = config[CONF_EFFECT]
if isinstance(effect, Lambda):
return config # Lambda effects resolved at runtime
if effect.lower() == "none":
return config # "None" is always valid
from . import EffectRef, _get_data
_get_data().effect_refs.append(
EffectRef(
light_id=config[CONF_ID],
effect_name=effect,
component_path=path_context.get(),
)
)
return config
LIGHT_CONTROL_ACTION_SCHEMA.add_extra(_record_effect_ref)
LIGHT_TURN_OFF_ACTION_SCHEMA = automation.maybe_simple_id(
{
cv.Required(CONF_ID): cv.use_id(LightState),
@@ -122,18 +147,24 @@ def _resolve_effect_index(config: ConfigType) -> int:
Effect index 0 means "None" (no effect). Effects are 1-indexed matching
the C++ convention in LightState.
"""
from . import available_effects_str, find_effect_index
original_name = config[CONF_EFFECT]
effect_name = original_name.lower()
if effect_name == "none":
if original_name.lower() == "none":
return 0
light_id = config[CONF_ID]
light_path = CORE.config.get_path_for_id(light_id)[:-1]
light_config = CORE.config.get_config_for_path(light_path)
for i, effect_conf in enumerate(light_config.get(CONF_EFFECTS, [])):
key = next(iter(effect_conf))
if effect_conf[key][CONF_NAME].lower() == effect_name:
return i + 1
raise ValueError(f"Effect '{original_name}' not found in light '{light_id}'")
effects = light_config.get(CONF_EFFECTS, [])
index = find_effect_index(effects, original_name)
if index is not None:
return index
# Should never reach here — effect names are validated during config
# validation in FINAL_VALIDATE_SCHEMA. This is a safety net.
raise EsphomeError(
f"Effect '{original_name}' not found for light '{light_id}'. "
f"Available effects: {available_effects_str(effects)}"
)
@automation.register_action(
+3 -3
View File
@@ -385,7 +385,7 @@ void LightCall::transform_parameters_() {
!(this->color_mode_ & ColorCapability::WHITE) && //
!(this->color_mode_ & ColorCapability::COLOR_TEMPERATURE) && //
min_mireds > 0.0f && max_mireds > 0.0f) {
ESP_LOGD(TAG, "'%s': setting cold/warm white channels using white/color temperature values",
ESP_LOGV(TAG, "'%s': setting cold/warm white channels using white/color temperature values",
this->parent_->get_name().c_str());
// Only compute cold_white/warm_white from color_temperature if they're not already explicitly set.
// This is important for state restoration, where both color_temperature and cold_white/warm_white
@@ -432,7 +432,7 @@ ColorMode LightCall::compute_color_mode_() {
// Don't change if the current mode is in the intersection (suitable AND supported)
if (ColorModeMask::mask_contains(intersection, current_mode)) {
ESP_LOGI(TAG, "'%s': color mode not specified; retaining %s", this->parent_->get_name().c_str(),
ESP_LOGV(TAG, "'%s': color mode not specified; retaining %s", this->parent_->get_name().c_str(),
LOG_STR_ARG(color_mode_to_human(current_mode)));
return current_mode;
}
@@ -440,7 +440,7 @@ ColorMode LightCall::compute_color_mode_() {
// Use the preferred suitable mode.
if (intersection != 0) {
ColorMode mode = ColorModeMask::first_value_from_mask(intersection);
ESP_LOGI(TAG, "'%s': color mode not specified; using %s", this->parent_->get_name().c_str(),
ESP_LOGV(TAG, "'%s': color mode not specified; using %s", this->parent_->get_name().c_str(),
LOG_STR_ARG(color_mode_to_human(mode)));
return mode;
}
+15 -19
View File
@@ -10,7 +10,6 @@ from esphome.const import (
CONF_MQTT_ID,
CONF_ON_LOCK,
CONF_ON_UNLOCK,
CONF_TRIGGER_ID,
CONF_WEB_SERVER,
)
from esphome.core import CORE, CoroPriority, coroutine_with_priority
@@ -31,8 +30,7 @@ OpenAction = lock_ns.class_("OpenAction", automation.Action)
LockPublishAction = lock_ns.class_("LockPublishAction", automation.Action)
LockCondition = lock_ns.class_("LockCondition", Condition)
LockLockTrigger = lock_ns.class_("LockLockTrigger", automation.Trigger.template())
LockUnlockTrigger = lock_ns.class_("LockUnlockTrigger", automation.Trigger.template())
LockStateForwarder = lock_ns.class_("LockStateForwarder")
LockState = lock_ns.enum("LockState")
@@ -52,16 +50,8 @@ _LOCK_SCHEMA = (
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTLockComponent),
cv.Optional(CONF_ON_LOCK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockLockTrigger),
}
),
cv.Optional(CONF_ON_UNLOCK): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LockUnlockTrigger),
}
),
cv.Optional(CONF_ON_LOCK): automation.validate_automation({}),
cv.Optional(CONF_ON_UNLOCK): automation.validate_automation({}),
}
)
)
@@ -93,12 +83,18 @@ def lock_schema(
@setup_entity("lock")
async def _setup_lock_core(var, config):
for conf in config.get(CONF_ON_LOCK, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf in config.get(CONF_ON_UNLOCK, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
for conf_key, state_enum in (
(CONF_ON_LOCK, LockState.LOCK_STATE_LOCKED),
(CONF_ON_UNLOCK, LockState.LOCK_STATE_UNLOCKED),
):
for conf in config.get(conf_key, []):
await automation.build_callback_automation(
var,
"add_on_state_callback",
[],
conf,
forwarder=LockStateForwarder.template(state_enum),
)
if mqtt_id := config.get(CONF_MQTT_ID):
mqtt_ = cg.new_Pvariable(mqtt_id, var)
+9 -13
View File
@@ -49,21 +49,17 @@ template<typename... Ts> class LockCondition : public Condition<Ts...> {
bool state_;
};
template<LockState State> class LockStateTrigger : public Trigger<> {
public:
explicit LockStateTrigger(Lock *a_lock) : lock_(a_lock) {
a_lock->add_on_state_callback([this]() {
if (this->lock_->state == State) {
this->trigger();
}
});
/// Callback forwarder that triggers an Automation<> only when a specific lock state is entered.
/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_.
template<LockState State> struct LockStateForwarder {
Automation<> *automation;
void operator()(LockState state) const {
if (state == State)
this->automation->trigger();
}
protected:
Lock *lock_;
};
using LockLockTrigger = LockStateTrigger<LockState::LOCK_STATE_LOCKED>;
using LockUnlockTrigger = LockStateTrigger<LockState::LOCK_STATE_UNLOCKED>;
static_assert(sizeof(LockStateForwarder<LockState::LOCK_STATE_LOCKED>) <= sizeof(void *));
static_assert(std::is_trivially_copyable_v<LockStateForwarder<LockState::LOCK_STATE_LOCKED>>);
} // namespace esphome::lock
+1 -1
View File
@@ -42,7 +42,7 @@ void Lock::publish_state(LockState state) {
this->state = state;
this->rtc_.save(&this->state);
ESP_LOGV(TAG, "'%s' >> %s", this->name_.c_str(), LOG_STR_ARG(lock_state_to_string(state)));
this->state_callback_.call();
this->state_callback_.call(state);
#if defined(USE_LOCK) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_lock_update(this);
#endif
+2 -2
View File
@@ -148,7 +148,7 @@ class Lock : public EntityBase {
/** Set callback for state changes.
*
* @param callback The void(bool) callback.
* @param callback The void(LockState) callback.
*/
template<typename F> void add_on_state_callback(F &&callback) {
this->state_callback_.add(std::forward<F>(callback));
@@ -178,7 +178,7 @@ class Lock : public EntityBase {
*/
virtual void control(const LockCall &call) = 0;
LazyCallbackManager<void()> state_callback_{};
LazyCallbackManager<void(LockState)> state_callback_{};
Deduplicator<LockState> publish_dedup_;
ESPPreferenceObject rtc_;
};
+10
View File
@@ -243,6 +243,16 @@ void Logger::dump_config() {
#endif
#ifdef USE_ZEPHYR
dump_crash_();
#endif
// Warn users that VERBOSE/VERY_VERBOSE logging impacts performance.
// Only the compiled log level matters — all log calls up to this level
// are in the binary and will be formatted (vsnprintf) and block UART.
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERY_VERBOSE
ESP_LOGW(TAG, "VERY_VERBOSE logging is active — significant performance impact, short-term debugging only\n"
" May cause connection instability. Set log level to DEBUG or lower for long-term use.");
#elif ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
ESP_LOGI(TAG, "VERBOSE logging is active — performance impact, short-term debugging only\n"
" Set log level to DEBUG or lower for long-term use.");
#endif
}
+8 -28
View File
@@ -58,6 +58,14 @@ class LTRAlsPs501Component : public PollingComponent, public i2c::I2CDevice {
void set_actual_integration_time_sensor(sensor::Sensor *sensor) { this->actual_integration_time_sensor_ = sensor; }
void set_proximity_counts_sensor(sensor::Sensor *sensor) { this->proximity_counts_sensor_ = sensor; }
template<typename F> void add_on_ps_high_trigger_callback(F &&callback) {
this->on_ps_high_trigger_callback_.add(std::forward<F>(callback));
}
template<typename F> void add_on_ps_low_trigger_callback(F &&callback) {
this->on_ps_low_trigger_callback_.add(std::forward<F>(callback));
}
protected:
//
// Internal state machine, used to split all the actions into
@@ -151,36 +159,8 @@ class LTRAlsPs501Component : public PollingComponent, public i2c::I2CDevice {
}
bool is_any_ps_sensor_enabled_() const { return this->proximity_counts_sensor_ != nullptr; }
//
// Trigger section for the automations
//
friend class LTRPsHighTrigger;
friend class LTRPsLowTrigger;
CallbackManager<void()> on_ps_high_trigger_callback_;
CallbackManager<void()> on_ps_low_trigger_callback_;
template<typename F> void add_on_ps_high_trigger_callback_(F &&callback) {
this->on_ps_high_trigger_callback_.add(std::forward<F>(callback));
}
template<typename F> void add_on_ps_low_trigger_callback_(F &&callback) {
this->on_ps_low_trigger_callback_.add(std::forward<F>(callback));
}
};
class LTRPsHighTrigger : public Trigger<> {
public:
explicit LTRPsHighTrigger(LTRAlsPs501Component *parent) {
parent->add_on_ps_high_trigger_callback_([this]() { this->trigger(); });
}
};
class LTRPsLowTrigger : public Trigger<> {
public:
explicit LTRPsLowTrigger(LTRAlsPs501Component *parent) {
parent->add_on_ps_low_trigger_callback_([this]() { this->trigger(); });
}
};
} // namespace ltr501
} // namespace esphome
+10 -21
View File
@@ -14,7 +14,6 @@ from esphome.const import (
CONF_INTEGRATION_TIME,
CONF_NAME,
CONF_REPEAT,
CONF_TRIGGER_ID,
CONF_TYPE,
DEVICE_CLASS_DISTANCE,
DEVICE_CLASS_ILLUMINANCE,
@@ -87,9 +86,6 @@ PS_GAINS = {
"16X": PsGain.PS_GAIN_16,
}
LTRPsHighTrigger = ltr501_ns.class_("LTRPsHighTrigger", automation.Trigger.template())
LTRPsLowTrigger = ltr501_ns.class_("LTRPsLowTrigger", automation.Trigger.template())
def validate_integration_time(value):
value = cv.positive_time_period_milliseconds(value).total_milliseconds
@@ -146,16 +142,8 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_PS_LOW_THRESHOLD, default=0): cv.int_range(
min=0, max=65535
),
cv.Optional(CONF_ON_PS_HIGH_THRESHOLD): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTRPsHighTrigger),
}
),
cv.Optional(CONF_ON_PS_LOW_THRESHOLD): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTRPsLowTrigger),
}
),
cv.Optional(CONF_ON_PS_HIGH_THRESHOLD): automation.validate_automation({}),
cv.Optional(CONF_ON_PS_LOW_THRESHOLD): automation.validate_automation({}),
cv.Optional(CONF_AMBIENT_LIGHT): cv.maybe_simple_value(
sensor.sensor_schema(
unit_of_measurement=UNIT_LUX,
@@ -252,13 +240,14 @@ async def to_code(config):
sens = await sensor.new_sensor(prox_cnt_config)
cg.add(var.set_proximity_counts_sensor(sens))
for prox_high_tr in config.get(CONF_ON_PS_HIGH_THRESHOLD, []):
trigger = cg.new_Pvariable(prox_high_tr[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], prox_high_tr)
for prox_low_tr in config.get(CONF_ON_PS_LOW_THRESHOLD, []):
trigger = cg.new_Pvariable(prox_low_tr[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], prox_low_tr)
for conf in config.get(CONF_ON_PS_HIGH_THRESHOLD, []):
await automation.build_callback_automation(
var, "add_on_ps_high_trigger_callback", [], conf
)
for conf in config.get(CONF_ON_PS_LOW_THRESHOLD, []):
await automation.build_callback_automation(
var, "add_on_ps_low_trigger_callback", [], conf
)
cg.add(var.set_ltr_type(config[CONF_TYPE]))
+8 -28
View File
@@ -58,6 +58,14 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice {
void set_actual_integration_time_sensor(sensor::Sensor *sensor) { this->actual_integration_time_sensor_ = sensor; }
void set_proximity_counts_sensor(sensor::Sensor *sensor) { this->proximity_counts_sensor_ = sensor; }
template<typename F> void add_on_ps_high_trigger_callback(F &&callback) {
this->on_ps_high_trigger_callback_.add(std::forward<F>(callback));
}
template<typename F> void add_on_ps_low_trigger_callback(F &&callback) {
this->on_ps_low_trigger_callback_.add(std::forward<F>(callback));
}
protected:
//
// Internal state machine, used to split all the actions into
@@ -151,36 +159,8 @@ class LTRAlsPsComponent : public PollingComponent, public i2c::I2CDevice {
}
bool is_any_ps_sensor_enabled_() const { return this->proximity_counts_sensor_ != nullptr; }
//
// Trigger section for the automations
//
friend class LTRPsHighTrigger;
friend class LTRPsLowTrigger;
CallbackManager<void()> on_ps_high_trigger_callback_;
CallbackManager<void()> on_ps_low_trigger_callback_;
template<typename F> void add_on_ps_high_trigger_callback_(F &&callback) {
this->on_ps_high_trigger_callback_.add(std::forward<F>(callback));
}
template<typename F> void add_on_ps_low_trigger_callback_(F &&callback) {
this->on_ps_low_trigger_callback_.add(std::forward<F>(callback));
}
};
class LTRPsHighTrigger : public Trigger<> {
public:
explicit LTRPsHighTrigger(LTRAlsPsComponent *parent) {
parent->add_on_ps_high_trigger_callback_([this]() { this->trigger(); });
}
};
class LTRPsLowTrigger : public Trigger<> {
public:
explicit LTRPsLowTrigger(LTRAlsPsComponent *parent) {
parent->add_on_ps_low_trigger_callback_([this]() { this->trigger(); });
}
};
} // namespace ltr_als_ps
} // namespace esphome
+10 -23
View File
@@ -14,7 +14,6 @@ from esphome.const import (
CONF_INTEGRATION_TIME,
CONF_NAME,
CONF_REPEAT,
CONF_TRIGGER_ID,
CONF_TYPE,
DEVICE_CLASS_ILLUMINANCE,
ICON_BRIGHTNESS_5,
@@ -93,11 +92,6 @@ PS_GAINS = {
"64X": PsGain.PS_GAIN_64,
}
LTRPsHighTrigger = ltr_als_ps_ns.class_(
"LTRPsHighTrigger", automation.Trigger.template()
)
LTRPsLowTrigger = ltr_als_ps_ns.class_("LTRPsLowTrigger", automation.Trigger.template())
def validate_integration_time(value):
value = cv.positive_time_period_milliseconds(value).total_milliseconds
@@ -143,16 +137,8 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(CONF_PS_LOW_THRESHOLD, default=0): cv.int_range(
min=0, max=65535
),
cv.Optional(CONF_ON_PS_HIGH_THRESHOLD): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTRPsHighTrigger),
}
),
cv.Optional(CONF_ON_PS_LOW_THRESHOLD): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(LTRPsLowTrigger),
}
),
cv.Optional(CONF_ON_PS_HIGH_THRESHOLD): automation.validate_automation({}),
cv.Optional(CONF_ON_PS_LOW_THRESHOLD): automation.validate_automation({}),
cv.Optional(CONF_AMBIENT_LIGHT): cv.maybe_simple_value(
sensor.sensor_schema(
unit_of_measurement=UNIT_LUX,
@@ -244,13 +230,14 @@ async def to_code(config):
sens = await sensor.new_sensor(prox_cnt_config)
cg.add(var.set_proximity_counts_sensor(sens))
for prox_high_tr in config.get(CONF_ON_PS_HIGH_THRESHOLD, []):
trigger = cg.new_Pvariable(prox_high_tr[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], prox_high_tr)
for prox_low_tr in config.get(CONF_ON_PS_LOW_THRESHOLD, []):
trigger = cg.new_Pvariable(prox_low_tr[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], prox_low_tr)
for conf in config.get(CONF_ON_PS_HIGH_THRESHOLD, []):
await automation.build_callback_automation(
var, "add_on_ps_high_trigger_callback", [], conf
)
for conf in config.get(CONF_ON_PS_LOW_THRESHOLD, []):
await automation.build_callback_automation(
var, "add_on_ps_low_trigger_callback", [], conf
)
cg.add(var.set_ltr_type(config[CONF_TYPE]))
+9 -9
View File
@@ -8,17 +8,17 @@ namespace max44009 {
static const char *const TAG = "max44009.sensor";
// REGISTERS
static const uint8_t MAX44009_REGISTER_CONFIGURATION = 0x02;
static const uint8_t MAX44009_LUX_READING_HIGH = 0x03;
static const uint8_t MAX44009_LUX_READING_LOW = 0x04;
static constexpr uint8_t MAX44009_REGISTER_CONFIGURATION = 0x02;
static constexpr uint8_t MAX44009_LUX_READING_HIGH = 0x03;
static constexpr uint8_t MAX44009_LUX_READING_LOW = 0x04;
// CONFIGURATION MASKS
static const uint8_t MAX44009_CFG_CONTINUOUS = 0x80;
static constexpr uint8_t MAX44009_CFG_CONTINUOUS = 0x80;
// ERROR CODES
static const uint8_t MAX44009_OK = 0;
static const uint8_t MAX44009_ERROR_WIRE_REQUEST = -10;
static const uint8_t MAX44009_ERROR_OVERFLOW = -20;
static const uint8_t MAX44009_ERROR_HIGH_BYTE = -30;
static const uint8_t MAX44009_ERROR_LOW_BYTE = -31;
static constexpr int8_t MAX44009_OK = 0;
static constexpr int8_t MAX44009_ERROR_WIRE_REQUEST = -10;
static constexpr int8_t MAX44009_ERROR_OVERFLOW = -20;
static constexpr int8_t MAX44009_ERROR_HIGH_BYTE = -30;
static constexpr int8_t MAX44009_ERROR_LOW_BYTE = -31;
void MAX44009Sensor::setup() {
bool state_ok = false;
+2 -2
View File
@@ -28,8 +28,8 @@ class MAX44009Sensor : public sensor::Sensor, public PollingComponent, public i2
uint8_t read_(uint8_t reg);
void write_(uint8_t reg, uint8_t value);
int error_;
MAX44009Mode mode_;
int8_t error_{0};
MAX44009Mode mode_{MAX44009_MODE_AUTO};
};
} // namespace max44009
+3 -1
View File
@@ -6,6 +6,8 @@ namespace mcp23008 {
static const char *const TAG = "mcp23008";
static constexpr uint8_t IOCON_ODR = 0x04; // Open-drain output for INT pin
void MCP23008::setup() {
uint8_t iocon;
if (!this->read_reg(mcp23x08_base::MCP23X08_IOCON, &iocon)) {
@@ -18,7 +20,7 @@ void MCP23008::setup() {
if (this->open_drain_ints_) {
// enable open-drain interrupt pins, 3.3V-safe
this->write_reg(mcp23x08_base::MCP23X08_IOCON, 0x04);
this->write_reg(mcp23x08_base::MCP23X08_IOCON, iocon | IOCON_ODR);
}
}
+4 -2
View File
@@ -6,6 +6,8 @@ namespace mcp23017 {
static const char *const TAG = "mcp23017";
static constexpr uint8_t IOCON_ODR = 0x04; // Open-drain output for INT pin
void MCP23017::setup() {
uint8_t iocon;
if (!this->read_reg(mcp23x17_base::MCP23X17_IOCONA, &iocon)) {
@@ -19,8 +21,8 @@ void MCP23017::setup() {
if (this->open_drain_ints_) {
// enable open-drain interrupt pins, 3.3V-safe
this->write_reg(mcp23x17_base::MCP23X17_IOCONA, 0x04);
this->write_reg(mcp23x17_base::MCP23X17_IOCONB, 0x04);
this->write_reg(mcp23x17_base::MCP23X17_IOCONA, iocon | IOCON_ODR);
this->write_reg(mcp23x17_base::MCP23X17_IOCONB, iocon | IOCON_ODR);
}
}
+10 -5
View File
@@ -6,6 +6,11 @@ namespace mcp23s08 {
static const char *const TAG = "mcp23s08";
// IOCON register bits
static constexpr uint8_t IOCON_SEQOP = 0x20; // Sequential operation mode
static constexpr uint8_t IOCON_HAEN = 0x08; // Hardware address enable
static constexpr uint8_t IOCON_ODR = 0x04; // Open-drain output for INT pin
void MCP23S08::set_device_address(uint8_t device_addr) {
if (device_addr != 0) {
this->device_opcode_ |= ((device_addr & 0x03) << 1);
@@ -15,19 +20,19 @@ void MCP23S08::set_device_address(uint8_t device_addr) {
void MCP23S08::setup() {
this->spi_setup();
// Enable HAEN (broadcast to all chips since HAEN isn't active yet)
this->enable();
uint8_t cmd = 0b01000000;
this->transfer_byte(cmd);
this->transfer_byte(0b01000000);
this->transfer_byte(mcp23x08_base::MCP23X08_IOCON);
this->transfer_byte(0b00011000); // Enable HAEN pins for addressing
this->transfer_byte(IOCON_SEQOP | IOCON_HAEN);
this->disable();
// Read current output register state
this->read_reg(mcp23x08_base::MCP23X08_OLAT, &this->olat_);
if (this->open_drain_ints_) {
// enable open-drain interrupt pins, 3.3V-safe
this->write_reg(mcp23x08_base::MCP23X08_IOCON, 0x04);
// enable open-drain interrupt pins, 3.3V-safe (addressed, only this chip)
this->write_reg(mcp23x08_base::MCP23X08_IOCON, IOCON_SEQOP | IOCON_HAEN | IOCON_ODR);
}
}
+13 -9
View File
@@ -6,6 +6,11 @@ namespace mcp23s17 {
static const char *const TAG = "mcp23s17";
// IOCON register bits
static constexpr uint8_t IOCON_SEQOP = 0x20; // Sequential operation mode
static constexpr uint8_t IOCON_HAEN = 0x08; // Hardware address enable
static constexpr uint8_t IOCON_ODR = 0x04; // Open-drain output for INT pin
void MCP23S17::set_device_address(uint8_t device_addr) {
if (device_addr != 0) {
this->device_opcode_ |= ((device_addr & 0b111) << 1);
@@ -15,18 +20,17 @@ void MCP23S17::set_device_address(uint8_t device_addr) {
void MCP23S17::setup() {
this->spi_setup();
// Enable HAEN (broadcast to addresses 0 and 4 since HAEN isn't active yet)
this->enable();
uint8_t cmd = 0b01000000;
this->transfer_byte(cmd);
this->transfer_byte(0b01000000);
this->transfer_byte(mcp23x17_base::MCP23X17_IOCONA);
this->transfer_byte(0b00011000); // Enable HAEN pins for addressing
this->transfer_byte(IOCON_SEQOP | IOCON_HAEN);
this->disable();
this->enable();
cmd = 0b01001000;
this->transfer_byte(cmd);
this->transfer_byte(0b01001000);
this->transfer_byte(mcp23x17_base::MCP23X17_IOCONA);
this->transfer_byte(0b00011000); // Enable HAEN pins for addressing
this->transfer_byte(IOCON_SEQOP | IOCON_HAEN);
this->disable();
// Read current output register state
@@ -34,9 +38,9 @@ void MCP23S17::setup() {
this->read_reg(mcp23x17_base::MCP23X17_OLATB, &this->olat_b_);
if (this->open_drain_ints_) {
// enable open-drain interrupt pins, 3.3V-safe
this->write_reg(mcp23x17_base::MCP23X17_IOCONA, 0x04);
this->write_reg(mcp23x17_base::MCP23X17_IOCONB, 0x04);
// enable open-drain interrupt pins, 3.3V-safe (addressed, only this chip)
this->write_reg(mcp23x17_base::MCP23X17_IOCONA, IOCON_SEQOP | IOCON_HAEN | IOCON_ODR);
this->write_reg(mcp23x17_base::MCP23X17_IOCONB, IOCON_SEQOP | IOCON_HAEN | IOCON_ODR);
}
}
+22 -20
View File
@@ -9,7 +9,6 @@ from esphome.const import (
CONF_ON_STATE,
CONF_ON_TURN_OFF,
CONF_ON_TURN_ON,
CONF_TRIGGER_ID,
CONF_VOLUME,
)
from esphome.core import CORE
@@ -65,15 +64,19 @@ _COMMAND_ACTIONS = [
"clear_playlist",
]
# State triggers: (config_key, C++ class name)
StateAnyForwarder = media_player_ns.class_("StateAnyForwarder")
StateEnterForwarder = media_player_ns.class_("StateEnterForwarder")
MediaPlayerState = media_player_ns.enum("MediaPlayerState")
# State triggers: (config_key, state enum or None for any-state)
_STATE_TRIGGERS = [
(CONF_ON_STATE, "StateTrigger"),
(CONF_ON_IDLE, "IdleTrigger"),
(CONF_ON_PLAY, "PlayTrigger"),
(CONF_ON_PAUSE, "PauseTrigger"),
(CONF_ON_ANNOUNCEMENT, "AnnouncementTrigger"),
(CONF_ON_TURN_ON, "OnTrigger"),
(CONF_ON_TURN_OFF, "OffTrigger"),
(CONF_ON_STATE, None),
(CONF_ON_IDLE, MediaPlayerState.MEDIA_PLAYER_STATE_IDLE),
(CONF_ON_PLAY, MediaPlayerState.MEDIA_PLAYER_STATE_PLAYING),
(CONF_ON_PAUSE, MediaPlayerState.MEDIA_PLAYER_STATE_PAUSED),
(CONF_ON_ANNOUNCEMENT, MediaPlayerState.MEDIA_PLAYER_STATE_ANNOUNCING),
(CONF_ON_TURN_ON, MediaPlayerState.MEDIA_PLAYER_STATE_ON),
(CONF_ON_TURN_OFF, MediaPlayerState.MEDIA_PLAYER_STATE_OFF),
]
# State conditions that all share the same schema and codegen handler
@@ -98,10 +101,15 @@ VolumeSetAction = media_player_ns.class_(
@setup_entity("media_player")
async def setup_media_player_core_(var, config):
for conf_key, _ in _STATE_TRIGGERS:
for conf_key, state_enum in _STATE_TRIGGERS:
for conf in config.get(conf_key, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
if state_enum is None:
forwarder = StateAnyForwarder
else:
forwarder = StateEnterForwarder.template(state_enum)
await automation.build_callback_automation(
var, "add_on_state_callback", [], conf, forwarder=forwarder
)
async def register_media_player(var, config):
@@ -120,14 +128,8 @@ async def new_media_player(config, *args):
_MEDIA_PLAYER_SCHEMA = cv.ENTITY_BASE_SCHEMA.extend(
{
cv.Optional(conf_key): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
media_player_ns.class_(class_name, automation.Trigger.template())
),
}
)
for conf_key, class_name in _STATE_TRIGGERS
cv.Optional(conf_key): automation.validate_automation({})
for conf_key, _ in _STATE_TRIGGERS
}
)
+18 -23
View File
@@ -71,32 +71,27 @@ template<typename... Ts> class VolumeSetAction : public Action<Ts...>, public Pa
void play(const Ts &...x) override { this->parent_->make_call().set_volume(this->volume_.value(x...)).perform(); }
};
class StateTrigger : public Trigger<> {
public:
explicit StateTrigger(MediaPlayer *player) {
player->add_on_state_callback([this]() { this->trigger(); });
/// Callback forwarder that triggers an Automation<> on any state change.
/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_.
struct StateAnyForwarder {
Automation<> *automation;
void operator()(MediaPlayerState /*state*/) const { this->automation->trigger(); }
};
/// Callback forwarder that triggers an Automation<> only when a specific media player state is entered.
/// Pointer-sized (single Automation* field) to fit inline in Callback::ctx_.
template<MediaPlayerState State> struct StateEnterForwarder {
Automation<> *automation;
void operator()(MediaPlayerState state) const {
if (state == State)
this->automation->trigger();
}
};
template<MediaPlayerState State> class MediaPlayerStateTrigger : public Trigger<> {
public:
explicit MediaPlayerStateTrigger(MediaPlayer *player) : player_(player) {
player->add_on_state_callback([this]() {
if (this->player_->state == State)
this->trigger();
});
}
protected:
MediaPlayer *player_;
};
using IdleTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_IDLE>;
using PlayTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_PLAYING>;
using PauseTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_PAUSED>;
using AnnouncementTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_ANNOUNCING>;
using OnTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_ON>;
using OffTrigger = MediaPlayerStateTrigger<MediaPlayerState::MEDIA_PLAYER_STATE_OFF>;
static_assert(sizeof(StateAnyForwarder) <= sizeof(void *));
static_assert(std::is_trivially_copyable_v<StateAnyForwarder>);
static_assert(sizeof(StateEnterForwarder<MediaPlayerState::MEDIA_PLAYER_STATE_IDLE>) <= sizeof(void *));
static_assert(std::is_trivially_copyable_v<StateEnterForwarder<MediaPlayerState::MEDIA_PLAYER_STATE_IDLE>>);
template<typename... Ts> class IsIdleCondition : public Condition<Ts...>, public Parented<MediaPlayer> {
public:
@@ -199,7 +199,7 @@ MediaPlayerCall &MediaPlayerCall::set_announcement(bool announce) {
}
void MediaPlayer::publish_state() {
this->state_callback_.call();
this->state_callback_.call(this->state);
#if defined(USE_MEDIA_PLAYER) && defined(USE_CONTROLLER_REGISTRY)
ControllerRegistry::notify_media_player_update(this);
#endif
@@ -168,7 +168,7 @@ class MediaPlayer : public EntityBase {
virtual void control(const MediaPlayerCall &call) = 0;
LazyCallbackManager<void()> state_callback_{};
LazyCallbackManager<void(MediaPlayerState)> state_callback_{};
};
} // namespace media_player
+3 -3
View File
@@ -126,21 +126,21 @@ void MMC5603Component::update() {
int32_t raw_x = 0;
raw_x |= buffer[0] << 12;
raw_x |= buffer[1] << 4;
raw_x |= buffer[2] << 0;
raw_x |= buffer[2] & 0x0F;
const float x = 0.00625 * (raw_x - 524288);
int32_t raw_y = 0;
raw_y |= buffer[3] << 12;
raw_y |= buffer[4] << 4;
raw_y |= buffer[5] << 0;
raw_y |= buffer[5] & 0x0F;
const float y = 0.00625 * (raw_y - 524288);
int32_t raw_z = 0;
raw_z |= buffer[6] << 12;
raw_z |= buffer[7] << 4;
raw_z |= buffer[8] << 0;
raw_z |= buffer[8] & 0x0F;
const float z = 0.00625 * (raw_z - 524288);
@@ -5,14 +5,7 @@ import esphome.codegen as cg
from esphome.components import modbus
from esphome.components.const import CONF_ENABLED
import esphome.config_validation as cv
from esphome.const import (
CONF_ADDRESS,
CONF_ID,
CONF_LAMBDA,
CONF_NAME,
CONF_OFFSET,
CONF_TRIGGER_ID,
)
from esphome.const import CONF_ADDRESS, CONF_ID, CONF_LAMBDA, CONF_NAME, CONF_OFFSET
from esphome.cpp_helpers import logging
from .const import (
@@ -135,17 +128,6 @@ CPP_TYPE_REGISTER_MAP = {
"FP32_R": cg.float_,
}
ModbusCommandSentTrigger = modbus_controller_ns.class_(
"ModbusCommandSentTrigger", automation.Trigger.template(cg.int_, cg.int_)
)
ModbusOnlineTrigger = modbus_controller_ns.class_(
"ModbusOnlineTrigger", automation.Trigger.template(cg.int_, cg.int_)
)
ModbusOfflineTrigger = modbus_controller_ns.class_(
"ModbusOfflineTrigger", automation.Trigger.template(cg.int_, cg.int_)
)
_LOGGER = logging.getLogger(__name__)
@@ -182,23 +164,9 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(
CONF_SERVER_REGISTERS,
): cv.ensure_list(ModbusServerRegisterSchema),
cv.Optional(CONF_ON_COMMAND_SENT): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
ModbusCommandSentTrigger
),
}
),
cv.Optional(CONF_ON_ONLINE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ModbusOnlineTrigger),
}
),
cv.Optional(CONF_ON_OFFLINE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ModbusOfflineTrigger),
}
),
cv.Optional(CONF_ON_COMMAND_SENT): automation.validate_automation({}),
cv.Optional(CONF_ON_ONLINE): automation.validate_automation({}),
cv.Optional(CONF_ON_OFFLINE): automation.validate_automation({}),
}
)
.extend(cv.polling_component_schema("60s"))
@@ -363,19 +331,25 @@ async def to_code(config):
cg.add(var.add_server_register(server_register_var))
await register_modbus_device(var, config)
for conf in config.get(CONF_ON_COMMAND_SENT, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.int_, "function_code"), (cg.int_, "address")], conf
await automation.build_callback_automation(
var,
"add_on_command_sent_callback",
[(cg.int_, "function_code"), (cg.int_, "address")],
conf,
)
for conf in config.get(CONF_ON_ONLINE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.int_, "function_code"), (cg.int_, "address")], conf
await automation.build_callback_automation(
var,
"add_on_online_callback",
[(cg.int_, "function_code"), (cg.int_, "address")],
conf,
)
for conf in config.get(CONF_ON_OFFLINE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(cg.int_, "function_code"), (cg.int_, "address")], conf
await automation.build_callback_automation(
var,
"add_on_offline_callback",
[(cg.int_, "function_code"), (cg.int_, "address")],
conf,
)
@@ -1,35 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/automation.h"
#include "esphome/components/modbus_controller/modbus_controller.h"
namespace esphome {
namespace modbus_controller {
class ModbusCommandSentTrigger : public Trigger<int, int> {
public:
ModbusCommandSentTrigger(ModbusController *a_modbuscontroller) {
a_modbuscontroller->add_on_command_sent_callback(
[this](int function_code, int address) { this->trigger(function_code, address); });
}
};
class ModbusOnlineTrigger : public Trigger<int, int> {
public:
ModbusOnlineTrigger(ModbusController *a_modbuscontroller) {
a_modbuscontroller->add_on_online_callback(
[this](int function_code, int address) { this->trigger(function_code, address); });
}
};
class ModbusOfflineTrigger : public Trigger<int, int> {
public:
ModbusOfflineTrigger(ModbusController *a_modbuscontroller) {
a_modbuscontroller->add_on_offline_callback(
[this](int function_code, int address) { this->trigger(function_code, address); });
}
};
} // namespace modbus_controller
} // namespace esphome
@@ -15,7 +15,7 @@ class ModbusFloatOutput : public output::FloatOutput, public Component, public S
this->register_type = ModbusRegisterType::HOLDING;
this->start_address = start_address;
this->offset = offset;
this->bitmask = bitmask;
this->bitmask = 0xFFFFFFFF;
this->register_count = register_count;
this->sensor_value_type = value_type;
this->skip_updates = 0;
@@ -47,7 +47,7 @@ class ModbusBinaryOutput : public output::BinaryOutput, public Component, public
ModbusBinaryOutput(uint16_t start_address, uint8_t offset) {
this->register_type = ModbusRegisterType::COIL;
this->start_address = start_address;
this->bitmask = bitmask;
this->bitmask = 0xFFFFFFFF;
this->sensor_value_type = SensorValueType::BIT;
this->skip_updates = 0;
this->register_count = 1;
@@ -48,7 +48,8 @@ static bool apply_command(AlarmControlPanelCall &call, const char *state) {
}
void MQTTAlarmControlPanelComponent::setup() {
this->alarm_control_panel_->add_on_state_callback([this]() { this->publish_state(); });
this->alarm_control_panel_->add_on_state_callback(
[this](AlarmControlPanelState /*state*/) { this->publish_state(); });
this->subscribe(this->get_command_topic_(), [this](const std::string &topic, const std::string &payload) {
auto call = this->alarm_control_panel_->make_call();
if (!payload.empty() && payload[0] == '{') {
+2 -1
View File
@@ -28,7 +28,8 @@ void MQTTLockComponent::setup() {
this->status_momentary_warning("state", 5000);
}
});
this->lock_->add_on_state_callback([this]() { this->defer("send", [this]() { this->publish_state(); }); });
this->lock_->add_on_state_callback(
[this](LockState /*state*/) { this->defer("send", [this]() { this->publish_state(); }); });
}
void MQTTLockComponent::dump_config() {
ESP_LOGCONFIG(TAG, "MQTT Lock '%s': ", this->lock_->get_name().c_str());
-44
View File
@@ -5,50 +5,6 @@
namespace esphome {
namespace nextion {
class BufferOverflowTrigger : public Trigger<> {
public:
explicit BufferOverflowTrigger(Nextion *nextion) {
nextion->add_buffer_overflow_event_callback([this]() { this->trigger(); });
}
};
class SetupTrigger : public Trigger<> {
public:
explicit SetupTrigger(Nextion *nextion) {
nextion->add_setup_state_callback([this]() { this->trigger(); });
}
};
class SleepTrigger : public Trigger<> {
public:
explicit SleepTrigger(Nextion *nextion) {
nextion->add_sleep_state_callback([this]() { this->trigger(); });
}
};
class WakeTrigger : public Trigger<> {
public:
explicit WakeTrigger(Nextion *nextion) {
nextion->add_wake_state_callback([this]() { this->trigger(); });
}
};
class PageTrigger : public Trigger<uint8_t> {
public:
explicit PageTrigger(Nextion *nextion) {
nextion->add_new_page_callback([this](const uint8_t page_id) { this->trigger(page_id); });
}
};
class TouchTrigger : public Trigger<uint8_t, uint8_t, bool> {
public:
explicit TouchTrigger(Nextion *nextion) {
nextion->add_touch_event_callback([this](uint8_t page_id, uint8_t component_id, bool touch_event) {
this->trigger(page_id, component_id, touch_event);
});
}
};
template<typename... Ts> class NextionSetBrightnessAction : public Action<Ts...> {
public:
explicit NextionSetBrightnessAction(Nextion *component) : component_(component) {}
+25 -65
View File
@@ -2,13 +2,7 @@ from esphome import automation
import esphome.codegen as cg
from esphome.components import display, esp32, uart
import esphome.config_validation as cv
from esphome.const import (
CONF_BRIGHTNESS,
CONF_ID,
CONF_LAMBDA,
CONF_ON_TOUCH,
CONF_TRIGGER_ID,
)
from esphome.const import CONF_BRIGHTNESS, CONF_ID, CONF_LAMBDA, CONF_ON_TOUCH
from esphome.core import CORE, TimePeriod
from . import ( # noqa: F401 pylint: disable=unused-import
@@ -55,14 +49,6 @@ def AUTO_LOAD() -> list[str]:
NextionSetBrightnessAction = nextion_ns.class_(
"NextionSetBrightnessAction", automation.Action
)
SetupTrigger = nextion_ns.class_("SetupTrigger", automation.Trigger.template())
SleepTrigger = nextion_ns.class_("SleepTrigger", automation.Trigger.template())
WakeTrigger = nextion_ns.class_("WakeTrigger", automation.Trigger.template())
PageTrigger = nextion_ns.class_("PageTrigger", automation.Trigger.template())
TouchTrigger = nextion_ns.class_("TouchTrigger", automation.Trigger.template())
BufferOverflowTrigger = nextion_ns.class_(
"BufferOverflowTrigger", automation.Trigger.template()
)
def _validate_tft_upload(config):
@@ -101,38 +87,12 @@ CONFIG_SCHEMA = cv.All(
),
cv.Optional(CONF_MAX_COMMANDS_PER_LOOP): cv.uint16_t,
cv.Optional(CONF_MAX_QUEUE_SIZE): cv.positive_int,
cv.Optional(CONF_ON_BUFFER_OVERFLOW): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
BufferOverflowTrigger
),
}
),
cv.Optional(CONF_ON_PAGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(PageTrigger),
}
),
cv.Optional(CONF_ON_SETUP): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SetupTrigger),
}
),
cv.Optional(CONF_ON_SLEEP): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(SleepTrigger),
}
),
cv.Optional(CONF_ON_TOUCH): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(TouchTrigger),
}
),
cv.Optional(CONF_ON_WAKE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(WakeTrigger),
}
),
cv.Optional(CONF_ON_BUFFER_OVERFLOW): automation.validate_automation({}),
cv.Optional(CONF_ON_PAGE): automation.validate_automation({}),
cv.Optional(CONF_ON_SETUP): automation.validate_automation({}),
cv.Optional(CONF_ON_SLEEP): automation.validate_automation({}),
cv.Optional(CONF_ON_TOUCH): automation.validate_automation({}),
cv.Optional(CONF_ON_WAKE): automation.validate_automation({}),
cv.Optional(CONF_SKIP_CONNECTION_HANDSHAKE, default=False): cv.boolean,
cv.Optional(CONF_STARTUP_OVERRIDE_MS, default="8000ms"): cv.All(
cv.positive_time_period_milliseconds,
@@ -273,25 +233,25 @@ async def to_code(config):
await display.register_display(var, config)
for conf in config.get(CONF_ON_SETUP, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_setup_state_callback", [], conf
)
for conf in config.get(CONF_ON_SLEEP, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_sleep_state_callback", [], conf
)
for conf in config.get(CONF_ON_WAKE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_wake_state_callback", [], conf
)
for conf in config.get(CONF_ON_PAGE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(cg.uint8, "x")], conf)
await automation.build_callback_automation(
var, "add_new_page_callback", [(cg.uint8, "x")], conf
)
for conf in config.get(CONF_ON_TOUCH, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger,
await automation.build_callback_automation(
var,
"add_touch_event_callback",
[
(cg.uint8, "page_id"),
(cg.uint8, "component_id"),
@@ -299,7 +259,7 @@ async def to_code(config):
],
conf,
)
for conf in config.get(CONF_ON_BUFFER_OVERFLOW, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_buffer_overflow_event_callback", [], conf
)
+16 -13
View File
@@ -10,6 +10,10 @@ namespace nextion {
static const char *const TAG = "nextion";
// Nextion command terminator: three consecutive 0xFF bytes (per Nextion Instruction Set v1.1).
static constexpr uint8_t COMMAND_DELIMITER[3] = {0xFF, 0xFF, 0xFF};
static constexpr size_t DELIMITER_SIZE = sizeof(COMMAND_DELIMITER);
void Nextion::setup() {
this->is_setup_ = false;
this->connection_state_.ignore_is_setup_ = true;
@@ -90,7 +94,7 @@ bool Nextion::check_connect_() {
#endif // NEXTION_PROTOCOL_LOG
ESP_LOGW(TAG, "Not connected");
comok_sent_ = 0;
this->comok_sent_ = 0;
return false;
}
@@ -415,7 +419,8 @@ void Nextion::process_nextion_commands_() {
#ifdef NEXTION_PROTOCOL_LOG
this->print_queue_members_();
#endif
while ((to_process_length = this->command_data_.find(COMMAND_DELIMITER)) != std::string::npos) {
while ((to_process_length = this->command_data_.find(reinterpret_cast<const char *>(COMMAND_DELIMITER), 0,
DELIMITER_SIZE)) != std::string::npos) {
#ifdef USE_NEXTION_MAX_COMMANDS_PER_LOOP
if (++commands_processed > this->max_commands_per_loop_) {
ESP_LOGW(TAG, "Command processing limit exceeded");
@@ -423,8 +428,8 @@ void Nextion::process_nextion_commands_() {
}
#endif // USE_NEXTION_MAX_COMMANDS_PER_LOOP
ESP_LOGN(TAG, "queue size: %zu", this->nextion_queue_.size());
while (to_process_length + COMMAND_DELIMITER.length() < this->command_data_.length() &&
static_cast<uint8_t>(this->command_data_[to_process_length + COMMAND_DELIMITER.length()]) == 0xFF) {
while (to_process_length + DELIMITER_SIZE < this->command_data_.length() &&
static_cast<uint8_t>(this->command_data_[to_process_length + DELIMITER_SIZE]) == 0xFF) {
++to_process_length;
ESP_LOGN(TAG, "Add 0xFF");
}
@@ -829,17 +834,17 @@ void Nextion::process_nextion_commands_() {
break;
}
this->command_data_.erase(0, to_process_length + COMMAND_DELIMITER.length() + 1);
this->command_data_.erase(0, to_process_length + DELIMITER_SIZE + 1);
}
const uint32_t ms = App.get_loop_component_start_time();
if (this->max_q_age_ms_ > 0 && !this->nextion_queue_.empty() &&
ms - this->nextion_queue_.front()->queue_time > this->max_q_age_ms_) {
for (size_t i = 0; i < this->nextion_queue_.size(); i++) {
NextionComponentBase *component = this->nextion_queue_[i]->component;
if (ms - this->nextion_queue_[i]->queue_time > this->max_q_age_ms_) {
if (this->nextion_queue_[i]->queue_time == 0) {
for (auto it = this->nextion_queue_.begin(); it != this->nextion_queue_.end();) {
NextionComponentBase *component = (*it)->component;
if (ms - (*it)->queue_time > this->max_q_age_ms_) {
if ((*it)->queue_time == 0) {
ESP_LOGD(TAG, "Remove old queue '%s':'%s' (t=0)", component->get_queue_type_string().c_str(),
component->get_variable_name().c_str());
}
@@ -858,10 +863,8 @@ void Nextion::process_nextion_commands_() {
delete component; // NOLINT(cppcoreguidelines-owning-memory)
}
delete this->nextion_queue_[i]; // NOLINT(cppcoreguidelines-owning-memory)
this->nextion_queue_.erase(this->nextion_queue_.begin() + i);
i--;
delete *it; // NOLINT(cppcoreguidelines-owning-memory)
it = this->nextion_queue_.erase(it);
} else {
break;
+6 -8
View File
@@ -1,16 +1,16 @@
#pragma once
#include <deque>
#include <list>
#include <vector>
#include "esphome/components/display/display.h"
#include "esphome/components/display/display_color_utils.h"
#include "esphome/components/uart/uart.h"
#include "esphome/core/defines.h"
#include "esphome/core/time.h"
#include "esphome/components/uart/uart.h"
#include "nextion_base.h"
#include "nextion_component.h"
#include "esphome/components/display/display.h"
#include "esphome/components/display/display_color_utils.h"
#ifdef USE_NEXTION_TFT_UPLOAD
#ifdef USE_ESP32
@@ -29,8 +29,6 @@ class NextionComponentBase;
using nextion_writer_t = display::DisplayWriter<Nextion>;
static const std::string COMMAND_DELIMITER{static_cast<char>(255), static_cast<char>(255), static_cast<char>(255)};
#ifdef USE_NEXTION_COMMAND_SPACING
class NextionCommandPacer {
public:
@@ -1393,8 +1391,8 @@ class Nextion : public NextionBase, public PollingComponent, public uart::UARTDe
void process_pending_in_queue_();
#endif // USE_NEXTION_COMMAND_SPACING
std::deque<NextionQueue *> nextion_queue_;
std::deque<NextionQueue *> waveform_queue_;
std::list<NextionQueue *> nextion_queue_;
std::list<NextionQueue *> waveform_queue_;
uint16_t recv_ret_string_(std::string &response, uint32_t timeout, bool recv_flag);
void all_components_send_state_(bool force_update = false);
uint32_t comok_sent_ = 0;
@@ -106,7 +106,7 @@ void Nextion::set_component_pressed_foreground_color(const char *component, uint
}
void Nextion::set_component_pressed_foreground_color(const char *component, const char *color) {
this->add_no_result_to_queue_with_printf_("set_component_pressed_foreground_color", " %s.pco2=%s", component, color);
this->add_no_result_to_queue_with_printf_("set_component_pressed_foreground_color", "%s.pco2=%s", component, color);
}
void Nextion::set_component_pressed_foreground_color(const char *component, Color color) {
@@ -134,7 +134,7 @@ void Nextion::set_component_pressed_font_color(const char *component, uint16_t c
}
void Nextion::set_component_pressed_font_color(const char *component, const char *color) {
this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", " %s.pco2=%s", component, color);
this->add_no_result_to_queue_with_printf_("set_component_pressed_font_color", "%s.pco2=%s", component, color);
}
void Nextion::set_component_pressed_font_color(const char *component, Color color) {
@@ -22,9 +22,9 @@ static constexpr size_t NEXTION_MAX_RESPONSE_LOG_BYTES = 16;
int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
uint32_t range_size = this->tft_size_ - range_start;
ESP_LOGV(TAG, "Heap: %" PRIu32, EspClass::getFreeHeap());
uint32_t range_end = ((upload_first_chunk_sent_ or this->tft_size_ < 4096) ? this->tft_size_ : 4096) - 1;
uint32_t range_end = ((this->upload_first_chunk_sent_ || this->tft_size_ < 4096) ? this->tft_size_ : 4096) - 1;
ESP_LOGD(TAG, "Range start: %" PRIu32, range_start);
if (range_size <= 0 or range_end <= range_start) {
if (range_size <= 0 || range_end <= range_start) {
ESP_LOGE(TAG, "Invalid range end: %" PRIu32 ", size: %" PRIu32, range_end, range_size);
return -1;
}
@@ -34,7 +34,7 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
ESP_LOGV(TAG, "Range: %s", range_header);
http_client.addHeader("Range", range_header);
int code = http_client.GET();
if (code != HTTP_CODE_OK and code != HTTP_CODE_PARTIAL_CONTENT) {
if (code != HTTP_CODE_OK && code != HTTP_CODE_PARTIAL_CONTENT) {
ESP_LOGW(TAG, "HTTP failed: %s", HTTPClient::errorToString(code).c_str());
return -1;
}
@@ -80,12 +80,12 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
recv_string.clear();
this->write_array(buffer, buffer_size);
App.feed_wdt();
this->recv_ret_string_(recv_string, upload_first_chunk_sent_ ? 500 : 5000, true);
this->recv_ret_string_(recv_string, this->upload_first_chunk_sent_ ? 500 : 5000, true);
this->content_length_ -= read_len;
const float upload_percentage = 100.0f * (this->tft_size_ - this->content_length_) / this->tft_size_;
ESP_LOGD(TAG, "Upload: %0.2f%% (%" PRIu32 " left, heap: %" PRIu32 ")", upload_percentage, this->content_length_,
EspClass::getFreeHeap());
upload_first_chunk_sent_ = true;
this->upload_first_chunk_sent_ = true;
if (recv_string.empty()) {
ESP_LOGW(TAG, "No response from display during upload");
allocator.deallocate(buffer, 4096);
@@ -112,7 +112,7 @@ int Nextion::upload_by_chunks_(HTTPClient &http_client, uint32_t &range_start) {
allocator.deallocate(buffer, 4096);
buffer = nullptr;
return range_end + 1;
} else if (recv_string[0] != 0x05 and recv_string[0] != 0x08) { // 0x05 == "ok"
} else if (recv_string[0] != 0x05 && recv_string[0] != 0x08) { // 0x05 == "ok"
char hex_buf[format_hex_pretty_size(NEXTION_MAX_RESPONSE_LOG_BYTES)];
ESP_LOGE(
TAG, "Invalid response: [%s]",
@@ -214,7 +214,7 @@ bool Nextion::upload_tft(uint32_t baud_rate, bool exit_reparse) {
++tries;
}
if (code != 200 and code != 206) {
if (code != 200 && code != 206) {
ESP_LOGE(TAG, "HTTP request failed with status %d", code);
return this->upload_end_(false);
}
+4 -10
View File
@@ -155,9 +155,6 @@ Number = number_ns.class_("Number", cg.EntityBase)
NumberPtr = Number.operator("ptr")
# Triggers
NumberStateTrigger = number_ns.class_(
"NumberStateTrigger", automation.Trigger.template(cg.float_)
)
ValueRangeTrigger = number_ns.class_(
"ValueRangeTrigger", automation.Trigger.template(cg.float_), cg.Component
)
@@ -198,11 +195,7 @@ _NUMBER_SCHEMA = (
.extend(
{
cv.OnlyWith(CONF_MQTT_ID, "mqtt"): cv.declare_id(mqtt.MQTTNumberComponent),
cv.Optional(CONF_ON_VALUE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(NumberStateTrigger),
}
),
cv.Optional(CONF_ON_VALUE): automation.validate_automation({}),
cv.Optional(CONF_ON_VALUE_RANGE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(ValueRangeTrigger),
@@ -248,8 +241,9 @@ def number_schema(
@coroutine_with_priority(CoroPriority.AUTOMATION)
async def _build_number_automations(var, config):
for conf in config.get(CONF_ON_VALUE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(float, "x")], conf)
await automation.build_callback_automation(
var, "add_on_state_callback", [(float, "x")], conf
)
for conf in config.get(CONF_ON_VALUE_RANGE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await cg.register_component(trigger, conf)
+9 -32
View File
@@ -7,14 +7,7 @@ from esphome.components.const import CONF_REQUEST_HEADERS
from esphome.components.http_request import CONF_HTTP_REQUEST_ID, HttpRequestComponent
from esphome.components.image import CONF_TRANSPARENCY, add_metadata
import esphome.config_validation as cv
from esphome.const import (
CONF_BUFFER_SIZE,
CONF_ID,
CONF_ON_ERROR,
CONF_TRIGGER_ID,
CONF_TYPE,
CONF_URL,
)
from esphome.const import CONF_BUFFER_SIZE, CONF_ID, CONF_ON_ERROR, CONF_TYPE, CONF_URL
from esphome.core import Lambda
AUTO_LOAD = ["image", "runtime_image"]
@@ -41,14 +34,6 @@ ReleaseImageAction = online_image_ns.class_(
"OnlineImageReleaseAction", automation.Action, cg.Parented.template(OnlineImage)
)
# Triggers
DownloadFinishedTrigger = online_image_ns.class_(
"DownloadFinishedTrigger", automation.Trigger.template()
)
DownloadErrorTrigger = online_image_ns.class_(
"DownloadErrorTrigger", automation.Trigger.template()
)
ONLINE_IMAGE_SCHEMA = (
runtime_image.runtime_image_schema(OnlineImage)
@@ -61,18 +46,8 @@ ONLINE_IMAGE_SCHEMA = (
cv.Optional(CONF_REQUEST_HEADERS): cv.All(
cv.Schema({cv.string: cv.templatable(cv.string)})
),
cv.Optional(CONF_ON_DOWNLOAD_FINISHED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
DownloadFinishedTrigger
),
}
),
cv.Optional(CONF_ON_ERROR): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(DownloadErrorTrigger),
}
),
cv.Optional(CONF_ON_DOWNLOAD_FINISHED): automation.validate_automation({}),
cv.Optional(CONF_ON_ERROR): automation.validate_automation({}),
}
)
.extend(cv.polling_component_schema("never"))
@@ -165,9 +140,11 @@ async def to_code(config):
cg.add(var.add_request_header(key, value))
for conf in config.get(CONF_ON_DOWNLOAD_FINISHED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(bool, "cached")], conf)
await automation.build_callback_automation(
var, "add_on_finished_callback", [(bool, "cached")], conf
)
for conf in config.get(CONF_ON_ERROR, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_error_callback", [], conf
)
@@ -129,18 +129,4 @@ template<typename... Ts> class OnlineImageReleaseAction : public Action<Ts...> {
OnlineImage *parent_;
};
class DownloadFinishedTrigger : public Trigger<bool> {
public:
explicit DownloadFinishedTrigger(OnlineImage *parent) {
parent->add_on_finished_callback([this](bool cached) { this->trigger(cached); });
}
};
class DownloadErrorTrigger : public Trigger<> {
public:
explicit DownloadErrorTrigger(OnlineImage *parent) {
parent->add_on_error_callback([this]() { this->trigger(); });
}
};
} // namespace esphome::online_image
+2 -2
View File
@@ -101,10 +101,10 @@ PIDAutotuner::PIDAutotuneResult PIDAutotuner::update(float setpoint, float proce
if (!zc_symmetrical || !amplitude_convergent) {
// The frequency/amplitude is not fully accurate yet, try to wait
// until the fault clears, or terminate after a while anyway
if (zc_symmetrical) {
if (!zc_symmetrical) {
ESP_LOGVV(TAG, "%s: ZC is not symmetrical", this->id_.c_str());
}
if (amplitude_convergent) {
if (!amplitude_convergent) {
ESP_LOGVV(TAG, "%s: Amplitude is not convergent", this->id_.c_str());
}
uint32_t phase = this->relay_function_.phase_count;
-1
View File
@@ -3,7 +3,6 @@
#include "esphome/core/component.h"
#include "esphome/core/optional.h"
#include "pid_controller.h"
#include "pid_simulator.h"
#include <vector>
-77
View File
@@ -1,77 +0,0 @@
#pragma once
#include "esphome/core/component.h"
#include "esphome/core/helpers.h"
#include "esphome/components/sensor/sensor.h"
#include "esphome/components/output/float_output.h"
#include <vector>
namespace esphome {
namespace pid {
class PIDSimulator : public PollingComponent, public output::FloatOutput {
public:
PIDSimulator() : PollingComponent(1000) {}
float surface = 1; /// surface area in m²
float mass = 3; /// mass of simulated object in kg
float temperature = 21; /// current temperature of object in °C
float efficiency = 0.98; /// heating efficiency, 1 is 100% efficient
float thermal_conductivity = 15; /// thermal conductivity of surface are in W/(m*K), here: steel
float specific_heat_capacity = 4.182; /// specific heat capacity of mass in kJ/(kg*K), here: water
float heat_power = 500; /// Heating power in W
float ambient_temperature = 20; /// Ambient temperature in °C
float update_interval = 1; /// The simulated updated interval in seconds
std::vector<float> delayed_temps; /// storage of past temperatures for delaying temperature reading
size_t delay_cycles = 15; /// how many update cycles to delay the output
float output_value = 0.0; /// Current output value of heating element
sensor::Sensor *sensor = new sensor::Sensor();
float delta_t(float power) {
// P = Q / t
// Q = c * m * 𝚫t
// 𝚫t = (P*t) / (c*m)
float c = this->specific_heat_capacity;
float t = this->update_interval;
float p = power / 1000; // in kW
float m = this->mass;
return (p * t) / (c * m);
}
float update_temp() {
float value = clamp(output_value, 0.0f, 1.0f);
// Heat
float power = value * heat_power * efficiency;
temperature += this->delta_t(power);
// Cool
// Q = k_w * A * (T_mass - T_ambient)
// P = Q / t
float dt = temperature - ambient_temperature;
float cool_power = (thermal_conductivity * surface * dt) / update_interval;
temperature -= this->delta_t(cool_power);
// Delay temperature readings
delayed_temps.push_back(temperature);
if (delayed_temps.size() > delay_cycles)
delayed_temps.erase(delayed_temps.begin());
float prev_temp = this->delayed_temps[0];
float alpha = 0.1f;
float ret = (1 - alpha) * prev_temp + alpha * prev_temp;
return ret;
}
void setup() override { sensor->publish_state(this->temperature); }
void update() override {
float new_temp = this->update_temp();
sensor->publish_state(new_temp);
}
protected:
void write_state(float state) override { this->output_value = state; }
};
} // namespace pid
} // namespace esphome
+4 -13
View File
@@ -19,10 +19,6 @@ CONF_PN532_ID = "pn532_id"
pn532_ns = cg.esphome_ns.namespace("pn532")
PN532 = pn532_ns.class_("PN532", cg.PollingComponent)
PN532OnFinishedWriteTrigger = pn532_ns.class_(
"PN532OnFinishedWriteTrigger", automation.Trigger.template()
)
PN532IsWritingCondition = pn532_ns.class_(
"PN532IsWritingCondition", automation.Condition
)
@@ -35,13 +31,7 @@ PN532_SCHEMA = cv.Schema(
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger),
}
),
cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
PN532OnFinishedWriteTrigger
),
}
),
cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation({}),
cv.Optional(CONF_ON_TAG_REMOVED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger),
@@ -77,8 +67,9 @@ async def setup_pn532(var, config):
)
for conf in config.get(CONF_ON_FINISHED_WRITE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_finished_write_callback", [], conf
)
@automation.register_condition(
-7
View File
@@ -133,13 +133,6 @@ class PN532BinarySensor : public binary_sensor::BinarySensor {
bool found_{false};
};
class PN532OnFinishedWriteTrigger : public Trigger<> {
public:
explicit PN532OnFinishedWriteTrigger(PN532 *parent) {
parent->add_on_finished_write_callback([this]() { this->trigger(); });
}
};
template<typename... Ts> class PN532IsWritingCondition : public Condition<Ts...>, public Parented<PN532> {
public:
bool check(const Ts &...x) override { return this->parent_->is_writing(); }
+8 -26
View File
@@ -50,14 +50,6 @@ SetWriteMessageAction = pn7150_ns.class_("SetWriteMessageAction", automation.Act
SetWriteModeAction = pn7150_ns.class_("SetWriteModeAction", automation.Action)
PN7150OnEmulatedTagScanTrigger = pn7150_ns.class_(
"PN7150OnEmulatedTagScanTrigger", automation.Trigger.template()
)
PN7150OnFinishedWriteTrigger = pn7150_ns.class_(
"PN7150OnFinishedWriteTrigger", automation.Trigger.template()
)
PN7150IsWritingCondition = pn7150_ns.class_(
"PN7150IsWritingCondition", automation.Condition
)
@@ -83,20 +75,8 @@ SET_MESSAGE_ACTION_SCHEMA = cv.Schema(
PN7150_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(PN7150),
cv.Optional(CONF_ON_EMULATED_TAG_SCAN): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
PN7150OnEmulatedTagScanTrigger
),
}
),
cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
PN7150OnFinishedWriteTrigger
),
}
),
cv.Optional(CONF_ON_EMULATED_TAG_SCAN): automation.validate_automation({}),
cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation({}),
cv.Optional(CONF_ON_TAG): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger),
@@ -215,12 +195,14 @@ async def setup_pn7150(var, config):
)
for conf in config.get(CONF_ON_EMULATED_TAG_SCAN, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_emulated_tag_scan_callback", [], conf
)
for conf in config.get(CONF_ON_FINISHED_WRITE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_finished_write_callback", [], conf
)
@automation.register_condition(
-14
View File
@@ -7,20 +7,6 @@
namespace esphome {
namespace pn7150 {
class PN7150OnEmulatedTagScanTrigger : public Trigger<> {
public:
explicit PN7150OnEmulatedTagScanTrigger(PN7150 *parent) {
parent->add_on_emulated_tag_scan_callback([this]() { this->trigger(); });
}
};
class PN7150OnFinishedWriteTrigger : public Trigger<> {
public:
explicit PN7150OnFinishedWriteTrigger(PN7150 *parent) {
parent->add_on_finished_write_callback([this]() { this->trigger(); });
}
};
template<typename... Ts> class PN7150IsWritingCondition : public Condition<Ts...>, public Parented<PN7150> {
public:
bool check(const Ts &...x) override { return this->parent_->is_writing(); }
+8 -26
View File
@@ -52,14 +52,6 @@ SetWriteMessageAction = pn7160_ns.class_("SetWriteMessageAction", automation.Act
SetWriteModeAction = pn7160_ns.class_("SetWriteModeAction", automation.Action)
PN7160OnEmulatedTagScanTrigger = pn7160_ns.class_(
"PN7160OnEmulatedTagScanTrigger", automation.Trigger.template()
)
PN7160OnFinishedWriteTrigger = pn7160_ns.class_(
"PN7160OnFinishedWriteTrigger", automation.Trigger.template()
)
PN7160IsWritingCondition = pn7160_ns.class_(
"PN7160IsWritingCondition", automation.Condition
)
@@ -85,20 +77,8 @@ SET_MESSAGE_ACTION_SCHEMA = cv.Schema(
PN7160_SCHEMA = cv.Schema(
{
cv.GenerateID(): cv.declare_id(PN7160),
cv.Optional(CONF_ON_EMULATED_TAG_SCAN): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
PN7160OnEmulatedTagScanTrigger
),
}
),
cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
PN7160OnFinishedWriteTrigger
),
}
),
cv.Optional(CONF_ON_EMULATED_TAG_SCAN): automation.validate_automation({}),
cv.Optional(CONF_ON_FINISHED_WRITE): automation.validate_automation({}),
cv.Optional(CONF_ON_TAG): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(nfc.NfcOnTagTrigger),
@@ -227,12 +207,14 @@ async def setup_pn7160(var, config):
)
for conf in config.get(CONF_ON_EMULATED_TAG_SCAN, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_emulated_tag_scan_callback", [], conf
)
for conf in config.get(CONF_ON_FINISHED_WRITE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
await automation.build_callback_automation(
var, "add_on_finished_write_callback", [], conf
)
@automation.register_condition(
-14
View File
@@ -7,20 +7,6 @@
namespace esphome {
namespace pn7160 {
class PN7160OnEmulatedTagScanTrigger : public Trigger<> {
public:
explicit PN7160OnEmulatedTagScanTrigger(PN7160 *parent) {
parent->add_on_emulated_tag_scan_callback([this]() { this->trigger(); });
}
};
class PN7160OnFinishedWriteTrigger : public Trigger<> {
public:
explicit PN7160OnFinishedWriteTrigger(PN7160 *parent) {
parent->add_on_finished_write_callback([this]() { this->trigger(); });
}
};
template<typename... Ts> class PN7160IsWritingCondition : public Condition<Ts...>, public Parented<PN7160> {
public:
bool check(const Ts &...x) override { return this->parent_->is_writing(); }
@@ -1,5 +1,6 @@
#include "gobox_protocol.h"
#include "esphome/core/log.h"
#include <cinttypes>
namespace esphome {
namespace remote_base {
@@ -25,7 +26,7 @@ void GoboxProtocol::encode(RemoteTransmitData *dst, const GoboxData &data) {
dst->set_carrier_frequency(38000);
dst->reserve((HEADER_SIZE + CODE_SIZE + 1) * 2);
uint64_t code = (HEADER << CODE_SIZE) | (data.code & ((1UL << CODE_SIZE) - 1));
ESP_LOGI(TAG, "Send Gobox: code=0x%Lx", code);
ESP_LOGI(TAG, "Send Gobox: code=0x%016" PRIx64, code);
for (int16_t i = (HEADER_SIZE + CODE_SIZE - 1); i >= 0; i--) {
if (code & ((uint64_t) 1 << i)) {
dst->item(BIT_MARK_US, BIT_ONE_SPACE_US);
+10 -27
View File
@@ -12,7 +12,6 @@ from esphome.const import (
CONF_PROTOCOL,
CONF_RAW,
CONF_SYNC,
CONF_TRIGGER_ID,
)
DEPENDENCIES = ["uart"]
@@ -26,14 +25,6 @@ RFBridgeComponent = rf_bridge_ns.class_(
RFBridgeData = rf_bridge_ns.struct("RFBridgeData")
RFBridgeAdvancedData = rf_bridge_ns.struct("RFBridgeAdvancedData")
RFBridgeReceivedCodeTrigger = rf_bridge_ns.class_(
"RFBridgeReceivedCodeTrigger", automation.Trigger.template(RFBridgeData)
)
RFBridgeReceivedAdvancedCodeTrigger = rf_bridge_ns.class_(
"RFBridgeReceivedAdvancedCodeTrigger",
automation.Trigger.template(RFBridgeAdvancedData),
)
RFBridgeSendCodeAction = rf_bridge_ns.class_(
"RFBridgeSendCodeAction", automation.Action
)
@@ -65,19 +56,9 @@ CONFIG_SCHEMA = cv.All(
cv.Schema(
{
cv.GenerateID(): cv.declare_id(RFBridgeComponent),
cv.Optional(CONF_ON_CODE_RECEIVED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
RFBridgeReceivedCodeTrigger
),
}
),
cv.Optional(CONF_ON_CODE_RECEIVED): automation.validate_automation({}),
cv.Optional(CONF_ON_ADVANCED_CODE_RECEIVED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
RFBridgeReceivedAdvancedCodeTrigger
),
}
{}
),
}
)
@@ -92,13 +73,15 @@ async def to_code(config):
await uart.register_uart_device(var, config)
for conf in config.get(CONF_ON_CODE_RECEIVED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [(RFBridgeData, "data")], conf)
await automation.build_callback_automation(
var, "add_on_code_received_callback", [(RFBridgeData, "data")], conf
)
for conf in config.get(CONF_ON_ADVANCED_CODE_RECEIVED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(RFBridgeAdvancedData, "data")], conf
await automation.build_callback_automation(
var,
"add_on_advanced_code_received_callback",
[(RFBridgeAdvancedData, "data")],
conf,
)

Some files were not shown because too many files have changed in this diff Show More