Fix intermittent unavailability for lamarzocco brew active sensor (#144120)

* Fix brew active intermittent unavailability for lamarzocco

* Whitespaces
This commit is contained in:
Josef Zweck 2025-05-02 22:29:54 +02:00 committed by GitHub
parent 4c2e9fc759
commit df4297be62
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
5 changed files with 41 additions and 17 deletions

View File

@ -52,7 +52,7 @@ ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = (
).status ).status
is MachineState.BREWING is MachineState.BREWING
), ),
available_fn=lambda device: device.websocket.connected, available_fn=lambda coordinator: not coordinator.websocket_terminated,
entity_category=EntityCategory.DIAGNOSTIC, entity_category=EntityCategory.DIAGNOSTIC,
), ),
LaMarzoccoBinarySensorEntityDescription( LaMarzoccoBinarySensorEntityDescription(

View File

@ -44,6 +44,7 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
_default_update_interval = SCAN_INTERVAL _default_update_interval = SCAN_INTERVAL
config_entry: LaMarzoccoConfigEntry config_entry: LaMarzoccoConfigEntry
websocket_terminated = True
def __init__( def __init__(
self, self,
@ -92,15 +93,9 @@ class LaMarzoccoConfigUpdateCoordinator(LaMarzoccoUpdateCoordinator):
await self.device.get_dashboard() await self.device.get_dashboard()
_LOGGER.debug("Current status: %s", self.device.dashboard.to_dict()) _LOGGER.debug("Current status: %s", self.device.dashboard.to_dict())
_LOGGER.debug("Init WebSocket in background task")
self.config_entry.async_create_background_task( self.config_entry.async_create_background_task(
hass=self.hass, hass=self.hass,
target=self.device.connect_dashboard_websocket( target=self.connect_websocket(),
update_callback=lambda _: self.async_set_updated_data(None),
connect_callback=self.async_update_listeners,
disconnect_callback=self.async_update_listeners,
),
name="lm_websocket_task", name="lm_websocket_task",
) )
@ -112,6 +107,23 @@ class LaMarzoccoConfigUpdateCoordinator(LaMarzoccoUpdateCoordinator):
) )
self.config_entry.async_on_unload(websocket_close) self.config_entry.async_on_unload(websocket_close)
async def connect_websocket(self) -> None:
"""Connect to the websocket."""
_LOGGER.debug("Init WebSocket in background task")
self.websocket_terminated = False
self.async_update_listeners()
await self.device.connect_dashboard_websocket(
update_callback=lambda _: self.async_set_updated_data(None),
connect_callback=self.async_update_listeners,
disconnect_callback=self.async_update_listeners,
)
self.websocket_terminated = True
self.async_update_listeners()
class LaMarzoccoSettingsUpdateCoordinator(LaMarzoccoUpdateCoordinator): class LaMarzoccoSettingsUpdateCoordinator(LaMarzoccoUpdateCoordinator):
"""Coordinator for La Marzocco settings.""" """Coordinator for La Marzocco settings."""

View File

@ -3,7 +3,6 @@
from collections.abc import Callable from collections.abc import Callable
from dataclasses import dataclass from dataclasses import dataclass
from pylamarzocco import LaMarzoccoMachine
from pylamarzocco.const import FirmwareType from pylamarzocco.const import FirmwareType
from homeassistant.const import CONF_ADDRESS, CONF_MAC from homeassistant.const import CONF_ADDRESS, CONF_MAC
@ -23,7 +22,7 @@ from .coordinator import LaMarzoccoUpdateCoordinator
class LaMarzoccoEntityDescription(EntityDescription): class LaMarzoccoEntityDescription(EntityDescription):
"""Description for all LM entities.""" """Description for all LM entities."""
available_fn: Callable[[LaMarzoccoMachine], bool] = lambda _: True available_fn: Callable[[LaMarzoccoUpdateCoordinator], bool] = lambda _: True
supported_fn: Callable[[LaMarzoccoUpdateCoordinator], bool] = lambda _: True supported_fn: Callable[[LaMarzoccoUpdateCoordinator], bool] = lambda _: True
@ -74,7 +73,7 @@ class LaMarzoccoEntity(LaMarzoccoBaseEntity):
def available(self) -> bool: def available(self) -> bool:
"""Return True if entity is available.""" """Return True if entity is available."""
if super().available: if super().available:
return self.entity_description.available_fn(self.coordinator.device) return self.entity_description.available_fn(self.coordinator)
return False return False
def __init__( def __init__(

View File

@ -100,8 +100,9 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = (
.seconds.seconds_out .seconds.seconds_out
), ),
available_fn=( available_fn=(
lambda machine: cast( lambda coordinator: cast(
PreBrewing, machine.dashboard.config[WidgetType.CM_PRE_BREWING] PreBrewing,
coordinator.device.dashboard.config[WidgetType.CM_PRE_BREWING],
).mode ).mode
is PreExtractionMode.PREINFUSION is PreExtractionMode.PREINFUSION
), ),
@ -140,8 +141,8 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = (
.times.pre_brewing[0] .times.pre_brewing[0]
.seconds.seconds_in .seconds.seconds_in
), ),
available_fn=lambda machine: cast( available_fn=lambda coordinator: cast(
PreBrewing, machine.dashboard.config[WidgetType.CM_PRE_BREWING] PreBrewing, coordinator.device.dashboard.config[WidgetType.CM_PRE_BREWING]
).mode ).mode
is PreExtractionMode.PREBREWING, is PreExtractionMode.PREBREWING,
supported_fn=( supported_fn=(
@ -180,8 +181,9 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = (
.seconds.seconds_out .seconds.seconds_out
), ),
available_fn=( available_fn=(
lambda machine: cast( lambda coordinator: cast(
PreBrewing, machine.dashboard.config[WidgetType.CM_PRE_BREWING] PreBrewing,
coordinator.device.dashboard.config[WidgetType.CM_PRE_BREWING],
).mode ).mode
is PreExtractionMode.PREBREWING is PreExtractionMode.PREBREWING
), ),

View File

@ -1,5 +1,6 @@
"""Tests for La Marzocco binary sensors.""" """Tests for La Marzocco binary sensors."""
from collections.abc import Generator
from datetime import timedelta from datetime import timedelta
from unittest.mock import MagicMock, patch from unittest.mock import MagicMock, patch
@ -33,6 +34,16 @@ async def test_binary_sensors(
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id) await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.fixture(autouse=True)
def mock_websocket_terminated() -> Generator[bool]:
"""Mock websocket terminated."""
with patch(
"homeassistant.components.lamarzocco.coordinator.LaMarzoccoUpdateCoordinator.websocket_terminated",
new=False,
) as mock_websocket_terminated:
yield mock_websocket_terminated
async def test_brew_active_unavailable( async def test_brew_active_unavailable(
hass: HomeAssistant, hass: HomeAssistant,
mock_lamarzocco: MagicMock, mock_lamarzocco: MagicMock,