mirror of
https://github.com/esphome/esphome.git
synced 2026-10-14 02:02:51 +00:00
[deep_sleep] Reject wakeup_pin configs that fail after validation (#18616)
Co-authored-by: clydebarrow <2366188+clydebarrow@users.noreply.github.com> Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
This commit is contained in:
co-authored by
clydebarrow
Claude Opus 5.5
parent
e289a94451
commit
7881dfc5d8
@@ -143,6 +143,8 @@ def validate_wakeup_pin(
|
||||
processed_pins: list[ConfigType] = [{CONF_PIN: value}]
|
||||
else:
|
||||
processed_pins = list(value)
|
||||
if not processed_pins:
|
||||
raise cv.Invalid("'wakeup_pin' needs at least one entry")
|
||||
|
||||
for i, pin_config in enumerate(processed_pins):
|
||||
# now validate each item
|
||||
@@ -154,29 +156,34 @@ def validate_wakeup_pin(
|
||||
|
||||
|
||||
def validate_config(config: ConfigType) -> ConfigType:
|
||||
# right now only BK72XX supports the list format for wakeup pins
|
||||
if CORE.is_bk72xx:
|
||||
if CONF_WAKEUP_PIN_MODE in config:
|
||||
wakeup_pins = config.get(CONF_WAKEUP_PIN, [])
|
||||
if len(wakeup_pins) > 1:
|
||||
raise cv.Invalid(
|
||||
"You need to remove the global wakeup_pin_mode and define it per pin"
|
||||
)
|
||||
if wakeup_pins:
|
||||
if CONF_WAKEUP_PIN_MODE in wakeup_pins[0]:
|
||||
raise cv.Invalid(
|
||||
"Specify wakeup_pin_mode either at the top level under deep_sleep "
|
||||
"or under the pin entry, not both"
|
||||
)
|
||||
wakeup_pins[0][CONF_WAKEUP_PIN_MODE] = config.pop(CONF_WAKEUP_PIN_MODE)
|
||||
elif (
|
||||
isinstance(config.get(CONF_WAKEUP_PIN), list)
|
||||
and len(config[CONF_WAKEUP_PIN]) > 1
|
||||
):
|
||||
wakeup_pins = config.get(CONF_WAKEUP_PIN)
|
||||
has_pin_mode = CONF_WAKEUP_PIN_MODE in config
|
||||
if has_pin_mode and wakeup_pins is None:
|
||||
raise cv.Invalid(
|
||||
"Your platform does not support providing multiple entries in wakeup_pin"
|
||||
"'wakeup_pin_mode' requires 'wakeup_pin'", path=[CONF_WAKEUP_PIN_MODE]
|
||||
)
|
||||
|
||||
multiple_pins = wakeup_pins is not None and len(wakeup_pins) > 1
|
||||
# wakeup_pin is limited to ESP32 and BK72XX, and only BK72XX accepts a list
|
||||
if not CORE.is_bk72xx:
|
||||
if multiple_pins:
|
||||
raise cv.Invalid(
|
||||
"ESP32 does not support multiple entries for 'wakeup_pin'",
|
||||
path=[CONF_WAKEUP_PIN],
|
||||
)
|
||||
elif has_pin_mode:
|
||||
if multiple_pins:
|
||||
raise cv.Invalid(
|
||||
"Remove the global 'wakeup_pin_mode' and define it per pin",
|
||||
path=[CONF_WAKEUP_PIN_MODE],
|
||||
)
|
||||
if CONF_WAKEUP_PIN_MODE in (pin := wakeup_pins[0]):
|
||||
raise cv.Invalid(
|
||||
"Specify 'wakeup_pin_mode' either at the top level under 'deep_sleep' "
|
||||
"or under the pin entry, not both",
|
||||
path=[CONF_WAKEUP_PIN, 0, CONF_WAKEUP_PIN_MODE],
|
||||
)
|
||||
# Move the global mode into the single pin entry, which to_code() reads
|
||||
pin[CONF_WAKEUP_PIN_MODE] = config.pop(CONF_WAKEUP_PIN_MODE)
|
||||
return config
|
||||
|
||||
|
||||
@@ -203,11 +210,11 @@ def _validate_sleep_duration(value: core.TimePeriod) -> core.TimePeriod:
|
||||
if CORE.is_bk72xx:
|
||||
max_duration = core.TimePeriod(hours=36)
|
||||
if value > max_duration:
|
||||
raise cv.Invalid("sleep duration cannot be more than 36 hours on BK72XX")
|
||||
raise cv.Invalid("'sleep_duration' cannot be more than 36 hours on BK72XX")
|
||||
elif CORE.using_zephyr:
|
||||
max_duration = core.TimePeriod(days=49)
|
||||
if value > max_duration:
|
||||
raise cv.Invalid("sleep duration cannot be more than 49 days on Zephyr")
|
||||
raise cv.Invalid("'sleep_duration' cannot be more than 49 days on Zephyr")
|
||||
return value
|
||||
|
||||
|
||||
@@ -290,7 +297,10 @@ CONFIG_SCHEMA = cv.All(
|
||||
cv.positive_time_period_milliseconds,
|
||||
_validate_sleep_duration,
|
||||
),
|
||||
cv.Optional(CONF_WAKEUP_PIN): validate_wakeup_pin,
|
||||
cv.Optional(CONF_WAKEUP_PIN): cv.All(
|
||||
cv.only_on([PLATFORM_ESP32, PLATFORM_BK72XX]),
|
||||
validate_wakeup_pin,
|
||||
),
|
||||
cv.Optional(CONF_WAKEUP_PIN_MODE): cv.All(
|
||||
cv.only_on([PLATFORM_ESP32, PLATFORM_BK72XX]),
|
||||
cv.enum(WAKEUP_PIN_MODES, upper=True),
|
||||
@@ -347,11 +357,10 @@ async def to_code(config: ConfigType) -> None:
|
||||
|
||||
if CONF_SLEEP_DURATION in config:
|
||||
cg.add(var.set_sleep_duration(config[CONF_SLEEP_DURATION]))
|
||||
if CONF_WAKEUP_PIN in config:
|
||||
pins_as_list = config.get(CONF_WAKEUP_PIN, [])
|
||||
if (wakeup_pins := config.get(CONF_WAKEUP_PIN)) is not None:
|
||||
if CORE.is_bk72xx:
|
||||
cg.add(var.init_wakeup_pins(len(pins_as_list)))
|
||||
for item in pins_as_list:
|
||||
cg.add(var.init_wakeup_pins(len(wakeup_pins)))
|
||||
for item in wakeup_pins:
|
||||
cg.add(
|
||||
var.add_wakeup_pin(
|
||||
await cg.gpio_pin_expression(item[CONF_PIN]),
|
||||
@@ -361,7 +370,7 @@ async def to_code(config: ConfigType) -> None:
|
||||
)
|
||||
)
|
||||
else:
|
||||
pin = await cg.gpio_pin_expression(pins_as_list[0][CONF_PIN])
|
||||
pin = await cg.gpio_pin_expression(wakeup_pins[0][CONF_PIN])
|
||||
cg.add(var.set_wakeup_pin(pin))
|
||||
if CONF_WAKEUP_PIN_MODE in config:
|
||||
cg.add(var.set_wakeup_pin_mode(config[CONF_WAKEUP_PIN_MODE]))
|
||||
|
||||
@@ -123,3 +123,66 @@ def test_deep_sleep_bk72xx_top_level_wakeup_pin_mode_moved_onto_single_pin(
|
||||
assert (
|
||||
result[CONF_WAKEUP_PIN][0][deep_sleep.CONF_WAKEUP_PIN_MODE] == "INVERT_WAKEUP"
|
||||
)
|
||||
|
||||
|
||||
def test_deep_sleep_empty_wakeup_pin_list_rejected() -> None:
|
||||
"""An empty wakeup_pin list is rejected at validation instead of crashing codegen."""
|
||||
with pytest.raises(cv.Invalid, match="at least one entry"):
|
||||
deep_sleep.validate_wakeup_pin([])
|
||||
|
||||
|
||||
def test_deep_sleep_bk72xx_wakeup_pin_mode_without_wakeup_pin_rejected(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
"""On BK72xx, a top-level wakeup_pin_mode with no wakeup_pin is rejected at validation."""
|
||||
set_core_config(PlatformFramework.BK72XX_ARDUINO)
|
||||
config = {deep_sleep.CONF_WAKEUP_PIN_MODE: "KEEP_AWAKE"}
|
||||
with pytest.raises(cv.Invalid, match="requires 'wakeup_pin'"):
|
||||
deep_sleep.validate_config(config)
|
||||
|
||||
|
||||
def test_deep_sleep_esp32_wakeup_pin_mode_without_wakeup_pin_rejected(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
"""On ESP32 the same mode-only config is rejected too, instead of being silently dead."""
|
||||
set_core_config(PlatformFramework.ESP32_IDF)
|
||||
config = {deep_sleep.CONF_WAKEUP_PIN_MODE: "KEEP_AWAKE"}
|
||||
with pytest.raises(cv.Invalid, match="requires 'wakeup_pin'"):
|
||||
deep_sleep.validate_config(config)
|
||||
|
||||
|
||||
@pytest.mark.parametrize(
|
||||
"platform", [PlatformFramework.ESP8266_ARDUINO, PlatformFramework.NRF52_ZEPHYR]
|
||||
)
|
||||
def test_deep_sleep_wakeup_pin_rejected_on_unsupported_platform(
|
||||
set_core_config: SetCoreConfigCallable, platform: PlatformFramework
|
||||
) -> None:
|
||||
"""wakeup_pin is rejected where the C++ component has no wakeup pin support."""
|
||||
set_core_config(platform)
|
||||
with pytest.raises(cv.Invalid, match="only available on"):
|
||||
deep_sleep.CONFIG_SCHEMA({CONF_WAKEUP_PIN: "GPIO4"})
|
||||
|
||||
|
||||
def test_deep_sleep_esp32_multiple_wakeup_pins_rejected(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
"""ESP32 accepts only one wakeup_pin entry."""
|
||||
set_core_config(PlatformFramework.ESP32_IDF)
|
||||
config = {CONF_WAKEUP_PIN: [{"pin": "GPIO4"}, {"pin": "GPIO5"}]}
|
||||
with pytest.raises(cv.Invalid, match="ESP32 does not support multiple") as exc:
|
||||
deep_sleep.validate_config(config)
|
||||
assert exc.value.path == [CONF_WAKEUP_PIN]
|
||||
|
||||
|
||||
def test_deep_sleep_bk72xx_global_wakeup_pin_mode_with_multiple_pins_rejected(
|
||||
set_core_config: SetCoreConfigCallable,
|
||||
) -> None:
|
||||
"""On BK72xx, a top-level wakeup_pin_mode cannot be applied to several pins."""
|
||||
set_core_config(PlatformFramework.BK72XX_ARDUINO)
|
||||
config = {
|
||||
CONF_WAKEUP_PIN: [{"pin": "GPIO12"}, {"pin": "GPIO13"}],
|
||||
deep_sleep.CONF_WAKEUP_PIN_MODE: "KEEP_AWAKE",
|
||||
}
|
||||
with pytest.raises(cv.Invalid, match="define it per pin") as exc:
|
||||
deep_sleep.validate_config(config)
|
||||
assert exc.value.path == [deep_sleep.CONF_WAKEUP_PIN_MODE]
|
||||
|
||||
Reference in New Issue
Block a user