Compare commits

...
Author SHA1 Message Date
J. Nick Koston f7b42f33a1 Merge remote-tracking branch 'origin/dev' into HEAD 2026-11-14 08:39:12 -06:00
J. Nick Koston d48ea6cd45 [binary_sensor] Write delayed_on and delayed_off as two plain statements 2026-11-11 19:09:33 -06:00
J. Nick Koston f60fe6113a Merge remote-tracking branch 'upstream/dev' into apply-filter-binary-delay-onoff
# Conflicts:
#	esphome/components/binary_sensor/__init__.py
#	esphome/components/binary_sensor/filter.cpp
#	esphome/components/binary_sensor/filter.h
2026-11-11 19:07:44 -06:00
J. Nick Koston 2169df4aa9 Merge remote-tracking branch 'origin/apply-filter-binary-delay2' into apply-filter-binary-delay-onoff 2026-11-11 18:15:30 -06:00
J. Nick Koston d53740333d Merge remote-tracking branch 'upstream/dev' into apply-filter-binary-delay2 2026-11-11 18:14:51 -06:00
J. Nick Koston b3a9677e04 Merge remote-tracking branch 'origin/apply-filter-binary-delay2' into apply-filter-binary-delay-onoff
# Conflicts:
#	esphome/components/binary_sensor/__init__.py
#	esphome/components/binary_sensor/filter.cpp
#	esphome/components/binary_sensor/filter.h
#	tests/components/binary_sensor/apply_filter_test.cpp
2026-11-11 18:03:03 -06:00
J. Nick Koston 85a54a85a4 Pass the schema of each delayed_on_off form to APPLY_FILTER.build 2026-11-11 18:26:55 -05:00
J. Nick Koston 10890c05db Merge branch 'apply-filter-helper2' into apply-filter-binary-delay2
The delay statements name the plain filter parameter.
2026-11-11 18:26:55 -05:00
J. Nick Koston 6c70240bcc Keep a delayed_on_off lambda per edge and evaluate only the edge's delay
A single lambda for both edges was two lambdas before, each with its own
static state, so it becomes one function per edge again. The generated
code picks the delay for the edge, so only that value is evaluated; the
helper that took both delays goes.
2026-11-11 18:26:55 -05:00
J. Nick Koston 8f920c017d Generate delayed_on_off as one call to a C++ helper
delayed_on_off_output picks the delay for the edge, so the generated
statements are plain strings with one placeholder per value.
2026-11-11 18:26:55 -05:00
J. Nick Koston c4a6af22eb Make delay_output a codegen-only free function and leave the delay classes alone
delay_output takes the filter instead of being a member of the public
Filter base, and DelayedOnFilter and DelayedOffFilter keep their own
code.
2026-11-11 18:26:55 -05:00
J. Nick Koston 2c5dc323b7 Emit a single delayed_on_off delay once and drop DelayedOnOffFilter
The single value form is its own statement, so a lambda delay is one
function. DelayedOnOffFilter only existed for codegen; DelayedOnFilter
and DelayedOffFilter schedule through Filter::delay_output, so all
delay filters share one scheduler callback type.
2026-11-11 18:26:55 -05:00
J. Nick Koston 61d68292b0 Generate the binary sensor delayed_on_off filter as an apply filter
The generated function picks the delay and schedules the output through
Filter::delay_output, so the filter object drops its two TemplatableFn
and equal filters share one function. A single delay value is used for
both edges.
2026-11-11 18:26:55 -05:00
J. Nick Koston 56870c31cc Merge remote-tracking branch 'origin/apply-filter-binary-delay2' into apply-filter-binary-delay-onoff 2026-11-11 13:02:37 -06:00
J. Nick Koston a8f47a1457 Merge remote-tracking branch 'origin/apply-filter-helper2' into apply-filter-binary-delay2 2026-11-11 13:02:33 -06:00
J. Nick Koston 7a51ca87b4 Merge remote-tracking branch 'origin/dev' into apply-filter-helper2 2026-11-11 13:01:08 -06:00
J. Nick Koston 9125361dac Share the delay schema between the delay filters 2026-11-10 11:04:15 -08:00
J. Nick Koston 70cc52bc71 Merge remote-tracking branch 'origin/apply-filter-binary-delay2' into st-apply-filter-binary-delay-onoff 2026-11-10 11:03:13 -08:00
J. Nick Koston bb3e9fab4b Pass the schema of each delayed_on_off form to APPLY_FILTER.build 2026-11-10 11:02:37 -08:00
J. Nick Koston 8a533631c1 Merge remote-tracking branch 'origin/apply-filter-helper2' into st-apply-filter-binary-delay2 2026-11-10 11:00:11 -08:00
J. Nick Koston 6521dd8269 Require the schema in ApplyFilterDomain.build so a misspelled key always fails 2026-11-10 10:58:38 -08:00
J. Nick Koston bdf4643b97 Sync the shared function helper: accept Statement lines in the body 2026-11-09 21:34:14 -08:00
J. Nick Koston bb9ec63e6b Reject a filter with no statements 2026-11-09 21:23:58 -08:00
J. Nick Koston aeb77b1e6b Keep the value range function name; sync the shared function helper; trim docstrings and tests 2026-11-09 21:08:48 -08:00
J. Nick Koston e627f13b42 Merge remote-tracking branch 'upstream/dev' into apply-filter-helper2
# Conflicts:
#	esphome/automation.py
2026-11-09 20:50:25 -08:00
J. Nick Koston 057ec35b1e Merge branch 'apply-filter-binary-delay2' into apply-filter-binary-delay-onoff 2026-11-04 18:35:36 -06:00
J. Nick Koston 64fa737ae3 Merge branch 'apply-filter-helper2' into apply-filter-binary-delay2 2026-11-04 18:35:36 -06:00
J. Nick Koston c4133a1af5 Merge remote-tracking branch 'origin/dev' into apply-filter-helper2
# Conflicts:
#	tests/unit_tests/test_cpp_generator.py
2026-11-04 18:33:16 -06:00
J. Nick Koston c2f651b803 Test that build() with a schema accepts valid keys 2026-11-03 19:43:43 -06:00
J. Nick Koston c7472e9068 Merge remote-tracking branch 'origin/dev' into apply-filter-helper2 2026-11-03 19:30:00 -06:00
J. Nick Koston 2fdd4434dd Reject a skipped last apply filter statement and let build check schema keys 2026-11-03 19:09:25 -06:00
J. Nick Koston acc7fd2143 [core] Adapt apply filters to the dev apply function changes
Filter functions keep the parameters the filter class passes, so they are
emitted directly instead of through _apply_function, which now turns value
parameters into const references. The filter body renders through
_render_call, which replaced _render_values.
2026-11-03 18:52:25 -06:00
J. Nick Koston fc9743e5c7 Merge remote-tracking branch 'origin/dev' into apply-filter-helper2
# Conflicts:
#	esphome/automation.py
2026-11-03 18:50:21 -06:00
J. Nick Koston c374138410 Merge branch 'apply-filter-binary-delay2' into apply-filter-binary-delay-onoff 2026-11-02 20:42:11 -06:00
J. Nick Koston 924b1f22ae Merge branch 'apply-filter-helper2' into apply-filter-binary-delay2 2026-11-02 20:42:04 -06:00
J. Nick Koston a26ab932e2 Merge remote-tracking branch 'origin/dev' into apply-filter-helper2
# Conflicts:
#	tests/unit_tests/test_automation.py
2026-11-02 20:41:39 -06:00
J. Nick Koston 8e4ca88f4a Merge apply-filter-binary-delay2 2026-11-02 09:36:56 -06:00
J. Nick Koston da88755288 Merge apply-filter-helper2 2026-11-02 09:36:44 -06:00
J. Nick Koston 3fd62a666c Merge dev 2026-11-02 09:36:15 -06:00
J. Nick Koston f34719cec5 Merge dev 2026-11-02 09:34:42 -06:00
J. Nick Koston d338b3d1ee Merge apply-filter-binary-delay2 2026-11-02 09:33:29 -06:00
J. Nick Koston 3ccba4c31c Merge remote-tracking branch 'origin/apply-filter-helper2' into apply-filter-binary-delay2 2026-10-30 20:11:49 -10:00
J. Nick Koston 3ebb022f55 Merge remote-tracking branch 'origin/dev' into apply-filter-helper2
# Conflicts:
#	esphome/automation.py
#	tests/unit_tests/test_automation.py
2026-10-30 20:11:25 -10:00
J. Nick Koston 8e66eb360b Merge branch 'apply-filter-binary-delay2' into apply-filter-binary-delay-onoff 2026-10-28 18:45:21 -10:00
J. Nick Koston 7b25caf9d1 Merge branch 'apply-filter-helper2' into apply-filter-binary-delay2 2026-10-28 18:45:20 -10:00
J. Nick Koston e732191329 Merge remote-tracking branch 'origin/codegen-shared-static-function' into apply-filter-helper2 2026-10-28 18:45:19 -10:00
J. Nick Koston 2be6a24507 Record apply functions where shared_static_function emits them 2026-10-28 18:43:42 -10:00
J. Nick Koston 6b8bce8385 Merge branch 'apply-filter-binary-delay2' into apply-filter-binary-delay-onoff
# Conflicts:
#	esphome/components/binary_sensor/__init__.py
2026-10-28 18:35:21 -10:00
J. Nick Koston b6e2e8ae1d Merge branch 'apply-filter-helper2' into apply-filter-binary-delay2
The delay statements name the plain filter parameter.
2026-10-28 18:35:00 -10:00
J. Nick Koston 136790e907 Merge branch 'apply-filter-helper2' into apply-filter-binary-delay2 2026-10-28 18:34:44 -10:00
J. Nick Koston 5a838cf0e6 Name the binary sensor apply filter's parameter plain filter
The generated function only holds statements from code generation; user
lambda code is always in functions of its own.
2026-10-28 18:34:13 -10:00
J. Nick Koston d3963a82db Merge remote-tracking branch 'origin/codegen-shared-static-function' into apply-filter-helper2
# Conflicts:
#	esphome/automation.py
2026-10-28 18:29:57 -10:00
J. Nick Koston 206ab92115 Give unshared apply functions config-id-safe names too
apply_static_function always goes through cg.shared_static_function, so
an unshared function also gets a name no config id uses.
2026-10-28 18:29:30 -10:00
J. Nick Koston d89c77ced5 Merge branch 'apply-filter-binary-delay2' into apply-filter-binary-delay-onoff 2026-10-28 18:23:00 -10:00
J. Nick Koston 2ee7ff993b Merge branch 'apply-filter-helper2' into apply-filter-binary-delay2 2026-10-28 18:22:59 -10:00
J. Nick Koston 2c7748606e Merge remote-tracking branch 'origin/codegen-shared-static-function' into apply-filter-helper2 2026-10-28 18:22:56 -10:00
J. Nick Koston 907b3c6d6a [core] Key shared static functions on the rendered body text 2026-10-28 18:06:43 -10:00
J. Nick Koston 651d8261a4 Merge branch 'apply-filter-binary-delay2' into apply-filter-binary-delay-onoff
delayed_on and delayed_off go back to picking the edge in the generated
code, so a lambda delay is only evaluated on its edge; their inline
helpers go.
2026-10-28 18:04:37 -10:00
J. Nick Koston 35b0f4b814 Keep a delayed_on_off lambda per edge and evaluate only the edge's delay
A single lambda for both edges was two lambdas before, each with its own
static state, so it becomes one function per edge again. The generated
code picks the delay for the edge, so only that value is evaluated; the
helper that took both delays goes.
2026-10-28 18:03:56 -10:00
J. Nick Koston 7db36bb9f1 Merge branch 'apply-filter-binary-delay2' into apply-filter-binary-delay-onoff
Resolved onto the C++ helpers: delayed_on and delayed_off call
delayed_on_output and delayed_off_output, so every delay statement is a
plain string with one placeholder per value.
2026-10-28 18:00:54 -10:00
J. Nick Koston de8da2ccf1 Generate delayed_on_off as one call to a C++ helper
delayed_on_off_output picks the delay for the edge, so the generated
statements are plain strings with one placeholder per value.
2026-10-28 18:00:31 -10:00
J. Nick Koston 5d4bb552ef Merge branch 'apply-filter-helper2' into apply-filter-binary-delay2 2026-10-28 18:00:23 -10:00
J. Nick Koston 7f7e4af3ea Name the binary sensor apply filter's filter parameter in one constant 2026-10-28 18:00:05 -10:00
J. Nick Koston 11e1ef3941 Merge branch 'apply-filter-binary-delay2' into apply-filter-binary-delay-onoff 2026-10-28 17:58:06 -10:00
J. Nick Koston 5e8446dfc6 Merge branch 'apply-filter-helper2' into apply-filter-binary-delay2 2026-10-28 17:58:06 -10:00
J. Nick Koston 62d074aae6 Merge branch 'filter-behaviour-tests' into apply-filter-helper2 2026-10-28 17:58:04 -10:00
J. Nick Koston 250ed963cc Merge commit 'e6bebc298f7ddf77c4d7d6322f79b22a08170ba5' into filter-behaviour-tests 2026-10-28 17:58:04 -10:00
J. Nick Koston f0c4ae5fce Merge branch 'apply-filter-binary-delay2' into apply-filter-binary-delay-onoff
Resolved onto the free delay_output: cancel_and_pass becomes a free
codegen-only function too, the delay ApplyCalls are module constants,
the delay_output tests move to apply_filter_test.cpp, and the duplicate
pending output test goes.
2026-10-28 17:31:14 -10:00
J. Nick Koston 56a9cc27a0 Make delay_output a codegen-only free function and leave the delay classes alone
delay_output takes the filter instead of being a member of the public
Filter base, and DelayedOnFilter and DelayedOffFilter keep their own
code.
2026-10-28 17:30:09 -10:00
J. Nick Koston dfec0df2a5 Merge branch 'apply-filter-helper2' into apply-filter-binary-delay2 2026-10-28 17:29:52 -10:00
J. Nick Koston ed076e9210 Share apply filter functions through cg.shared_static_function
The private cache goes; _apply_function passes share on to
apply_static_function. ApplyFilterDomain.new shares only when asked,
value lambda functions are named by apply_static_function, statements
become ApplyCall in one place and render with _render_values, and one
helper checks a call's keys against the schema.
2026-10-28 17:27:28 -10:00
J. Nick Koston 9aa0a835c4 Merge branch 'codegen-shared-static-function' into apply-filter-helper2
# Conflicts:
#	tests/unit_tests/test_automation.py
2026-10-28 17:26:36 -10:00
J. Nick Koston 8c9d117be5 Merge branch 'filter-behaviour-tests' into apply-filter-helper2 2026-10-28 17:26:24 -10:00
J. Nick Koston 1c33d6b960 Pin value lambdas reading an id named x, and keep only the filters the apply stack changes
A sensor offset lambda and a binary delayed_on_off lambda read an entity
with id x. The round, round_to_multiple_of and calibrate_polynomial cases
go, since no change touches those filters; calibrate_linear exact stays
as a control. Each test reads the log lines once.
2026-10-28 17:26:20 -10:00
J. Nick Koston f4d3dfa01c Keep a 0 ms delay on the scheduler for the delay filters
delay_output schedules even a 0 ms delay again, so edges within one loop
pass still coalesce. The undelayed edge of delayed_on and delayed_off
calls cancel_and_pass, which drops the pending output and passes the
value on, as the old classes did.
2026-10-28 17:20:20 -10:00
J. Nick Koston beb66e71bf Generate the delayed_on and delayed_off filters as apply filters
Filter::delay_output with a delay of 0 drops any pending output and
passes the value on at once, which is what delayed_on and delayed_off do
on their undelayed edge, so each is one call with the delay picked by the
value. DelayedOnFilter and DelayedOffFilter only existed for codegen.
A delayed_on_off delay of 0 now passes each value at once instead of one
scheduler pass later.
2026-10-28 17:14:45 -10:00
J. Nick Koston 10f582067c Merge branch 'apply-filter-helper2' into apply-filter-binary-delay2 2026-10-28 16:47:09 -10:00
J. Nick Koston be88427d00 Merge branch 'filter-behaviour-tests' into apply-filter-helper2 2026-10-28 16:46:08 -10:00
J. Nick Koston fbeb90208b Explain why the b_constant to_ntc_resistance case stays NaN 2026-10-28 16:46:04 -10:00
J. Nick Koston 5836fd1583 Note in the description why removing the codegen-only classes is not breaking 2026-10-28 16:37:04 -10:00
J. Nick Koston b593034a2f Make shared static function names unique and add share to apply_static_function 2026-10-28 16:34:16 -10:00
J. Nick Koston bb1713e733 Merge branch 'apply-filter-helper2' into apply-filter-binary-delay2 2026-10-28 16:27:36 -10:00
J. Nick Koston a644d8d9a0 Check the timing of the delayed_on_off filter that reads an id x 2026-10-28 16:27:31 -10:00
J. Nick Koston f2651d794d Merge branch 'filter-behaviour-tests' into apply-filter-helper2 2026-10-28 16:27:16 -10:00
J. Nick Koston 2ed6453467 Check when each delayed binary sensor output lands
Each output must come its delay after the source edge it follows, within
a margin for scheduler jitter, so a lost delay fails the test.
2026-10-28 16:27:13 -10:00
J. Nick Koston 9f6b78930b [core] Add cg.shared_static_function to emit one function per distinct body 2026-10-28 16:25:42 -10:00
J. Nick Koston 55c21d180c Merge branch 'apply-filter-helper2' into apply-filter-binary-delay2 2026-10-28 16:22:09 -10:00
J. Nick Koston cc7a9db510 Merge branch 'filter-behaviour-tests' into apply-filter-helper2 2026-10-28 16:21:59 -10:00
J. Nick Koston a20755a857 Fix the fixture indentation for yamllint 2026-10-28 16:21:57 -10:00
J. Nick Koston bc3accb433 Merge branch 'apply-filter-helper2' into apply-filter-binary-delay2 2026-10-28 16:21:01 -10:00
J. Nick Koston 5859db98cb Parenthesize the multi-line strings in the shared value test 2026-10-28 16:20:54 -10:00
J. Nick Koston d99dcf3af6 Emit a single delayed_on_off delay once and drop DelayedOnOffFilter
The single value form is its own statement, so a lambda delay is one
function. DelayedOnOffFilter only existed for codegen; DelayedOnFilter
and DelayedOffFilter schedule through Filter::delay_output, so all
delay filters share one scheduler callback type.
2026-10-28 16:03:17 -10:00
J. Nick Koston e295823b74 Merge branch 'apply-filter-helper2' into apply-filter-binary-delay2 2026-10-28 16:01:39 -10:00
J. Nick Koston 9a8fbf2e2b Let an apply filter builder pick its statements, and keep shared functions in a dataclass
ApplyFilterDomain.build renders the statements over a config, for a
builder that picks them by the shape of its config; prepare is dropped.
The shared functions live in a CORE.data dataclass. Each domain's
APPLY_FILTER sits under its ApplyFilter, and every domain's test checks
the filter has no bytes beyond its base.
2026-10-28 16:01:34 -10:00
J. Nick Koston c0ff8d7e98 Pin text_sensor lambda filters that replace and drop a value
Also mark the to_ntc_resistance b_constant NaN outputs as a known bug.
2026-10-28 16:00:42 -10:00
J. Nick Koston 15aaaae719 Merge branch 'filter-behaviour-tests' into apply-filter-helper2 2026-10-28 16:00:42 -10:00
J. Nick Koston 88eaaa86c8 Give each apply filter value lambda its own function
Inlined into the filter's function, a value lambda saw the function's x
parameter, so an id named x, such as id(x).state, no longer named that
entity and failed to compile. Each value lambda is now a function
without parameters, called from the filter's function, as it was its
own lambda before. The integration tests cover an id named x in a
sensor offset lambda and a binary sensor delayed_on_off lambda.
2026-10-28 15:59:06 -10:00
J. Nick Koston a951a26225 Generate the binary sensor delayed_on_off filter as an apply filter
The generated function picks the delay and schedules the output through
Filter::delay_output, so the filter object drops its two TemplatableFn
and equal filters share one function. A single delay value is used for
both edges.
2026-10-28 14:44:35 -10:00
J. Nick Koston aac2f28df2 Let an apply filter map its config before rendering
prepare turns a shorthand value into the mapping the statements read.
2026-10-28 14:33:08 -10:00
J. Nick Koston 5daa9693d0 Add an apply filter for sensor, binary_sensor and text_sensor
ApplyFilter<Fn> in each domain is a filter whose work and constants are
one generated function, so the object is only the Filter base; the
binary sensor one passes the filter to the function so it can schedule
output. ApplyFilterDomain declares a domain's filters: register() builds
the function from ApplyCall statements, with constants baked in and
value lambdas called inline, and new() takes the statements directly.
Identical functions without user code are emitted once, so equal
constant filters share one class. LambdaExpression.body_lines gives a
lambda's statements with their #line marks for a generated function.
2026-10-28 14:31:25 -10:00
J. Nick Koston 1e6a3c55b6 Pin the output of the stateless sensor filters and the binary sensor delay filters
Integration tests run offset, multiply, clamp, NTC, calibrate_linear,
calibrate_polynomial, lambda and round filters over finite, NaN and
infinite inputs, and the binary sensor delayed_on_off, delayed_on,
delayed_off and lambda filters over one timed input sequence.
2026-10-28 13:58:08 -10:00
7 changed files with 87 additions and 82 deletions

