Add dynamic device handling to Gatus (#176757)

This commit is contained in:
Hamish
2026-08-10 12:11:07 +02:00
committed by GitHub
parent b06d180f7e
commit 4752aca591
3 changed files with 87 additions and 6 deletions
@@ -6,7 +6,7 @@ from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
)
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import GatusConfigEntry, GatusDataUpdateCoordinator
@@ -23,10 +23,22 @@ async def async_setup_entry(
"""Set up the Gatus binary sensor platform."""
coordinator = entry.runtime_data
async_add_entities(
GatusEndpointBinarySensor(coordinator, entry, endpoint_key)
for endpoint_key in coordinator.data
)
known_endpoints: set[str] = set()
@callback
def _check_endpoints() -> None:
current_endpoints = set(coordinator.data)
new_endpoints = current_endpoints - known_endpoints
if new_endpoints:
known_endpoints.update(new_endpoints)
async_add_entities(
GatusEndpointBinarySensor(coordinator, entry, endpoint_key)
for endpoint_key in new_endpoints
)
known_endpoints.intersection_update(current_endpoints)
_check_endpoints()
entry.async_on_unload(coordinator.async_add_listener(_check_endpoints))
class GatusEndpointBinarySensor(GatusEndpointEntity, BinarySensorEntity):
@@ -63,7 +63,7 @@ rules:
docs-supported-functions: done
docs-troubleshooting: done
docs-use-cases: done
dynamic-devices: todo
dynamic-devices: done
entity-category: done
entity-device-class: done
entity-disabled-by-default:
@@ -164,3 +164,72 @@ async def test_binary_sensor_missing_status(
state = hass.states.get("binary_sensor.backend_service")
assert state is not None
assert state.state == "off"
async def test_binary_sensor_dynamic_endpoints(
hass: HomeAssistant,
mock_gatus_client: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
) -> None:
"""Test that new endpoints returned by the Gatus API at runtime dynamically create entities."""
await setup_integration(hass, mock_config_entry)
assert hass.states.get("binary_sensor.core_backend_service") is not None
assert hass.states.get("binary_sensor.core_new_service") is None
mock_gatus_client.get_endpoints_statuses.return_value = [
EndpointStatus(
key="backend_service",
name="Backend Service",
group="Core",
results=[Result(success=True, status=200)],
),
EndpointStatus(
key="new_service",
name="New Service",
group="Core",
results=[Result(success=True, status=200)],
),
]
freezer.tick(30)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get("binary_sensor.core_new_service") is not None
async def test_binary_sensor_readded_endpoint(
hass: HomeAssistant,
mock_gatus_client: AsyncMock,
mock_config_entry: MockConfigEntry,
freezer: FrozenDateTimeFactory,
entity_registry: er.EntityRegistry,
) -> None:
"""Test that a removed endpoint can be successfully re-added and has its entity recreated."""
await setup_integration(hass, mock_config_entry)
assert hass.states.get("binary_sensor.core_backend_service") is not None
mock_gatus_client.get_endpoints_statuses.return_value = []
freezer.tick(30)
async_fire_time_changed(hass)
await hass.async_block_till_done()
entity_registry.async_remove("binary_sensor.core_backend_service")
assert hass.states.get("binary_sensor.core_backend_service") is None
mock_gatus_client.get_endpoints_statuses.return_value = [
EndpointStatus(
key="backend_service",
name="Backend Service",
group="Core",
results=[Result(success=True, status=200)],
)
]
freezer.tick(30)
async_fire_time_changed(hass)
await hass.async_block_till_done()
assert hass.states.get("binary_sensor.core_backend_service") is not None