mirror of
https://github.com/esphome/esphome.git
synced 2026-09-17 18:18:43 +00:00
[bk72xx_ble] Keep wifi power save off while BLE is compiled in (#19317)
This commit is contained in:
@@ -23,7 +23,7 @@ public ble_api.h.
|
||||
import logging
|
||||
|
||||
import esphome.codegen as cg
|
||||
from esphome.components import libretiny
|
||||
from esphome.components import libretiny, wifi
|
||||
from esphome.components.libretiny.const import (
|
||||
FAMILY_BK7231N,
|
||||
FAMILY_BK7231Q,
|
||||
@@ -84,6 +84,15 @@ def _final_validate(config: ConfigType) -> None:
|
||||
# which run on a BLE 4.2 board. The hard error is raised at codegen.
|
||||
if msg := _unsupported_family_message(libretiny.get_libretiny_family()):
|
||||
_LOGGER.warning("%s (this configuration cannot compile)", msg)
|
||||
# Any wifi power_save_mode other than NONE also arms the Beken SDK's MCU
|
||||
# sleep. With the BLE controller running, that sleep never wakes up once the
|
||||
# station is stopped (adapter restart after failed roams, wifi.disable): the
|
||||
# device is dead until a power cycle (esphome#18592). Keep power save off
|
||||
# until LibreTiny ships the SDK-side fix (libretiny-eu/libretiny#414).
|
||||
wifi.force_power_save_off(
|
||||
"with BLE running, the Beken SDK's MCU sleep halts the device once the "
|
||||
"station is stopped (https://github.com/esphome/esphome/issues/18592)"
|
||||
)
|
||||
|
||||
|
||||
FINAL_VALIDATE_SCHEMA = _final_validate
|
||||
|
||||
@@ -690,7 +690,16 @@ async def to_code(config):
|
||||
):
|
||||
cg.add(var.set_reboot_timeout(reboot_timeout))
|
||||
if (power_save_mode := config[CONF_POWER_SAVE_MODE]) != "NONE":
|
||||
cg.add(var.set_power_save_mode(power_save_mode))
|
||||
if reasons := CORE.data.get(POWER_SAVE_OFF_REASONS_KEY):
|
||||
_LOGGER.warning(
|
||||
"power_save_mode %s is not applied: %s",
|
||||
power_save_mode,
|
||||
"; ".join(reasons),
|
||||
)
|
||||
else:
|
||||
cg.add(var.set_power_save_mode(power_save_mode))
|
||||
# From here on force_power_save_off() can no longer take effect
|
||||
CORE.data[POWER_SAVE_APPLIED_KEY] = True
|
||||
if (
|
||||
min_auth_mode := config.get(CONF_MIN_AUTH_MODE)
|
||||
) is not None and min_auth_mode != "WPA2":
|
||||
@@ -864,6 +873,8 @@ async def wifi_roam_to_code(
|
||||
|
||||
KEEP_SCAN_RESULTS_KEY = "wifi_keep_scan_results"
|
||||
RUNTIME_POWER_SAVE_KEY = "wifi_runtime_power_save"
|
||||
POWER_SAVE_OFF_REASONS_KEY = "wifi_power_save_off_reasons"
|
||||
POWER_SAVE_APPLIED_KEY = "wifi_power_save_applied"
|
||||
RUNTIME_ROAMING_SUPPRESSION_KEY = "wifi_runtime_roaming_suppression"
|
||||
# Keys for listener counts
|
||||
IP_STATE_LISTENERS_KEY = "wifi_ip_state_listeners"
|
||||
@@ -896,6 +907,25 @@ def request_wifi_scan_results_lock() -> None:
|
||||
CORE.data[SCAN_RESULTS_LOCK_KEY] = True
|
||||
|
||||
|
||||
def force_power_save_off(reason: str) -> None:
|
||||
"""Keep the station out of WiFi power save regardless of power_save_mode.
|
||||
|
||||
Components whose platform cannot run power save safely call this from their
|
||||
final validation (FINAL_VALIDATE_SCHEMA), which always runs before any code
|
||||
generation. Every distinct reason is kept; when the configured mode is not
|
||||
NONE, wifi's code generation logs them and skips the mode. Calling it once
|
||||
wifi has generated its code is too late and raises.
|
||||
"""
|
||||
if POWER_SAVE_APPLIED_KEY in CORE.data:
|
||||
raise EsphomeError(
|
||||
"wifi.force_power_save_off() must be called from final validation, "
|
||||
"before wifi generates its code"
|
||||
)
|
||||
reasons: list[str] = CORE.data.setdefault(POWER_SAVE_OFF_REASONS_KEY, [])
|
||||
if reason not in reasons:
|
||||
reasons.append(reason)
|
||||
|
||||
|
||||
def enable_runtime_power_save_control():
|
||||
"""Enable runtime WiFi power save control.
|
||||
|
||||
|
||||
@@ -0,0 +1,12 @@
|
||||
esphome:
|
||||
name: bk-power-save
|
||||
|
||||
bk72xx:
|
||||
board: cb2s
|
||||
|
||||
wifi:
|
||||
ssid: test
|
||||
password: testtest
|
||||
power_save_mode: high
|
||||
|
||||
bk72xx_ble:
|
||||
@@ -0,0 +1,20 @@
|
||||
"""bk72xx_ble keeps WiFi power save off: the Beken SDK's MCU sleep does not
|
||||
wake up once the station is stopped while the BLE controller runs."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
|
||||
def test_power_save_mode_is_not_applied_with_ble(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
main_cpp = generate_main(component_config_path("test_power_save.yaml"))
|
||||
|
||||
assert "bk72xx_ble::BK72xxBLE" in main_cpp
|
||||
assert "set_power_save_mode(" not in main_cpp
|
||||
assert "power_save_mode HIGH is not applied" in caplog.text
|
||||
assert "issues/18592" in caplog.text
|
||||
@@ -0,0 +1,46 @@
|
||||
"""Tests for wifi.force_power_save_off(), the hook platforms use to keep the
|
||||
station out of power save."""
|
||||
|
||||
from collections.abc import Callable
|
||||
from pathlib import Path
|
||||
|
||||
import pytest
|
||||
|
||||
from esphome.components import wifi
|
||||
from esphome.core import CORE, EsphomeError
|
||||
|
||||
|
||||
def test_reasons_accumulate_without_duplicates() -> None:
|
||||
"""Every caller's reason is kept once; a repeated reason is not duplicated."""
|
||||
wifi.force_power_save_off("first")
|
||||
wifi.force_power_save_off("first")
|
||||
wifi.force_power_save_off("second")
|
||||
|
||||
assert CORE.data[wifi.POWER_SAVE_OFF_REASONS_KEY] == ["first", "second"]
|
||||
|
||||
|
||||
def test_forced_off_skips_the_setter_and_warns(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
caplog: pytest.LogCaptureFixture,
|
||||
) -> None:
|
||||
"""With a reason recorded, power_save_mode is reported and not applied."""
|
||||
wifi.force_power_save_off("the platform cannot sleep")
|
||||
|
||||
main_cpp = generate_main(component_config_path("custom.yaml"))
|
||||
|
||||
assert "set_power_save_mode(" not in main_cpp
|
||||
assert (
|
||||
"power_save_mode LIGHT is not applied: the platform cannot sleep" in caplog.text
|
||||
)
|
||||
|
||||
|
||||
def test_call_after_wifi_codegen_raises(
|
||||
generate_main: Callable[[str | Path], str],
|
||||
component_config_path: Callable[[str], Path],
|
||||
) -> None:
|
||||
"""Once wifi has generated its code the hook cannot take effect any more."""
|
||||
generate_main(component_config_path("custom.yaml"))
|
||||
|
||||
with pytest.raises(EsphomeError, match="before wifi generates its code"):
|
||||
wifi.force_power_save_off("too late")
|
||||
@@ -0,0 +1,9 @@
|
||||
# A wifi power_save_mode other than NONE is forced off with a warning while
|
||||
# bk72xx_ble is configured (esphome#18592); this config must still validate.
|
||||
packages:
|
||||
bk72xx_ble: !include common.yaml
|
||||
|
||||
wifi:
|
||||
ssid: MySSID
|
||||
password: password1
|
||||
power_save_mode: high
|
||||
Reference in New Issue
Block a user