mirror of
https://github.com/esphome/esphome.git
synced 2026-09-04 03:56:04 +00:00
Merge branch 'dev' into api/peel-first-write-iteration
This commit is contained in:
@@ -124,6 +124,28 @@ This document provides essential context for AI models interacting with this pro
|
||||
* **Indentation:** Use spaces (two per indentation level), not tabs
|
||||
* **Type aliases:** Prefer `using type_t = int;` over `typedef int type_t;`
|
||||
* **Line length:** Wrap lines at no more than 120 characters
|
||||
* **Constructor parameters vs setters:** Component properties that are both **required** and **invariant**
|
||||
(never change after construction) should be constructor parameters rather than set via setter methods.
|
||||
This makes the dependency explicit and prevents use of the object in an incompletely-initialized state.
|
||||
In code generation, when calling `cg.new_Pvariable()` or the relevant helper function to create the component, pass these as arguments.
|
||||
```cpp
|
||||
// Good - required invariant dependency as constructor parameter
|
||||
class SourceTextSensor : public text_sensor::TextSensor, public Component {
|
||||
public:
|
||||
explicit SourceTextSensor(text::Text *source) : source_(source) {}
|
||||
protected:
|
||||
text::Text *source_;
|
||||
};
|
||||
```
|
||||
```cpp
|
||||
// Bad - required invariant dependency as setter
|
||||
class SourceTextSensor : public text_sensor::TextSensor, public Component {
|
||||
public:
|
||||
void set_source(text::Text *source) { this->source_ = source; }
|
||||
protected:
|
||||
text::Text *source_{nullptr};
|
||||
};
|
||||
```
|
||||
|
||||
* **Component Structure:**
|
||||
* **Standard Files:**
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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}}"
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
+10
-1
@@ -1046,7 +1046,11 @@ def show_logs(config: ConfigType, args: ArgsProtocol, devices: list[str]) -> int
|
||||
):
|
||||
from esphome.components.api.client import run_logs
|
||||
|
||||
return run_logs(config, network_devices)
|
||||
return run_logs(
|
||||
config,
|
||||
network_devices,
|
||||
subscribe_states=not getattr(args, "no_states", False),
|
||||
)
|
||||
|
||||
if port_type in (PortType.NETWORK, PortType.MQTT) and has_mqtt_logging():
|
||||
from esphome import mqtt
|
||||
@@ -1664,6 +1668,11 @@ def parse_args(argv):
|
||||
help="Reset the device before starting serial logs.",
|
||||
default=os.getenv("ESPHOME_SERIAL_LOGGING_RESET"),
|
||||
)
|
||||
parser_logs.add_argument(
|
||||
"--no-states",
|
||||
action="store_true",
|
||||
help="Do not show entity state changes in log output.",
|
||||
)
|
||||
|
||||
parser_discover = subparsers.add_parser(
|
||||
"discover",
|
||||
|
||||
+49
-2
@@ -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)
|
||||
@@ -413,13 +416,16 @@ async def if_action_to_code(
|
||||
template_arg: cg.TemplateArguments,
|
||||
args: TemplateArgsType,
|
||||
) -> MockObj:
|
||||
has_else = CONF_ELSE in config
|
||||
# Prepend HasElse bool to template arguments: IfAction<HasElse, Ts...>
|
||||
if_template_arg = cg.TemplateArguments(has_else, *template_arg)
|
||||
cond_conf = next(el for el in config if el in (CONF_ANY, CONF_ALL, CONF_CONDITION))
|
||||
condition = await build_condition(config[cond_conf], template_arg, args)
|
||||
var = cg.new_Pvariable(action_id, template_arg, condition)
|
||||
var = cg.new_Pvariable(action_id, if_template_arg, condition)
|
||||
if CONF_THEN in config:
|
||||
actions = await build_action_list(config[CONF_THEN], template_arg, args)
|
||||
cg.add(var.add_then(actions))
|
||||
if CONF_ELSE in config:
|
||||
if has_else:
|
||||
actions = await build_action_list(config[CONF_ELSE], template_arg, args)
|
||||
cg.add(var.add_else(actions))
|
||||
return var
|
||||
@@ -658,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}}}")))
|
||||
|
||||
@@ -53,6 +53,13 @@ def get_project_cmakelists() -> str:
|
||||
variant = get_esp32_variant()
|
||||
idf_target = variant.lower().replace("-", "")
|
||||
|
||||
# Extract compile definitions from build flags (-DXXX -> XXX)
|
||||
compile_defs = [flag for flag in CORE.build_flags if flag.startswith("-D")]
|
||||
extra_compile_options = "\n".join(
|
||||
f'idf_build_set_property(COMPILE_OPTIONS "{compile_def}" APPEND)'
|
||||
for compile_def in compile_defs
|
||||
)
|
||||
|
||||
return f"""\
|
||||
# Auto-generated by ESPHome
|
||||
cmake_minimum_required(VERSION 3.16)
|
||||
@@ -61,6 +68,9 @@ set(IDF_TARGET {idf_target})
|
||||
set(EXTRA_COMPONENT_DIRS ${{CMAKE_SOURCE_DIR}}/src)
|
||||
|
||||
include($ENV{{IDF_PATH}}/tools/cmake/project.cmake)
|
||||
|
||||
{extra_compile_options}
|
||||
|
||||
project({CORE.name})
|
||||
"""
|
||||
|
||||
@@ -70,10 +80,6 @@ def get_component_cmakelists(minimal: bool = False) -> str:
|
||||
idf_requires = [] if minimal else (get_available_components() or [])
|
||||
requires_str = " ".join(idf_requires)
|
||||
|
||||
# Extract compile definitions from build flags (-DXXX -> XXX)
|
||||
compile_defs = [flag[2:] for flag in CORE.build_flags if flag.startswith("-D")]
|
||||
compile_defs_str = "\n ".join(sorted(compile_defs)) if compile_defs else ""
|
||||
|
||||
# Extract compile options (-W flags, excluding linker flags)
|
||||
compile_opts = [
|
||||
flag
|
||||
@@ -104,11 +110,6 @@ idf_component_register(
|
||||
# Apply C++ standard
|
||||
target_compile_features(${{COMPONENT_LIB}} PUBLIC cxx_std_20)
|
||||
|
||||
# ESPHome compile definitions
|
||||
target_compile_definitions(${{COMPONENT_LIB}} PUBLIC
|
||||
{compile_defs_str}
|
||||
)
|
||||
|
||||
# ESPHome compile options
|
||||
target_compile_options(${{COMPONENT_LIB}} PUBLIC
|
||||
{compile_opts_str}
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -31,12 +31,12 @@ void AlarmControlPanel::publish_state(AlarmControlPanelState state) {
|
||||
this->last_update_ = millis();
|
||||
if (state != this->current_state_) {
|
||||
auto prev_state = this->current_state_;
|
||||
ESP_LOGD(TAG, "'%s' >> %s (was %s)", this->get_name().c_str(),
|
||||
ESP_LOGV(TAG, "'%s' >> %s (was %s)", this->get_name().c_str(),
|
||||
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:
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -2249,10 +2249,14 @@ bool SubscribeBluetoothLEAdvertisementsRequest::decode_varint(uint32_t field_id,
|
||||
return true;
|
||||
}
|
||||
void BluetoothLERawAdvertisement::encode(ProtoWriteBuffer &buffer) const {
|
||||
buffer.encode_uint64(1, this->address, true);
|
||||
buffer.encode_sint32(2, this->rssi, true);
|
||||
buffer.write_raw_byte(8);
|
||||
buffer.encode_varint_raw_64(this->address);
|
||||
buffer.write_raw_byte(16);
|
||||
buffer.encode_varint_raw(encode_zigzag32(this->rssi));
|
||||
buffer.encode_uint32(3, this->address_type);
|
||||
buffer.encode_bytes(4, this->data, this->data_len, true);
|
||||
buffer.write_raw_byte(34);
|
||||
buffer.encode_varint_raw(this->data_len);
|
||||
buffer.encode_raw(this->data, this->data_len);
|
||||
}
|
||||
uint32_t BluetoothLERawAdvertisement::calculate_size() const {
|
||||
uint32_t size = 0;
|
||||
@@ -3653,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;
|
||||
@@ -3668,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
|
||||
|
||||
+139
-138
File diff suppressed because it is too large
Load Diff
+1226
-1200
File diff suppressed because it is too large
Load Diff
@@ -9,8 +9,8 @@ namespace esphome::api {
|
||||
static const char *const TAG = "api.service";
|
||||
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
void APIServerConnectionBase::log_send_message_(const char *name, const char *dump) {
|
||||
ESP_LOGVV(TAG, "send_message %s: %s", name, dump);
|
||||
void APIServerConnectionBase::log_send_message_(const LogString *name, const char *dump) {
|
||||
ESP_LOGVV(TAG, "send_message %s: %s", LOG_STR_ARG(name), dump);
|
||||
}
|
||||
void APIServerConnectionBase::log_receive_message_(const LogString *name, const ProtoMessage &msg) {
|
||||
DumpBuffer dump_buf;
|
||||
|
||||
@@ -12,7 +12,7 @@ class APIServerConnectionBase {
|
||||
public:
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
protected:
|
||||
void log_send_message_(const char *name, const char *dump);
|
||||
void log_send_message_(const LogString *name, const char *dump);
|
||||
void log_receive_message_(const LogString *name, const ProtoMessage &msg);
|
||||
void log_receive_message_(const LogString *name);
|
||||
|
||||
|
||||
@@ -46,10 +46,8 @@ void APIServer::setup() {
|
||||
|
||||
#ifndef USE_API_NOISE_PSK_FROM_YAML
|
||||
// Only load saved PSK if not set from YAML
|
||||
SavedNoisePsk noise_pref_saved{};
|
||||
if (this->noise_pref_.load(&noise_pref_saved)) {
|
||||
if (this->load_and_apply_noise_psk_()) {
|
||||
ESP_LOGD(TAG, "Loaded saved Noise PSK");
|
||||
this->set_noise_psk(noise_pref_saved.psk);
|
||||
}
|
||||
#endif
|
||||
#endif
|
||||
@@ -110,7 +108,7 @@ void APIServer::setup() {
|
||||
this->last_connected_ = App.get_loop_component_start_time();
|
||||
// Set warning status if reboot timeout is enabled
|
||||
if (this->reboot_timeout_ != 0) {
|
||||
this->status_set_warning();
|
||||
this->status_set_warning(LOG_STR("waiting for client connection"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,7 +187,7 @@ void APIServer::remove_client_(size_t client_index) {
|
||||
|
||||
// Last client disconnected - set warning and start tracking for reboot timeout
|
||||
if (this->clients_.empty() && this->reboot_timeout_ != 0) {
|
||||
this->status_set_warning();
|
||||
this->status_set_warning(LOG_STR("waiting for client connection"));
|
||||
this->last_connected_ = App.get_loop_component_start_time();
|
||||
}
|
||||
|
||||
@@ -514,7 +512,7 @@ void APIServer::set_reboot_timeout(uint32_t reboot_timeout) { this->reboot_timeo
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg,
|
||||
const LogString *fail_log_msg, const psk_t &active_psk, bool make_active) {
|
||||
const LogString *fail_log_msg, bool make_active) {
|
||||
if (!this->noise_pref_.save(&new_psk)) {
|
||||
ESP_LOGW(TAG, "%s", LOG_STR_ARG(fail_log_msg));
|
||||
return false;
|
||||
@@ -526,9 +524,14 @@ bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString
|
||||
}
|
||||
ESP_LOGD(TAG, "%s", LOG_STR_ARG(save_log_msg));
|
||||
if (make_active) {
|
||||
this->set_timeout(100, [this, active_psk]() {
|
||||
this->set_timeout(100, [this]() {
|
||||
// Re-read the PSK from preferences rather than capturing the 32-byte array
|
||||
// in the lambda (which would exceed std::function SBO and heap-allocate).
|
||||
if (!this->load_and_apply_noise_psk_()) {
|
||||
ESP_LOGW(TAG, "Failed to load saved PSK for activation");
|
||||
return;
|
||||
}
|
||||
ESP_LOGW(TAG, "Disconnecting all clients to reset PSK");
|
||||
this->set_noise_psk(active_psk);
|
||||
for (auto &c : this->clients_) {
|
||||
DisconnectRequest req;
|
||||
c->send_message(req);
|
||||
@@ -538,6 +541,14 @@ bool APIServer::update_noise_psk_(const SavedNoisePsk &new_psk, const LogString
|
||||
return true;
|
||||
}
|
||||
|
||||
bool APIServer::load_and_apply_noise_psk_() {
|
||||
SavedNoisePsk saved{};
|
||||
if (!this->noise_pref_.load(&saved))
|
||||
return false;
|
||||
this->set_noise_psk(saved.psk);
|
||||
return true;
|
||||
}
|
||||
|
||||
bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
||||
#ifdef USE_API_NOISE_PSK_FROM_YAML
|
||||
// When PSK is set from YAML, this function should never be called
|
||||
@@ -552,7 +563,7 @@ bool APIServer::save_noise_psk(psk_t psk, bool make_active) {
|
||||
}
|
||||
|
||||
SavedNoisePsk new_saved_psk{psk};
|
||||
return this->update_noise_psk_(new_saved_psk, LOG_STR("Noise PSK saved"), LOG_STR("Failed to save Noise PSK"), psk,
|
||||
return this->update_noise_psk_(new_saved_psk, LOG_STR("Noise PSK saved"), LOG_STR("Failed to save Noise PSK"),
|
||||
make_active);
|
||||
#endif
|
||||
}
|
||||
@@ -564,8 +575,7 @@ bool APIServer::clear_noise_psk(bool make_active) {
|
||||
return false;
|
||||
#else
|
||||
SavedNoisePsk empty_psk{};
|
||||
psk_t empty{};
|
||||
return this->update_noise_psk_(empty_psk, LOG_STR("Noise PSK cleared"), LOG_STR("Failed to clear Noise PSK"), empty,
|
||||
return this->update_noise_psk_(empty_psk, LOG_STR("Noise PSK cleared"), LOG_STR("Failed to clear Noise PSK"),
|
||||
make_active);
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -239,7 +239,9 @@ class APIServer final : public Component,
|
||||
|
||||
#ifdef USE_API_NOISE
|
||||
bool update_noise_psk_(const SavedNoisePsk &new_psk, const LogString *save_log_msg, const LogString *fail_log_msg,
|
||||
const psk_t &active_psk, bool make_active);
|
||||
bool make_active);
|
||||
// Load saved PSK from preferences and apply it. Returns true on success.
|
||||
bool load_and_apply_noise_psk_();
|
||||
#endif // USE_API_NOISE
|
||||
#ifdef USE_API_HOMEASSISTANT_STATES
|
||||
// Helper methods to reduce code duplication
|
||||
|
||||
@@ -32,7 +32,11 @@ if TYPE_CHECKING:
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
|
||||
async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
|
||||
async def async_run_logs(
|
||||
config: dict[str, Any],
|
||||
addresses: list[str],
|
||||
subscribe_states: bool = True,
|
||||
) -> None:
|
||||
"""Run the logs command in the event loop."""
|
||||
conf = config["api"]
|
||||
name = config["esphome"]["name"]
|
||||
@@ -89,14 +93,20 @@ async def async_run_logs(config: dict[str, Any], addresses: list[str]) -> None:
|
||||
config, raw_line, backtrace_state=backtrace_state
|
||||
)
|
||||
|
||||
stop = await async_run(cli, on_log, name=name)
|
||||
stop = await async_run(cli, on_log, name=name, subscribe_states=subscribe_states)
|
||||
try:
|
||||
await asyncio.Event().wait()
|
||||
finally:
|
||||
await stop()
|
||||
|
||||
|
||||
def run_logs(config: dict[str, Any], addresses: list[str]) -> None:
|
||||
def run_logs(
|
||||
config: dict[str, Any],
|
||||
addresses: list[str],
|
||||
subscribe_states: bool = True,
|
||||
) -> None:
|
||||
"""Run the logs command."""
|
||||
with contextlib.suppress(KeyboardInterrupt):
|
||||
asyncio.run(async_run_logs(config, addresses))
|
||||
asyncio.run(
|
||||
async_run_logs(config, addresses, subscribe_states=subscribe_states)
|
||||
)
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
#include "esphome/core/component.h"
|
||||
#include "esphome/core/helpers.h"
|
||||
#include "esphome/core/log.h"
|
||||
#include "esphome/core/progmem.h"
|
||||
#include "esphome/core/string_ref.h"
|
||||
|
||||
#include <cassert>
|
||||
@@ -228,6 +229,17 @@ class ProtoWriteBuffer {
|
||||
* Following https://protobuf.dev/programming-guides/encoding/#structure
|
||||
*/
|
||||
void encode_field_raw(uint32_t field_id, uint32_t type) { this->encode_varint_raw((field_id << 3) | type); }
|
||||
/// Write a single precomputed tag byte. Tag must be < 128.
|
||||
inline void write_raw_byte(uint8_t b) ESPHOME_ALWAYS_INLINE {
|
||||
this->debug_check_bounds_(1);
|
||||
*this->pos_++ = b;
|
||||
}
|
||||
/// Write raw bytes to the buffer (no tag, no length prefix).
|
||||
inline void encode_raw(const void *data, size_t len) ESPHOME_ALWAYS_INLINE {
|
||||
this->debug_check_bounds_(len);
|
||||
std::memcpy(this->pos_, data, len);
|
||||
this->pos_ += len;
|
||||
}
|
||||
/// Write a precomputed tag byte + 32-bit value in one operation.
|
||||
/// Tag must be a single-byte varint (< 128). No zero check.
|
||||
inline void write_tag_and_fixed32(uint8_t tag, uint32_t value) ESPHOME_ALWAYS_INLINE {
|
||||
@@ -400,6 +412,23 @@ class DumpBuffer {
|
||||
return *this;
|
||||
}
|
||||
|
||||
/// Append a PROGMEM string (flash-safe on ESP8266, regular append on other platforms)
|
||||
DumpBuffer &append_p(const char *str) {
|
||||
if (str) {
|
||||
#ifdef USE_ESP8266
|
||||
append_p_esp8266(str);
|
||||
#else
|
||||
append_impl_(str, strlen(str));
|
||||
#endif
|
||||
}
|
||||
return *this;
|
||||
}
|
||||
|
||||
#ifdef USE_ESP8266
|
||||
/// Out-of-line ESP8266 PROGMEM append to avoid inlining strlen_P/memcpy_P at every call site
|
||||
void append_p_esp8266(const char *str);
|
||||
#endif
|
||||
|
||||
const char *c_str() const { return buf_; }
|
||||
size_t size() const { return pos_; }
|
||||
|
||||
@@ -445,7 +474,7 @@ class ProtoMessage {
|
||||
uint32_t calculate_size() const { return 0; }
|
||||
#ifdef HAS_PROTO_MESSAGE_DUMP
|
||||
virtual const char *dump_to(DumpBuffer &out) const = 0;
|
||||
virtual const char *message_name() const { return "unknown"; }
|
||||
virtual const LogString *message_name() const { return LOG_STR("unknown"); }
|
||||
#endif
|
||||
|
||||
#ifndef USE_HOST
|
||||
|
||||
@@ -2,11 +2,9 @@ import esphome.codegen as cg
|
||||
from esphome.components import sensor
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_ANGLE,
|
||||
CONF_GAIN,
|
||||
CONF_ID,
|
||||
CONF_MAGNITUDE,
|
||||
CONF_POSITION,
|
||||
CONF_STATUS,
|
||||
ENTITY_CATEGORY_DIAGNOSTIC,
|
||||
ICON_MAGNET,
|
||||
@@ -21,7 +19,6 @@ DEPENDENCIES = ["as5600"]
|
||||
|
||||
AS5600Sensor = as5600_ns.class_("AS5600Sensor", sensor.Sensor, cg.PollingComponent)
|
||||
|
||||
CONF_RAW_ANGLE = "raw_angle"
|
||||
CONF_RAW_POSITION = "raw_position"
|
||||
CONF_SLOW_FILTER = "slow_filter"
|
||||
CONF_FAST_FILTER = "fast_filter"
|
||||
@@ -89,18 +86,6 @@ async def to_code(config):
|
||||
if out_of_range_mode_config := config.get(CONF_OUT_OF_RANGE_MODE):
|
||||
cg.add(var.set_out_of_range_mode(out_of_range_mode_config))
|
||||
|
||||
if angle_config := config.get(CONF_ANGLE):
|
||||
sens = await sensor.new_sensor(angle_config)
|
||||
cg.add(var.set_angle_sensor(sens))
|
||||
|
||||
if raw_angle_config := config.get(CONF_RAW_ANGLE):
|
||||
sens = await sensor.new_sensor(raw_angle_config)
|
||||
cg.add(var.set_raw_angle_sensor(sens))
|
||||
|
||||
if position_config := config.get(CONF_POSITION):
|
||||
sens = await sensor.new_sensor(position_config)
|
||||
cg.add(var.set_position_sensor(sens))
|
||||
|
||||
if raw_position_config := config.get(CONF_RAW_POSITION):
|
||||
sens = await sensor.new_sensor(raw_position_config)
|
||||
cg.add(var.set_raw_position_sensor(sens))
|
||||
|
||||
@@ -25,27 +25,10 @@ static const uint8_t REGISTER_MAGNITUDE = 0x1B; // 16 bytes / R
|
||||
void AS5600Sensor::dump_config() {
|
||||
LOG_SENSOR("", "AS5600 Sensor", this);
|
||||
ESP_LOGCONFIG(TAG, " Out of Range Mode: %u", this->out_of_range_mode_);
|
||||
if (this->angle_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Angle Sensor", this->angle_sensor_);
|
||||
}
|
||||
if (this->raw_angle_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Raw Angle Sensor", this->raw_angle_sensor_);
|
||||
}
|
||||
if (this->position_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Position Sensor", this->position_sensor_);
|
||||
}
|
||||
if (this->raw_position_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Raw Position Sensor", this->raw_position_sensor_);
|
||||
}
|
||||
if (this->gain_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Gain Sensor", this->gain_sensor_);
|
||||
}
|
||||
if (this->magnitude_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Magnitude Sensor", this->magnitude_sensor_);
|
||||
}
|
||||
if (this->status_sensor_ != nullptr) {
|
||||
LOG_SENSOR(" ", "Status Sensor", this->status_sensor_);
|
||||
}
|
||||
LOG_SENSOR(" ", "Raw Position Sensor", this->raw_position_sensor_);
|
||||
LOG_SENSOR(" ", "Gain Sensor", this->gain_sensor_);
|
||||
LOG_SENSOR(" ", "Magnitude Sensor", this->magnitude_sensor_);
|
||||
LOG_SENSOR(" ", "Status Sensor", this->status_sensor_);
|
||||
LOG_UPDATE_INTERVAL(this);
|
||||
}
|
||||
|
||||
|
||||
@@ -15,9 +15,6 @@ class AS5600Sensor : public PollingComponent, public Parented<AS5600Component>,
|
||||
void update() override;
|
||||
void dump_config() override;
|
||||
|
||||
void set_angle_sensor(sensor::Sensor *angle_sensor) { this->angle_sensor_ = angle_sensor; }
|
||||
void set_raw_angle_sensor(sensor::Sensor *raw_angle_sensor) { this->raw_angle_sensor_ = raw_angle_sensor; }
|
||||
void set_position_sensor(sensor::Sensor *position_sensor) { this->position_sensor_ = position_sensor; }
|
||||
void set_raw_position_sensor(sensor::Sensor *raw_position_sensor) {
|
||||
this->raw_position_sensor_ = raw_position_sensor;
|
||||
}
|
||||
@@ -28,9 +25,6 @@ class AS5600Sensor : public PollingComponent, public Parented<AS5600Component>,
|
||||
OutRangeMode get_out_of_range_mode() { return this->out_of_range_mode_; }
|
||||
|
||||
protected:
|
||||
sensor::Sensor *angle_sensor_{nullptr};
|
||||
sensor::Sensor *raw_angle_sensor_{nullptr};
|
||||
sensor::Sensor *position_sensor_{nullptr};
|
||||
sensor::Sensor *raw_position_sensor_{nullptr};
|
||||
sensor::Sensor *gain_sensor_{nullptr};
|
||||
sensor::Sensor *magnitude_sensor_{nullptr};
|
||||
|
||||
@@ -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) {
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -204,7 +204,7 @@ async def to_code(config):
|
||||
|
||||
add_idf_component(
|
||||
name="esphome/esp-audio-libs",
|
||||
ref="2.0.3",
|
||||
ref="2.0.4",
|
||||
)
|
||||
|
||||
data = _get_data()
|
||||
|
||||
@@ -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()
|
||||
@@ -132,13 +128,6 @@ MultiClickTrigger = binary_sensor_ns.class_(
|
||||
"MultiClickTrigger", automation.Trigger.template(), cg.Component
|
||||
)
|
||||
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
|
||||
@@ -458,16 +447,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(
|
||||
{
|
||||
@@ -509,16 +490,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({}),
|
||||
}
|
||||
)
|
||||
)
|
||||
@@ -556,13 +529,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(
|
||||
@@ -593,13 +567,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"),
|
||||
|
||||
@@ -45,7 +45,7 @@ bool BinarySensor::set_new_state(const optional<bool> &new_state) {
|
||||
#if defined(USE_BINARY_SENSOR) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_binary_sensor_update(this);
|
||||
#endif
|
||||
ESP_LOGD(TAG, "'%s' >> %s", this->get_name().c_str(), ONOFFMAYBE(new_state));
|
||||
ESP_LOGV(TAG, "'%s' >> %s", this->get_name().c_str(), ONOFFMAYBE(new_state));
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -56,7 +56,6 @@ void BM8563::read_time() {
|
||||
ESPTime rtc_time;
|
||||
this->get_time_(rtc_time);
|
||||
this->get_date_(rtc_time);
|
||||
rtc_time.day_of_year = 1; // unused by recalc_timestamp_utc, but needs to be valid
|
||||
ESP_LOGD(TAG, "Read time: %i-%i-%i %i, %i:%i:%i", rtc_time.year, rtc_time.month, rtc_time.day_of_month,
|
||||
rtc_time.day_of_week, rtc_time.hour, rtc_time.minute, rtc_time.second);
|
||||
|
||||
|
||||
@@ -276,8 +276,8 @@ void BME68xBSEC2Component::run_() {
|
||||
}
|
||||
|
||||
if (this->bsec_settings_.trigger_measurement && this->bsec_settings_.op_mode != BME68X_SLEEP_MODE) {
|
||||
uint32_t meas_dur = 0;
|
||||
meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_);
|
||||
bme68x_get_conf(&bme68x_conf, &this->bme68x_);
|
||||
uint32_t meas_dur = bme68x_get_meas_dur(this->op_mode_, &bme68x_conf, &this->bme68x_);
|
||||
ESP_LOGV(TAG, "Queueing read in %uus", meas_dur);
|
||||
this->trigger_time_ns_ = curr_time_ns;
|
||||
this->set_timeout("read", meas_dur / 1000, [this]() { this->read_(this->trigger_time_ns_); });
|
||||
|
||||
@@ -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
|
||||
@@ -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)
|
||||
@@ -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)
|
||||
|
||||
|
||||
@@ -46,45 +46,45 @@ constexpr StringToUint8 CLIMATE_SWING_MODES_BY_STR[] = {
|
||||
|
||||
void ClimateCall::perform() {
|
||||
this->parent_->control_callback_.call(*this);
|
||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
ESP_LOGV(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
this->validate_();
|
||||
if (this->mode_.has_value()) {
|
||||
const LogString *mode_s = climate_mode_to_string(*this->mode_);
|
||||
ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(mode_s));
|
||||
ESP_LOGV(TAG, " Mode: %s", LOG_STR_ARG(mode_s));
|
||||
}
|
||||
if (this->custom_fan_mode_ != nullptr) {
|
||||
this->fan_mode_.reset();
|
||||
ESP_LOGD(TAG, " Custom Fan: %s", this->custom_fan_mode_);
|
||||
ESP_LOGV(TAG, " Custom Fan: %s", this->custom_fan_mode_);
|
||||
}
|
||||
if (this->fan_mode_.has_value()) {
|
||||
this->custom_fan_mode_ = nullptr;
|
||||
const LogString *fan_mode_s = climate_fan_mode_to_string(*this->fan_mode_);
|
||||
ESP_LOGD(TAG, " Fan: %s", LOG_STR_ARG(fan_mode_s));
|
||||
ESP_LOGV(TAG, " Fan: %s", LOG_STR_ARG(fan_mode_s));
|
||||
}
|
||||
if (this->custom_preset_ != nullptr) {
|
||||
this->preset_.reset();
|
||||
ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset_);
|
||||
ESP_LOGV(TAG, " Custom Preset: %s", this->custom_preset_);
|
||||
}
|
||||
if (this->preset_.has_value()) {
|
||||
this->custom_preset_ = nullptr;
|
||||
const LogString *preset_s = climate_preset_to_string(*this->preset_);
|
||||
ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(preset_s));
|
||||
ESP_LOGV(TAG, " Preset: %s", LOG_STR_ARG(preset_s));
|
||||
}
|
||||
if (this->swing_mode_.has_value()) {
|
||||
const LogString *swing_mode_s = climate_swing_mode_to_string(*this->swing_mode_);
|
||||
ESP_LOGD(TAG, " Swing: %s", LOG_STR_ARG(swing_mode_s));
|
||||
ESP_LOGV(TAG, " Swing: %s", LOG_STR_ARG(swing_mode_s));
|
||||
}
|
||||
if (this->target_temperature_.has_value()) {
|
||||
ESP_LOGD(TAG, " Target Temperature: %.2f", *this->target_temperature_);
|
||||
ESP_LOGV(TAG, " Target Temperature: %.2f", *this->target_temperature_);
|
||||
}
|
||||
if (this->target_temperature_low_.has_value()) {
|
||||
ESP_LOGD(TAG, " Target Temperature Low: %.2f", *this->target_temperature_low_);
|
||||
ESP_LOGV(TAG, " Target Temperature Low: %.2f", *this->target_temperature_low_);
|
||||
}
|
||||
if (this->target_temperature_high_.has_value()) {
|
||||
ESP_LOGD(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_);
|
||||
ESP_LOGV(TAG, " Target Temperature High: %.2f", *this->target_temperature_high_);
|
||||
}
|
||||
if (this->target_humidity_.has_value()) {
|
||||
ESP_LOGD(TAG, " Target Humidity: %.0f", *this->target_humidity_);
|
||||
ESP_LOGV(TAG, " Target Humidity: %.0f", *this->target_humidity_);
|
||||
}
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
@@ -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;
|
||||
@@ -435,43 +434,43 @@ void Climate::save_state_() {
|
||||
}
|
||||
|
||||
void Climate::publish_state() {
|
||||
ESP_LOGD(TAG, "'%s' >>", this->name_.c_str());
|
||||
ESP_LOGV(TAG, "'%s' >>", this->name_.c_str());
|
||||
auto traits = this->get_traits();
|
||||
|
||||
ESP_LOGD(TAG, " Mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode)));
|
||||
ESP_LOGV(TAG, " Mode: %s", LOG_STR_ARG(climate_mode_to_string(this->mode)));
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_ACTION)) {
|
||||
ESP_LOGD(TAG, " Action: %s", LOG_STR_ARG(climate_action_to_string(this->action)));
|
||||
ESP_LOGV(TAG, " Action: %s", LOG_STR_ARG(climate_action_to_string(this->action)));
|
||||
}
|
||||
if (traits.get_supports_fan_modes() && this->fan_mode.has_value()) {
|
||||
ESP_LOGD(TAG, " Fan Mode: %s", LOG_STR_ARG(climate_fan_mode_to_string(this->fan_mode.value())));
|
||||
ESP_LOGV(TAG, " Fan Mode: %s", LOG_STR_ARG(climate_fan_mode_to_string(this->fan_mode.value())));
|
||||
}
|
||||
if (!traits.get_supported_custom_fan_modes().empty() && this->has_custom_fan_mode()) {
|
||||
ESP_LOGD(TAG, " Custom Fan Mode: %s", this->custom_fan_mode_);
|
||||
ESP_LOGV(TAG, " Custom Fan Mode: %s", this->custom_fan_mode_);
|
||||
}
|
||||
if (traits.get_supports_presets() && this->preset.has_value()) {
|
||||
ESP_LOGD(TAG, " Preset: %s", LOG_STR_ARG(climate_preset_to_string(this->preset.value())));
|
||||
ESP_LOGV(TAG, " Preset: %s", LOG_STR_ARG(climate_preset_to_string(this->preset.value())));
|
||||
}
|
||||
if (!traits.get_supported_custom_presets().empty() && this->has_custom_preset()) {
|
||||
ESP_LOGD(TAG, " Custom Preset: %s", this->custom_preset_);
|
||||
ESP_LOGV(TAG, " Custom Preset: %s", this->custom_preset_);
|
||||
}
|
||||
if (traits.get_supports_swing_modes()) {
|
||||
ESP_LOGD(TAG, " Swing Mode: %s", LOG_STR_ARG(climate_swing_mode_to_string(this->swing_mode)));
|
||||
ESP_LOGV(TAG, " Swing Mode: %s", LOG_STR_ARG(climate_swing_mode_to_string(this->swing_mode)));
|
||||
}
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_TEMPERATURE)) {
|
||||
ESP_LOGD(TAG, " Current Temperature: %.2f°C", this->current_temperature);
|
||||
ESP_LOGV(TAG, " Current Temperature: %.2f°C", this->current_temperature);
|
||||
}
|
||||
if (traits.has_feature_flags(CLIMATE_SUPPORTS_TWO_POINT_TARGET_TEMPERATURE |
|
||||
CLIMATE_REQUIRES_TWO_POINT_TARGET_TEMPERATURE)) {
|
||||
ESP_LOGD(TAG, " Target Temperature: Low: %.2f°C High: %.2f°C", this->target_temperature_low,
|
||||
ESP_LOGV(TAG, " Target Temperature: Low: %.2f°C High: %.2f°C", this->target_temperature_low,
|
||||
this->target_temperature_high);
|
||||
} else {
|
||||
ESP_LOGD(TAG, " Target Temperature: %.2f°C", this->target_temperature);
|
||||
ESP_LOGV(TAG, " Target Temperature: %.2f°C", this->target_temperature);
|
||||
}
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_CURRENT_HUMIDITY)) {
|
||||
ESP_LOGD(TAG, " Current Humidity: %.0f%%", this->current_humidity);
|
||||
ESP_LOGV(TAG, " Current Humidity: %.0f%%", this->current_humidity);
|
||||
}
|
||||
if (traits.has_feature_flags(climate::CLIMATE_SUPPORTS_TARGET_HUMIDITY)) {
|
||||
ESP_LOGD(TAG, " Target Humidity: %.0f%%", this->target_humidity);
|
||||
ESP_LOGV(TAG, " Target Humidity: %.0f%%", this->target_humidity);
|
||||
}
|
||||
|
||||
// Send state to frontend
|
||||
@@ -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() {
|
||||
|
||||
@@ -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);
|
||||
|
||||
|
||||
@@ -13,10 +13,12 @@ CONF_DATA_BITS = "data_bits"
|
||||
CONF_DRAW_ROUNDING = "draw_rounding"
|
||||
CONF_ENABLED = "enabled"
|
||||
CONF_IGNORE_NOT_FOUND = "ignore_not_found"
|
||||
CONF_LIBRETINY = "libretiny"
|
||||
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"
|
||||
|
||||
@@ -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());
|
||||
|
||||
@@ -68,24 +68,24 @@ CoverCall &CoverCall::set_tilt(float tilt) {
|
||||
return *this;
|
||||
}
|
||||
void CoverCall::perform() {
|
||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
ESP_LOGV(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
auto traits = this->parent_->get_traits();
|
||||
this->validate_();
|
||||
if (this->stop_) {
|
||||
ESP_LOGD(TAG, " Command: STOP");
|
||||
ESP_LOGV(TAG, " Command: STOP");
|
||||
}
|
||||
if (this->position_.has_value()) {
|
||||
if (traits.get_supports_position()) {
|
||||
ESP_LOGD(TAG, " Position: %.0f%%", *this->position_ * 100.0f);
|
||||
ESP_LOGV(TAG, " Position: %.0f%%", *this->position_ * 100.0f);
|
||||
} else {
|
||||
ESP_LOGD(TAG, " Command: %s", LOG_STR_ARG(cover_command_to_str(*this->position_)));
|
||||
ESP_LOGV(TAG, " Command: %s", LOG_STR_ARG(cover_command_to_str(*this->position_)));
|
||||
}
|
||||
}
|
||||
if (this->tilt_.has_value()) {
|
||||
ESP_LOGD(TAG, " Tilt: %.0f%%", *this->tilt_ * 100.0f);
|
||||
ESP_LOGV(TAG, " Tilt: %.0f%%", *this->tilt_ * 100.0f);
|
||||
}
|
||||
if (this->toggle_.has_value()) {
|
||||
ESP_LOGD(TAG, " Command: TOGGLE");
|
||||
ESP_LOGV(TAG, " Command: TOGGLE");
|
||||
}
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
@@ -143,23 +143,23 @@ void Cover::publish_state(bool save) {
|
||||
this->position = clamp(this->position, 0.0f, 1.0f);
|
||||
this->tilt = clamp(this->tilt, 0.0f, 1.0f);
|
||||
|
||||
ESP_LOGD(TAG, "'%s' >>", this->name_.c_str());
|
||||
ESP_LOGV(TAG, "'%s' >>", this->name_.c_str());
|
||||
auto traits = this->get_traits();
|
||||
if (traits.get_supports_position()) {
|
||||
ESP_LOGD(TAG, " Position: %.0f%%", this->position * 100.0f);
|
||||
ESP_LOGV(TAG, " Position: %.0f%%", this->position * 100.0f);
|
||||
} else {
|
||||
if (this->position == COVER_OPEN) {
|
||||
ESP_LOGD(TAG, " State: OPEN");
|
||||
ESP_LOGV(TAG, " State: OPEN");
|
||||
} else if (this->position == COVER_CLOSED) {
|
||||
ESP_LOGD(TAG, " State: CLOSED");
|
||||
ESP_LOGV(TAG, " State: CLOSED");
|
||||
} else {
|
||||
ESP_LOGD(TAG, " State: UNKNOWN");
|
||||
ESP_LOGV(TAG, " State: UNKNOWN");
|
||||
}
|
||||
}
|
||||
if (traits.get_supports_tilt()) {
|
||||
ESP_LOGD(TAG, " Tilt: %.0f%%", this->tilt * 100.0f);
|
||||
ESP_LOGV(TAG, " Tilt: %.0f%%", this->tilt * 100.0f);
|
||||
}
|
||||
ESP_LOGD(TAG, " Current Operation: %s", LOG_STR_ARG(cover_operation_to_str(this->current_operation)));
|
||||
ESP_LOGV(TAG, " Current Operation: %s", LOG_STR_ARG(cover_operation_to_str(this->current_operation)));
|
||||
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_COVER) && defined(USE_CONTROLLER_REGISTRY)
|
||||
|
||||
@@ -30,7 +30,7 @@ void DateEntity::publish_state() {
|
||||
return;
|
||||
}
|
||||
this->set_has_state(true);
|
||||
ESP_LOGD(TAG, "'%s' >> %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_);
|
||||
ESP_LOGV(TAG, "'%s' >> %d-%d-%d", this->get_name().c_str(), this->year_, this->month_, this->day_);
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_DATETIME_DATE) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_date_update(this);
|
||||
@@ -83,16 +83,16 @@ void DateCall::validate_() {
|
||||
|
||||
void DateCall::perform() {
|
||||
this->validate_();
|
||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
ESP_LOGV(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
|
||||
if (this->year_.has_value()) {
|
||||
ESP_LOGD(TAG, " Year: %d", *this->year_);
|
||||
ESP_LOGV(TAG, " Year: %d", *this->year_);
|
||||
}
|
||||
if (this->month_.has_value()) {
|
||||
ESP_LOGD(TAG, " Month: %d", *this->month_);
|
||||
ESP_LOGV(TAG, " Month: %d", *this->month_);
|
||||
}
|
||||
if (this->day_.has_value()) {
|
||||
ESP_LOGD(TAG, " Day: %d", *this->day_);
|
||||
ESP_LOGV(TAG, " Day: %d", *this->day_);
|
||||
}
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
|
||||
@@ -45,7 +45,7 @@ void DateTimeEntity::publish_state() {
|
||||
return;
|
||||
}
|
||||
this->set_has_state(true);
|
||||
ESP_LOGD(TAG, "'%s' >> %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_, this->month_,
|
||||
ESP_LOGV(TAG, "'%s' >> %04u-%02u-%02u %02d:%02d:%02d", this->get_name().c_str(), this->year_, this->month_,
|
||||
this->day_, this->hour_, this->minute_, this->second_);
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_DATETIME_DATETIME) && defined(USE_CONTROLLER_REGISTRY)
|
||||
@@ -60,6 +60,9 @@ ESPTime DateTimeEntity::state_as_esptime() const {
|
||||
obj.year = this->year_;
|
||||
obj.month = this->month_;
|
||||
obj.day_of_month = this->day_;
|
||||
obj.day_of_week = 0;
|
||||
obj.day_of_year = 0;
|
||||
obj.is_dst = false;
|
||||
obj.hour = this->hour_;
|
||||
obj.minute = this->minute_;
|
||||
obj.second = this->second_;
|
||||
@@ -124,25 +127,25 @@ void DateTimeCall::validate_() {
|
||||
|
||||
void DateTimeCall::perform() {
|
||||
this->validate_();
|
||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
ESP_LOGV(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
|
||||
if (this->year_.has_value()) {
|
||||
ESP_LOGD(TAG, " Year: %d", *this->year_);
|
||||
ESP_LOGV(TAG, " Year: %d", *this->year_);
|
||||
}
|
||||
if (this->month_.has_value()) {
|
||||
ESP_LOGD(TAG, " Month: %d", *this->month_);
|
||||
ESP_LOGV(TAG, " Month: %d", *this->month_);
|
||||
}
|
||||
if (this->day_.has_value()) {
|
||||
ESP_LOGD(TAG, " Day: %d", *this->day_);
|
||||
ESP_LOGV(TAG, " Day: %d", *this->day_);
|
||||
}
|
||||
if (this->hour_.has_value()) {
|
||||
ESP_LOGD(TAG, " Hour: %d", *this->hour_);
|
||||
ESP_LOGV(TAG, " Hour: %d", *this->hour_);
|
||||
}
|
||||
if (this->minute_.has_value()) {
|
||||
ESP_LOGD(TAG, " Minute: %d", *this->minute_);
|
||||
ESP_LOGV(TAG, " Minute: %d", *this->minute_);
|
||||
}
|
||||
if (this->second_.has_value()) {
|
||||
ESP_LOGD(TAG, " Second: %d", *this->second_);
|
||||
ESP_LOGV(TAG, " Second: %d", *this->second_);
|
||||
}
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
|
||||
@@ -26,7 +26,7 @@ void TimeEntity::publish_state() {
|
||||
return;
|
||||
}
|
||||
this->set_has_state(true);
|
||||
ESP_LOGD(TAG, "'%s' >> %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_, this->second_);
|
||||
ESP_LOGV(TAG, "'%s' >> %02d:%02d:%02d", this->get_name().c_str(), this->hour_, this->minute_, this->second_);
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_DATETIME_TIME) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_time_update(this);
|
||||
@@ -52,15 +52,15 @@ void TimeCall::validate_() {
|
||||
|
||||
void TimeCall::perform() {
|
||||
this->validate_();
|
||||
ESP_LOGD(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
ESP_LOGV(TAG, "'%s' - Setting", this->parent_->get_name().c_str());
|
||||
if (this->hour_.has_value()) {
|
||||
ESP_LOGD(TAG, " Hour: %d", *this->hour_);
|
||||
ESP_LOGV(TAG, " Hour: %d", *this->hour_);
|
||||
}
|
||||
if (this->minute_.has_value()) {
|
||||
ESP_LOGD(TAG, " Minute: %d", *this->minute_);
|
||||
ESP_LOGV(TAG, " Minute: %d", *this->minute_);
|
||||
}
|
||||
if (this->second_.has_value()) {
|
||||
ESP_LOGD(TAG, " Second: %d", *this->second_);
|
||||
ESP_LOGV(TAG, " Second: %d", *this->second_);
|
||||
}
|
||||
this->parent_->control(*this);
|
||||
}
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -40,11 +40,8 @@ void DS1307Component::read_time() {
|
||||
.hour = uint8_t(ds1307_.reg.hour + 10u * ds1307_.reg.hour_10),
|
||||
.day_of_week = uint8_t(ds1307_.reg.weekday),
|
||||
.day_of_month = uint8_t(ds1307_.reg.day + 10u * ds1307_.reg.day_10),
|
||||
.day_of_year = 1, // ignored by recalc_timestamp_utc(false)
|
||||
.month = uint8_t(ds1307_.reg.month + 10u * ds1307_.reg.month_10),
|
||||
.year = uint16_t(ds1307_.reg.year + 10u * ds1307_.reg.year_10 + 2000),
|
||||
.is_dst = false, // not used
|
||||
.timestamp = 0 // overwritten by recalc_timestamp_utc(false)
|
||||
};
|
||||
rtc_time.recalc_timestamp_utc(false);
|
||||
if (!rtc_time.is_valid()) {
|
||||
|
||||
@@ -97,6 +97,7 @@ CONF_ENABLE_LWIP_ASSERT = "enable_lwip_assert"
|
||||
CONF_EXECUTE_FROM_PSRAM = "execute_from_psram"
|
||||
CONF_MINIMUM_CHIP_REVISION = "minimum_chip_revision"
|
||||
CONF_RELEASE = "release"
|
||||
CONF_SRAM1_AS_IRAM = "sram1_as_iram"
|
||||
CONF_SUBTYPE = "subtype"
|
||||
|
||||
ARDUINO_FRAMEWORK_NAME = "framework-arduinoespressif32"
|
||||
@@ -378,12 +379,11 @@ FULL_CPU_FREQUENCIES = set(itertools.chain.from_iterable(CPU_FREQUENCIES.values(
|
||||
def set_core_data(config):
|
||||
cpu_frequency = config.get(CONF_CPU_FREQUENCY, None)
|
||||
variant = config[CONF_VARIANT]
|
||||
# if not specified in config, set to 160MHz if supported, the fastest otherwise
|
||||
# if not specified in config, default to the maximum supported frequency
|
||||
# (ESP32-P4 engineering samples are limited to 360MHz, non-engineering can do 400MHz)
|
||||
if cpu_frequency is None:
|
||||
choices = CPU_FREQUENCIES[variant]
|
||||
if "160MHZ" in choices:
|
||||
cpu_frequency = "160MHZ"
|
||||
elif "360MHZ" in choices:
|
||||
if variant == VARIANT_ESP32P4 and config.get(CONF_ENGINEERING_SAMPLE):
|
||||
cpu_frequency = "360MHZ"
|
||||
else:
|
||||
cpu_frequency = choices[-1]
|
||||
@@ -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),
|
||||
@@ -884,6 +890,13 @@ def final_validate(config):
|
||||
path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_MINIMUM_CHIP_REVISION],
|
||||
)
|
||||
)
|
||||
if config[CONF_VARIANT] != VARIANT_ESP32 and advanced[CONF_SRAM1_AS_IRAM]:
|
||||
errs.append(
|
||||
cv.Invalid(
|
||||
f"'{CONF_SRAM1_AS_IRAM}' is only supported on {VARIANT_ESP32}",
|
||||
path=[CONF_FRAMEWORK, CONF_ADVANCED, CONF_SRAM1_AS_IRAM],
|
||||
)
|
||||
)
|
||||
if (
|
||||
config[CONF_VARIANT] != VARIANT_ESP32P4
|
||||
and config.get(CONF_ENGINEERING_SAMPLE) is not None
|
||||
@@ -1131,6 +1144,7 @@ FRAMEWORK_SCHEMA = cv.Schema(
|
||||
cv.Optional(CONF_MINIMUM_CHIP_REVISION): cv.one_of(
|
||||
*ESP32_CHIP_REVISIONS
|
||||
),
|
||||
cv.Optional(CONF_SRAM1_AS_IRAM, default=False): cv.boolean,
|
||||
# DHCP server is needed for WiFi AP mode. When WiFi component is used,
|
||||
# it will handle disabling DHCP server when AP is not configured.
|
||||
# Default to false (disabled) when WiFi is not used.
|
||||
@@ -1579,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")
|
||||
@@ -1655,6 +1669,16 @@ async def to_code(config):
|
||||
for rev, flag in ESP32_CHIP_REVISIONS.items():
|
||||
add_idf_sdkconfig_option(flag, rev == min_rev)
|
||||
cg.add_define("USE_ESP32_MIN_CHIP_REVISION_SET")
|
||||
|
||||
# Use SRAM1 region as IRAM on ESP32 (original) variant
|
||||
# This provides an additional 40KB of IRAM by using SRAM1 memory that was previously
|
||||
# reserved for bootloader DRAM. Requires a bootloader from ESP-IDF v5.1 or later.
|
||||
# WARNING: If the device has an old bootloader (pre-v5.1), the app will fail to boot.
|
||||
# A USB flash will update the bootloader automatically. OTA updates do not.
|
||||
# See: https://docs.espressif.com/projects/esp-idf/en/stable/esp32/api-guides/performance/ram-usage.html
|
||||
if variant == VARIANT_ESP32 and conf[CONF_ADVANCED][CONF_SRAM1_AS_IRAM]:
|
||||
add_idf_sdkconfig_option("CONFIG_ESP_SYSTEM_ESP32_SRAM1_REGION_AS_IRAM", True)
|
||||
cg.add_define("USE_ESP32_SRAM1_AS_IRAM")
|
||||
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_SINGLE_APP", False)
|
||||
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM", True)
|
||||
add_idf_sdkconfig_option("CONFIG_PARTITION_TABLE_CUSTOM_FILENAME", "partitions.csv")
|
||||
|
||||
@@ -14,6 +14,8 @@ _ESP32C3_SPI_PSRAM_PINS = {
|
||||
17: "SPIQ",
|
||||
}
|
||||
|
||||
_ESP32C3_USB_JTAG_PINS = {18, 19}
|
||||
|
||||
_ESP32C3_STRAPPING_PINS = {2, 8, 9}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
@@ -26,6 +28,12 @@ def esp32_c3_validate_gpio_pin(value: int) -> int:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32-C3s and is already used by the SPI/PSRAM interface (function: {_ESP32C3_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
if value in _ESP32C3_USB_JTAG_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is used by the USB-Serial-JTAG interface."
|
||||
" Using this pin as GPIO will conflict with USB-Serial-JTAG.",
|
||||
value,
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
@@ -18,7 +18,9 @@ _ESP32C6_SPI_PSRAM_PINS = {
|
||||
30: "SPID",
|
||||
}
|
||||
|
||||
_ESP32C6_STRAPPING_PINS = {8, 9, 15}
|
||||
_ESP32C6_USB_JTAG_PINS = {12, 13}
|
||||
|
||||
_ESP32C6_STRAPPING_PINS = {4, 5, 8, 9, 15}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -30,6 +32,12 @@ def esp32_c6_validate_gpio_pin(value: int) -> int:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32-C6s and is already used by the SPI/PSRAM interface (function: {_ESP32C6_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
if value in _ESP32C6_USB_JTAG_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is used by the USB-Serial-JTAG interface."
|
||||
" Using this pin as GPIO will conflict with USB-Serial-JTAG.",
|
||||
value,
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
|
||||
@@ -9,7 +9,7 @@ _ESP32H2_SPI_FLASH_PINS = {6, 7, 15, 16, 17, 18, 19, 20, 21}
|
||||
|
||||
_ESP32H2_USB_JTAG_PINS = {26, 27}
|
||||
|
||||
_ESP32H2_STRAPPING_PINS = {2, 3, 8, 9, 25}
|
||||
_ESP32H2_STRAPPING_PINS = {8, 9, 25}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -26,8 +26,8 @@ def esp32_h2_validate_gpio_pin(value: int) -> int:
|
||||
)
|
||||
if value in _ESP32H2_USB_JTAG_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is reserved for the USB-Serial-JTAG interface.\n"
|
||||
"To use this pin as GPIO, USB-Serial-JTAG will be disabled.",
|
||||
"GPIO%d is used by the USB-Serial-JTAG interface."
|
||||
" Using this pin as GPIO will conflict with USB-Serial-JTAG.",
|
||||
value,
|
||||
)
|
||||
|
||||
|
||||
@@ -20,8 +20,8 @@ def esp32_p4_validate_gpio_pin(value: int) -> int:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-54)")
|
||||
if value in _ESP32P4_USB_JTAG_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is reserved for the USB-Serial-JTAG interface.\n"
|
||||
"To use this pin as GPIO, USB-Serial-JTAG will be disabled.",
|
||||
"GPIO%d is used by the USB-Serial-JTAG interface."
|
||||
" Using this pin as GPIO will conflict with USB-Serial-JTAG.",
|
||||
value,
|
||||
)
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@ import esphome.config_validation as cv
|
||||
from esphome.const import CONF_INPUT, CONF_MODE, CONF_NUMBER
|
||||
from esphome.pins import check_strapping_pin
|
||||
|
||||
_ESP_32S3_SPI_PSRAM_PINS = {
|
||||
_ESP32S3_SPI_PSRAM_PINS = {
|
||||
26: "SPICS1",
|
||||
27: "SPIHD",
|
||||
28: "SPIWP",
|
||||
@@ -15,7 +15,7 @@ _ESP_32S3_SPI_PSRAM_PINS = {
|
||||
32: "SPID",
|
||||
}
|
||||
|
||||
_ESP_32_ESP32_S3R8_PSRAM_PINS = {
|
||||
_ESP32S3R8_PSRAM_PINS = {
|
||||
33: "SPIIO4",
|
||||
34: "SPIIO5",
|
||||
35: "SPIIO6",
|
||||
@@ -23,7 +23,9 @@ _ESP_32_ESP32_S3R8_PSRAM_PINS = {
|
||||
37: "SPIDQS",
|
||||
}
|
||||
|
||||
_ESP_32S3_STRAPPING_PINS = {0, 3, 45, 46}
|
||||
_ESP32S3_USB_JTAG_PINS = {19, 20}
|
||||
|
||||
_ESP32S3_STRAPPING_PINS = {0, 3, 45, 46}
|
||||
|
||||
_LOGGER = logging.getLogger(__name__)
|
||||
|
||||
@@ -32,11 +34,11 @@ def esp32_s3_validate_gpio_pin(value: int) -> int:
|
||||
if value < 0 or value > 48:
|
||||
raise cv.Invalid(f"Invalid pin number: {value} (must be 0-48)")
|
||||
|
||||
if value in _ESP_32S3_SPI_PSRAM_PINS:
|
||||
if value in _ESP32S3_SPI_PSRAM_PINS:
|
||||
raise cv.Invalid(
|
||||
f"This pin cannot be used on ESP32-S3s and is already used by the SPI/PSRAM interface(function: {_ESP_32S3_SPI_PSRAM_PINS[value]})"
|
||||
f"This pin cannot be used on ESP32-S3s and is already used by the SPI/PSRAM interface(function: {_ESP32S3_SPI_PSRAM_PINS[value]})"
|
||||
)
|
||||
if value in _ESP_32_ESP32_S3R8_PSRAM_PINS:
|
||||
if value in _ESP32S3R8_PSRAM_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is used by the PSRAM interface on ESP32-S3R8 / ESP32-S3R8V and should be avoided on these models",
|
||||
value,
|
||||
@@ -46,6 +48,12 @@ def esp32_s3_validate_gpio_pin(value: int) -> int:
|
||||
# These pins are not exposed in GPIO mux (reason unknown)
|
||||
# but they're missing from IO_MUX list in datasheet
|
||||
raise cv.Invalid(f"The pin GPIO{value} is not usable on ESP32-S3s.")
|
||||
if value in _ESP32S3_USB_JTAG_PINS:
|
||||
_LOGGER.warning(
|
||||
"GPIO%d is used by the USB-Serial-JTAG interface."
|
||||
" Using this pin as GPIO will conflict with USB-Serial-JTAG.",
|
||||
value,
|
||||
)
|
||||
|
||||
return value
|
||||
|
||||
@@ -61,5 +69,5 @@ def esp32_s3_validate_supports(value: dict[str, Any]) -> dict[str, Any]:
|
||||
# All ESP32 pins support input mode
|
||||
pass
|
||||
|
||||
check_strapping_pin(value, _ESP_32S3_STRAPPING_PINS, _LOGGER)
|
||||
check_strapping_pin(value, _ESP32S3_STRAPPING_PINS, _LOGGER)
|
||||
return value
|
||||
|
||||
@@ -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: {
|
||||
|
||||
@@ -70,6 +70,7 @@ template<typename... Ts> class BLECharacteristicSetValueAction : public Action<T
|
||||
public:
|
||||
BLECharacteristicSetValueAction(BLECharacteristic *characteristic) : parent_(characteristic) {}
|
||||
TEMPLATABLE_VALUE(std::vector<uint8_t>, buffer)
|
||||
void set_buffer(std::initializer_list<uint8_t> buffer) { this->buffer_ = std::vector<uint8_t>(buffer); }
|
||||
void set_buffer(ByteBuffer buffer) { this->set_buffer(buffer.get_data()); }
|
||||
void play(const Ts &...x) override {
|
||||
// If the listener is already set, do nothing
|
||||
@@ -115,6 +116,7 @@ template<typename... Ts> class BLEDescriptorSetValueAction : public Action<Ts...
|
||||
public:
|
||||
BLEDescriptorSetValueAction(BLEDescriptor *descriptor) : parent_(descriptor) {}
|
||||
TEMPLATABLE_VALUE(std::vector<uint8_t>, buffer)
|
||||
void set_buffer(std::initializer_list<uint8_t> buffer) { this->buffer_ = std::vector<uint8_t>(buffer); }
|
||||
void set_buffer(ByteBuffer buffer) { this->set_buffer(buffer.get_data()); }
|
||||
void play(const Ts &...x) override { this->parent_->set_value(this->buffer_.value(x...)); }
|
||||
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -115,6 +115,7 @@ ETHERNET_TYPES = {
|
||||
"JL1101": EthernetType.ETHERNET_TYPE_JL1101,
|
||||
"KSZ8081": EthernetType.ETHERNET_TYPE_KSZ8081,
|
||||
"KSZ8081RNA": EthernetType.ETHERNET_TYPE_KSZ8081RNA,
|
||||
"W5100": EthernetType.ETHERNET_TYPE_W5100,
|
||||
"W5500": EthernetType.ETHERNET_TYPE_W5500,
|
||||
"OPENETH": EthernetType.ETHERNET_TYPE_OPENETH,
|
||||
"DM9051": EthernetType.ETHERNET_TYPE_DM9051,
|
||||
@@ -132,6 +133,7 @@ _PHY_TYPE_TO_DEFINE = {
|
||||
"JL1101": "USE_ETHERNET_JL1101",
|
||||
"KSZ8081": "USE_ETHERNET_KSZ8081",
|
||||
"KSZ8081RNA": "USE_ETHERNET_KSZ8081",
|
||||
"W5100": "USE_ETHERNET_W5100",
|
||||
"W5500": "USE_ETHERNET_W5500",
|
||||
"DM9051": "USE_ETHERNET_DM9051",
|
||||
"LAN8670": "USE_ETHERNET_LAN8670",
|
||||
@@ -164,9 +166,15 @@ _IDF6_ETHERNET_COMPONENTS: dict[str, IDFRegistryComponent] = {
|
||||
# These types are always external IDF components (never built-in to ESP-IDF)
|
||||
_ALWAYS_EXTERNAL_IDF_COMPONENTS = {"LAN8670", "ENC28J60"}
|
||||
|
||||
SPI_ETHERNET_TYPES = ["W5500", "DM9051", "ENC28J60"]
|
||||
# ESP32-only SPI ethernet types (W5100 is RP2040-only, no ESP-IDF driver)
|
||||
SPI_ETHERNET_TYPES = {"W5500", "DM9051", "ENC28J60"}
|
||||
# RP2040-supported SPI ethernet types
|
||||
RP2040_SPI_ETHERNET_TYPES = ["W5500", "ENC28J60"]
|
||||
RP2040_SPI_ETHERNET_TYPES = {"W5100", "W5500", "ENC28J60"}
|
||||
_RP2040_SPI_LIBRARIES = {
|
||||
"W5100": "lwIP_w5100",
|
||||
"W5500": "lwIP_w5500",
|
||||
"ENC28J60": "lwIP_enc28j60",
|
||||
}
|
||||
SPI_ETHERNET_DEFAULT_POLLING_INTERVAL = TimePeriodMilliseconds(milliseconds=10)
|
||||
|
||||
emac_rmii_clock_mode_t = cg.global_ns.enum("emac_rmii_clock_mode_t")
|
||||
@@ -295,7 +303,7 @@ def _validate(config):
|
||||
)
|
||||
elif CORE.is_rp2040 and config[CONF_TYPE] not in RP2040_SPI_ETHERNET_TYPES:
|
||||
raise cv.Invalid(
|
||||
f"Only {', '.join(RP2040_SPI_ETHERNET_TYPES)} are supported on RP2040, "
|
||||
f"Only {', '.join(sorted(RP2040_SPI_ETHERNET_TYPES))} are supported on RP2040, "
|
||||
f"not {config[CONF_TYPE]}"
|
||||
)
|
||||
return config
|
||||
@@ -382,6 +390,7 @@ CONFIG_SCHEMA = cv.All(
|
||||
"JL1101": RMII_SCHEMA,
|
||||
"KSZ8081": RMII_SCHEMA,
|
||||
"KSZ8081RNA": RMII_SCHEMA,
|
||||
"W5100": cv.All(SPI_SCHEMA, cv.only_on([Platform.RP2040])),
|
||||
"W5500": SPI_SCHEMA,
|
||||
"OPENETH": cv.All(BASE_SCHEMA, cv.only_on([Platform.ESP32])),
|
||||
"DM9051": SPI_SCHEMA,
|
||||
@@ -574,10 +583,7 @@ async def _to_code_rp2040(var: cg.Pvariable, config: ConfigType) -> None:
|
||||
cg.add(var.set_reset_pin(config[CONF_RESET_PIN]))
|
||||
|
||||
cg.add_define("USE_ETHERNET_SPI")
|
||||
if config[CONF_TYPE] == "ENC28J60":
|
||||
cg.add_library("lwIP_enc28j60", None)
|
||||
else:
|
||||
cg.add_library("lwIP_w5500", None)
|
||||
cg.add_library(_RP2040_SPI_LIBRARIES[config[CONF_TYPE]], None)
|
||||
|
||||
|
||||
def _final_validate_rmii_pins(config: ConfigType) -> None:
|
||||
|
||||
@@ -25,6 +25,8 @@ extern "C" eth_esp32_emac_config_t eth_esp32_emac_default_config(void);
|
||||
#ifdef USE_RP2040
|
||||
#if defined(USE_ETHERNET_W5500)
|
||||
#include <W5500lwIP.h>
|
||||
#elif defined(USE_ETHERNET_W5100)
|
||||
#include <W5100lwIP.h>
|
||||
#elif defined(USE_ETHERNET_ENC28J60)
|
||||
#include <ENC28J60lwIP.h>
|
||||
#else
|
||||
@@ -59,6 +61,7 @@ enum EthernetType : uint8_t {
|
||||
ETHERNET_TYPE_JL1101,
|
||||
ETHERNET_TYPE_KSZ8081,
|
||||
ETHERNET_TYPE_KSZ8081RNA,
|
||||
ETHERNET_TYPE_W5100,
|
||||
ETHERNET_TYPE_W5500,
|
||||
ETHERNET_TYPE_OPENETH,
|
||||
ETHERNET_TYPE_DM9051,
|
||||
@@ -222,8 +225,15 @@ class EthernetComponent final : public Component {
|
||||
|
||||
#ifdef USE_RP2040
|
||||
static constexpr uint32_t LINK_CHECK_INTERVAL = 500; // ms between link/IP polls
|
||||
#if defined(USE_ETHERNET_W5100)
|
||||
static constexpr uint32_t RESET_DELAY_MS = 150; // W5100S PLL lock time
|
||||
#else
|
||||
static constexpr uint32_t RESET_DELAY_MS = 10;
|
||||
#endif
|
||||
#if defined(USE_ETHERNET_W5500)
|
||||
Wiznet5500lwIP *eth_{nullptr};
|
||||
#elif defined(USE_ETHERNET_W5100)
|
||||
Wiznet5100lwIP *eth_{nullptr};
|
||||
#elif defined(USE_ETHERNET_ENC28J60)
|
||||
ENC28J60lwIP *eth_{nullptr};
|
||||
#else
|
||||
|
||||
@@ -31,12 +31,15 @@ void EthernetComponent::setup() {
|
||||
reset_pin.digital_write(false);
|
||||
delay(1); // NOLINT
|
||||
reset_pin.digital_write(true);
|
||||
delay(10); // NOLINT - wait for chip to initialize after reset
|
||||
// W5100S needs 150ms for PLL lock; W5500/ENC28J60 need ~10ms
|
||||
delay(RESET_DELAY_MS); // NOLINT
|
||||
}
|
||||
|
||||
// Create the SPI Ethernet device instance
|
||||
#if defined(USE_ETHERNET_W5500)
|
||||
this->eth_ = new Wiznet5500lwIP(this->cs_pin_, SPI, this->interrupt_pin_); // NOLINT
|
||||
#elif defined(USE_ETHERNET_W5100)
|
||||
this->eth_ = new Wiznet5100lwIP(this->cs_pin_, SPI, this->interrupt_pin_); // NOLINT
|
||||
#elif defined(USE_ETHERNET_ENC28J60)
|
||||
this->eth_ = new ENC28J60lwIP(this->cs_pin_, SPI, this->interrupt_pin_); // NOLINT
|
||||
#endif
|
||||
@@ -80,8 +83,8 @@ void EthernetComponent::setup() {
|
||||
// or via GPIO interrupt when one is provided.
|
||||
|
||||
// Don't set started_ here — let the link polling in loop() set it
|
||||
// when the W5500 link is actually up. Setting it prematurely causes
|
||||
// a "Starting → Stopped → Starting" log sequence because the W5500
|
||||
// when the link is actually up. Setting it prematurely causes
|
||||
// a "Starting → Stopped → Starting" log sequence because the chip
|
||||
// needs time after begin() before the PHY link is ready.
|
||||
}
|
||||
|
||||
@@ -89,14 +92,21 @@ void EthernetComponent::loop() {
|
||||
// On RP2040, we need to poll connection state since there are no events.
|
||||
const uint32_t now = App.get_loop_component_start_time();
|
||||
|
||||
// Throttle link/IP polling to avoid excessive SPI transactions from linkStatus()
|
||||
// which reads the W5500 PHY register via SPI on every call.
|
||||
// Throttle link/IP polling to avoid excessive SPI transactions.
|
||||
// W5500/ENC28J60 read PHY register via SPI on every linkStatus() call.
|
||||
// W5100 can't detect link state, so we skip the SPI read and assume link-up.
|
||||
// connected() reads netif->ip_addr without LwIPLock, but this is a single
|
||||
// 32-bit aligned read (atomic on ARM) — worst case is a one-iteration-stale
|
||||
// value, which is benign for polling.
|
||||
if (this->eth_ != nullptr && now - this->last_link_check_ >= LINK_CHECK_INTERVAL) {
|
||||
this->last_link_check_ = now;
|
||||
#if defined(USE_ETHERNET_W5100)
|
||||
// W5100 can't detect link (isLinkDetectable() returns false), so linkStatus()
|
||||
// returns Unknown — assume link is up after successful begin()
|
||||
bool link_up = true;
|
||||
#else
|
||||
bool link_up = this->eth_->linkStatus() == LinkON;
|
||||
#endif
|
||||
bool has_ip = this->eth_->connected();
|
||||
|
||||
if (!link_up) {
|
||||
@@ -171,6 +181,8 @@ void EthernetComponent::dump_config() {
|
||||
const char *type_str = "Unknown";
|
||||
#if defined(USE_ETHERNET_W5500)
|
||||
type_str = "W5500";
|
||||
#elif defined(USE_ETHERNET_W5100)
|
||||
type_str = "W5100";
|
||||
#elif defined(USE_ETHERNET_ENC28J60)
|
||||
type_str = "ENC28J60";
|
||||
#endif
|
||||
@@ -226,7 +238,7 @@ const char *EthernetComponent::get_eth_mac_address_pretty_into_buffer(
|
||||
}
|
||||
|
||||
eth_duplex_t EthernetComponent::get_duplex_mode() {
|
||||
// Both W5500 and ENC28J60 are full-duplex on RP2040
|
||||
// W5100, W5500, and ENC28J60 are full-duplex on RP2040
|
||||
return ETH_DUPLEX_FULL;
|
||||
}
|
||||
|
||||
@@ -235,7 +247,7 @@ eth_speed_t EthernetComponent::get_link_speed() {
|
||||
// ENC28J60 is 10Mbps only
|
||||
return ETH_SPEED_10M;
|
||||
#else
|
||||
// W5500 is always 100Mbps
|
||||
// W5100 and W5500 are 100Mbps
|
||||
return ETH_SPEED_100M;
|
||||
#endif
|
||||
}
|
||||
|
||||
@@ -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))
|
||||
|
||||
|
||||
@@ -22,7 +22,7 @@ void Event::trigger(const std::string &event_type) {
|
||||
return;
|
||||
}
|
||||
this->last_event_type_ = found;
|
||||
ESP_LOGD(TAG, "'%s' >> '%s'", this->get_name().c_str(), this->last_event_type_);
|
||||
ESP_LOGV(TAG, "'%s' >> '%s'", this->get_name().c_str(), this->last_event_type_);
|
||||
this->event_callback_.call(StringRef(found));
|
||||
#if defined(USE_EVENT) && defined(USE_CONTROLLER_REGISTRY)
|
||||
ControllerRegistry::notify_event(this);
|
||||
|
||||
@@ -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
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -44,22 +44,22 @@ FanCall &FanCall::set_preset_mode(const char *preset_mode, size_t len) {
|
||||
}
|
||||
|
||||
void FanCall::perform() {
|
||||
ESP_LOGD(TAG, "'%s' - Setting:", this->parent_.get_name().c_str());
|
||||
ESP_LOGV(TAG, "'%s' - Setting:", this->parent_.get_name().c_str());
|
||||
this->validate_();
|
||||
if (this->binary_state_.has_value()) {
|
||||
ESP_LOGD(TAG, " State: %s", ONOFF(*this->binary_state_));
|
||||
ESP_LOGV(TAG, " State: %s", ONOFF(*this->binary_state_));
|
||||
}
|
||||
if (this->oscillating_.has_value()) {
|
||||
ESP_LOGD(TAG, " Oscillating: %s", YESNO(*this->oscillating_));
|
||||
ESP_LOGV(TAG, " Oscillating: %s", YESNO(*this->oscillating_));
|
||||
}
|
||||
if (this->speed_.has_value()) {
|
||||
ESP_LOGD(TAG, " Speed: %d", *this->speed_);
|
||||
ESP_LOGV(TAG, " Speed: %d", *this->speed_);
|
||||
}
|
||||
if (this->direction_.has_value()) {
|
||||
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_)));
|
||||
ESP_LOGV(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(*this->direction_)));
|
||||
}
|
||||
if (this->preset_mode_ != nullptr) {
|
||||
ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_);
|
||||
ESP_LOGV(TAG, " Preset Mode: %s", this->preset_mode_);
|
||||
}
|
||||
this->parent_.control(*this);
|
||||
}
|
||||
@@ -196,21 +196,21 @@ void Fan::apply_preset_mode_(const FanCall &call) {
|
||||
void Fan::publish_state() {
|
||||
auto traits = this->get_traits();
|
||||
|
||||
ESP_LOGD(TAG,
|
||||
ESP_LOGV(TAG,
|
||||
"'%s' >>\n"
|
||||
" State: %s",
|
||||
this->name_.c_str(), ONOFF(this->state));
|
||||
if (traits.supports_speed()) {
|
||||
ESP_LOGD(TAG, " Speed: %d", this->speed);
|
||||
ESP_LOGV(TAG, " Speed: %d", this->speed);
|
||||
}
|
||||
if (traits.supports_oscillation()) {
|
||||
ESP_LOGD(TAG, " Oscillating: %s", YESNO(this->oscillating));
|
||||
ESP_LOGV(TAG, " Oscillating: %s", YESNO(this->oscillating));
|
||||
}
|
||||
if (traits.supports_direction()) {
|
||||
ESP_LOGD(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction)));
|
||||
ESP_LOGV(TAG, " Direction: %s", LOG_STR_ARG(fan_direction_to_string(this->direction)));
|
||||
}
|
||||
if (this->preset_mode_ != nullptr) {
|
||||
ESP_LOGD(TAG, " Preset Mode: %s", this->preset_mode_);
|
||||
ESP_LOGV(TAG, " Preset Mode: %s", this->preset_mode_);
|
||||
}
|
||||
this->state_callback_.call();
|
||||
#if defined(USE_FAN) && defined(USE_CONTROLLER_REGISTRY)
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -23,9 +23,8 @@ static const LogString *gpio_mode_to_string(bool use_interrupt) {
|
||||
|
||||
void IRAM_ATTR GPIOBinarySensorStore::gpio_intr(GPIOBinarySensorStore *arg) {
|
||||
bool new_state = arg->isr_pin_.digital_read();
|
||||
if (new_state != arg->last_state_) {
|
||||
if (new_state != arg->state_) {
|
||||
arg->state_ = new_state;
|
||||
arg->last_state_ = new_state;
|
||||
arg->changed_ = true;
|
||||
// Wake up the component from its disabled loop state
|
||||
if (arg->component_ != nullptr) {
|
||||
@@ -34,28 +33,27 @@ void IRAM_ATTR GPIOBinarySensorStore::gpio_intr(GPIOBinarySensorStore *arg) {
|
||||
}
|
||||
}
|
||||
|
||||
void GPIOBinarySensorStore::setup(InternalGPIOPin *pin, gpio::InterruptType type, Component *component) {
|
||||
void GPIOBinarySensorStore::setup(InternalGPIOPin *pin, Component *component) {
|
||||
pin->setup();
|
||||
this->isr_pin_ = pin->to_isr();
|
||||
this->component_ = component;
|
||||
|
||||
// Read initial state
|
||||
this->last_state_ = pin->digital_read();
|
||||
this->state_ = this->last_state_;
|
||||
this->state_ = pin->digital_read();
|
||||
|
||||
// Attach interrupt - from this point on, any changes will be caught by the interrupt
|
||||
pin->attach_interrupt(&GPIOBinarySensorStore::gpio_intr, this, type);
|
||||
pin->attach_interrupt(&GPIOBinarySensorStore::gpio_intr, this, this->interrupt_type_);
|
||||
}
|
||||
|
||||
void GPIOBinarySensor::setup() {
|
||||
if (this->use_interrupt_ && !this->pin_->is_internal()) {
|
||||
if (this->store_.use_interrupt_ && !this->pin_->is_internal()) {
|
||||
ESP_LOGD(TAG, "GPIO is not internal, falling back to polling mode");
|
||||
this->use_interrupt_ = false;
|
||||
this->store_.use_interrupt_ = false;
|
||||
}
|
||||
|
||||
if (this->use_interrupt_) {
|
||||
if (this->store_.use_interrupt_) {
|
||||
auto *internal_pin = static_cast<InternalGPIOPin *>(this->pin_);
|
||||
this->store_.setup(internal_pin, this->interrupt_type_, this);
|
||||
this->store_.setup(internal_pin, this);
|
||||
this->publish_initial_state(this->store_.get_state());
|
||||
} else {
|
||||
this->pin_->setup();
|
||||
@@ -66,14 +64,14 @@ void GPIOBinarySensor::setup() {
|
||||
void GPIOBinarySensor::dump_config() {
|
||||
LOG_BINARY_SENSOR("", "GPIO Binary Sensor", this);
|
||||
LOG_PIN(" Pin: ", this->pin_);
|
||||
ESP_LOGCONFIG(TAG, " Mode: %s", LOG_STR_ARG(gpio_mode_to_string(this->use_interrupt_)));
|
||||
if (this->use_interrupt_) {
|
||||
ESP_LOGCONFIG(TAG, " Interrupt Type: %s", LOG_STR_ARG(interrupt_type_to_string(this->interrupt_type_)));
|
||||
ESP_LOGCONFIG(TAG, " Mode: %s", LOG_STR_ARG(gpio_mode_to_string(this->store_.use_interrupt_)));
|
||||
if (this->store_.use_interrupt_) {
|
||||
ESP_LOGCONFIG(TAG, " Interrupt Type: %s", LOG_STR_ARG(interrupt_type_to_string(this->store_.interrupt_type_)));
|
||||
}
|
||||
}
|
||||
|
||||
void GPIOBinarySensor::loop() {
|
||||
if (this->use_interrupt_) {
|
||||
if (this->store_.use_interrupt_) {
|
||||
if (this->store_.is_changed()) {
|
||||
// Clear the flag immediately to minimize the window where we might miss changes
|
||||
this->store_.clear_changed();
|
||||
|
||||
@@ -8,10 +8,10 @@
|
||||
namespace esphome {
|
||||
namespace gpio {
|
||||
|
||||
// Store class for ISR data (no vtables, ISR-safe)
|
||||
// Store class for ISR data and configuration (no vtables, ISR-safe)
|
||||
class GPIOBinarySensorStore {
|
||||
public:
|
||||
void setup(InternalGPIOPin *pin, gpio::InterruptType type, Component *component);
|
||||
void setup(InternalGPIOPin *pin, Component *component);
|
||||
|
||||
static void gpio_intr(GPIOBinarySensorStore *arg);
|
||||
|
||||
@@ -32,11 +32,13 @@ class GPIOBinarySensorStore {
|
||||
}
|
||||
|
||||
protected:
|
||||
friend class GPIOBinarySensor;
|
||||
ISRInternalGPIOPin isr_pin_;
|
||||
volatile bool state_{false};
|
||||
volatile bool last_state_{false};
|
||||
volatile bool changed_{false};
|
||||
Component *component_{nullptr}; // Pointer to the component for enable_loop_soon_any_context()
|
||||
volatile bool state_{false};
|
||||
volatile bool changed_{false};
|
||||
bool use_interrupt_{true};
|
||||
gpio::InterruptType interrupt_type_{gpio::INTERRUPT_ANY_EDGE};
|
||||
};
|
||||
|
||||
class GPIOBinarySensor final : public binary_sensor::BinarySensor, public Component {
|
||||
@@ -44,9 +46,9 @@ class GPIOBinarySensor final : public binary_sensor::BinarySensor, public Compon
|
||||
// No destructor needed: ESPHome components are created at boot and live forever.
|
||||
// Interrupts are only detached on reboot when memory is cleared anyway.
|
||||
|
||||
void set_pin(GPIOPin *pin) { pin_ = pin; }
|
||||
void set_use_interrupt(bool use_interrupt) { use_interrupt_ = use_interrupt; }
|
||||
void set_interrupt_type(gpio::InterruptType type) { interrupt_type_ = type; }
|
||||
void set_pin(GPIOPin *pin) { this->pin_ = pin; }
|
||||
void set_use_interrupt(bool use_interrupt) { this->store_.use_interrupt_ = use_interrupt; }
|
||||
void set_interrupt_type(gpio::InterruptType type) { this->store_.interrupt_type_ = type; }
|
||||
// ========== INTERNAL METHODS ==========
|
||||
// (In most use cases you won't need these)
|
||||
/// Setup pin
|
||||
@@ -59,8 +61,6 @@ class GPIOBinarySensor final : public binary_sensor::BinarySensor, public Compon
|
||||
|
||||
protected:
|
||||
GPIOPin *pin_;
|
||||
bool use_interrupt_{true};
|
||||
gpio::InterruptType interrupt_type_{gpio::INTERRUPT_ANY_EDGE};
|
||||
GPIOBinarySensorStore store_;
|
||||
};
|
||||
|
||||
|
||||
@@ -16,10 +16,6 @@ void GPSTime::from_tiny_gps_(TinyGPSPlus &tiny_gps) {
|
||||
val.year = tiny_gps.date.year();
|
||||
val.month = tiny_gps.date.month();
|
||||
val.day_of_month = tiny_gps.date.day();
|
||||
// Set these to valid value for recalc_timestamp_utc - it's not used for calculation
|
||||
val.day_of_week = 1;
|
||||
val.day_of_year = 1;
|
||||
|
||||
val.hour = tiny_gps.time.hour();
|
||||
val.minute = tiny_gps.time.minute();
|
||||
val.second = tiny_gps.time.second();
|
||||
|
||||
@@ -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")
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -748,7 +748,7 @@ void HonClimate::update_sub_sensor_(SubSensorType type, float value) {
|
||||
if (type < SubSensorType::SUB_SENSOR_TYPE_COUNT) {
|
||||
size_t index = (size_t) type;
|
||||
if ((this->sub_sensors_[index] != nullptr) &&
|
||||
((!this->sub_sensors_[index]->has_state()) || (this->sub_sensors_[index]->raw_state != value)))
|
||||
((!this->sub_sensors_[index]->has_state()) || (this->sub_sensors_[index]->get_raw_state() != value)))
|
||||
this->sub_sensors_[index]->publish_state(value);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -128,6 +128,7 @@ SCAN_WIRINGS = {
|
||||
"STANDARD_TWO_SCAN": Hub75ScanWiring.STANDARD_TWO_SCAN,
|
||||
"SCAN_1_4_16PX_HIGH": Hub75ScanWiring.SCAN_1_4_16PX_HIGH,
|
||||
"SCAN_1_8_32PX_HIGH": Hub75ScanWiring.SCAN_1_8_32PX_HIGH,
|
||||
"SCAN_1_8_32PX_FULL": Hub75ScanWiring.SCAN_1_8_32PX_FULL,
|
||||
"SCAN_1_8_40PX_HIGH": Hub75ScanWiring.SCAN_1_8_40PX_HIGH,
|
||||
"SCAN_1_8_64PX_HIGH": Hub75ScanWiring.SCAN_1_8_64PX_HIGH,
|
||||
}
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -229,7 +229,7 @@ void Inkplate::eink_off_() {
|
||||
this->oe_pin_->digital_write(false);
|
||||
this->gmod_pin_->digital_write(false);
|
||||
|
||||
GPIO.out &= ~(this->get_data_pin_mask_() | (1UL << this->cl_pin_->get_pin()) | (1UL << this->le_pin_->get_pin()));
|
||||
GPIO.out_w1tc = this->get_data_pin_mask_() | (1UL << this->cl_pin_->get_pin()) | (1UL << this->le_pin_->get_pin());
|
||||
this->ckv_pin_->digital_write(false);
|
||||
this->sph_pin_->digital_write(false);
|
||||
this->spv_pin_->digital_write(false);
|
||||
|
||||
@@ -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};
|
||||
|
||||
@@ -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
|
||||
)
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -14,7 +14,6 @@ class LibreTinyComponent:
|
||||
supports_atomics: bool = False # True for Cortex-M4(F) with LDREX/STREX
|
||||
|
||||
|
||||
CONF_LIBRETINY = "libretiny"
|
||||
CONF_LOGLEVEL = "loglevel"
|
||||
CONF_SDK_SILENT = "sdk_silent"
|
||||
CONF_GPIO_RECOVER = "gpio_recover"
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import text_sensor
|
||||
from esphome.components.const import CONF_LIBRETINY
|
||||
import esphome.config_validation as cv
|
||||
from esphome.const import (
|
||||
CONF_VERSION,
|
||||
@@ -7,7 +8,7 @@ from esphome.const import (
|
||||
ICON_CELLPHONE_ARROW_DOWN,
|
||||
)
|
||||
|
||||
from .const import CONF_LIBRETINY, LTComponent
|
||||
from .const import LTComponent
|
||||
|
||||
DEPENDENCIES = ["libretiny"]
|
||||
|
||||
|
||||
@@ -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,
|
||||
@@ -38,9 +39,11 @@ from esphome.const import (
|
||||
CONF_WEB_SERVER,
|
||||
CONF_WHITE,
|
||||
)
|
||||
from esphome.core import CORE, ID, CoroPriority, HexInt, coroutine_with_priority
|
||||
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,
|
||||
@@ -262,6 +337,8 @@ async def setup_light_core_(light_var, config, output_var):
|
||||
cg.add(light_var.set_restore_mode(config[CONF_RESTORE_MODE]))
|
||||
|
||||
if (initial_state_config := config.get(CONF_INITIAL_STATE)) is not None:
|
||||
# Emit a stateless lambda that constructs the initial state — values live
|
||||
# in flash as code, not stored in the LightState object (~40 bytes saved).
|
||||
initial_state = LightStateRTCState(
|
||||
initial_state_config.get(CONF_COLOR_MODE, ColorMode.UNKNOWN),
|
||||
initial_state_config.get(CONF_STATE, False),
|
||||
@@ -275,7 +352,13 @@ async def setup_light_core_(light_var, config, output_var):
|
||||
initial_state_config.get(CONF_COLD_WHITE, 1.0),
|
||||
initial_state_config.get(CONF_WARM_WHITE, 1.0),
|
||||
)
|
||||
cg.add(light_var.set_initial_state(initial_state))
|
||||
args = [(LightStateRTCState.operator("ref"), "s")]
|
||||
lamb = await cg.process_lambda(
|
||||
Lambda(f"s = {initial_state};"),
|
||||
args,
|
||||
return_type=cg.void,
|
||||
)
|
||||
cg.add(light_var.set_initial_state(lamb))
|
||||
|
||||
if (
|
||||
default_transition_length := config.get(CONF_DEFAULT_TRANSITION_LENGTH)
|
||||
|
||||
@@ -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(
|
||||
|
||||
@@ -62,9 +62,9 @@ static const LogString *color_mode_to_human(ColorMode color_mode) {
|
||||
}
|
||||
|
||||
// Helper to log percentage values
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_DEBUG
|
||||
#if ESPHOME_LOG_LEVEL >= ESPHOME_LOG_LEVEL_VERBOSE
|
||||
static void log_percent(const LogString *param, float value) {
|
||||
ESP_LOGD(TAG, " %s: %.0f%%", LOG_STR_ARG(param), value * 100.0f);
|
||||
ESP_LOGV(TAG, " %s: %.0f%%", LOG_STR_ARG(param), value * 100.0f);
|
||||
}
|
||||
#else
|
||||
#define log_percent(param, value)
|
||||
@@ -76,20 +76,20 @@ void LightCall::perform() {
|
||||
const bool publish = this->get_publish_();
|
||||
|
||||
if (publish) {
|
||||
ESP_LOGD(TAG, "'%s' Setting:", name);
|
||||
ESP_LOGV(TAG, "'%s' Setting:", name);
|
||||
|
||||
// Only print color mode when it's being changed
|
||||
ColorMode current_color_mode = this->parent_->remote_values.get_color_mode();
|
||||
ColorMode target_color_mode = this->has_color_mode() ? this->color_mode_ : current_color_mode;
|
||||
if (target_color_mode != current_color_mode) {
|
||||
ESP_LOGD(TAG, " Color mode: %s", LOG_STR_ARG(color_mode_to_human(v.get_color_mode())));
|
||||
ESP_LOGV(TAG, " Color mode: %s", LOG_STR_ARG(color_mode_to_human(v.get_color_mode())));
|
||||
}
|
||||
|
||||
// Only print state when it's being changed
|
||||
bool current_state = this->parent_->remote_values.is_on();
|
||||
bool target_state = this->has_state() ? this->state_ : current_state;
|
||||
if (target_state != current_state) {
|
||||
ESP_LOGD(TAG, " State: %s", ONOFF(v.is_on()));
|
||||
ESP_LOGV(TAG, " State: %s", ONOFF(v.is_on()));
|
||||
}
|
||||
|
||||
if (this->has_brightness()) {
|
||||
@@ -100,7 +100,7 @@ void LightCall::perform() {
|
||||
log_percent(LOG_STR("Color brightness"), v.get_color_brightness());
|
||||
}
|
||||
if (this->has_red() || this->has_green() || this->has_blue()) {
|
||||
ESP_LOGD(TAG, " Red: %.0f%%, Green: %.0f%%, Blue: %.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f,
|
||||
ESP_LOGV(TAG, " Red: %.0f%%, Green: %.0f%%, Blue: %.0f%%", v.get_red() * 100.0f, v.get_green() * 100.0f,
|
||||
v.get_blue() * 100.0f);
|
||||
}
|
||||
|
||||
@@ -108,11 +108,11 @@ void LightCall::perform() {
|
||||
log_percent(LOG_STR("White"), v.get_white());
|
||||
}
|
||||
if (this->has_color_temperature()) {
|
||||
ESP_LOGD(TAG, " Color temperature: %.1f mireds", v.get_color_temperature());
|
||||
ESP_LOGV(TAG, " Color temperature: %.1f mireds", v.get_color_temperature());
|
||||
}
|
||||
|
||||
if (this->has_cold_white() || this->has_warm_white()) {
|
||||
ESP_LOGD(TAG, " Cold white: %.0f%%, warm white: %.0f%%", v.get_cold_white() * 100.0f,
|
||||
ESP_LOGV(TAG, " Cold white: %.0f%%, warm white: %.0f%%", v.get_cold_white() * 100.0f,
|
||||
v.get_warm_white() * 100.0f);
|
||||
}
|
||||
}
|
||||
@@ -120,20 +120,20 @@ void LightCall::perform() {
|
||||
if (this->has_flash_()) {
|
||||
// FLASH
|
||||
if (publish) {
|
||||
ESP_LOGD(TAG, " Flash length: %.1fs", this->flash_length_ / 1e3f);
|
||||
ESP_LOGV(TAG, " Flash length: %.1fs", this->flash_length_ / 1e3f);
|
||||
}
|
||||
|
||||
this->parent_->start_flash_(v, this->flash_length_, publish);
|
||||
} else if (this->has_transition_()) {
|
||||
// TRANSITION
|
||||
if (publish) {
|
||||
ESP_LOGD(TAG, " Transition length: %.1fs", this->transition_length_ / 1e3f);
|
||||
ESP_LOGV(TAG, " Transition length: %.1fs", this->transition_length_ / 1e3f);
|
||||
}
|
||||
|
||||
// Special case: Transition and effect can be set when turning off
|
||||
if (this->has_effect_()) {
|
||||
if (publish) {
|
||||
ESP_LOGD(TAG, " Effect: 'None'");
|
||||
ESP_LOGV(TAG, " Effect: 'None'");
|
||||
}
|
||||
this->parent_->stop_effect_();
|
||||
}
|
||||
@@ -150,7 +150,7 @@ void LightCall::perform() {
|
||||
}
|
||||
|
||||
if (publish) {
|
||||
ESP_LOGD(TAG, " Effect: '%.*s'", (int) effect_s.size(), effect_s.c_str());
|
||||
ESP_LOGV(TAG, " Effect: '%.*s'", (int) effect_s.size(), effect_s.c_str());
|
||||
}
|
||||
|
||||
this->parent_->start_effect_(this->effect_);
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -37,8 +37,9 @@ void LightState::setup() {
|
||||
|
||||
auto call = this->make_call();
|
||||
LightStateRTCState recovered{};
|
||||
if (this->initial_state_.has_value()) {
|
||||
recovered = *this->initial_state_;
|
||||
if (this->initial_state_callback_) {
|
||||
this->initial_state_callback_(recovered);
|
||||
this->initial_state_callback_ = nullptr; // One-shot — no longer needed
|
||||
}
|
||||
switch (this->restore_mode_) {
|
||||
case LIGHT_RESTORE_DEFAULT_OFF:
|
||||
@@ -195,7 +196,7 @@ void LightState::set_flash_transition_length(uint32_t flash_transition_length) {
|
||||
uint32_t LightState::get_flash_transition_length() const { return this->flash_transition_length_; }
|
||||
void LightState::set_gamma_correct(float gamma_correct) { this->gamma_correct_ = gamma_correct; }
|
||||
void LightState::set_restore_mode(LightRestoreMode restore_mode) { this->restore_mode_ = restore_mode; }
|
||||
void LightState::set_initial_state(const LightStateRTCState &initial_state) { this->initial_state_ = initial_state; }
|
||||
void LightState::set_initial_state(void (*callback)(LightStateRTCState &)) { this->initial_state_callback_ = callback; }
|
||||
bool LightState::supports_effects() { return !this->effects_.empty(); }
|
||||
const FixedVector<LightEffect *> &LightState::get_effects() const { return this->effects_; }
|
||||
void LightState::add_effects(const std::initializer_list<LightEffect *> &effects) {
|
||||
|
||||
@@ -188,8 +188,9 @@ class LightState : public EntityBase, public Component {
|
||||
/// Set the restore mode of this light
|
||||
void set_restore_mode(LightRestoreMode restore_mode);
|
||||
|
||||
/// Set the initial state of this light
|
||||
void set_initial_state(const LightStateRTCState &initial_state);
|
||||
/// Set a callback to populate the initial state defaults during setup.
|
||||
/// The callback is called once, then cleared. Values live in flash as code.
|
||||
void set_initial_state(void (*callback)(LightStateRTCState &));
|
||||
|
||||
/// Return whether the light has any effects that meet the trait requirements.
|
||||
bool supports_effects();
|
||||
@@ -322,22 +323,6 @@ class LightState : public EntityBase, public Component {
|
||||
FixedVector<LightEffect *> effects_;
|
||||
/// Object used to store the persisted values of the light.
|
||||
ESPPreferenceObject rtc_;
|
||||
/// Value for storing the index of the currently active effect. 0 if no effect is active
|
||||
uint32_t active_effect_index_{};
|
||||
/// Default transition length for all transitions in ms.
|
||||
uint32_t default_transition_length_{};
|
||||
/// Transition length to use for flash transitions.
|
||||
uint32_t flash_transition_length_{};
|
||||
/// Gamma correction factor for the light.
|
||||
float gamma_correct_{};
|
||||
#ifdef USE_LIGHT_GAMMA_LUT
|
||||
const uint16_t *gamma_table_{nullptr};
|
||||
#endif // USE_LIGHT_GAMMA_LUT
|
||||
|
||||
/// Whether the light value should be written in the next cycle.
|
||||
bool next_write_{true};
|
||||
// for effects, true if a transformer (transition) is active.
|
||||
bool is_transformer_active_ = false;
|
||||
|
||||
/** Listeners for remote values changes.
|
||||
*
|
||||
@@ -358,9 +343,26 @@ class LightState : public EntityBase, public Component {
|
||||
*/
|
||||
std::unique_ptr<std::vector<LightTargetStateReachedListener *>> target_state_reached_listeners_;
|
||||
|
||||
/// Initial state of the light.
|
||||
optional<LightStateRTCState> initial_state_{};
|
||||
/// Callback to populate initial state defaults — called once during setup, then cleared.
|
||||
/// Values live in flash as function body; no per-instance data storage beyond this pointer.
|
||||
void (*initial_state_callback_)(LightStateRTCState &){nullptr};
|
||||
|
||||
/// Value for storing the index of the currently active effect. 0 if no effect is active
|
||||
uint32_t active_effect_index_{};
|
||||
/// Default transition length for all transitions in ms.
|
||||
uint32_t default_transition_length_{};
|
||||
/// Transition length to use for flash transitions.
|
||||
uint32_t flash_transition_length_{};
|
||||
/// Gamma correction factor for the light.
|
||||
float gamma_correct_{};
|
||||
#ifdef USE_LIGHT_GAMMA_LUT
|
||||
const uint16_t *gamma_table_{nullptr};
|
||||
#endif // USE_LIGHT_GAMMA_LUT
|
||||
|
||||
/// Whether the light value should be written in the next cycle.
|
||||
bool next_write_{true};
|
||||
// for effects, true if a transformer (transition) is active.
|
||||
bool is_transformer_active_{false};
|
||||
/// Restore mode of the light.
|
||||
LightRestoreMode restore_mode_;
|
||||
};
|
||||
|
||||
@@ -27,7 +27,7 @@ class LightTransitionTransformer : public LightTransformer {
|
||||
}
|
||||
|
||||
// When changing color mode, go through off state, as color modes are orthogonal and there can't be two active.
|
||||
if (this->start_values_.get_color_mode() != this->target_values_.get_color_mode()) {
|
||||
if (this->start_values_.get_color_mode() != this->end_values_.get_color_mode()) {
|
||||
this->changing_color_mode_ = true;
|
||||
this->intermediate_values_ = this->start_values_;
|
||||
this->intermediate_values_.set_state(false);
|
||||
@@ -39,8 +39,8 @@ class LightTransitionTransformer : public LightTransformer {
|
||||
|
||||
// Halfway through, when intermediate state (off) is reached, flip it to the target, but remain off.
|
||||
if (this->changing_color_mode_ && p > 0.5f &&
|
||||
this->intermediate_values_.get_color_mode() != this->target_values_.get_color_mode()) {
|
||||
this->intermediate_values_ = this->target_values_;
|
||||
this->intermediate_values_.get_color_mode() != this->end_values_.get_color_mode()) {
|
||||
this->intermediate_values_ = this->end_values_;
|
||||
this->intermediate_values_.set_state(false);
|
||||
}
|
||||
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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
|
||||
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user