Add support for room sensors in ViCare integration (#125243)

* Add room sensors

* set humidity device class

* add labels

* Create RoomSensor2.json

* Create RoomSensor1.json

* Update conftest.py

* Create test_sensor.py

* enable E3_RoomSensor

* use setup_integration

* fix ruff findings

* add test case

* fix entity id

* Apply suggestions from code review

* update

* fix findings

* reuse labels

* Apply suggestions from code review

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Apply suggestions from code review

* fix test snapshot

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
This commit is contained in:
Christopher Fenner 2024-09-30 14:32:04 +02:00 committed by GitHub
parent e1db5f3cac
commit 404b3fcd03
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
8 changed files with 459 additions and 3 deletions

View File

@ -23,7 +23,6 @@ UNSUPPORTED_DEVICES = [
"E3_FloorHeatingCircuitChannel",
"E3_FloorHeatingCircuitDistributorBox",
"E3_RoomControl_One_522",
"E3_RoomSensor",
]
DEVICE_LIST = "device_list"

View File

@ -29,7 +29,7 @@ class ViCareEntity(Entity):
gateway_serial = device_config.getConfig().serial
device_id = device_config.getId()
identifier = f"{gateway_serial}_{device_serial if device_serial is not None else device_id}"
identifier = f"{gateway_serial}_{device_serial.replace("zigbee-", "zigbee_") if device_serial is not None else device_id}"
self._api: PyViCareDevice | PyViCareHeatingDeviceComponent = (
component if component else device

View File

@ -751,6 +751,20 @@ GLOBAL_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (
options=["ready", "production"],
value_getter=lambda api: _filter_pv_states(api.getPhotovoltaicStatus()),
),
ViCareSensorEntityDescription(
key="room_temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
device_class=SensorDeviceClass.TEMPERATURE,
state_class=SensorStateClass.MEASUREMENT,
value_getter=lambda api: api.getTemperature(),
),
ViCareSensorEntityDescription(
key="room_humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
value_getter=lambda api: api.getHumidity(),
),
)
CIRCUIT_SENSORS: tuple[ViCareSensorEntityDescription, ...] = (

View File

@ -92,6 +92,24 @@ async def mock_vicare_gas_boiler(
yield mock_config_entry
@pytest.fixture
async def mock_vicare_room_sensors(
hass: HomeAssistant, mock_config_entry: MockConfigEntry
) -> AsyncGenerator[MockConfigEntry]:
"""Return a mocked ViCare API representing multiple room sensor devices."""
fixtures: list[Fixture] = [
Fixture({"type:climateSensor"}, "vicare/RoomSensor1.json"),
Fixture({"type:climateSensor"}, "vicare/RoomSensor2.json"),
]
with patch(
f"{MODULE}.vicare_login",
return_value=MockPyViCare(fixtures),
):
await setup_integration(hass, mock_config_entry)
yield mock_config_entry
@pytest.fixture
def mock_setup_entry() -> Generator[AsyncMock]:
"""Mock setting up a config entry."""

View File

@ -0,0 +1,99 @@
{
"data": [
{
"apiVersion": 1,
"commands": {},
"deviceId": "zigbee-d87a3bfffe5d844a",
"feature": "device.messages.errors.raw",
"gatewayId": "################",
"isEnabled": true,
"isReady": true,
"properties": {
"entries": {
"type": "array",
"value": []
}
},
"timestamp": "2024-03-01T04:40:59.911Z",
"uri": "https://api.viessmann.com/iot/v1/features/installations/#######/gateways/################/devices/zigbee-d87a3bfffe5d844a/features/device.messages.errors.raw"
},
{
"apiVersion": 1,
"commands": {
"setName": {
"isExecutable": true,
"name": "setName",
"params": {
"name": {
"constraints": {
"maxLength": 40,
"minLength": 1,
"regEx": "^[\\p{L}0-9]+( [\\p{L}0-9]+)*$"
},
"required": true,
"type": "string"
}
},
"uri": "https://api.viessmann.com/iot/v1/features/installations/#######/gateways/################/devices/zigbee-d87a3bfffe5d844a/features/device.name/commands/setName"
}
},
"deviceId": "zigbee-d87a3bfffe5d844a",
"feature": "device.name",
"gatewayId": "################",
"isEnabled": true,
"isReady": true,
"properties": {
"name": {
"type": "string",
"value": "Office"
}
},
"timestamp": "2024-03-01T04:40:59.911Z",
"uri": "https://api.viessmann.com/iot/v1/features/installations/#######/gateways/################/devices/zigbee-d87a3bfffe5d844a/features/device.name"
},
{
"apiVersion": 1,
"commands": {},
"deviceId": "zigbee-d87a3bfffe5d844a",
"feature": "device.sensors.humidity",
"gatewayId": "################",
"isEnabled": true,
"isReady": true,
"properties": {
"status": {
"type": "string",
"value": "connected"
},
"value": {
"type": "number",
"unit": "percent",
"value": 53
}
},
"timestamp": "2024-03-02T07:51:07.303Z",
"uri": "https://api.viessmann.com/iot/v1/features/installations/#######/gateways/################/devices/zigbee-d87a3bfffe5d844a/features/device.sensors.humidity"
},
{
"apiVersion": 1,
"commands": {},
"deviceId": "zigbee-d87a3bfffe5d844a",
"feature": "device.sensors.temperature",
"gatewayId": "################",
"isEnabled": true,
"isReady": true,
"properties": {
"status": {
"type": "string",
"value": "connected"
},
"value": {
"type": "number",
"unit": "celsius",
"value": 17.5
}
},
"timestamp": "2024-03-02T07:52:42.043Z",
"uri": "https://api.viessmann.com/iot/v1/features/installations/#######/gateways/################/devices/zigbee-d87a3bfffe5d844a/features/device.sensors.temperature"
}
]
}

View File

@ -0,0 +1,99 @@
{
"data": [
{
"apiVersion": 1,
"commands": {},
"deviceId": "zigbee-5cc7c1fffea33a3b",
"feature": "device.messages.errors.raw",
"gatewayId": "################",
"isEnabled": true,
"isReady": true,
"properties": {
"entries": {
"type": "array",
"value": []
}
},
"timestamp": "2024-03-01T04:40:59.911Z",
"uri": "https://api.viessmann.com/iot/v1/features/installations/#######/gateways/################/devices/zigbee-5cc7c1fffea33a3b/features/device.messages.errors.raw"
},
{
"apiVersion": 1,
"commands": {
"setName": {
"isExecutable": true,
"name": "setName",
"params": {
"name": {
"constraints": {
"maxLength": 40,
"minLength": 1,
"regEx": "^[\\p{L}0-9]+( [\\p{L}0-9]+)*$"
},
"required": true,
"type": "string"
}
},
"uri": "https://api.viessmann.com/iot/v1/features/installations/#######/gateways/################/devices/zigbee-5cc7c1fffea33a3b/features/device.name/commands/setName"
}
},
"deviceId": "zigbee-5cc7c1fffea33a3b",
"feature": "device.name",
"gatewayId": "################",
"isEnabled": true,
"isReady": true,
"properties": {
"name": {
"type": "string",
"value": ""
}
},
"timestamp": "2024-03-01T04:40:59.911Z",
"uri": "https://api.viessmann.com/iot/v1/features/installations/#######/gateways/################/devices/zigbee-5cc7c1fffea33a3b/features/device.name"
},
{
"apiVersion": 1,
"commands": {},
"deviceId": "zigbee-5cc7c1fffea33a3b",
"feature": "device.sensors.humidity",
"gatewayId": "################",
"isEnabled": true,
"isReady": true,
"properties": {
"status": {
"type": "string",
"value": "connected"
},
"value": {
"type": "number",
"unit": "percent",
"value": 52
}
},
"timestamp": "2024-03-02T07:42:06.922Z",
"uri": "https://api.viessmann.com/iot/v1/features/installations/#######/gateways/################/devices/zigbee-5cc7c1fffea33a3b/features/device.sensors.humidity"
},
{
"apiVersion": 1,
"commands": {},
"deviceId": "zigbee-5cc7c1fffea33a3b",
"feature": "device.sensors.temperature",
"gatewayId": "################",
"isEnabled": true,
"isReady": true,
"properties": {
"status": {
"type": "string",
"value": "connected"
},
"value": {
"type": "number",
"unit": "celsius",
"value": 16.9
}
},
"timestamp": "2024-03-02T07:24:48.056Z",
"uri": "https://api.viessmann.com/iot/v1/features/installations/#######/gateways/################/devices/zigbee-5cc7c1fffea33a3b/features/device.sensors.temperature"
}
]
}

View File

@ -1050,3 +1050,207 @@
'state': '25.5',
})
# ---
# name: test_room_sensors[sensor.model0_humidity-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.model0_humidity',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.HUMIDITY: 'humidity'>,
'original_icon': None,
'original_name': 'Humidity',
'platform': 'vicare',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'gateway0_zigbee_d87a3bfffe5d844a-room_humidity',
'unit_of_measurement': '%',
})
# ---
# name: test_room_sensors[sensor.model0_humidity-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'humidity',
'friendly_name': 'model0 Humidity',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.model0_humidity',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '53',
})
# ---
# name: test_room_sensors[sensor.model0_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.model0_temperature',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Temperature',
'platform': 'vicare',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'gateway0_zigbee_d87a3bfffe5d844a-room_temperature',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_room_sensors[sensor.model0_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'friendly_name': 'model0 Temperature',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.model0_temperature',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '17.5',
})
# ---
# name: test_room_sensors[sensor.model1_humidity-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.model1_humidity',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.HUMIDITY: 'humidity'>,
'original_icon': None,
'original_name': 'Humidity',
'platform': 'vicare',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'gateway1_zigbee_5cc7c1fffea33a3b-room_humidity',
'unit_of_measurement': '%',
})
# ---
# name: test_room_sensors[sensor.model1_humidity-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'humidity',
'friendly_name': 'model1 Humidity',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': '%',
}),
'context': <ANY>,
'entity_id': 'sensor.model1_humidity',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '52',
})
# ---
# name: test_room_sensors[sensor.model1_temperature-entry]
EntityRegistryEntrySnapshot({
'aliases': set({
}),
'area_id': None,
'capabilities': dict({
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
}),
'config_entry_id': <ANY>,
'device_class': None,
'device_id': <ANY>,
'disabled_by': None,
'domain': 'sensor',
'entity_category': None,
'entity_id': 'sensor.model1_temperature',
'has_entity_name': True,
'hidden_by': None,
'icon': None,
'id': <ANY>,
'labels': set({
}),
'name': None,
'options': dict({
}),
'original_device_class': <SensorDeviceClass.TEMPERATURE: 'temperature'>,
'original_icon': None,
'original_name': 'Temperature',
'platform': 'vicare',
'previous_unique_id': None,
'supported_features': 0,
'translation_key': None,
'unique_id': 'gateway1_zigbee_5cc7c1fffea33a3b-room_temperature',
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
})
# ---
# name: test_room_sensors[sensor.model1_temperature-state]
StateSnapshot({
'attributes': ReadOnlyDict({
'device_class': 'temperature',
'friendly_name': 'model1 Temperature',
'state_class': <SensorStateClass.MEASUREMENT: 'measurement'>,
'unit_of_measurement': <UnitOfTemperature.CELSIUS: '°C'>,
}),
'context': <ANY>,
'entity_id': 'sensor.model1_temperature',
'last_changed': <ANY>,
'last_reported': <ANY>,
'last_updated': <ANY>,
'state': '16.9',
})
# ---

