[improv_ble] Build the state triggers with build_callback_automation (#20444)

This commit is contained in:
J. Nick Koston
2026-10-09 16:06:14 -10:00
committed by GitHub
parent 0ae24f7536
commit f7b9646705
4 changed files with 68 additions and 174 deletions
+37 -80
View File
@@ -2,13 +2,7 @@ from esphome import automation
import esphome.codegen as cg
from esphome.components import binary_sensor, improv_base, output
import esphome.config_validation as cv
from esphome.const import (
CONF_ID,
CONF_ON_START,
CONF_ON_STATE,
CONF_TRIGGER_ID,
PLATFORM_ESP32,
)
from esphome.const import CONF_ID, CONF_ON_START, CONF_ON_STATE, PLATFORM_ESP32
from esphome.core import CORE
from esphome.types import ConfigType
@@ -59,20 +53,35 @@ State = improv_ns.enum("State")
improv_ble_ns = cg.esphome_ns.namespace("improv_ble")
ImprovBLEComponent = improv_ble_ns.class_("ImprovBLEComponent", cg.Component)
ImprovBLEProvisionedTrigger = improv_ble_ns.class_(
"ImprovBLEProvisionedTrigger", automation.Trigger.template()
)
ImprovBLEProvisioningTrigger = improv_ble_ns.class_(
"ImprovBLEProvisioningTrigger", automation.Trigger.template()
)
ImprovBLEStartTrigger = improv_ble_ns.class_(
"ImprovBLEStartTrigger", automation.Trigger.template()
)
ImprovBLEStateTrigger = improv_ble_ns.class_(
"ImprovBLEStateTrigger", automation.Trigger.template()
)
ImprovBLEStoppedTrigger = improv_ble_ns.class_(
"ImprovBLEStoppedTrigger", automation.Trigger.template()
_STATE_PARAMS = [(State, "state"), (Error, "error")]
_NOT_FAILED = "!{parent}->is_failed()"
def _state_automation(
conf_key: str, args: list, states: str | None
) -> automation.CallbackAutomation:
# Every automation hangs off the state callback and is silent while the component has failed.
return automation.CallbackAutomation(
conf_key,
"add_on_state_callback",
args,
params=_STATE_PARAMS,
forward=[name for _, name in args],
when=_NOT_FAILED if states is None else f"({states}) && {_NOT_FAILED}",
)
_CALLBACK_AUTOMATIONS = (
_state_automation(CONF_ON_PROVISIONED, [], "state == improv::STATE_PROVISIONED"),
_state_automation(CONF_ON_PROVISIONING, [], "state == improv::STATE_PROVISIONING"),
_state_automation(
CONF_ON_START,
[],
"state == improv::STATE_AUTHORIZED || state == improv::STATE_AWAITING_AUTHORIZATION",
),
_state_automation(CONF_ON_STATE, _STATE_PARAMS, None),
_state_automation(CONF_ON_STOP, [], "state == improv::STATE_STOPPED"),
)
@@ -93,41 +102,11 @@ CONFIG_SCHEMA = cv.All(
cv.Optional(
CONF_WIFI_TIMEOUT, default=DEFAULT_WIFI_TIMEOUT
): cv.positive_time_period_milliseconds,
cv.Optional(CONF_ON_PROVISIONED): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
ImprovBLEProvisionedTrigger
),
}
),
cv.Optional(CONF_ON_PROVISIONING): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
ImprovBLEProvisioningTrigger
),
}
),
cv.Optional(CONF_ON_START): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
ImprovBLEStartTrigger
),
}
),
cv.Optional(CONF_ON_STATE): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
ImprovBLEStateTrigger
),
}
),
cv.Optional(CONF_ON_STOP): automation.validate_automation(
{
cv.GenerateID(CONF_TRIGGER_ID): cv.declare_id(
ImprovBLEStoppedTrigger
),
}
),
cv.Optional(CONF_ON_PROVISIONED): automation.validate_automation({}),
cv.Optional(CONF_ON_PROVISIONING): automation.validate_automation({}),
cv.Optional(CONF_ON_START): automation.validate_automation({}),
cv.Optional(CONF_ON_STATE): automation.validate_automation({}),
cv.Optional(CONF_ON_STOP): automation.validate_automation({}),
}
)
.extend(improv_base.IMPROV_SCHEMA)
@@ -164,28 +143,6 @@ async def to_code(config: ConfigType) -> None:
status_indicator = await cg.get_variable(config[CONF_STATUS_INDICATOR])
cg.add(var.set_status_indicator(status_indicator))
use_state_callback = False
for conf in config.get(CONF_ON_PROVISIONED, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
use_state_callback = True
for conf in config.get(CONF_ON_PROVISIONING, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
use_state_callback = True
for conf in config.get(CONF_ON_START, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
use_state_callback = True
for conf in config.get(CONF_ON_STATE, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(
trigger, [(State, "state"), (Error, "error")], conf
)
use_state_callback = True
for conf in config.get(CONF_ON_STOP, []):
trigger = cg.new_Pvariable(conf[CONF_TRIGGER_ID], var)
await automation.build_automation(trigger, [], conf)
use_state_callback = True
if use_state_callback:
if any(config.get(entry.conf_key) for entry in _CALLBACK_AUTOMATIONS):
cg.add_define("USE_IMPROV_BLE_STATE_CALLBACK")
await automation.build_callback_automations(var, config, _CALLBACK_AUTOMATIONS)
@@ -1,86 +0,0 @@
#pragma once
#ifdef USE_ESP32
#ifdef USE_IMPROV_BLE_STATE_CALLBACK
#include "improv_ble_component.h"
#include "esphome/core/automation.h"
#include <improv.h>
namespace esphome::improv_ble {
class ImprovBLEProvisionedTrigger final : public Trigger<> {
public:
explicit ImprovBLEProvisionedTrigger(ImprovBLEComponent *parent) : parent_(parent) {
parent->add_on_state_callback([this](improv::State state, improv::Error error) {
if (state == improv::STATE_PROVISIONED && !this->parent_->is_failed()) {
this->trigger();
}
});
}
protected:
ImprovBLEComponent *parent_;
};
class ImprovBLEProvisioningTrigger final : public Trigger<> {
public:
explicit ImprovBLEProvisioningTrigger(ImprovBLEComponent *parent) : parent_(parent) {
parent->add_on_state_callback([this](improv::State state, improv::Error error) {
if (state == improv::STATE_PROVISIONING && !this->parent_->is_failed()) {
this->trigger();
}
});
}
protected:
ImprovBLEComponent *parent_;
};
class ImprovBLEStartTrigger final : public Trigger<> {
public:
explicit ImprovBLEStartTrigger(ImprovBLEComponent *parent) : parent_(parent) {
parent->add_on_state_callback([this](improv::State state, improv::Error error) {
if ((state == improv::STATE_AUTHORIZED || state == improv::STATE_AWAITING_AUTHORIZATION) &&
!this->parent_->is_failed()) {
this->trigger();
}
});
}
protected:
ImprovBLEComponent *parent_;
};
class ImprovBLEStateTrigger final : public Trigger<improv::State, improv::Error> {
public:
explicit ImprovBLEStateTrigger(ImprovBLEComponent *parent) : parent_(parent) {
parent->add_on_state_callback([this](improv::State state, improv::Error error) {
if (!this->parent_->is_failed()) {
this->trigger(state, error);
}
});
}
protected:
ImprovBLEComponent *parent_;
};
class ImprovBLEStoppedTrigger final : public Trigger<> {
public:
explicit ImprovBLEStoppedTrigger(ImprovBLEComponent *parent) : parent_(parent) {
parent->add_on_state_callback([this](improv::State state, improv::Error error) {
if (state == improv::STATE_STOPPED && !this->parent_->is_failed()) {
this->trigger();
}
});
}
protected:
ImprovBLEComponent *parent_;
};
} // namespace esphome::improv_ble
#endif
#endif
@@ -42,18 +42,20 @@ def test_platform_without_ble_server_rejected(
assert "only available on" in capsys.readouterr().out
def test_automations_emit_renamed_triggers(
def test_automations_register_state_callbacks(
generate_main: Callable[[str | Path], str],
component_config_path: Callable[[str], Path],
) -> None:
main_cpp = generate_main(component_config_path("automations.yaml"))
for trigger in (
"ImprovBLEProvisionedTrigger",
"ImprovBLEProvisioningTrigger",
"ImprovBLEStartTrigger",
"ImprovBLEStateTrigger",
"ImprovBLEStoppedTrigger",
assert "Trigger" not in main_cpp
for state in (
"improv::STATE_PROVISIONED",
"improv::STATE_PROVISIONING",
"improv::STATE_AUTHORIZED",
"improv::STATE_STOPPED",
):
assert f"improv_ble::{trigger}" in main_cpp
assert f"state == {state}" in main_cpp
assert main_cpp.count("add_on_state_callback(") == 5
assert "->is_failed()" in main_cpp
assert "set_authorizer" in main_cpp
assert "set_status_indicator" in main_cpp
@@ -0,0 +1,21 @@
wifi:
ssid: MySSID
password: password1
binary_sensor:
- platform: gpio
pin: 0
id: improv_trigger_authorizer
improv_ble:
authorizer: improv_trigger_authorizer
on_start:
- logger.log: Improv started
on_provisioning:
- logger.log: Provisioning
on_provisioned:
- logger.log: Provisioned
on_stop:
- logger.log: Improv stopped
on_state:
- lambda: ESP_LOGD("improv", "State %d error %d", (int) state, (int) error);