M custom_components/ouman_eh800/__init__.py => custom_components/ouman_eh800/__init__.py +49 -12
@@ 1,28 1,65 @@
-"""
-ouman_eh800
-"""
-
+from datetime import timedelta
import logging
from homeassistant.core import HomeAssistant
from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import Platform
+from homeassistant.helpers.device_registry import DeviceInfo
+from homeassistant.util import Throttle
-from .const import DOMAIN
+from .const import DOMAIN, CONF_HOST, CONF_PORT, CONF_USERNAME, CONF_PASSWORD
+from .eh800 import EH800
_LOGGER = logging.getLogger(__name__)
-PLATFORMS = ["sensor"]
+PLATFORMS = [Platform.CLIMATE, Platform.NUMBER, Platform.SENSOR, Platform.VALVE]
+UPDATE_INTERVAL = timedelta(minutes=1)
-async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
- """TODO"""
+async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
_LOGGER.debug("Setting up Ouman EH-800")
- hass.data.setdefault(DOMAIN, {})
- hass_data = dict(entry.data)
- hass.data[DOMAIN][entry.entry_id] = hass_data
+ config = dict(entry.data)
+ eh800 = EH800(
+ config[CONF_HOST],
+ config[CONF_PORT],
+ config[CONF_USERNAME],
+ config[CONF_PASSWORD],
+ )
- await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
+ device = OumanEH800Device(hass, eh800, entry)
+ hass.data.setdefault(DOMAIN, {}).update({entry.entry_id: device})
+ await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
+
+
+class OumanEH800Device:
+ """Ouman EH-800 Device instance."""
+
+ def __init__(self, hass: HomeAssistant, device: EH800, entry: ConfigEntry) -> None:
+ self._hass = hass
+ self.device = device
+ self.entry = entry
+
+ self._available = True
+
+ @Throttle(UPDATE_INTERVAL)
+ async def async_update(
+ self,
+ **kwargs, # pylint: disable=unused-argument
+ ):
+ """Pull data from Ouman EH-800."""
+
+ update_ok = await self.device.update()
+ if not update_ok:
+ _LOGGER.warning("Failed to update EH-800 device!")
+
+ @property
+ def device_info(self) -> DeviceInfo:
+ return DeviceInfo(
+ identifiers={(DOMAIN, self.entry.entry_id)},
+ manufacturer="Ouman",
+ name="Ouman EH-800",
+ )
A custom_components/ouman_eh800/climate.py => custom_components/ouman_eh800/climate.py +137 -0
@@ 0,0 1,137 @@
+import logging
+
+from homeassistant.components.climate import (
+ ClimateEntity,
+ ClimateEntityDescription,
+ ClimateEntityFeature,
+ HVACMode,
+)
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import UnitOfTemperature
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers.entity_platform import AddEntitiesCallback
+
+from . import OumanEH800Device
+from .const import DOMAIN
+from .eh800 import OPERATION_MODES
+
+_LOGGER = logging.getLogger(__name__)
+
+
+class OumanEH800DeviceClimateEntityDescription(
+ ClimateEntityDescription, frozen_or_thawed=True
+): # pylint: disable=too-few-public-methods
+ current_temperature_key: str
+ target_temperature_key: str
+ operation_mode_key: str
+
+
+async def async_setup_entry(
+ hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
+) -> None:
+ """Set up Ouman EH-800 device climate control."""
+ device = hass.data[DOMAIN].get(entry.entry_id)
+
+ entities: list[OumanEH800DeviceClimate] = [
+ OumanEH800DeviceClimate(
+ device,
+ OumanEH800DeviceClimateEntityDescription(
+ key="l1_climate",
+ current_temperature_key="l1_room_temperature",
+ target_temperature_key="l1_target_room_temperature",
+ operation_mode_key="l1_operation_mode",
+ ),
+ )
+ ]
+
+ async_add_entities(entities, True)
+
+
+class OumanEH800DeviceClimate(ClimateEntity):
+ entity_description: OumanEH800DeviceClimateEntityDescription
+
+ _attr_supported_features = (
+ ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE
+ )
+ _attr_temperature_unit = UnitOfTemperature.CELSIUS
+
+ def __init__(
+ self,
+ device: OumanEH800Device,
+ description: OumanEH800DeviceClimateEntityDescription,
+ ) -> None:
+ self._device = device
+ self.entity_description = description
+
+ self._attr_name = description.key.replace("_", " ").capitalize()
+ self._attr_unique_id = f"ouman_eh800_{description.key}"
+ self._attr_device_info = device.device_info
+
+ @property
+ def extra_state_attributes(self) -> dict:
+ return self._device.device.data
+
+ @property
+ def hvac_mode(self) -> HVACMode:
+ operation_mode = int(
+ self._device.device.data.get(self.entity_description.operation_mode_key, 0)
+ )
+ if operation_mode == 5:
+ return HVACMode.OFF
+ if operation_mode == 0:
+ return HVACMode.AUTO
+ return HVACMode.HEAT
+
+ @property
+ def hvac_modes(self) -> list[HVACMode]:
+ return []
+
+ @property
+ def preset_mode(self) -> str:
+ operation_mode = int(
+ self._device.device.data.get(self.entity_description.operation_mode_key, 0)
+ )
+ return [om.name for om in OPERATION_MODES if om.value == operation_mode][0]
+
+ @property
+ def preset_modes(self) -> list[str]:
+ return [om.name for om in OPERATION_MODES]
+
+ @property
+ def current_temperature(self) -> float:
+ return float(
+ self._device.device.data.get(
+ self.entity_description.current_temperature_key, 0.0
+ )
+ )
+
+ @property
+ def target_temperature(self) -> float:
+ return float(
+ self._device.device.data.get(
+ self.entity_description.target_temperature_key, 0.0
+ )
+ )
+
+ async def async_set_temperature(self, **kwargs) -> None:
+ await self._device.device.update_value(
+ self.entity_description.target_temperature_key,
+ kwargs.get("temperature", self.target_temperature),
+ )
+ self.async_write_ha_state()
+
+ async def async_set_preset_mode(self, preset_mode: str) -> None:
+ operation_mode = [om for om in OPERATION_MODES if om.name == preset_mode][0]
+ _LOGGER.debug(
+ "Setting operation mode to '%s' (%s)",
+ operation_mode.name,
+ operation_mode.value,
+ )
+ await self._device.device.update_value(
+ self.entity_description.operation_mode_key,
+ operation_mode.value,
+ )
+ self.async_write_ha_state()
+
+ async def async_update(self) -> None:
+ await self._device.async_update()
M custom_components/ouman_eh800/config_flow.py => custom_components/ouman_eh800/config_flow.py +0 -2
@@ 39,7 39,6 @@ class OumanEH800ConfigFlow(
async def _create_entry(
self, host: str, port: int, username: str, password: str
) -> ConfigFlowResult:
- """Register new entry."""
return self.async_create_entry(
title=f"Ouman {host}",
data={
@@ 53,7 52,6 @@ class OumanEH800ConfigFlow(
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
- """TODO"""
if user_input is None:
return self.async_show_form(step_id="user", data_schema=USER_SCHEMA)
M custom_components/ouman_eh800/const.py => custom_components/ouman_eh800/const.py +0 -7
@@ 8,10 8,3 @@ CONF_HOST = "host"
CONF_PORT = "port"
CONF_USERNAME = "username"
CONF_PASSWORD = "password"
-
-TEMPERATURE_SENSOR_TYPE_OUTSIDE = "outside"
-TEMPERATURE_SENSOR_TYPE_L1_ROOM = "l1_room"
-TEMPERATURE_SENSOR_TYPE_L1_SUPPLY = "l1_supply"
-# TEMPERATURE_SENSOR_TYPE_ = ""
-# TEMPERATURE_SENSOR_TYPE_ = ""
-# TEMPERATURE_SENSOR_TYPE_ = ""
M custom_components/ouman_eh800/eh800.py => custom_components/ouman_eh800/eh800.py +119 -42
@@ 1,64 1,141 @@
-"""TODO"""
-
+import dataclasses
import logging
-import requests
+
+from httpx import AsyncClient
_LOGGER = logging.getLogger(__name__)
-class EH800:
- """TODO"""
+@dataclasses.dataclass(frozen=True, kw_only=True)
+class Value:
+ key: str
+ register: str
+
+
+VALUES: tuple[Value, ...] = (
+ # Asetusarvot > Menoveden minimiraja
+ Value(key="l1_supply_temperature_minimum", register="S_54_85"),
+ # Asetusarvot > Menoveden maksimiraja
+ Value(key="l1_supply_temperature_maximum", register="S_55_85"),
+ # Asetusarvot > L1 Säätökäyrä
+ # n = negative, p = positive
+ Value(key="l1_heating_curve_n_20", register="S_67_85"),
+ Value(key="l1_heating_curve_n_10", register="S_69_85"),
+ Value(key="l1_heating_curve_zero", register="S_71_85"),
+ Value(key="l1_heating_curve_p_10", register="S_73_85"),
+ Value(key="l1_heating_curve_p_20", register="S_75_85"),
+ # Asetusarvot > Huonelämpötila
+ Value(key="l1_target_room_temperature", register="S_81_85"),
+ # Asetusarvot > Lämmönpudotus (huonelämpö)
+ Value(key="l1_temperature_drop", register="S_87_85"),
+ # Asetusarvot > Suuri lämmönpudotus (huonelämpö)
+ Value(key="l1_temperature_drop_big", register="S_88_85"),
+ # Ohjaustavat
+ # 0 = Automaatti
+ # 3 = Pakko-ohjaus, norm. lämpötaso
+ # 1 = Pakko-ohjaus, lämmönpudotus
+ # 2 = Pakko-ohjaus, suuri lämmönpudotus
+ # 6 = Käsiajo, sähköinen
+ # 5 = Alasajo
+ Value(key="l1_operation_mode", register="S_59_85"),
+ Value(key="l1_manual_drive_valve_position", register="S_92_85"),
+ # Mittaukset > Ulkolämpötila
+ Value(key="outside_temperature", register="S_227_85"),
+ # Mittaukset > L1 Menoveden lämpötila
+ Value(key="l1_supply_temperature", register="S_259_85"),
+ # Mittaukset > L1 Huonelämpötila
+ Value(key="l1_room_temperature", register="S_284_85"),
+ # Mittaukset > Huonelämpökaukoasetus TMR/SP
+ Value(key="l1_tmrsp", register="S_274_85"),
+ # Mittaukset > L1 Venttiilin asento
+ Value(key="l1_valve_position", register="S_272_85"),
+ # EH-800 > Huonelämpötila
+ Value(key="room_temperature", register="S_261_85"),
+ # EH-800 > Huonelämpötilan hienosäätö
+ Value(key="room_temperature_fine_adjustment", register="S_102_85"),
+ # EH-800 > Lämpötaso:: (UI placement is strange)
+ Value(key="l1_operation_mode_str", register="S_1000_0"),
+)
+
+
+@dataclasses.dataclass(frozen=True, kw_only=True)
+class OperationMode:
+ name: str
+ value: int
+
+
+OPERATION_MODES: tuple[OperationMode, ...] = (
+ OperationMode(name="Automatic", value=0),
+ OperationMode(name="Forced - Normal", value=3),
+ OperationMode(name="Forced - Drop", value=1),
+ OperationMode(name="Forced - Big Drop", value=2),
+ OperationMode(name="Manual", value=6),
+ OperationMode(name="Off", value=5),
+)
+
+class EH800:
def __init__(self, host: str, port: int, username: str, password: str) -> None:
- """TODO"""
self._uri = f"http://{host}:{port}"
self._login = f"uid={username};pwd={password};"
- self._outside_temp = 0.0
- self._l1_room_temp = 0.0
- self._l1_supply_temp = 0.0
+ self._client = AsyncClient()
+
+ self.data = {}
+
+ async def _refresh_login(self) -> bool:
+ """
+ Refresh login.
- def _refresh_login(self) -> bool:
- """TODO"""
- r = requests.get(f"{self._uri}/login?{self._login}")
+ Logs an error if the login failed.
+ """
+ r = await self._client.get(f"{self._uri}/login?{self._login}")
if r.text[:-1] == "login?result=ok;":
- _LOGGER.debug("Login ok")
return True
- _LOGGER.debug("Login error")
+ _LOGGER.error("Login error")
return False
- def _request_value(self, register) -> str:
- """TODO"""
- if not self._refresh_login():
- return ""
-
- r = requests.get(f"{self._uri}/request?{register}")
+ async def _request_value(self, register) -> str:
+ """Request a value from the API."""
+ r = await self._client.get(f"{self._uri}/request?{register}")
eq_index = r.text.find("=")
sc_index = r.text.find(";")
return r.text[eq_index + 1 : sc_index]
- def get_outside_temp(self) -> float:
- """TODO"""
- return self._outside_temp
-
- def update_outside_temp(self):
- """TODO"""
- self._outside_temp = self._request_value("S_227_85")
-
- def get_l1_room_temp(self) -> float:
- """TODO"""
- return self._l1_room_temp
-
- def update_l1_room_temp(self):
- """TODO"""
- self._l1_room_temp = self._request_value("S_261_85")
+ async def _update_value(self, value: Value, new_value) -> None:
+ """
+ Update a value via the API.
- def get_l1_supply_temp(self) -> float:
- """TODO"""
- return self._l1_supply_temp
-
- def update_l1_supply_temp(self):
- """TODO"""
- self._l1_supply_temp = self._request_value("S_259_85")
+ Checks the API return value and logs an error if the value update failed.
+ """
+ r = await self._client.get(f"{self._uri}/update?{value.register}={new_value};")
+ eq_index = r.text.find("=")
+ sc_index = r.text.find(";")
+ got_value = r.text[eq_index + 1 : sc_index]
+ if str(got_value) != str(new_value):
+ _LOGGER.error(
+ "Value update failed, got '%s', wanted '%s'", got_value, new_value
+ )
+ else:
+ # Update data to match the new value
+ self.data[value.key] = new_value
+
+ async def update(self) -> bool:
+ """Update data values from the API."""
+ if not await self._refresh_login():
+ return False
+
+ for value in VALUES:
+ self.data[value.key] = await self._request_value(value.register)
+
+ return True
+
+ async def update_value(self, key, new_value) -> None:
+ """Update a value via the API."""
+ value = [value for value in VALUES if value.key == key][0]
+ _LOGGER.debug(
+ "Updating '%s' (%s) to '%s'", value.key, value.register, new_value
+ )
+ await self._update_value(value, new_value)
M custom_components/ouman_eh800/manifest.json => custom_components/ouman_eh800/manifest.json +2 -1
@@ 2,5 2,6 @@
"domain": "ouman_eh800",
"name": "Ouman EH-800",
"config_flow": true,
- "version": "0.1.0"
+ "version": "0.1.0",
+ "requirements": ["httpx==0.27.2"]
}
A custom_components/ouman_eh800/number.py => custom_components/ouman_eh800/number.py +80 -0
@@ 0,0 1,80 @@
+import logging
+
+from homeassistant.components.number import (
+ NumberDeviceClass,
+ NumberEntity,
+ NumberEntityDescription,
+)
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.const import UnitOfTemperature
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers.entity_platform import AddEntitiesCallback
+
+from . import OumanEH800Device
+from .const import DOMAIN
+
+_LOGGER = logging.getLogger(__name__)
+
+
+NUMBERS: tuple[str, ...] = (
+ "l1_temperature_drop",
+ "l1_temperature_drop_big",
+)
+NUMBERS: tuple[NumberEntityDescription, ...] = (
+ NumberEntityDescription(
+ key="l1_temperature_drop",
+ device_class=NumberDeviceClass.TEMPERATURE,
+ mode="box",
+ native_max_value=90.0,
+ native_min_value=0.0,
+ native_step=0.5,
+ native_unit_of_measurement=UnitOfTemperature.CELSIUS,
+ ),
+ NumberEntityDescription(
+ key="l1_temperature_drop_big",
+ device_class=NumberDeviceClass.TEMPERATURE,
+ mode="box",
+ native_max_value=90.0,
+ native_min_value=0.0,
+ native_step=0.5,
+ native_unit_of_measurement=UnitOfTemperature.CELSIUS,
+ ),
+)
+
+
+async def async_setup_entry(
+ hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
+) -> None:
+ """Set up Ouman EH-800 device numbers."""
+ device = hass.data[DOMAIN].get(entry.entry_id)
+
+ entities: list[OumanEH800DeviceNumber] = [
+ OumanEH800DeviceNumber(device, description) for description in NUMBERS
+ ]
+
+ async_add_entities(entities, True)
+
+
+class OumanEH800DeviceNumber(NumberEntity):
+ entity_description: NumberEntityDescription
+
+ def __init__(
+ self, device: OumanEH800Device, description: NumberEntityDescription
+ ) -> None:
+ self._device = device
+ self.entity_description = description
+
+ self._attr_name = description.key.replace("_", " ").capitalize()
+ self._attr_unique_id = f"ouman_eh800_{description.key}"
+ self._attr_device_info = device.device_info
+
+ @property
+ def native_value(self) -> float:
+ return self._device.device.data.get(self.entity_description.key, 0.0)
+
+ async def async_set_native_value(self, value: float) -> None:
+ await self._device.device.update_value(self.entity_description.key, value)
+ self.async_write_ha_state()
+
+ async def async_update(self) -> None:
+ await self._device.async_update()
M custom_components/ouman_eh800/sensor.py => custom_components/ouman_eh800/sensor.py +53 -101
@@ 1,121 1,73 @@
-"""
-TODO
-"""
-
import logging
-from datetime import timedelta
-
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
+ SensorEntityDescription,
SensorStateClass,
)
-from homeassistant.core import HomeAssistant
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfTemperature
+from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
-from homeassistant.util import Throttle
-from .const import (
- DOMAIN,
- CONF_HOST,
- CONF_PORT,
- CONF_USERNAME,
- CONF_PASSWORD,
- TEMPERATURE_SENSOR_TYPE_L1_SUPPLY,
- TEMPERATURE_SENSOR_TYPE_L1_ROOM,
- TEMPERATURE_SENSOR_TYPE_OUTSIDE,
-)
-from .eh800 import EH800
+from . import OumanEH800Device
+from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
-async def async_setup_entry(
- hass: HomeAssistant,
- entry: ConfigEntry,
- async_add_entities: AddEntitiesCallback,
-) -> None:
- """TODO"""
- config = hass.data[DOMAIN][entry.entry_id]
-
- eh800 = EH800(
- config[CONF_HOST],
- config[CONF_PORT],
- config[CONF_USERNAME],
- config[CONF_PASSWORD],
- )
-
- entities = []
- entities.append(TemperatureSensor(hass, eh800, TEMPERATURE_SENSOR_TYPE_OUTSIDE))
- entities.append(TemperatureSensor(hass, eh800, TEMPERATURE_SENSOR_TYPE_L1_ROOM))
- entities.append(TemperatureSensor(hass, eh800, TEMPERATURE_SENSOR_TYPE_L1_SUPPLY))
-
- async_add_entities(entities)
-
- return True
-
-
-class TemperatureSensor(SensorEntity):
- """Temperature sensor"""
-
- def __init__(self, hass: HomeAssistant, api: EH800, sensor_type: str):
- self._hass = hass
- self._api = api
- self._sensor_type = sensor_type
-
- self._unique_id = f"{DOMAIN}_temperature_{sensor_type}".lower()
-
- self._state = 0.0
-
- @property
- def device_class(self):
- """TODO"""
- return SensorDeviceClass.TEMPERATURE
-
- @property
- def device_info(self):
- """TODO"""
- return {
- "identifiers": {(DOMAIN, self.unique_id)},
- "name": self.name,
- }
-
- @property
- def name(self):
- """TODO"""
- return self.unique_id
-
- @property
- def native_unit_of_measurement(self):
- """TODO"""
- return UnitOfTemperature.CELSIUS
+TEMPERATURE_SENSORS: tuple[str, ...] = (
+ "outside_temperature",
+ "l1_supply_temperature",
+ "l1_room_temperature",
+ "l1_tmrsp",
+)
- @property
- def state(self):
- """TODO"""
- return self._state
- @property
- def state_class(self):
- """TODO"""
- return SensorStateClass.MEASUREMENT
+async def async_setup_entry(
+ hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
+) -> None:
+ """Set up Ouman EH-800 device sensors."""
+ device = hass.data[DOMAIN].get(entry.entry_id)
+
+ entities: list[OumanEH800DeviceSensor] = [
+ OumanEH800DeviceSensor(
+ device,
+ sensor,
+ SensorEntityDescription(
+ key=sensor,
+ native_unit_of_measurement=UnitOfTemperature.CELSIUS,
+ device_class=SensorDeviceClass.TEMPERATURE,
+ state_class=SensorStateClass.MEASUREMENT,
+ ),
+ )
+ for sensor in TEMPERATURE_SENSORS
+ ]
+
+ async_add_entities(entities, True)
+
+
+class OumanEH800DeviceSensor(SensorEntity):
+ entity_description: SensorEntityDescription
+
+ def __init__(
+ self,
+ device: OumanEH800Device,
+ value_key: str,
+ description: SensorEntityDescription,
+ ) -> None:
+ self._device = device
+ self._value_key = value_key
+ self.entity_description = description
+
+ self._attr_name = description.key.replace("_", " ").capitalize()
+ self._attr_unique_id = f"ouman_eh800_{description.key}"
+ self._attr_device_info = device.device_info
@property
- def unique_id(self):
- """TODO"""
- return self._unique_id
+ def native_value(self) -> float:
+ return self._device.device.data.get(self._value_key, 0.0)
- @Throttle(timedelta(minutes=1))
- async def async_update(self):
- """TODO"""
- if self._sensor_type == TEMPERATURE_SENSOR_TYPE_OUTSIDE:
- await self._hass.async_add_executor_job(self._api.update_outside_temp)
- self._state = self._api.get_outside_temp()
- elif self._sensor_type == TEMPERATURE_SENSOR_TYPE_L1_ROOM:
- await self._hass.async_add_executor_job(self._api.update_l1_room_temp)
- self._state = self._api.get_l1_room_temp()
- elif self._sensor_type == TEMPERATURE_SENSOR_TYPE_L1_SUPPLY:
- await self._hass.async_add_executor_job(self._api.update_l1_supply_temp)
- self._state = self._api.get_l1_supply_temp()
+ async def async_update(self) -> None:
+ await self._device.async_update()
A custom_components/ouman_eh800/valve.py => custom_components/ouman_eh800/valve.py +109 -0
@@ 0,0 1,109 @@
+import logging
+
+from homeassistant.components.valve import (
+ ValveDeviceClass,
+ ValveEntity,
+ ValveEntityDescription,
+ ValveEntityFeature,
+)
+from homeassistant.config_entries import ConfigEntry
+from homeassistant.core import HomeAssistant
+from homeassistant.helpers.entity_platform import AddEntitiesCallback
+
+from . import OumanEH800Device
+from .const import DOMAIN
+
+_LOGGER = logging.getLogger(__name__)
+
+
+VALVES_RO: tuple[str, ...] = ("l1_valve_position",)
+
+VALVES_RW: tuple[str, ...] = ("l1_manual_drive_valve_position",)
+
+
+async def async_setup_entry(
+ hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
+) -> None:
+ """Set up Ouman EH-800 device valves."""
+ device = hass.data[DOMAIN].get(entry.entry_id)
+
+ entities: list[OumanEH800DeviceValve] = []
+
+ entities.extend(
+ [
+ OumanEH800DeviceValveRO(
+ device,
+ valve,
+ ValveEntityDescription(
+ key=valve,
+ device_class=ValveDeviceClass.WATER,
+ ),
+ )
+ for valve in VALVES_RO
+ ]
+ )
+
+ entities.extend(
+ [
+ OumanEH800DeviceValveRW(
+ device,
+ valve,
+ ValveEntityDescription(
+ key=valve,
+ device_class=ValveDeviceClass.WATER,
+ ),
+ )
+ for valve in VALVES_RW
+ ]
+ )
+
+ async_add_entities(entities, True)
+
+
+class OumanEH800DeviceValve(ValveEntity):
+ entity_description: ValveEntityDescription
+
+ def __init__(
+ self,
+ device: OumanEH800Device,
+ value_key: str,
+ description: ValveEntityDescription,
+ ) -> None:
+ self._device = device
+ self._value_key = value_key
+ self.entity_description = description
+
+ self._attr_name = description.key.replace("_", " ").capitalize()
+ self._attr_unique_id = f"ouman_eh800_{description.key}"
+ self._attr_device_info = device.device_info
+
+ @property
+ def current_valve_position(self) -> int:
+ return int(self._device.device.data.get(self._value_key, 0))
+
+ @property
+ def reports_position(self) -> bool:
+ return True
+
+ async def async_update(self) -> None:
+ await self._device.async_update()
+
+
+class OumanEH800DeviceValveRO(OumanEH800DeviceValve):
+ """A valve that can only be read."""
+
+
+class OumanEH800DeviceValveRW(OumanEH800DeviceValve):
+ """
+ A valve that can be read and set.
+
+ Supports setting the position of the valve, and closing the valve.
+ """
+
+ _attr_supported_features = (
+ ValveEntityFeature.CLOSE | ValveEntityFeature.SET_POSITION
+ )
+
+ async def async_set_valve_position(self, position: int) -> None:
+ await self._device.device.update_value(self.entity_description.key, position)
+ self.async_write_ha_state()
M flake.nix => flake.nix +6 -1
@@ 37,7 37,12 @@
# Python linting and formatting
pylint = {
enable = true;
- args = ["--disable=import-error"];
+ args = [
+ "--disable=import-error"
+ "--disable=missing-class-docstring"
+ "--disable=missing-function-docstring"
+ "--disable=missing-module-docstring"
+ ];
};
black.enable = true;
M requirements.dev.txt => requirements.dev.txt +1 -0
@@ 1,1 1,2 @@
homeassistant==2024.10.4
+httpx==0.27.2