diff --git a/.coveragerc b/.coveragerc index d884e6e8c13..dd03c3782c0 100644 --- a/.coveragerc +++ b/.coveragerc @@ -724,8 +724,10 @@ omit = homeassistant/components/modem_callerid/button.py homeassistant/components/modem_callerid/sensor.py homeassistant/components/moehlenhoff_alpha2/__init__.py + homeassistant/components/moehlenhoff_alpha2/binary_sensor.py homeassistant/components/moehlenhoff_alpha2/climate.py homeassistant/components/moehlenhoff_alpha2/const.py + homeassistant/components/moehlenhoff_alpha2/sensor.py homeassistant/components/motion_blinds/__init__.py homeassistant/components/motion_blinds/const.py homeassistant/components/motion_blinds/cover.py diff --git a/homeassistant/components/moehlenhoff_alpha2/__init__.py b/homeassistant/components/moehlenhoff_alpha2/__init__.py index 62e18917dc6..93ddaa781ab 100644 --- a/homeassistant/components/moehlenhoff_alpha2/__init__.py +++ b/homeassistant/components/moehlenhoff_alpha2/__init__.py @@ -17,7 +17,7 @@ from .const import DOMAIN _LOGGER = logging.getLogger(__name__) -PLATFORMS = [Platform.CLIMATE] +PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.BINARY_SENSOR] UPDATE_INTERVAL = timedelta(seconds=60) @@ -65,10 +65,16 @@ class Alpha2BaseCoordinator(DataUpdateCoordinator[dict[str, dict]]): update_interval=UPDATE_INTERVAL, ) - async def _async_update_data(self) -> dict[str, dict]: + async def _async_update_data(self) -> dict[str, dict[str, dict]]: """Fetch the latest data from the source.""" await self.base.update_data() - return {ha["ID"]: ha for ha in self.base.heat_areas if ha.get("ID")} + return { + "heat_areas": {ha["ID"]: ha for ha in self.base.heat_areas if ha.get("ID")}, + "heat_controls": { + hc["ID"]: hc for hc in self.base.heat_controls if hc.get("ID") + }, + "io_devices": {io["ID"]: io for io in self.base.io_devices if io.get("ID")}, + } def get_cooling(self) -> bool: """Return if cooling mode is enabled.""" diff --git a/homeassistant/components/moehlenhoff_alpha2/binary_sensor.py b/homeassistant/components/moehlenhoff_alpha2/binary_sensor.py new file mode 100644 index 00000000000..ddd92c3a70b --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/binary_sensor.py @@ -0,0 +1,56 @@ +"""Support for Alpha2 IO device battery sensors.""" + +from homeassistant.components.binary_sensor import ( + BinarySensorDeviceClass, + BinarySensorEntity, +) +from homeassistant.config_entries import ConfigEntry +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity import EntityCategory +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from . import Alpha2BaseCoordinator +from .const import DOMAIN + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Add Alpha2 sensor entities from a config_entry.""" + + coordinator: Alpha2BaseCoordinator = hass.data[DOMAIN][config_entry.entry_id] + + async_add_entities( + Alpha2IODeviceBatterySensor(coordinator, io_device_id) + for io_device_id, io_device in coordinator.data["io_devices"].items() + if io_device["_HEATAREA_ID"] + ) + + +class Alpha2IODeviceBatterySensor( + CoordinatorEntity[Alpha2BaseCoordinator], BinarySensorEntity +): + """Alpha2 IO device battery binary sensor.""" + + _attr_device_class = BinarySensorDeviceClass.BATTERY + _attr_entity_category = EntityCategory.DIAGNOSTIC + + def __init__(self, coordinator: Alpha2BaseCoordinator, io_device_id: str) -> None: + """Initialize Alpha2IODeviceBatterySensor.""" + super().__init__(coordinator) + self.io_device_id = io_device_id + self._attr_unique_id = f"{io_device_id}:battery" + io_device = self.coordinator.data["io_devices"][io_device_id] + heat_area = self.coordinator.data["heat_areas"][io_device["_HEATAREA_ID"]] + self._attr_name = ( + f"{heat_area['HEATAREA_NAME']} IO device {io_device['NR']} battery" + ) + + @property + def is_on(self): + """Return the state of the sensor.""" + # 0=empty, 1=weak, 2=good + return self.coordinator.data["io_devices"][self.io_device_id]["BATTERY"] < 2 diff --git a/homeassistant/components/moehlenhoff_alpha2/climate.py b/homeassistant/components/moehlenhoff_alpha2/climate.py index 225735293ca..e14f4801661 100644 --- a/homeassistant/components/moehlenhoff_alpha2/climate.py +++ b/homeassistant/components/moehlenhoff_alpha2/climate.py @@ -29,7 +29,8 @@ async def async_setup_entry( coordinator: Alpha2BaseCoordinator = hass.data[DOMAIN][config_entry.entry_id] async_add_entities( - Alpha2Climate(coordinator, heat_area_id) for heat_area_id in coordinator.data + Alpha2Climate(coordinator, heat_area_id) + for heat_area_id in coordinator.data["heat_areas"] ) @@ -51,26 +52,34 @@ class Alpha2Climate(CoordinatorEntity[Alpha2BaseCoordinator], ClimateEntity): super().__init__(coordinator) self.heat_area_id = heat_area_id self._attr_unique_id = heat_area_id - - @property - def name(self) -> str: - """Return the name of the climate device.""" - return self.coordinator.data[self.heat_area_id]["HEATAREA_NAME"] + self._attr_name = self.coordinator.data["heat_areas"][heat_area_id][ + "HEATAREA_NAME" + ] @property def min_temp(self) -> float: """Return the minimum temperature.""" - return float(self.coordinator.data[self.heat_area_id].get("T_TARGET_MIN", 0.0)) + return float( + self.coordinator.data["heat_areas"][self.heat_area_id].get( + "T_TARGET_MIN", 0.0 + ) + ) @property def max_temp(self) -> float: """Return the maximum temperature.""" - return float(self.coordinator.data[self.heat_area_id].get("T_TARGET_MAX", 30.0)) + return float( + self.coordinator.data["heat_areas"][self.heat_area_id].get( + "T_TARGET_MAX", 30.0 + ) + ) @property def current_temperature(self) -> float: """Return the current temperature.""" - return float(self.coordinator.data[self.heat_area_id].get("T_ACTUAL", 0.0)) + return float( + self.coordinator.data["heat_areas"][self.heat_area_id].get("T_ACTUAL", 0.0) + ) @property def hvac_mode(self) -> HVACMode: @@ -86,7 +95,9 @@ class Alpha2Climate(CoordinatorEntity[Alpha2BaseCoordinator], ClimateEntity): @property def hvac_action(self) -> HVACAction: """Return the current running hvac operation.""" - if not self.coordinator.data[self.heat_area_id]["_HEATCTRL_STATE"]: + if not self.coordinator.data["heat_areas"][self.heat_area_id][ + "_HEATCTRL_STATE" + ]: return HVACAction.IDLE if self.coordinator.get_cooling(): return HVACAction.COOLING @@ -95,7 +106,9 @@ class Alpha2Climate(CoordinatorEntity[Alpha2BaseCoordinator], ClimateEntity): @property def target_temperature(self) -> float: """Return the temperature we try to reach.""" - return float(self.coordinator.data[self.heat_area_id].get("T_TARGET", 0.0)) + return float( + self.coordinator.data["heat_areas"][self.heat_area_id].get("T_TARGET", 0.0) + ) async def async_set_temperature(self, **kwargs) -> None: """Set new target temperatures.""" @@ -109,9 +122,9 @@ class Alpha2Climate(CoordinatorEntity[Alpha2BaseCoordinator], ClimateEntity): @property def preset_mode(self) -> str: """Return the current preset mode.""" - if self.coordinator.data[self.heat_area_id]["HEATAREA_MODE"] == 1: + if self.coordinator.data["heat_areas"][self.heat_area_id]["HEATAREA_MODE"] == 1: return PRESET_DAY - if self.coordinator.data[self.heat_area_id]["HEATAREA_MODE"] == 2: + if self.coordinator.data["heat_areas"][self.heat_area_id]["HEATAREA_MODE"] == 2: return PRESET_NIGHT return PRESET_AUTO diff --git a/homeassistant/components/moehlenhoff_alpha2/manifest.json b/homeassistant/components/moehlenhoff_alpha2/manifest.json index db362163e72..12e7a927906 100644 --- a/homeassistant/components/moehlenhoff_alpha2/manifest.json +++ b/homeassistant/components/moehlenhoff_alpha2/manifest.json @@ -3,7 +3,7 @@ "name": "Möhlenhoff Alpha 2", "config_flow": true, "documentation": "https://www.home-assistant.io/integrations/moehlenhoff_alpha2", - "requirements": ["moehlenhoff-alpha2==1.1.2"], + "requirements": ["moehlenhoff-alpha2==1.2.1"], "iot_class": "local_push", "codeowners": ["@j-a-n"] } diff --git a/homeassistant/components/moehlenhoff_alpha2/sensor.py b/homeassistant/components/moehlenhoff_alpha2/sensor.py new file mode 100644 index 00000000000..d26786e1923 --- /dev/null +++ b/homeassistant/components/moehlenhoff_alpha2/sensor.py @@ -0,0 +1,56 @@ +"""Support for Alpha2 heat control valve opening sensors.""" + +from homeassistant.components.sensor import SensorEntity +from homeassistant.config_entries import ConfigEntry +from homeassistant.const import PERCENTAGE +from homeassistant.core import HomeAssistant +from homeassistant.helpers.entity_platform import AddEntitiesCallback +from homeassistant.helpers.update_coordinator import CoordinatorEntity + +from . import Alpha2BaseCoordinator +from .const import DOMAIN + + +async def async_setup_entry( + hass: HomeAssistant, + config_entry: ConfigEntry, + async_add_entities: AddEntitiesCallback, +) -> None: + """Add Alpha2 sensor entities from a config_entry.""" + + coordinator: Alpha2BaseCoordinator = hass.data[DOMAIN][config_entry.entry_id] + + # HEATCTRL attribute ACTOR_PERCENT is not available in older firmware versions + async_add_entities( + Alpha2HeatControlValveOpeningSensor(coordinator, heat_control_id) + for heat_control_id, heat_control in coordinator.data["heat_controls"].items() + if heat_control["INUSE"] + and heat_control["_HEATAREA_ID"] + and heat_control.get("ACTOR_PERCENT") is not None + ) + + +class Alpha2HeatControlValveOpeningSensor( + CoordinatorEntity[Alpha2BaseCoordinator], SensorEntity +): + """Alpha2 heat control valve opening sensor.""" + + _attr_native_unit_of_measurement = PERCENTAGE + + def __init__( + self, coordinator: Alpha2BaseCoordinator, heat_control_id: str + ) -> None: + """Initialize Alpha2HeatControlValveOpeningSensor.""" + super().__init__(coordinator) + self.heat_control_id = heat_control_id + self._attr_unique_id = f"{heat_control_id}:valve_opening" + heat_control = self.coordinator.data["heat_controls"][heat_control_id] + heat_area = self.coordinator.data["heat_areas"][heat_control["_HEATAREA_ID"]] + self._attr_name = f"{heat_area['HEATAREA_NAME']} heat control {heat_control['NR']} valve opening" + + @property + def native_value(self) -> int: + """Return the current valve opening percentage.""" + return self.coordinator.data["heat_controls"][self.heat_control_id][ + "ACTOR_PERCENT" + ] diff --git a/requirements_all.txt b/requirements_all.txt index b5c731367c5..9bfc8b3671e 100644 --- a/requirements_all.txt +++ b/requirements_all.txt @@ -1035,7 +1035,7 @@ minio==5.0.10 mitemp_bt==0.0.5 # homeassistant.components.moehlenhoff_alpha2 -moehlenhoff-alpha2==1.1.2 +moehlenhoff-alpha2==1.2.1 # homeassistant.components.motion_blinds motionblinds==0.6.7 diff --git a/requirements_test_all.txt b/requirements_test_all.txt index f862c719f50..1e6700d3add 100644 --- a/requirements_test_all.txt +++ b/requirements_test_all.txt @@ -712,7 +712,7 @@ millheater==0.9.0 minio==5.0.10 # homeassistant.components.moehlenhoff_alpha2 -moehlenhoff-alpha2==1.1.2 +moehlenhoff-alpha2==1.2.1 # homeassistant.components.motion_blinds motionblinds==0.6.7