Add alarm and connectivity binary_sensors to myuplink (#111643)
* Add alarm and connectivity binary_sensors * Get is_on for correct system * Make coverage 100% in binary_sensor * Address review comments * Revert dict comprehension for now
This commit is contained in:
parent
8eaf471dd2
commit
c4c96be880
4 changed files with 182 additions and 11 deletions
|
@ -1,8 +1,9 @@
|
|||
"""Binary sensors for myUplink."""
|
||||
|
||||
from myuplink import DevicePoint
|
||||
from myuplink import DeviceConnectionState, DevicePoint
|
||||
|
||||
from homeassistant.components.binary_sensor import (
|
||||
BinarySensorDeviceClass,
|
||||
BinarySensorEntity,
|
||||
BinarySensorEntityDescription,
|
||||
)
|
||||
|
@ -13,7 +14,7 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
|
|||
|
||||
from . import MyUplinkDataCoordinator
|
||||
from .const import DOMAIN
|
||||
from .entity import MyUplinkEntity
|
||||
from .entity import MyUplinkEntity, MyUplinkSystemEntity
|
||||
from .helpers import find_matching_platform
|
||||
|
||||
CATEGORY_BASED_DESCRIPTIONS: dict[str, dict[str, BinarySensorEntityDescription]] = {
|
||||
|
@ -25,6 +26,17 @@ CATEGORY_BASED_DESCRIPTIONS: dict[str, dict[str, BinarySensorEntityDescription]]
|
|||
},
|
||||
}
|
||||
|
||||
CONNECTED_BINARY_SENSOR_DESCRIPTION = BinarySensorEntityDescription(
|
||||
key="connected_state",
|
||||
device_class=BinarySensorDeviceClass.CONNECTIVITY,
|
||||
)
|
||||
|
||||
ALARM_BINARY_SENSOR_DESCRIPTION = BinarySensorEntityDescription(
|
||||
key="has_alarm",
|
||||
device_class=BinarySensorDeviceClass.PROBLEM,
|
||||
translation_key="alarm",
|
||||
)
|
||||
|
||||
|
||||
def get_description(device_point: DevicePoint) -> BinarySensorEntityDescription | None:
|
||||
"""Get description for a device point.
|
||||
|
@ -46,7 +58,7 @@ async def async_setup_entry(
|
|||
entities: list[BinarySensorEntity] = []
|
||||
coordinator: MyUplinkDataCoordinator = hass.data[DOMAIN][config_entry.entry_id]
|
||||
|
||||
# Setup device point sensors
|
||||
# Setup device point bound sensors
|
||||
for device_id, point_data in coordinator.data.points.items():
|
||||
for point_id, device_point in point_data.items():
|
||||
if find_matching_platform(device_point) == Platform.BINARY_SENSOR:
|
||||
|
@ -61,11 +73,37 @@ async def async_setup_entry(
|
|||
unique_id_suffix=point_id,
|
||||
)
|
||||
)
|
||||
|
||||
# Setup device bound sensors
|
||||
entities.extend(
|
||||
MyUplinkDeviceBinarySensor(
|
||||
coordinator=coordinator,
|
||||
device_id=device.id,
|
||||
entity_description=CONNECTED_BINARY_SENSOR_DESCRIPTION,
|
||||
unique_id_suffix="connection_state",
|
||||
)
|
||||
for system in coordinator.data.systems
|
||||
for device in system.devices
|
||||
)
|
||||
|
||||
# Setup system bound sensors
|
||||
for system in coordinator.data.systems:
|
||||
device_id = system.devices[0].id
|
||||
entities.append(
|
||||
MyUplinkSystemBinarySensor(
|
||||
coordinator=coordinator,
|
||||
device_id=device_id,
|
||||
system_id=system.id,
|
||||
entity_description=ALARM_BINARY_SENSOR_DESCRIPTION,
|
||||
unique_id_suffix="has_alarm",
|
||||
)
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class MyUplinkDevicePointBinarySensor(MyUplinkEntity, BinarySensorEntity):
|
||||
"""Representation of a myUplink device point binary sensor."""
|
||||
"""Representation of a myUplink device point bound binary sensor."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
|
@ -94,3 +132,73 @@ class MyUplinkDevicePointBinarySensor(MyUplinkEntity, BinarySensorEntity):
|
|||
"""Binary sensor state value."""
|
||||
device_point = self.coordinator.data.points[self.device_id][self.point_id]
|
||||
return int(device_point.value) != 0
|
||||
|
||||
@property
|
||||
def available(self) -> bool:
|
||||
"""Return device data availability."""
|
||||
return super().available and (
|
||||
self.coordinator.data.devices[self.device_id].connectionState
|
||||
== DeviceConnectionState.Connected
|
||||
)
|
||||
|
||||
|
||||
class MyUplinkDeviceBinarySensor(MyUplinkEntity, BinarySensorEntity):
|
||||
"""Representation of a myUplink device bound binary sensor."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: MyUplinkDataCoordinator,
|
||||
device_id: str,
|
||||
entity_description: BinarySensorEntityDescription | None,
|
||||
unique_id_suffix: str,
|
||||
) -> None:
|
||||
"""Initialize the binary_sensor."""
|
||||
super().__init__(
|
||||
coordinator=coordinator,
|
||||
device_id=device_id,
|
||||
unique_id_suffix=unique_id_suffix,
|
||||
)
|
||||
|
||||
if entity_description is not None:
|
||||
self.entity_description = entity_description
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool:
|
||||
"""Binary sensor state value."""
|
||||
return (
|
||||
self.coordinator.data.devices[self.device_id].connectionState
|
||||
== DeviceConnectionState.Connected
|
||||
)
|
||||
|
||||
|
||||
class MyUplinkSystemBinarySensor(MyUplinkSystemEntity, BinarySensorEntity):
|
||||
"""Representation of a myUplink system bound binary sensor."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: MyUplinkDataCoordinator,
|
||||
system_id: str,
|
||||
device_id: str,
|
||||
entity_description: BinarySensorEntityDescription | None,
|
||||
unique_id_suffix: str,
|
||||
) -> None:
|
||||
"""Initialize the binary_sensor."""
|
||||
super().__init__(
|
||||
coordinator=coordinator,
|
||||
system_id=system_id,
|
||||
device_id=device_id,
|
||||
unique_id_suffix=unique_id_suffix,
|
||||
)
|
||||
|
||||
if entity_description is not None:
|
||||
self.entity_description = entity_description
|
||||
|
||||
@property
|
||||
def is_on(self) -> bool | None:
|
||||
"""Binary sensor state value."""
|
||||
retval = None
|
||||
for system in self.coordinator.data.systems:
|
||||
if system.id == self.system_id:
|
||||
retval = system.has_alarm
|
||||
break
|
||||
return retval
|
||||
|
|
|
@ -8,7 +8,7 @@ from .coordinator import MyUplinkDataCoordinator
|
|||
|
||||
|
||||
class MyUplinkEntity(CoordinatorEntity[MyUplinkDataCoordinator]):
|
||||
"""Representation of a sensor."""
|
||||
"""Representation of myuplink entity."""
|
||||
|
||||
_attr_has_entity_name = True
|
||||
|
||||
|
@ -18,7 +18,7 @@ class MyUplinkEntity(CoordinatorEntity[MyUplinkDataCoordinator]):
|
|||
device_id: str,
|
||||
unique_id_suffix: str,
|
||||
) -> None:
|
||||
"""Initialize the sensor."""
|
||||
"""Initialize the entity."""
|
||||
super().__init__(coordinator=coordinator)
|
||||
|
||||
# Internal properties
|
||||
|
@ -27,3 +27,27 @@ class MyUplinkEntity(CoordinatorEntity[MyUplinkDataCoordinator]):
|
|||
# Basic values
|
||||
self._attr_unique_id = f"{device_id}-{unique_id_suffix}"
|
||||
self._attr_device_info = DeviceInfo(identifiers={(DOMAIN, device_id)})
|
||||
|
||||
|
||||
class MyUplinkSystemEntity(MyUplinkEntity):
|
||||
"""Representation of a system bound entity."""
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: MyUplinkDataCoordinator,
|
||||
system_id: str,
|
||||
device_id: str,
|
||||
unique_id_suffix: str,
|
||||
) -> None:
|
||||
"""Initialize the entity."""
|
||||
super().__init__(
|
||||
coordinator=coordinator,
|
||||
device_id=device_id,
|
||||
unique_id_suffix=unique_id_suffix,
|
||||
)
|
||||
|
||||
# Internal properties
|
||||
self.system_id = system_id
|
||||
|
||||
# Basic values
|
||||
self._attr_unique_id = f"{system_id}-{unique_id_suffix}"
|
||||
|
|
|
@ -25,5 +25,12 @@
|
|||
"create_entry": {
|
||||
"default": "[%key:common::config_flow::create_entry::authenticated%]"
|
||||
}
|
||||
},
|
||||
"entity": {
|
||||
"binary_sensor": {
|
||||
"alarm": {
|
||||
"name": "Alarm"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -2,6 +2,9 @@
|
|||
|
||||
from unittest.mock import MagicMock
|
||||
|
||||
import pytest
|
||||
|
||||
from homeassistant.const import STATE_OFF, STATE_ON
|
||||
from homeassistant.core import HomeAssistant
|
||||
|
||||
from . import setup_integration
|
||||
|
@ -9,17 +12,46 @@ from . import setup_integration
|
|||
from tests.common import MockConfigEntry
|
||||
|
||||
|
||||
# Test one entity from each of binary_sensor classes.
|
||||
@pytest.mark.parametrize(
|
||||
("entity_id", "friendly_name", "test_attributes", "expected_state"),
|
||||
[
|
||||
(
|
||||
"binary_sensor.gotham_city_pump_heating_medium_gp1",
|
||||
"Gotham City Pump: Heating medium (GP1)",
|
||||
True,
|
||||
STATE_ON,
|
||||
),
|
||||
(
|
||||
"binary_sensor.gotham_city_connectivity",
|
||||
"Gotham City Connectivity",
|
||||
False,
|
||||
STATE_ON,
|
||||
),
|
||||
(
|
||||
"binary_sensor.gotham_city_alarm",
|
||||
"Gotham City Pump: Alarm",
|
||||
False,
|
||||
STATE_OFF,
|
||||
),
|
||||
],
|
||||
)
|
||||
async def test_sensor_states(
|
||||
hass: HomeAssistant,
|
||||
mock_myuplink_client: MagicMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
entity_id: str,
|
||||
friendly_name: str,
|
||||
test_attributes: bool,
|
||||
expected_state: str,
|
||||
) -> None:
|
||||
"""Test sensor state."""
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
state = hass.states.get("binary_sensor.gotham_city_pump_heating_medium_gp1")
|
||||
state = hass.states.get(entity_id)
|
||||
assert state is not None
|
||||
assert state.state == "on"
|
||||
assert state.attributes == {
|
||||
"friendly_name": "Gotham City Pump: Heating medium (GP1)",
|
||||
}
|
||||
assert state.state == expected_state
|
||||
if test_attributes:
|
||||
assert state.attributes == {
|
||||
"friendly_name": friendly_name,
|
||||
}
|
||||
|
|
Loading…
Add table
Reference in a new issue