diff --git a/custom_components/span_panel/lock.py b/custom_components/span_panel/lock.py index 2bc99963..877b243f 100644 --- a/custom_components/span_panel/lock.py +++ b/custom_components/span_panel/lock.py @@ -133,11 +133,17 @@ def _apply(self, evse: SpanEvseSnapshot) -> None: @property def available(self) -> bool: - """False while the panel is offline: a control that cannot reach the panel is not a control.""" + """False while the panel is offline, or the charger no longer offers its lock. + + A control that cannot reach the panel is not a control, and neither is one + whose charger has left the snapshot or stopped declaring the lock settable. + """ if not self._transport_available: return False if self.coordinator.panel_offline: return False + if self._evse().lock_control is None: + return False return super().available async def async_lock(self, **kwargs: Any) -> None: diff --git a/tests/test_evse_lock.py b/tests/test_evse_lock.py index ed4816fb..2d0b46a6 100644 --- a/tests/test_evse_lock.py +++ b/tests/test_evse_lock.py @@ -126,3 +126,22 @@ async def test_the_lock_is_unavailable_while_the_panel_is_offline(hass: HomeAssi lock.coordinator.panel_offline = True assert lock.available is False + + +@pytest.mark.parametrize("withdrawn", ["charger_gone", "lock_not_settable"]) +async def test_the_lock_is_withdrawn_when_the_charger_stops_offering_it( + hass: HomeAssistant, withdrawn: str +) -> None: + snapshot = _snapshot() + (lock,) = await _created(hass, snapshot) + lock.coordinator.panel_offline = False + assert lock.available is True + + evse = dict(snapshot.evse) + if withdrawn == "charger_gone": + del evse[SETTABLE] + else: + evse[SETTABLE] = replace(evse[SETTABLE], lock_control=None) + lock.coordinator.data = replace(snapshot, evse=evse) + + assert lock.available is False