feat(sensor): add disabled-by-default connection-mode diagnostic sensor

Exposes whether a device is currently in observe (push) or poll mode,
for troubleshooting the observe-mode feature without digging through
logs. Disabled by default since it's not everyday-use information.
This commit is contained in:
Marc Billow
2026-07-10 19:50:43 -05:00
parent 569029c116
commit d6fef45029
4 changed files with 81 additions and 2 deletions
+34 -2
View File
@@ -3,9 +3,13 @@ from __future__ import annotations
from homeassistant.components.sensor import SensorEntity, SensorDeviceClass, SensorStateClass
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .observe import MODE_OBSERVE, MODE_POLL
from .registry.entities import SensorDesc
from .const import DOMAIN
@@ -19,11 +23,13 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
coordinator: LocalThingsCoordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities(
entities = [
LocalThingsSensor(coordinator, b)
for b in coordinator.bound
if isinstance(b.desc, SensorDesc) and _is_included(b, coordinator)
)
]
entities.append(LocalThingsConnectionModeSensor(coordinator))
async_add_entities(entities)
class LocalThingsSensor(LocalThingsEntity, SensorEntity):
@@ -40,3 +46,29 @@ class LocalThingsSensor(LocalThingsEntity, SensorEntity):
@property
def native_value(self):
return (self.coordinator.data or {}).get(self._state_key)
class LocalThingsConnectionModeSensor(CoordinatorEntity[LocalThingsCoordinator], SensorEntity):
"""Diagnostic sensor exposing whether this device is currently
receiving push notifications (observe mode) or being polled only.
Disabled by default — it's for troubleshooting, not everyday use."""
_attr_has_entity_name = True
_attr_name = 'Connection mode'
_attr_translation_key = 'connection_mode'
_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_entity_registry_enabled_default = False
_attr_device_class = SensorDeviceClass.ENUM
_attr_options = [MODE_OBSERVE, MODE_POLL]
def __init__(self, coordinator: LocalThingsCoordinator) -> None:
super().__init__(coordinator)
self._attr_unique_id = f"{DOMAIN}_{coordinator.device_serial}_connection_mode"
@property
def device_info(self) -> DeviceInfo:
return self.coordinator.device_info
@property
def native_value(self) -> str:
return self.coordinator.observe_mode
@@ -45,6 +45,12 @@
"icestatus_stop": "Idle",
"icestatus_run": "Making ice"
}
},
"connection_mode": {
"state": {
"observe": "Push (observe)",
"poll": "Polling"
}
}
}
},
@@ -45,6 +45,12 @@
"icestatus_stop": "Idle",
"icestatus_run": "Making ice"
}
},
"connection_mode": {
"state": {
"observe": "Push (observe)",
"poll": "Polling"
}
}
}
},
+35
View File
@@ -6,6 +6,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers import entity_registry as er
from custom_components.localthings.const import DOMAIN
from custom_components.localthings.observe import MODE_POLL
async def test_sensors_registered(
@@ -45,3 +46,37 @@ async def test_sensor_state_from_coordinator(
assert state.state not in ('unknown', 'unavailable'), (
f"Sensor state is {state.state}"
)
async def test_connection_mode_sensor_registered_disabled_by_default(
hass: HomeAssistant, mock_entry, mock_coordinator_session
) -> None:
"""The connection-mode diagnostic sensor is registered but disabled by
default — it's for troubleshooting, not everyday use."""
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
ent_reg = er.async_get(hass)
entries = er.async_entries_for_config_entry(ent_reg, mock_entry.entry_id)
mode_entries = [e for e in entries if e.unique_id.endswith('_connection_mode')]
assert len(mode_entries) == 1
entry = mode_entries[0]
assert entry.disabled_by is not None
assert entry.entity_category == 'diagnostic'
async def test_connection_mode_sensor_reflects_coordinator_mode(
hass: HomeAssistant, mock_entry, mock_coordinator_session
) -> None:
"""native_value reads coordinator.observe_mode directly, live."""
from custom_components.localthings.sensor import LocalThingsConnectionModeSensor
await hass.config_entries.async_setup(mock_entry.entry_id)
await hass.async_block_till_done()
coordinator = hass.data[DOMAIN][mock_entry.entry_id]
sensor = LocalThingsConnectionModeSensor(coordinator)
assert sensor.native_value == MODE_POLL
coordinator._observe._set_mode('observe')
assert sensor.native_value == 'observe'