1
0
mirror of https://github.com/home-assistant/core.git synced 2026-04-02 00:20:30 +01:00

Fix optional static values in bsblan (#165488)

This commit is contained in:
Willem-Jan van Rootselaar
2026-03-19 18:07:49 +01:00
committed by GitHub
parent 625603839c
commit 450d46f652
6 changed files with 85 additions and 14 deletions

View File

@@ -32,7 +32,7 @@ from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.typing import ConfigType
from .const import CONF_PASSKEY, DOMAIN
from .const import CONF_PASSKEY, DOMAIN, LOGGER
from .coordinator import BSBLanFastCoordinator, BSBLanSlowCoordinator
from .services import async_setup_services
@@ -52,7 +52,7 @@ class BSBLanData:
client: BSBLAN
device: Device
info: Info
static: StaticState
static: StaticState | None
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
@@ -82,11 +82,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: BSBLanConfigEntry) -> bo
# the connection by fetching firmware version
await bsblan.initialize()
# Fetch device metadata in parallel for faster startup
device, info, static = await asyncio.gather(
# Fetch required device metadata in parallel for faster startup
device, info = await asyncio.gather(
bsblan.device(),
bsblan.info(),
bsblan.static_values(),
)
except BSBLANConnectionError as err:
raise ConfigEntryNotReady(
@@ -111,6 +110,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: BSBLanConfigEntry) -> bo
translation_key="setup_general_error",
) from err
try:
static = await bsblan.static_values()
except (BSBLANError, TimeoutError) as err:
LOGGER.debug(
"Static values not available for %s: %s",
entry.data[CONF_HOST],
err,
)
static = None
# Create coordinators with the already-initialized client
fast_coordinator = BSBLanFastCoordinator(hass, entry, bsblan)
slow_coordinator = BSBLanSlowCoordinator(hass, entry, bsblan)

View File

@@ -90,10 +90,11 @@ class BSBLANClimate(BSBLanEntity, ClimateEntity):
self._attr_unique_id = f"{format_mac(data.device.MAC)}-climate"
# Set temperature range if available, otherwise use Home Assistant defaults
if data.static.min_temp is not None and data.static.min_temp.value is not None:
self._attr_min_temp = data.static.min_temp.value
if data.static.max_temp is not None and data.static.max_temp.value is not None:
self._attr_max_temp = data.static.max_temp.value
if (static := data.static) is not None:
if (min_temp := static.min_temp) is not None and min_temp.value is not None:
self._attr_min_temp = min_temp.value
if (max_temp := static.max_temp) is not None and max_temp.value is not None:
self._attr_max_temp = max_temp.value
self._attr_temperature_unit = data.fast_coordinator.client.get_temperature_unit
@property

View File

@@ -24,7 +24,7 @@ async def async_get_config_entry_diagnostics(
"sensor": data.fast_coordinator.data.sensor.model_dump(),
"dhw": data.fast_coordinator.data.dhw.model_dump(),
},
"static": data.static.model_dump(),
"static": data.static.model_dump() if data.static is not None else None,
}
# Add DHW config and schedule from slow coordinator if available

View File

@@ -45,6 +45,21 @@ async def test_celsius_fahrenheit(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
async def test_climate_entity_loads_without_static_values(
hass: HomeAssistant,
mock_bsblan: AsyncMock,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test the climate entity still loads when static values are unavailable."""
mock_bsblan.static_values.side_effect = BSBLANError("General error")
await setup_with_selected_platforms(hass, mock_config_entry, [Platform.CLIMATE])
state = hass.states.get(ENTITY_ID)
assert state is not None
assert state.attributes["current_temperature"] is not None
async def test_climate_entity_properties(
hass: HomeAssistant,
mock_bsblan: AsyncMock,

View File

@@ -2,6 +2,7 @@
from unittest.mock import AsyncMock
from bsblan import BSBLANError
from syrupy.assertion import SnapshotAssertion
from homeassistant.core import HomeAssistant
@@ -27,3 +28,26 @@ async def test_diagnostics(
hass, hass_client, mock_config_entry
)
assert diagnostics_data == snapshot
async def test_diagnostics_without_static_values(
hass: HomeAssistant,
mock_bsblan: AsyncMock,
hass_client: ClientSessionGenerator,
mock_config_entry: MockConfigEntry,
) -> None:
"""Test diagnostics when static values are not available."""
mock_bsblan.static_values.side_effect = BSBLANError("General error")
mock_config_entry.add_to_hass(hass)
await hass.config_entries.async_setup(mock_config_entry.entry_id)
await hass.async_block_till_done()
diagnostics_data = await get_diagnostics_for_config_entry(
hass, hass_client, mock_config_entry
)
assert "info" in diagnostics_data
assert "device" in diagnostics_data
assert "fast_coordinator_data" in diagnostics_data
assert diagnostics_data["static"] is None

View File

@@ -78,30 +78,50 @@ async def test_config_entry_auth_failed_triggers_reauth(
@pytest.mark.parametrize(
("method", "exception", "expected_state"),
("method", "exception", "expected_state", "assert_static_fallback"),
[
(
"initialize",
BSBLANError("General error"),
ConfigEntryState.SETUP_ERROR,
False,
),
(
"device",
BSBLANConnectionError("Connection failed"),
ConfigEntryState.SETUP_RETRY,
False,
),
(
"info",
BSBLANAuthError("Authentication failed"),
ConfigEntryState.SETUP_ERROR,
False,
),
(
"static_values",
BSBLANError("General error"),
ConfigEntryState.LOADED,
True,
),
(
"static_values",
TimeoutError("Connection timeout"),
ConfigEntryState.LOADED,
True,
),
("static_values", BSBLANError("General error"), ConfigEntryState.SETUP_ERROR),
],
)
async def test_config_entry_static_data_errors(
async def test_config_entry_setup_errors(
hass: HomeAssistant,
mock_config_entry: MockConfigEntry,
mock_bsblan: MagicMock,
method: str,
exception: Exception,
expected_state: ConfigEntryState,
assert_static_fallback: bool,
) -> None:
"""Test various errors during static data fetching trigger appropriate config entry states."""
"""Test setup errors trigger appropriate config entry states."""
# Mock the specified method to raise the exception
getattr(mock_bsblan, method).side_effect = exception
@@ -110,6 +130,8 @@ async def test_config_entry_static_data_errors(
await hass.async_block_till_done()
assert mock_config_entry.state is expected_state
if assert_static_fallback:
assert mock_config_entry.runtime_data.static is None
async def test_coordinator_dhw_config_update_error(