View File

@ -23,7 +23,30 @@ async def test_all_entities(
entity_registry: er.EntityRegistry,
) -> None:
"""Test all entities."""
fixtures: list[Fixture] = [Fixture({"type:boiler"}, "vicare/Vitodens300W.json")]
fixtures: list[Fixture] = [
Fixture({"type:boiler"}, "vicare/Vitodens300W.json"),
]
with (
patch(f"{MODULE}.vicare_login", return_value=MockPyViCare(fixtures)),
patch(f"{MODULE}.PLATFORMS", [Platform.SENSOR]),
):
await setup_integration(hass, mock_config_entry)
await snapshot_platform(hass, entity_registry, snapshot, mock_config_entry.entry_id)
@pytest.mark.usefixtures("entity_registry_enabled_by_default")
async def test_room_sensors(
hass: HomeAssistant,
snapshot: SnapshotAssertion,
mock_config_entry: MockConfigEntry,
entity_registry: er.EntityRegistry,
) -> None:
"""Test all entities."""
fixtures: list[Fixture] = [
Fixture({"type:climateSensor"}, "vicare/RoomSensor1.json"),
Fixture({"type:climateSensor"}, "vicare/RoomSensor2.json"),
]
with (
patch(f"{MODULE}.vicare_login", return_value=MockPyViCare(fixtures)),
patch(f"{MODULE}.PLATFORMS", [Platform.SENSOR]),