mirror of
https://github.com/home-assistant/core.git
synced 2026-09-26 17:31:15 -04:00
Add DALI line status diagnostic sensor to lunatone (#179706)
This commit is contained in:
@@ -0,0 +1,9 @@
|
||||
{
|
||||
"entity": {
|
||||
"sensor": {
|
||||
"dali_line_status": {
|
||||
"default": "mdi:current-ac"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -3,7 +3,7 @@
|
||||
from typing import Final, override
|
||||
|
||||
from lunatone_rest_api_client import Sensor
|
||||
from lunatone_rest_api_client.models import SensorAddressType, SensorType
|
||||
from lunatone_rest_api_client.models import LineStatus, SensorAddressType, SensorType
|
||||
|
||||
from homeassistant.components.sensor import (
|
||||
SensorDeviceClass,
|
||||
@@ -13,6 +13,7 @@ from homeassistant.components.sensor import (
|
||||
)
|
||||
from homeassistant.const import (
|
||||
LIGHT_LUX,
|
||||
EntityCategory,
|
||||
UnitOfPressure,
|
||||
UnitOfRatio,
|
||||
UnitOfTemperature,
|
||||
@@ -24,8 +25,18 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
|
||||
from homeassistant.helpers.update_coordinator import CoordinatorEntity
|
||||
|
||||
from .const import DOMAIN
|
||||
from .coordinator import LunatoneConfigEntry, LunatoneSensorsDataUpdateCoordinator
|
||||
from .coordinator import (
|
||||
LunatoneConfigEntry,
|
||||
LunatoneInfoDataUpdateCoordinator,
|
||||
LunatoneSensorsDataUpdateCoordinator,
|
||||
)
|
||||
|
||||
DALI_LINE_STATUS_SENSOR_MAPPING: dict[str, str] = {
|
||||
LineStatus.LOW_POWER: "low_power",
|
||||
LineStatus.NO_POWER: "no_power",
|
||||
LineStatus.NOT_REACHABLE: "not_reachable",
|
||||
LineStatus.OK: "ok",
|
||||
}
|
||||
PARALLEL_UPDATES = 0
|
||||
SENSOR_TYPES: Final[dict[str, SensorEntityDescription]] = {
|
||||
SensorType.AIR_HUMIDITY: SensorEntityDescription(
|
||||
@@ -78,18 +89,25 @@ async def async_setup_entry(
|
||||
async_add_entities: AddConfigEntryEntitiesCallback,
|
||||
) -> None:
|
||||
"""Set up Lunatone sensors from the config entry."""
|
||||
coordinator_info = config_entry.runtime_data.coordinator_info
|
||||
coordinator_sensors = config_entry.runtime_data.coordinator_sensors
|
||||
|
||||
assert config_entry.unique_id is not None
|
||||
|
||||
async_add_entities(
|
||||
entities: list[SensorEntity] = [
|
||||
LunatoneSensor(
|
||||
coordinator_sensors, description, sensor_id, config_entry.unique_id
|
||||
)
|
||||
for sensor_id, sensor_data in coordinator_sensors.data.items()
|
||||
if (description := SENSOR_TYPES.get(sensor_data.data.type))
|
||||
]
|
||||
entities.extend(
|
||||
LunatoneDALILineStatusSensor(coordinator_info, line_id, config_entry.unique_id)
|
||||
for line_id in coordinator_info.data.lines
|
||||
)
|
||||
|
||||
async_add_entities(entities)
|
||||
|
||||
|
||||
class LunatoneSensor(
|
||||
CoordinatorEntity[LunatoneSensorsDataUpdateCoordinator], SensorEntity
|
||||
@@ -160,3 +178,42 @@ class LunatoneSensor(
|
||||
def native_value(self) -> float | None:
|
||||
"""Return the measurement value of the sensor."""
|
||||
return self.sensor.data.value
|
||||
|
||||
|
||||
class LunatoneDALILineStatusSensor(
|
||||
CoordinatorEntity[LunatoneInfoDataUpdateCoordinator], SensorEntity
|
||||
):
|
||||
"""Representation of a Lunatone DALI line status sensor."""
|
||||
|
||||
_attr_device_class = SensorDeviceClass.ENUM
|
||||
_attr_entity_category = EntityCategory.DIAGNOSTIC
|
||||
_attr_has_entity_name = True
|
||||
_attr_options = list(DALI_LINE_STATUS_SENSOR_MAPPING.values())
|
||||
_attr_state_class = None
|
||||
_attr_translation_key = "dali_line_status"
|
||||
|
||||
def __init__(
|
||||
self,
|
||||
coordinator: LunatoneInfoDataUpdateCoordinator,
|
||||
line_id: str,
|
||||
config_entry_unique_id: str,
|
||||
) -> None:
|
||||
"""Initialize a Lunatone DALI line status sensor."""
|
||||
super().__init__(coordinator)
|
||||
|
||||
self._config_entry_unique_id = config_entry_unique_id
|
||||
self._line_id = line_id
|
||||
|
||||
line_unique_id = f"{config_entry_unique_id}-line{line_id}"
|
||||
self._attr_device_info = DeviceInfo(
|
||||
identifiers={(DOMAIN, line_unique_id)},
|
||||
)
|
||||
self._attr_unique_id = f"{line_unique_id}-status"
|
||||
|
||||
@property
|
||||
@override
|
||||
def native_value(self) -> str:
|
||||
"""Return the value of the sensor."""
|
||||
return DALI_LINE_STATUS_SENSOR_MAPPING[
|
||||
self.coordinator.data.lines[self._line_id].line_status
|
||||
]
|
||||
|
||||
@@ -42,6 +42,17 @@
|
||||
"scan_status": {
|
||||
"name": "DALI scan"
|
||||
}
|
||||
},
|
||||
"sensor": {
|
||||
"dali_line_status": {
|
||||
"name": "Status",
|
||||
"state": {
|
||||
"low_power": "Low bus power",
|
||||
"no_power": "Bus power failure",
|
||||
"not_reachable": "Not reachable",
|
||||
"ok": "OK"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
"exceptions": {
|
||||
|
||||
@@ -57,6 +57,134 @@
|
||||
'state': 'unknown',
|
||||
})
|
||||
# ---
|
||||
# name: test_setup[sensor.dali_line_0_status-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'low_power',
|
||||
'no_power',
|
||||
'not_reachable',
|
||||
'ok',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.dali_line_0_status',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Status',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Status',
|
||||
'platform': 'lunatone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'dali_line_status',
|
||||
'unique_id': 'be37ca9c47c24498a38bc62c7c711840-line0-status',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_setup[sensor.dali_line_0_status-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'enum',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'DALI Line 0 Status',
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'low_power',
|
||||
'no_power',
|
||||
'not_reachable',
|
||||
'ok',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.dali_line_0_status',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'ok',
|
||||
})
|
||||
# ---
|
||||
# name: test_setup[sensor.dali_line_1_status-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
None,
|
||||
]),
|
||||
'area_id': None,
|
||||
'capabilities': dict({
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'low_power',
|
||||
'no_power',
|
||||
'not_reachable',
|
||||
'ok',
|
||||
]),
|
||||
}),
|
||||
'config_entry_id': <ANY>,
|
||||
'config_subentry_id': <ANY>,
|
||||
'device_class': None,
|
||||
'device_id': <ANY>,
|
||||
'disabled_by': None,
|
||||
'domain': 'sensor',
|
||||
'entity_category': <EntityCategory.DIAGNOSTIC: 'diagnostic'>,
|
||||
'entity_id': 'sensor.dali_line_1_status',
|
||||
'has_entity_name': True,
|
||||
'hidden_by': None,
|
||||
'icon': None,
|
||||
'id': <ANY>,
|
||||
'labels': set({
|
||||
}),
|
||||
'name': None,
|
||||
'object_id_base': 'Status',
|
||||
'options': dict({
|
||||
}),
|
||||
'original_device_class': <SensorDeviceClass.ENUM: 'enum'>,
|
||||
'original_icon': None,
|
||||
'original_name': 'Status',
|
||||
'platform': 'lunatone',
|
||||
'previous_unique_id': None,
|
||||
'suggested_object_id': None,
|
||||
'supported_features': 0,
|
||||
'translation_key': 'dali_line_status',
|
||||
'unique_id': 'be37ca9c47c24498a38bc62c7c711840-line1-status',
|
||||
'unit_of_measurement': None,
|
||||
})
|
||||
# ---
|
||||
# name: test_setup[sensor.dali_line_1_status-state]
|
||||
StateSnapshot({
|
||||
'attributes': ReadOnlyDict({
|
||||
<EntityStateAttribute.DEVICE_CLASS: 'device_class'>: 'enum',
|
||||
<EntityStateAttribute.FRIENDLY_NAME: 'friendly_name'>: 'DALI Line 1 Status',
|
||||
<SensorEntityCapabilityAttribute.OPTIONS: 'options'>: list([
|
||||
'low_power',
|
||||
'no_power',
|
||||
'not_reachable',
|
||||
'ok',
|
||||
]),
|
||||
}),
|
||||
'context': <ANY>,
|
||||
'entity_id': 'sensor.dali_line_1_status',
|
||||
'last_changed': <ANY>,
|
||||
'last_reported': <ANY>,
|
||||
'last_updated': <ANY>,
|
||||
'state': 'ok',
|
||||
})
|
||||
# ---
|
||||
# name: test_setup[sensor.test_sensor_1-entry]
|
||||
EntityRegistryEntrySnapshot({
|
||||
'aliases': list([
|
||||
|
||||
@@ -1,10 +1,11 @@
|
||||
"""Tests for the lights provided by the Lunatone integration."""
|
||||
|
||||
from copy import deepcopy
|
||||
from datetime import timedelta
|
||||
from unittest.mock import AsyncMock
|
||||
|
||||
from freezegun.api import FrozenDateTimeFactory
|
||||
from lunatone_rest_api_client.models import SensorData
|
||||
from lunatone_rest_api_client.models import LineStatus, SensorData
|
||||
from syrupy.assertion import SnapshotAssertion
|
||||
|
||||
from homeassistant.const import Platform
|
||||
@@ -71,3 +72,40 @@ async def test_sensor_value_update(
|
||||
assert entities[0].state == "22"
|
||||
assert entities[1].state == "55"
|
||||
assert entities[2].state == "20"
|
||||
|
||||
|
||||
async def test_dali_line_status_value_update(
|
||||
hass: HomeAssistant,
|
||||
mock_lunatone_info: AsyncMock,
|
||||
mock_lunatone_devices: AsyncMock,
|
||||
mock_lunatone_sensors: AsyncMock,
|
||||
mock_lunatone_scan: AsyncMock,
|
||||
mock_config_entry: MockConfigEntry,
|
||||
freezer: FrozenDateTimeFactory,
|
||||
) -> None:
|
||||
"""Test the Lunatone sensor value update."""
|
||||
line_id = 0
|
||||
entity_id = f"sensor.dali_line_{line_id}_status"
|
||||
|
||||
line_statuses = iter((LineStatus.NO_POWER, LineStatus.OK))
|
||||
|
||||
async def fake_update() -> None:
|
||||
info_data = deepcopy(mock_lunatone_info.data)
|
||||
info_data.lines[str(line_id)].line_status = next(line_statuses)
|
||||
mock_lunatone_info.data = info_data
|
||||
|
||||
mock_lunatone_info.async_update.side_effect = fake_update
|
||||
|
||||
await setup_integration(hass, mock_config_entry)
|
||||
|
||||
entity = hass.states.get(entity_id)
|
||||
assert entity
|
||||
assert entity.state == "no_power"
|
||||
|
||||
freezer.tick(timedelta(seconds=60))
|
||||
async_fire_time_changed(hass)
|
||||
await hass.async_block_till_done()
|
||||
|
||||
entity = hass.states.get(entity_id)
|
||||
assert entity
|
||||
assert entity.state == "ok"
|
||||
|
||||
Reference in New Issue
Block a user