No files matched your search

+13 -25
View File
@@ -147,8 +147,6 @@ MultiClickTriggerEvent = binary_sensor_ns.struct("MultiClickTriggerEvent")
# Filters
Filter = binary_sensor_ns.class_("Filter")
TimeoutFilter = binary_sensor_ns.class_("TimeoutFilter", Filter)
DelayedOnFilter = binary_sensor_ns.class_("DelayedOnFilter", Filter)
DelayedOffFilter = binary_sensor_ns.class_("DelayedOffFilter", Filter)
InvertFilter = binary_sensor_ns.class_("InvertFilter", Filter)
AutorepeatFilter = binary_sensor_ns.class_("AutorepeatFilter", Filter)
SettleFilter = binary_sensor_ns.class_("SettleFilter", Filter)
@@ -189,11 +187,10 @@ async def timeout_filter_to_code(config, filter_id):
return var
_DELAY = ((automation.WHOLE_CONFIG, cg.uint32),)
_DELAY_EITHER = automation.ApplyCall(
"return binary_sensor::delay_output(filter, {}, x);",
((automation.WHOLE_CONFIG, cg.uint32),),
"return binary_sensor::delay_output(filter, {}, x);", _DELAY
)
# The delay for the edge is picked in the generated code, so only that value is evaluated
_DELAY_SCHEMA = cv.templatable(cv.positive_time_period_milliseconds)
_DELAY_ON_OFF_SCHEMA = cv.Schema(
{
@@ -201,10 +198,19 @@ _DELAY_ON_OFF_SCHEMA = cv.Schema(
cv.Required(CONF_TIME_OFF): _DELAY_SCHEMA,
}
)
# The delay for the edge is picked in the generated code, so only that value is evaluated
_DELAY_ON_OFF = automation.ApplyCall(
"return binary_sensor::delay_output(filter, x ? {} : {}, x);",
((CONF_TIME_ON, cg.uint32), (CONF_TIME_OFF, cg.uint32)),
)
# delayed_on and delayed_off: the delayed value waits, the other drops the pending output and passes
_PASS_AT_ONCE = "return binary_sensor::cancel_and_pass(filter, x);"
_DELAY_ON = automation.ApplyCall(
"if (x) return binary_sensor::delay_output(filter, {}, x);", _DELAY
)
_DELAY_OFF = automation.ApplyCall(
"if (!x) return binary_sensor::delay_output(filter, {}, x);", _DELAY
)
@register_filter(
@@ -232,26 +238,8 @@ async def delayed_on_off_filter_to_code(config: Any, filter_id: ID) -> MockObj:
)
@register_filter(
"delayed_on", DelayedOnFilter, cv.templatable(cv.positive_time_period_milliseconds)
)
async def delayed_on_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id)
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_delay(template_))
return var
@register_filter(
"delayed_off",
DelayedOffFilter,
cv.templatable(cv.positive_time_period_milliseconds),
)
async def delayed_off_filter_to_code(config, filter_id):
var = cg.new_Pvariable(filter_id)
template_ = await cg.templatable(config, [], cg.uint32)
cg.add(var.set_delay(template_))
return var
APPLY_FILTER.register("delayed_on", _DELAY_SCHEMA, _DELAY_ON, _PASS_AT_ONCE)
APPLY_FILTER.register("delayed_off", _DELAY_SCHEMA, _DELAY_OFF, _PASS_AT_ONCE)
@register_filter(
+5 -20
View File
@@ -37,32 +37,17 @@ optional<bool> delay_output(Filter *filter, uint32_t delay, bool value) {
return {};
}
optional<bool> cancel_and_pass(Filter *filter, bool value) {
App.scheduler.cancel_timeout(filter);
return value;
}
void TimeoutFilter::input(bool value) {
App.scheduler.set_timeout(this, this->timeout_delay_.value(), [this]() { this->parent_->invalidate_state(); });
// Every input re-arms the timeout, including repeats, so this filter must not de-dup
this->output(value);
}
optional<bool> DelayedOnFilter::new_value(bool value) {
if (value) {
App.scheduler.set_timeout(this, this->delay_.value(), [this]() { this->output(true); });
return {};
} else {
App.scheduler.cancel_timeout(this);
return false;
}
}
optional<bool> DelayedOffFilter::new_value(bool value) {
if (!value) {
App.scheduler.set_timeout(this, this->delay_.value(), [this]() { this->output(false); });
return {};
} else {
App.scheduler.cancel_timeout(this);
return true;
}
}
optional<bool> InvertFilter::new_value(bool value) { return !value; }
// AutorepeatFilterBase
+2 -26
View File
@@ -42,32 +42,6 @@ class TimeoutFilter : public Filter {
TemplatableFn<uint32_t> timeout_delay_{};
};
class DelayedOnFilter : public Filter {
public:
// User provided, not "= default": `new(p) DelayedOnFilter()` would zero-fill .bss that is already zero.
DelayedOnFilter() {}
optional<bool> new_value(bool value) override;
template<typename T> void set_delay(T delay) { this->delay_ = delay; }
protected:
TemplatableFn<uint32_t> delay_{};
};
class DelayedOffFilter : public Filter {
public:
// User provided, not "= default": `new(p) DelayedOffFilter()` would zero-fill .bss that is already zero.
DelayedOffFilter() {}
optional<bool> new_value(bool value) override;
template<typename T> void set_delay(T delay) { this->delay_ = delay; }
protected:
TemplatableFn<uint32_t> delay_{};
};
class InvertFilter : public Filter {
public:
optional<bool> new_value(bool value) override;
@@ -127,6 +101,8 @@ class LambdaFilter : public Filter {
/// Codegen use only: output `value` from `filter` after `delay` ms, replacing its pending output, and return empty
/// to stop the chain for now.
optional<bool> delay_output(Filter *filter, uint32_t delay, bool value);
/// Codegen use only: drop the pending output of `filter` and pass `value` on at once.
optional<bool> cancel_and_pass(Filter *filter, bool value);
/// Codegen use only: the filter's work and constants are one generated function, so the object is only the base.
/// The function gets the filter, so it can schedule output on it.
@@ -85,3 +85,19 @@ def test_delayed_on_off_shorthand(generate_main) -> None:
assert re.search(
rf"static uint32_t {fn}\(\) {{\n(.*\n)? static uint32_t n = 0;", main_cpp
)
def test_delayed_on_and_off(generate_main) -> None:
"""Only the delayed value waits; the other drops the pending output and passes."""
main_cpp = generate_main(
"tests/component_tests/binary_sensor/test_delayed_on_off.yaml"
)
assert (
" if (x) return binary_sensor::delay_output(filter, 20, x);\n"
" return binary_sensor::cancel_and_pass(filter, x);\n"
) in main_cpp
assert (
" if (!x) return binary_sensor::delay_output(filter, 30, x);\n"
" return binary_sensor::cancel_and_pass(filter, x);\n"
) in main_cpp
@@ -18,3 +18,9 @@ binary_sensor:
- delayed_on_off: !lambda |-
static uint32_t n = 0;
return ++n * 100;
- platform: template
id: delayed_on_constant
lambda: return true;
filters:
- delayed_on: 20ms
- delayed_off: 30ms
@@ -4,6 +4,8 @@
#include "esphome/components/binary_sensor/binary_sensor.h"
#include "esphome/components/binary_sensor/filter.h"
#include "esphome/core/application.h"
#include "esphome/core/hal.h"
namespace esphome::binary_sensor::testing {
@@ -50,4 +52,47 @@ TEST(BinarySensorApplyFilter, PassesItselfToTheFunction) {
EXPECT_FALSE(sensor.state);
}
static void run_scheduler_after(uint32_t ms) {
delay(ms);
App.scheduler.call(millis());
}
// Mirrors the code generated for `delayed_on: 10ms`
static optional<bool> delayed_on_10ms(Filter *filter, bool x) {
return x ? delay_output(filter, 10, true) : cancel_and_pass(filter, false);
}
TEST(BinarySensorDelayOutput, UndelayedEdgeCancelsAndPassesAtOnce) {
BinarySensor sensor;
ApplyFilter<delayed_on_10ms> delayed_on;
sensor.add_filter(&delayed_on);
sensor.publish_state(true);
EXPECT_FALSE(sensor.has_state());
sensor.publish_state(false);
ASSERT_TRUE(sensor.has_state());
EXPECT_FALSE(sensor.state);
run_scheduler_after(30);
EXPECT_FALSE(sensor.state);
}
// Mirrors the code generated for `delayed_on_off: 0ms`
static optional<bool> delayed_on_off_0ms(Filter *filter, bool x) { return delay_output(filter, 0, x); }
// A 0 ms delay still goes through the scheduler, so edges within one pass coalesce
TEST(BinarySensorDelayOutput, ZeroDelayWaitsForTheSchedulerAndCoalesces) {
BinarySensor sensor;
ApplyFilter<delayed_on_off_0ms> delayed_on_off;
sensor.add_filter(&delayed_on_off);
int changes = 0;
sensor.add_on_state_callback([&changes](bool) { changes++; });
sensor.publish_state(true);
sensor.publish_state(false);
sensor.publish_state(true);
EXPECT_FALSE(sensor.has_state());
run_scheduler_after(1);
ASSERT_TRUE(sensor.has_state());
EXPECT_TRUE(sensor.state);
EXPECT_EQ(changes, 1);
}
} // namespace esphome::binary_sensor::testing
@@ -32,17 +32,6 @@ TEST(BinarySensorInvalidate, RepeatedValuePassesTheFiltersAfterInvalidate) {
EXPECT_FALSE(sensor.state);
}
TEST(BinarySensorInvalidate, PendingDelayedOutputIsDropped) {
BinarySensor sensor;
DelayedOnFilter delayed_on;
delayed_on.set_delay([]() -> uint32_t { return 10; });
sensor.add_filter(&delayed_on);
sensor.publish_state(true);
sensor.invalidate_state();
run_scheduler_after(30);
EXPECT_FALSE(sensor.has_state());
}
// Mirrors the code generated for `delayed_on_off: 10ms`
static optional<bool> delayed_on_off_10ms(Filter *filter, bool x) { return delay_output(filter, 10, x); }