diff --git a/esphome/components/binary_sensor/__init__.py b/esphome/components/binary_sensor/__init__.py index 8fcda9fe762..c9d299dc5de 100644 --- a/esphome/components/binary_sensor/__init__.py +++ b/esphome/components/binary_sensor/__init__.py @@ -235,36 +235,29 @@ async def delayed_off_filter_to_code(config, filter_id): return var +AUTOREPEAT_TIMING_SCHEMA = cv.Schema( + { + cv.Optional( + CONF_DELAY, default=DEFAULT_DELAY + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_TIME_OFF, default=DEFAULT_TIME_OFF + ): cv.positive_time_period_milliseconds, + cv.Optional( + CONF_TIME_ON, default=DEFAULT_TIME_ON + ): cv.positive_time_period_milliseconds, + } +) + + @register_filter( "autorepeat", AutorepeatFilter, - cv.All( - cv.ensure_list( - { - cv.Optional( - CONF_DELAY, default=DEFAULT_DELAY - ): cv.positive_time_period_milliseconds, - cv.Optional( - CONF_TIME_OFF, default=DEFAULT_TIME_OFF - ): cv.positive_time_period_milliseconds, - cv.Optional( - CONF_TIME_ON, default=DEFAULT_TIME_ON - ): cv.positive_time_period_milliseconds, - } - ), - cv.Length(max=254), - ), + cv.All(cv.ensure_list(AUTOREPEAT_TIMING_SCHEMA), cv.Length(max=254)), ) -async def autorepeat_filter_to_code(config, filter_id): - if not config: - config = [ - { - CONF_DELAY: cv.time_period_str_unit(DEFAULT_DELAY), - CONF_TIME_OFF: cv.time_period_str_unit(DEFAULT_TIME_OFF), - CONF_TIME_ON: cv.time_period_str_unit(DEFAULT_TIME_ON), - } - ] - timings = tuple( +async def autorepeat_filter_to_code(config: list[ConfigType], filter_id: ID) -> MockObj: + config = config or [AUTOREPEAT_TIMING_SCHEMA({})] + timings = [ cg.StructInitializer( AutorepeatFilterTiming, ("delay", conf[CONF_DELAY].total_milliseconds), @@ -272,11 +265,11 @@ async def autorepeat_filter_to_code(config, filter_id): ("time_on", conf[CONF_TIME_ON].total_milliseconds), ) for conf in config - ) + ] tables = _get_data().autorepeat_timings key = tuple(str(t) for t in timings) if (table := tables.get(key)) is None: - # filter_id is generated, so the derived name is unique. + # Derived from the filter id, like noise _psk and socket _ipv4_allow. table_id = ID( f"{filter_id}_timings", is_declaration=True, type=AutorepeatFilterTiming ) diff --git a/tests/component_tests/binary_sensor/test_autorepeat.py b/tests/component_tests/binary_sensor/test_autorepeat.py index ead730e6266..7bcf2813ec4 100644 --- a/tests/component_tests/binary_sensor/test_autorepeat.py +++ b/tests/component_tests/binary_sensor/test_autorepeat.py @@ -7,7 +7,7 @@ import re def test_autorepeat_timings_shared_progmem_table( generate_main: Callable[[str], str], ) -> None: - """Filters with identical timings share one PROGMEM table; nothing is copied.""" + """Identical timings share one PROGMEM table; an empty autorepeat gets the defaults.""" main_cpp = generate_main("tests/component_tests/binary_sensor/test_autorepeat.yaml") tables = re.findall( @@ -16,20 +16,8 @@ def test_autorepeat_timings_shared_progmem_table( ) assert len(tables) == 2 shared, default = tables - assert f"AutorepeatFilter({shared}, 2);" in main_cpp assert main_cpp.count(f"AutorepeatFilter({shared}, 2);") == 2 assert f"AutorepeatFilter({default}, 1);" in main_cpp - assert "AutorepeatFilter<" not in main_cpp - - -def test_autorepeat_default_timing(generate_main: Callable[[str], str]) -> None: - """An empty autorepeat uses the documented default timing.""" - main_cpp = generate_main("tests/component_tests/binary_sensor/test_autorepeat.yaml") - - default = re.search( - r"AutorepeatFilterTiming (\w+)\[\] PROGMEM = \{binary_sensor::AutorepeatFilterTiming\{\n" - r" \.delay = 1000,\n \.time_off = 100,\n \.time_on = 900,\n\}\};", - main_cpp, - ) - assert default is not None - assert f"AutorepeatFilter({default.group(1)}, 1);" in main_cpp + default_table = main_cpp.split(f"{default}[] PROGMEM = ", 1)[1].split(";", 1)[0] + for field in (".delay = 1000", ".time_off = 100", ".time_on = 900"): + assert field in default_table