From 077beeaecce678f44993ead021be8db72987afe2 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 1 Jun 2026 08:44:36 -0700 Subject: [PATCH 1/2] Add controller connectivity binary sensor Why: the integration exposed no reliable signal for whether the InFusion controller is reachable. The hub-level switch/update entities never go unavailable, and the per-object entities only drop one by one as their polls fail, staggered over ~15 min, so offline-detection automations had to watch a single module's power going unavailable as a fragile proxy. How: add VantageControllerConnectivityEntity, a per-master binary_sensor with device_class connectivity (diagnostic), registered via add_entities_from_controller over vantage.masters. It tracks the aiovantage event_stream Connected/Reconnected/Disconnected events and defaults to on after a successful initialize(). It does not poll and never marks itself unavailable, so it stays available and reports off during an outage, unlike the per-object entities. Tested: ruff check, ruff format --check, and pyright (strict) all pass. Real-hardware outage test on a live controller still pending. Co-Authored-By: Claude Opus 4.8 (1M context) --- custom_components/vantage/binary_sensor.py | 62 +++++++++++++++++++++- 1 file changed, 60 insertions(+), 2 deletions(-) diff --git a/custom_components/vantage/binary_sensor.py b/custom_components/vantage/binary_sensor.py index cbe9124..3e33e3a 100644 --- a/custom_components/vantage/binary_sensor.py +++ b/custom_components/vantage/binary_sensor.py @@ -3,9 +3,14 @@ from typing import override from aiovantage.controllers import Controller -from aiovantage.objects import DryContact +from aiovantage.events import Connected, Disconnected, Reconnected +from aiovantage.objects import DryContact, Master -from homeassistant.components.binary_sensor import BinarySensorEntity +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, +) +from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant from homeassistant.helpers.entity_platform import AddEntitiesCallback @@ -26,6 +31,11 @@ async def async_setup_entry( entry, async_add_entities, VantageBinarySensorEntity, vantage.dry_contacts ) + # Add a connectivity binary sensor for each controller + add_entities_from_controller( + entry, async_add_entities, VantageControllerConnectivityEntity, vantage.masters + ) + class VantageBinarySensorEntity(VantageEntity[DryContact], BinarySensorEntity): """Binary sensor entity provided by a Vantage DryContact object.""" @@ -47,3 +57,51 @@ def __init__( @override def is_on(self) -> bool | None: return self.obj.is_down + + +class VantageControllerConnectivityEntity(VantageEntity[Master], BinarySensorEntity): + """Binary sensor reflecting the live connection to a Vantage controller. + + Unlike the per-object entities, this sensor stays available and reports `off` + while the controller is unreachable, giving a single reliable signal that does + not depend on individual object polls failing one by one. + """ + + _attr_device_class = BinarySensorDeviceClass.CONNECTIVITY + _attr_entity_category = EntityCategory.DIAGNOSTIC + + # Override the base "vantage" translation key so the name comes from the + # connectivity device class ("Connectivity"). + _attr_translation_key = None + + # Setup only completes after a successful initialize(), so the event stream is + # connected at this point. Connection events keep this in sync afterwards. + _attr_is_on = True + + @property + @override + def unique_id(self) -> str: + return f"{self.obj.vid}:connectivity" + + @property + @override + def is_on(self) -> bool | None: + return self._attr_is_on + + @override + async def async_added_to_hass(self) -> None: + await super().async_added_to_hass() + + event_stream = self.client.event_stream + + def set_connected(_event: Connected | Reconnected) -> None: + self._attr_is_on = True + self.async_write_ha_state() + + def set_disconnected(_event: Disconnected) -> None: + self._attr_is_on = False + self.async_write_ha_state() + + self.async_on_remove(event_stream.subscribe(Connected, set_connected)) + self.async_on_remove(event_stream.subscribe(Reconnected, set_connected)) + self.async_on_remove(event_stream.subscribe(Disconnected, set_disconnected)) From 07c4013137abf657454b610cb73a2736a4eeea39 Mon Sep 17 00:00:00 2001 From: Pieter Viljoen Date: Mon, 1 Jun 2026 11:20:17 -0700 Subject: [PATCH 2/2] Add Python 3.14 to the lint matrix Run the lint/type-check job on Python 3.13 and 3.14 (current Home Assistant supports 3.14). Co-Authored-By: Claude Opus 4.8 (1M context) --- .github/workflows/lint.yml | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml index da9ac22..07cca4a 100644 --- a/.github/workflows/lint.yml +++ b/.github/workflows/lint.yml @@ -8,6 +8,11 @@ on: jobs: pre-commit: runs-on: ubuntu-latest + strategy: + matrix: + python-version: + - "3.13" + - "3.14" steps: - uses: actions/checkout@v6 @@ -15,7 +20,7 @@ jobs: - name: Set up Python uses: actions/setup-python@v6 with: - python-version: "3.13" + python-version: ${{ matrix.python-version }} - name: Install uv uses: astral-sh/setup-uv@v7