diff --git a/.coveragerc b/.coveragerc index 9eb32f7cda8..6f382bcb780 100644 --- a/.coveragerc +++ b/.coveragerc @@ -986,6 +986,7 @@ omit = homeassistant/components/orvibo/switch.py homeassistant/components/osoenergy/__init__.py homeassistant/components/osoenergy/const.py + homeassistant/components/osoenergy/sensor.py homeassistant/components/osoenergy/water_heater.py homeassistant/components/osramlightify/light.py homeassistant/components/otp/sensor.py diff --git a/homeassistant/components/osoenergy/__init__.py b/homeassistant/components/osoenergy/__init__.py index 48ea01e8bb8..20ff22cea23 100644 --- a/homeassistant/components/osoenergy/__init__.py +++ b/homeassistant/components/osoenergy/__init__.py @@ -16,18 +16,25 @@ from homeassistant.const import CONF_API_KEY, Platform from homeassistant.core import HomeAssistant from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady from homeassistant.helpers import aiohttp_client +from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity import Entity from .const import DOMAIN -_T = TypeVar( - "_T", OSOEnergyBinarySensorData, OSOEnergySensorData, OSOEnergyWaterHeaterData +_OSOEnergyT = TypeVar( + "_OSOEnergyT", + OSOEnergyBinarySensorData, + OSOEnergySensorData, + OSOEnergyWaterHeaterData, ) +MANUFACTURER = "OSO Energy" PLATFORMS = [ + Platform.SENSOR, Platform.WATER_HEATER, ] PLATFORM_LOOKUP = { + Platform.SENSOR: "sensor", Platform.WATER_HEATER: "water_heater", } @@ -70,13 +77,18 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: return unload_ok -class OSOEnergyEntity(Entity, Generic[_T]): +class OSOEnergyEntity(Entity, Generic[_OSOEnergyT]): """Initiate OSO Energy Base Class.""" _attr_has_entity_name = True - def __init__(self, osoenergy: OSOEnergy, osoenergy_device: _T) -> None: + def __init__(self, osoenergy: OSOEnergy, entity_data: _OSOEnergyT) -> None: """Initialize the instance.""" self.osoenergy = osoenergy - self.device = osoenergy_device - self._attr_unique_id = osoenergy_device.device_id + self.entity_data = entity_data + self._attr_device_info = DeviceInfo( + identifiers={(DOMAIN, entity_data.device_id)}, + manufacturer=MANUFACTURER, + model=entity_data.device_type, + name=entity_data.device_name, + ) diff --git a/homeassistant/components/osoenergy/sensor.py b/homeassistant/components/osoenergy/sensor.py new file mode 100644 index 00000000000..0be6ad83281 --- /dev/null +++ b/homeassistant/components/osoenergy/sensor.py @@ -0,0 +1,151 @@ +"""Support for OSO Energy sensors.""" + +from collections.abc import Callable +from dataclasses import dataclass + +from apyosoenergyapi import OSOEnergy +from apyosoenergyapi.helper.const import OSOEnergySensorData + +from homeassistant.components.sensor import ( + SensorDeviceClass, + SensorEntity, + SensorEntityDescription, + SensorStateClass, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import UnitOfEnergy, UnitOfPower, UnitOfVolume +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.typing import StateType + +from . import OSOEnergyEntity +from .const import DOMAIN + + +@dataclass(frozen=True, kw_only=True) +class OSOEnergySensorEntityDescription(SensorEntityDescription): + """Class describing OSO Energy heater sensor entities.""" + + value_fn: Callable[[OSOEnergy], StateType] + + +SENSOR_TYPES: dict[str, OSOEnergySensorEntityDescription] = { + "heater_mode": OSOEnergySensorEntityDescription( + key="heater_mode", + translation_key="heater_mode", + device_class=SensorDeviceClass.ENUM, + options=[ + "auto", + "manual", + "off", + "legionella", + "powersave", + "extraenergy", + "voltage", + "ffr", + ], + value_fn=lambda entity_data: entity_data.state.lower(), + ), + "optimization_mode": OSOEnergySensorEntityDescription( + key="optimization_mode", + translation_key="optimization_mode", + device_class=SensorDeviceClass.ENUM, + options=["off", "oso", "gridcompany", "smartcompany", "advanced"], + value_fn=lambda entity_data: entity_data.state.lower(), + ), + "power_load": OSOEnergySensorEntityDescription( + key="power_load", + device_class=SensorDeviceClass.POWER, + state_class=SensorStateClass.MEASUREMENT, + native_unit_of_measurement=UnitOfPower.KILO_WATT, + value_fn=lambda entity_data: entity_data.state, + ), + "tapping_capacity": OSOEnergySensorEntityDescription( + key="tapping_capacity", + translation_key="tapping_capacity", + device_class=SensorDeviceClass.ENERGY, + native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR, + value_fn=lambda entity_data: entity_data.state, + ), + "capacity_mixed_water_40": OSOEnergySensorEntityDescription( + key="capacity_mixed_water_40", + translation_key="capacity_mixed_water_40", + device_class=SensorDeviceClass.VOLUME, + native_unit_of_measurement=UnitOfVolume.LITERS, + value_fn=lambda entity_data: entity_data.state, + ), + "v40_min": OSOEnergySensorEntityDescription( + key="v40_min", + translation_key="v40_min", + device_class=SensorDeviceClass.VOLUME, + native_unit_of_measurement=UnitOfVolume.LITERS, + value_fn=lambda entity_data: entity_data.state, + ), + "v40_level_min": OSOEnergySensorEntityDescription( + key="v40_level_min", + translation_key="v40_level_min", + device_class=SensorDeviceClass.VOLUME, + native_unit_of_measurement=UnitOfVolume.LITERS, + value_fn=lambda entity_data: entity_data.state, + ), + "v40_level_max": OSOEnergySensorEntityDescription( + key="v40_level_max", + translation_key="v40_level_max", + device_class=SensorDeviceClass.VOLUME, + native_unit_of_measurement=UnitOfVolume.LITERS, + value_fn=lambda entity_data: entity_data.state, + ), + "volume": OSOEnergySensorEntityDescription( + key="volume", + device_class=SensorDeviceClass.VOLUME, + native_unit_of_measurement=UnitOfVolume.LITERS, + value_fn=lambda entity_data: entity_data.state, + ), +} + + +async def async_setup_entry( + hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback +) -> None: + """Set up OSO Energy sensor.""" + osoenergy = hass.data[DOMAIN][entry.entry_id] + devices = osoenergy.session.device_list.get("sensor") + entities = [] + if devices: + for dev in devices: + sensor_type = dev.osoEnergyType.lower() + if sensor_type in SENSOR_TYPES: + entities.append( + OSOEnergySensor(osoenergy, SENSOR_TYPES[sensor_type], dev) + ) + + async_add_entities(entities, True) + + +class OSOEnergySensor(OSOEnergyEntity[OSOEnergySensorData], SensorEntity): + """OSO Energy Sensor Entity.""" + + entity_description: OSOEnergySensorEntityDescription + + def __init__( + self, + instance: OSOEnergy, + description: OSOEnergySensorEntityDescription, + entity_data: OSOEnergySensorData, + ) -> None: + """Initialize the OSO Energy sensor.""" + super().__init__(instance, entity_data) + + device_id = entity_data.device_id + self._attr_unique_id = f"{device_id}_{description.key}" + self.entity_description = description + + @property + def native_value(self) -> StateType: + """Return the state of the sensor.""" + return self.entity_description.value_fn(self.entity_data) + + async def async_update(self) -> None: + """Update all data for OSO Energy.""" + await self.osoenergy.session.update_data() + self.entity_data = await self.osoenergy.sensor.get_sensor(self.entity_data) diff --git a/homeassistant/components/osoenergy/strings.json b/homeassistant/components/osoenergy/strings.json index a45482bf030..5313f1d6565 100644 --- a/homeassistant/components/osoenergy/strings.json +++ b/homeassistant/components/osoenergy/strings.json @@ -17,13 +17,56 @@ } }, "error": { - "cannot_connect": "[%key:common::config_flow::error::cannot_connect%]", - "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]", - "unknown": "[%key:common::config_flow::error::unknown%]" + "invalid_auth": "[%key:common::config_flow::error::invalid_auth%]" }, "abort": { "already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]" } + }, + "entity": { + "sensor": { + "tapping_capacity": { + "name": "Tapping capacity" + }, + "capacity_mixed_water_40": { + "name": "Capacity mixed water 40°C" + }, + "v40_min": { + "name": "Mixed water at 40°C" + }, + "v40_level_min": { + "name": "Minimum level of mixed water at 40°C" + }, + "v40_level_max": { + "name": "Maximum level of mixed water at 40°C" + }, + "heater_mode": { + "name": "Heater mode", + "state": { + "auto": "Auto", + "extraenergy": "Extra energy", + "ffr": "Fast frequency reserve", + "legionella": "Legionella", + "manual": "Manual", + "off": "Off", + "powersave": "Power save", + "voltage": "Voltage" + } + }, + "optimization_mode": { + "name": "Optimization mode", + "state": { + "advanced": "Advanced", + "gridcompany": "Grid company", + "off": "Off", + "oso": "OSO", + "smartcompany": "Smart company" + } + }, + "profile": { + "name": "Profile local" + } + } } } diff --git a/homeassistant/components/osoenergy/water_heater.py b/homeassistant/components/osoenergy/water_heater.py index eaf54a9f9a4..b7fb2ba16e6 100644 --- a/homeassistant/components/osoenergy/water_heater.py +++ b/homeassistant/components/osoenergy/water_heater.py @@ -2,6 +2,7 @@ from typing import Any +from apyosoenergyapi import OSOEnergy from apyosoenergyapi.helper.const import OSOEnergyWaterHeaterData from homeassistant.components.water_heater import ( @@ -15,7 +16,6 @@ from homeassistant.components.water_heater import ( from homeassistant.config_entries import ConfigEntry from homeassistant.const import UnitOfTemperature from homeassistant.core import HomeAssistant -from homeassistant.helpers.device_registry import DeviceInfo from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import OSOEnergyEntity @@ -34,9 +34,6 @@ CURRENT_OPERATION_MAP: dict[str, Any] = { "extraenergy": STATE_HIGH_DEMAND, }, } -HEATER_MIN_TEMP = 10 -HEATER_MAX_TEMP = 80 -MANUFACTURER = "OSO Energy" async def async_setup_entry( @@ -59,30 +56,29 @@ class OSOEnergyWaterHeater( _attr_supported_features = WaterHeaterEntityFeature.TARGET_TEMPERATURE _attr_temperature_unit = UnitOfTemperature.CELSIUS - @property - def device_info(self) -> DeviceInfo: - """Return device information.""" - return DeviceInfo( - identifiers={(DOMAIN, self.device.device_id)}, - manufacturer=MANUFACTURER, - model=self.device.device_type, - name=self.device.device_name, - ) + def __init__( + self, + instance: OSOEnergy, + entity_data: OSOEnergyWaterHeaterData, + ) -> None: + """Initialize the OSO Energy water heater.""" + super().__init__(instance, entity_data) + self._attr_unique_id = entity_data.device_id @property def available(self) -> bool: """Return if the device is available.""" - return self.device.available + return self.entity_data.available @property def current_operation(self) -> str: """Return current operation.""" - status = self.device.current_operation + status = self.entity_data.current_operation if status == "off": return STATE_OFF - optimization_mode = self.device.optimization_mode.lower() - heater_mode = self.device.heater_mode.lower() + optimization_mode = self.entity_data.optimization_mode.lower() + heater_mode = self.entity_data.heater_mode.lower() if optimization_mode in CURRENT_OPERATION_MAP: return CURRENT_OPERATION_MAP[optimization_mode].get( heater_mode, STATE_ELECTRIC @@ -93,49 +89,51 @@ class OSOEnergyWaterHeater( @property def current_temperature(self) -> float: """Return the current temperature of the heater.""" - return self.device.current_temperature + return self.entity_data.current_temperature @property def target_temperature(self) -> float: """Return the temperature we try to reach.""" - return self.device.target_temperature + return self.entity_data.target_temperature @property def target_temperature_high(self) -> float: """Return the temperature we try to reach.""" - return self.device.target_temperature_high + return self.entity_data.target_temperature_high @property def target_temperature_low(self) -> float: """Return the temperature we try to reach.""" - return self.device.target_temperature_low + return self.entity_data.target_temperature_low @property def min_temp(self) -> float: """Return the minimum temperature.""" - return self.device.min_temperature + return self.entity_data.min_temperature @property def max_temp(self) -> float: """Return the maximum temperature.""" - return self.device.max_temperature + return self.entity_data.max_temperature async def async_turn_on(self, **kwargs) -> None: """Turn on hotwater.""" - await self.osoenergy.hotwater.turn_on(self.device, True) + await self.osoenergy.hotwater.turn_on(self.entity_data, True) async def async_turn_off(self, **kwargs) -> None: """Turn off hotwater.""" - await self.osoenergy.hotwater.turn_off(self.device, True) + await self.osoenergy.hotwater.turn_off(self.entity_data, True) async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" target_temperature = int(kwargs.get("temperature", self.target_temperature)) profile = [target_temperature] * 24 - await self.osoenergy.hotwater.set_profile(self.device, profile) + await self.osoenergy.hotwater.set_profile(self.entity_data, profile) async def async_update(self) -> None: """Update all Node data from Hive.""" await self.osoenergy.session.update_data() - self.device = await self.osoenergy.hotwater.get_water_heater(self.device) + self.entity_data = await self.osoenergy.hotwater.get_water_heater( + self.entity_data + )