Add YoLink Water Meter Support (#114148)

* Add YS-5006/5007/5008 Water Meter Support

* Add YoLink Water Meter Support

* Update .coveragerc

* fix as suggestion
This commit is contained in:
Matrix 2024-03-26 16:29:14 +08:00 committed by GitHub
parent 54a69a2687
commit 63b4fd09c1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
6 changed files with 142 additions and 0 deletions

View file

@ -1687,6 +1687,7 @@ omit =
homeassistant/components/yolink/services.py
homeassistant/components/yolink/siren.py
homeassistant/components/yolink/switch.py
homeassistant/components/yolink/valve.py
homeassistant/components/youless/__init__.py
homeassistant/components/youless/sensor.py
homeassistant/components/zabbix/*

View file

@ -46,6 +46,7 @@ PLATFORMS = [
Platform.SENSOR,
Platform.SIREN,
Platform.SWITCH,
Platform.VALVE,
]

View file

@ -14,3 +14,5 @@ ATTR_REPEAT = "repeat"
ATTR_TONE = "tone"
YOLINK_EVENT = f"{DOMAIN}_event"
YOLINK_OFFLINE_TIME = 32400
DEV_MODEL_WATER_METER_YS5007 = "YS5007"

View file

@ -24,6 +24,7 @@ from yolink.const import (
ATTR_DEVICE_THERMOSTAT,
ATTR_DEVICE_VIBRATION_SENSOR,
ATTR_DEVICE_WATER_DEPTH_SENSOR,
ATTR_DEVICE_WATER_METER_CONTROLLER,
ATTR_GARAGE_DOOR_CONTROLLER,
)
from yolink.device import YoLinkDevice
@ -41,6 +42,7 @@ from homeassistant.const import (
EntityCategory,
UnitOfLength,
UnitOfTemperature,
UnitOfVolume,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
@ -76,6 +78,7 @@ SENSOR_DEVICE_TYPE = [
ATTR_DEVICE_THERMOSTAT,
ATTR_DEVICE_VIBRATION_SENSOR,
ATTR_DEVICE_WATER_DEPTH_SENSOR,
ATTR_DEVICE_WATER_METER_CONTROLLER,
ATTR_DEVICE_LOCK,
ATTR_DEVICE_MANIPULATOR,
ATTR_DEVICE_CO_SMOKE_SENSOR,
@ -96,6 +99,7 @@ BATTERY_POWER_SENSOR = [
ATTR_DEVICE_MANIPULATOR,
ATTR_DEVICE_CO_SMOKE_SENSOR,
ATTR_DEVICE_WATER_DEPTH_SENSOR,
ATTR_DEVICE_WATER_METER_CONTROLLER,
]
MCU_DEV_TEMPERATURE_SENSOR = [
@ -202,6 +206,17 @@ SENSOR_TYPES: tuple[YoLinkSensorEntityDescription, ...] = (
native_unit_of_measurement=UnitOfLength.METERS,
exists_fn=lambda device: device.device_type in ATTR_DEVICE_WATER_DEPTH_SENSOR,
),
YoLinkSensorEntityDescription(
key="meter_reading",
translation_key="water_meter_reading",
device_class=SensorDeviceClass.WATER,
icon="mdi:gauge",
native_unit_of_measurement=UnitOfVolume.CUBIC_METERS,
state_class=SensorStateClass.TOTAL_INCREASING,
should_update_entity=lambda value: value is not None,
exists_fn=lambda device: device.device_type
in ATTR_DEVICE_WATER_METER_CONTROLLER,
),
)

View file

@ -73,12 +73,20 @@
"enabled": "[%key:common::state::enabled%]",
"disabled": "[%key:common::state::disabled%]"
}
},
"water_meter_reading": {
"name": "Water meter reading"
}
},
"number": {
"config_volume": {
"name": "Volume"
}
},
"valve": {
"meter_valve_state": {
"name": "Valve state"
}
}
},
"services": {

View file

@ -0,0 +1,115 @@
"""YoLink Valve."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from yolink.client_request import ClientRequest
from yolink.const import ATTR_DEVICE_WATER_METER_CONTROLLER
from yolink.device import YoLinkDevice
from homeassistant.components.valve import (
ValveDeviceClass,
ValveEntity,
ValveEntityDescription,
ValveEntityFeature,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DEV_MODEL_WATER_METER_YS5007, DOMAIN
from .coordinator import YoLinkCoordinator
from .entity import YoLinkEntity
@dataclass(frozen=True)
class YoLinkValveEntityDescription(ValveEntityDescription):
"""YoLink ValveEntityDescription."""
exists_fn: Callable[[YoLinkDevice], bool] = lambda _: True
value: Callable = lambda state: state
DEVICE_TYPES: tuple[YoLinkValveEntityDescription, ...] = (
YoLinkValveEntityDescription(
key="valve_state",
translation_key="meter_valve_state",
device_class=ValveDeviceClass.WATER,
value=lambda value: value == "closed" if value is not None else None,
exists_fn=lambda device: device.device_type
== ATTR_DEVICE_WATER_METER_CONTROLLER
and not device.device_model_name.startswith(DEV_MODEL_WATER_METER_YS5007),
),
)
DEVICE_TYPE = [ATTR_DEVICE_WATER_METER_CONTROLLER]
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up YoLink valve from a config entry."""
device_coordinators = hass.data[DOMAIN][config_entry.entry_id].device_coordinators
valve_device_coordinators = [
device_coordinator
for device_coordinator in device_coordinators.values()
if device_coordinator.device.device_type in DEVICE_TYPE
]
async_add_entities(
YoLinkValveEntity(config_entry, valve_device_coordinator, description)
for valve_device_coordinator in valve_device_coordinators
for description in DEVICE_TYPES
if description.exists_fn(valve_device_coordinator.device)
)
class YoLinkValveEntity(YoLinkEntity, ValveEntity):
"""YoLink Valve Entity."""
entity_description: YoLinkValveEntityDescription
def __init__(
self,
config_entry: ConfigEntry,
coordinator: YoLinkCoordinator,
description: YoLinkValveEntityDescription,
) -> None:
"""Init YoLink valve."""
super().__init__(config_entry, coordinator)
self._attr_supported_features = (
ValveEntityFeature.OPEN | ValveEntityFeature.CLOSE
)
self.entity_description = description
self._attr_unique_id = (
f"{coordinator.device.device_id} {self.entity_description.key}"
)
@callback
def update_entity_state(self, state: dict[str, str | list[str]]) -> None:
"""Update HA Entity State."""
if (
attr_val := self.entity_description.value(
state.get(self.entity_description.key)
)
) is None:
return
self._attr_is_closed = attr_val
self.async_write_ha_state()
async def _async_invoke_device(self, state: str) -> None:
"""Call setState api to change valve state."""
await self.call_device(ClientRequest("setState", {"valve": state}))
self._attr_is_closed = state == "close"
self.async_write_ha_state()
async def async_open_valve(self) -> None:
"""Open the valve."""
await self._async_invoke_device("open")
async def async_close_valve(self) -> None:
"""Close valve."""
await self._async_invoke_device("close")