diff --git a/README.md b/README.md index 9792b19..0aae6f8 100644 --- a/README.md +++ b/README.md @@ -86,9 +86,16 @@ Unable to fetch data from multimatic, API says:`xxx Service Unavailable, status: ### [1.5.0b4](https://github.com/thomasgermain/vaillant-component/releases/tag/1.5.0b4) - allow multiple integration fixes -### [1.5.0b5](https://github.com/thomasgermain/vaillant-component/releases/tag/1.5.0b4) +### [1.5.0b5](https://github.com/thomasgermain/vaillant-component/releases/tag/1.5.0b5) - Fan for HA > 2021.3.x +### [1.5.0b6](https://github.com/thomasgermain/vaillant-component/releases/tag/1.5.0b6) +- Many technical improvements +- Remove speed from fan entity +- add `version` in the manifest +- Bugfix for room climate to detect if the room is heating or not + + ## Provided entities - 1 water_heater entity, if any water heater: `water_heater.`, basically `water_heater.control_dhw` - 1 climate entity per zone (expect if the zone is controlled by room) `climate.` diff --git a/multimatic/binary_sensor.py b/multimatic/binary_sensor.py index d652b72..e300569 100644 --- a/multimatic/binary_sensor.py +++ b/multimatic/binary_sensor.py @@ -2,14 +2,7 @@ import logging -from pymultimatic.model import ( - Device, - OperatingModes, - QuickModes, - Room, - SettingModes, - SystemInfo, -) +from pymultimatic.model import Device, OperatingModes, QuickModes, Room, SettingModes from homeassistant.components.binary_sensor import ( DEVICE_CLASS_BATTERY, @@ -35,18 +28,18 @@ async def async_setup_entry(hass, entry, async_add_entities): """Set up the multimatic binary sensor platform.""" sensors = [] hub: ApiHub = hass.data[MULTIMATIC][entry.unique_id][HUB] - if hub.system: - if hub.system.dhw and hub.system.dhw.circulation: + if hub.data: + if hub.data.dhw and hub.data.dhw.circulation: sensors.append(CirculationSensor(hub)) - if hub.system.boiler_status: + if hub.data.boiler_status: sensors.append(BoilerStatus(hub)) - if hub.system.info: - sensors.append(BoxOnline(hub, hub.system.info)) - sensors.append(BoxUpdate(hub, hub.system.info)) + if hub.data.info: + sensors.append(BoxOnline(hub)) + sensors.append(BoxUpdate(hub)) - for room in hub.system.rooms: + for room in hub.data.rooms: sensors.append(RoomWindow(hub, room)) for device in room.devices: if device.device_type == "VALVE": @@ -55,18 +48,13 @@ async def async_setup_entry(hass, entry, async_add_entities): sensors.append(RoomDeviceBattery(hub, device, room)) sensors.append(RoomDeviceConnectivity(hub, device, room)) - entity = HolidayModeSensor(hub) - sensors.append(entity) - - entity = QuickModeSensor(hub) - sensors.append(entity) - - entity = MultimaticErrors(hub) - sensors.append(entity) + sensors.extend( + [HolidayModeSensor(hub), QuickModeSensor(hub), MultimaticErrors(hub)] + ) _LOGGER.info("Adding %s binary sensor entities", len(sensors)) - async_add_entities(sensors, True) + async_add_entities(sensors) return True @@ -75,36 +63,39 @@ class CirculationSensor(MultimaticEntity, BinarySensorEntity): def __init__(self, hub: ApiHub): """Initialize entity.""" - self._circulation = hub.system.dhw.circulation super().__init__( hub, DOMAIN, - self._circulation.id, - self._circulation.name, + "dhw circulation", + "Circulation", DEVICE_CLASS_POWER, False, ) - self._active_mode = None @property def is_on(self): """Return true if the binary sensor is on.""" return ( - self._active_mode.current == OperatingModes.ON - or self._active_mode.sub == SettingModes.ON - or self._active_mode.current == QuickModes.HOTWATER_BOOST + self.active_mode.current == OperatingModes.ON + or self.active_mode.sub == SettingModes.ON + or self.active_mode.current == QuickModes.HOTWATER_BOOST ) @property def available(self): """Return True if entity is available.""" - return self._circulation is not None + return super().available and self.component is not None - async def async_custom_update(self): - """Update specific for multimatic.""" - self._circulation = self.coordinator.system.dhw.circulation - self._active_mode = self.coordinator.system.get_active_mode_circulation() + @property + def active_mode(self): + """Return the active mode of the circulation.""" + return self.coordinator.data.get_active_mode_circulation() + + @property + def component(self): + """Return the circulation.""" + return self.coordinator.data.dhw.circulation class RoomWindow(MultimaticEntity, BinarySensorEntity): @@ -118,24 +109,12 @@ def __init__(self, hub: ApiHub, room: Room): @property def is_on(self): """Return true if the binary sensor is on.""" - return self._room.window_open + return self.coordinator.find_component(self._room).window_open @property def available(self): """Return True if entity is available.""" - return self._room is not None - - async def async_custom_update(self): - """Update specific for multimatic.""" - new_room: Room = self.coordinator.find_component(self._room) - - if new_room: - _LOGGER.debug( - "New / old state: %s / %s", new_room.child_lock, self._room.child_lock - ) - else: - _LOGGER.debug("Room %s doesn't exist anymore", self._room.id) - self._room = new_room + return super().available and self._room is not None class RoomDeviceEntity(MultimaticEntity, BinarySensorEntity): @@ -168,37 +147,17 @@ def device_state_attributes(self): "connected": not self.device.radio_out_of_reach, } - # pylint: disable=no-self-use - def _find_device(self, new_room: Room, sgtin: str): + def find_device(self): """Find a device in a room.""" - if new_room: - for device in new_room.devices: - if device.sgtin == sgtin: + if self.room: + for device in self.coordinator.find_component(self.room).devices: + if device.sgtin == self.device.sgtin: return device @property def available(self): """Return True if entity is available.""" - return self.device is not None - - async def async_custom_update(self): - """Update specific for multimatic.""" - new_room: Room = self.coordinator.find_component(self.room) - new_device: Device = self._find_device(new_room, self.device.sgtin) - - if new_room: - if new_device: - _LOGGER.debug( - "New / old state: %s / %s", - new_device.battery_low, - self.device.battery_low, - ) - else: - _LOGGER.debug("Device %s doesn't exist anymore", self.device.sgtin) - else: - _LOGGER.debug("Room %s doesn't exist anymore", self.room.id) - self.room = new_room - self.device = new_device + return super().available and self.find_device() is not None class RoomDeviceChildLock(RoomDeviceEntity): @@ -215,7 +174,7 @@ def __init__(self, hub: ApiHub, device: Device, room: Room): @property def is_on(self): """According to the doc, true means unlock, false lock.""" - return not self.room.child_lock + return not self.coordinator.find_component(self.room).child_lock class RoomDeviceBattery(RoomDeviceEntity): @@ -228,7 +187,7 @@ def __init__(self, hub: ApiHub, device: Device, room: Room): @property def is_on(self): """According to the doc, true means normal, false low.""" - return self.device.battery_low + return self.find_device().battery_low class RoomDeviceConnectivity(RoomDeviceEntity): @@ -241,16 +200,15 @@ def __init__(self, hub: ApiHub, device: Device, room: Room): @property def is_on(self): """According to the doc, true means connected, false disconnected.""" - return not self.device.radio_out_of_reach + return not self.find_device().radio_out_of_reach class VRBoxEntity(MultimaticEntity, BinarySensorEntity): """multimatic gateway device (ex: VR920).""" - def __init__(self, hub: ApiHub, info: SystemInfo, device_class, name, comp_id): + def __init__(self, hub: ApiHub, device_class, name, comp_id): """Initialize entity.""" MultimaticEntity.__init__(self, hub, DOMAIN, comp_id, name, device_class, False) - self.system_info = info @property def device_info(self): @@ -276,31 +234,21 @@ def device_state_attributes(self): @property def available(self): """Return True if entity is available.""" - return self.system_info is not None - - async def async_custom_update(self): - """Update specific for multimatic.""" - system_info: SystemInfo = self.coordinator.system.info + return super().available and self.system_info is not None - if system_info: - _LOGGER.debug( - "Found new system status " "online? %s, up to date? %s", - system_info.is_online, - system_info.is_up_to_date, - ) - else: - _LOGGER.debug("System status doesn't exist anymore") - self.system_info = system_info + @property + def system_info(self): + """Return the system information.""" + return self.coordinator.data.info class BoxUpdate(VRBoxEntity): """Update binary sensor.""" - def __init__(self, hub: ApiHub, info: SystemInfo): + def __init__(self, hub: ApiHub): """Init.""" super().__init__( hub, - info, DEVICE_CLASS_POWER, "Multimatic system update", "multimatic_system_update", @@ -309,17 +257,16 @@ def __init__(self, hub: ApiHub, info: SystemInfo): @property def is_on(self): """Return true if the binary sensor is on.""" - return not self.system_info.is_up_to_date + return not self.coordinator.data.info.is_up_to_date class BoxOnline(VRBoxEntity): """Check if box is online.""" - def __init__(self, hub: ApiHub, info: SystemInfo): + def __init__(self, hub: ApiHub): """Init.""" super().__init__( hub, - info, DEVICE_CLASS_CONNECTIVITY, "multimatic_system_online", "Multimatic system Online", @@ -328,7 +275,7 @@ def __init__(self, hub: ApiHub, info: SystemInfo): @property def is_on(self): """Return true if the binary sensor is on.""" - return self.system_info.is_online + return self.coordinator.data.info.is_online class BoilerStatus(MultimaticEntity, BinarySensorEntity): @@ -336,65 +283,68 @@ class BoilerStatus(MultimaticEntity, BinarySensorEntity): def __init__(self, hub: ApiHub): """Initialize entity.""" - self._boiler_status = hub.system.boiler_status MultimaticEntity.__init__( self, hub, DOMAIN, - self._boiler_status.device_name, - self._boiler_status.device_name, + hub.data.boiler_status.device_name, + hub.data.boiler_status.device_name, DEVICE_CLASS_PROBLEM, False, ) - self._boiler_id = slugify(self._boiler_status.device_name) + self._boiler_id = slugify(hub.data.boiler_status.device_name) @property def is_on(self): """Return true if the binary sensor is on.""" - return self._boiler_status is not None and self._boiler_status.is_error + return self.boiler_status is not None and self.boiler_status.is_error @property def state_attributes(self): """Return the state attributes.""" - if self._boiler_status is not None: + if self.boiler_status is not None: return { - "status_code": self._boiler_status.status_code, - "title": self._boiler_status.title, - "timestamp": self._boiler_status.timestamp, + "status_code": self.boiler_status.status_code, + "title": self.boiler_status.title, + "timestamp": self.boiler_status.timestamp, } return None - async def async_custom_update(self): - """Update specific for multimatic.""" - _LOGGER.debug("new boiler status is %s", self.coordinator.system.boiler_status) - self._boiler_status = self.coordinator.system.boiler_status - @property def device_info(self): """Return device specific attributes.""" - if self._boiler_status is not None: + if self.boiler_status is not None: return { "identifiers": {(MULTIMATIC, self._boiler_id, self.coordinator.serial)}, - "name": self._boiler_status.device_name, + "name": self.boiler_status.device_name, "manufacturer": "Vaillant", - "model": self._boiler_status.device_name, + "model": self.boiler_status.device_name, } return None @property def device_state_attributes(self): """Return the state attributes.""" - if self._boiler_status is not None: - return {"device_id": self._boiler_id, "error": self._boiler_status.is_error} + if self.boiler_status is not None: + return {"device_id": self._boiler_id, "error": self.boiler_status.is_error} return None + @property + def available(self) -> bool: + """Return if entity is available.""" + return super().available and self.boiler_status is not None + + @property + def boiler_status(self): + """Return the boiler status.""" + return self.coordinator.data.boiler_status + class MultimaticErrors(MultimaticEntity, BinarySensorEntity): """Check if there is any error message from system.""" def __init__(self, hub: ApiHub): """Init.""" - self._errors = hub.system.errors super().__init__( hub, DOMAIN, @@ -407,17 +357,13 @@ def __init__(self, hub: ApiHub): @property def is_on(self): """Return true if the binary sensor is on.""" - return len(self._errors) > 0 - - async def async_custom_update(self): - """Update specific for multimatic.""" - self._errors = self.coordinator.system.errors + return len(self.coordinator.data.errors) > 0 @property def state_attributes(self): """Return the state attributes.""" state_attributes = {} - for error in self._errors: + for error in self.coordinator.data.errors: state_attributes.update( { error.status_code: { @@ -431,6 +377,11 @@ def state_attributes(self): ) return state_attributes + @property + def available(self) -> bool: + """Return if entity is available.""" + return super().available and self.coordinator.data.errors is not None + class HolidayModeSensor(MultimaticEntity, BinarySensorEntity): """Binary sensor for holiday mode.""" @@ -445,28 +396,25 @@ def __init__(self, hub: ApiHub): DEVICE_CLASS_POWER, False, ) - self._holiday = hub.system.holiday @property def is_on(self): """Return true if the binary sensor is on.""" - return self._holiday is not None and self._holiday.is_applied + return ( + self.coordinator.data.holiday and self.coordinator.data.holiday.is_applied + ) @property def state_attributes(self): """Return the state attributes.""" if self.is_on: return { - "start_date": self._holiday.start_date.isoformat(), - "end_date": self._holiday.end_date.isoformat(), - "temperature": self._holiday.target, + "start_date": self.coordinator.data.holiday.start_date.isoformat(), + "end_date": self.coordinator.data.holiday.end_date.isoformat(), + "temperature": self.coordinator.data.holiday.target, } return {} - async def async_custom_update(self): - """Update specific for multimatic.""" - self._holiday = self.coordinator.system.holiday - @property def listening(self): """Return whether this entity is listening for system changes or not.""" @@ -486,24 +434,19 @@ def __init__(self, hub: ApiHub): DEVICE_CLASS_POWER, False, ) - self._quick_mode = hub.system.quick_mode @property def is_on(self): """Return true if the binary sensor is on.""" - return self._quick_mode is not None + return self.coordinator.data.quick_mode is not None @property def state_attributes(self): """Return the state attributes.""" if self.is_on: - return {"quick_mode": self._quick_mode.name} + return {"quick_mode": self.coordinator.data.quick_mode.name} return {} - async def async_custom_update(self): - """Update specific for multimatic.""" - self._quick_mode = self.coordinator.system.quick_mode - @property def listening(self): """Return whether this entity is listening for system changes or not.""" diff --git a/multimatic/climate.py b/multimatic/climate.py index 34915b0..6371d09 100644 --- a/multimatic/climate.py +++ b/multimatic/climate.py @@ -68,22 +68,22 @@ async def async_setup_entry(hass, entry, async_add_entities): climates = [] hub = hass.data[MULTIMATIC][entry.unique_id][HUB] - if hub.system: - if hub.system.zones: - for zone in hub.system.zones: + if hub.data: + if hub.data.zones: + for zone in hub.data.zones: if not zone.rbr and zone.enabled: entity = ZoneClimate(hub, zone) climates.append(entity) - if hub.system.rooms: - rbr_zone = [zone for zone in hub.system.zones if zone.rbr][0] - for room in hub.system.rooms: + if hub.data.rooms: + rbr_zone = [zone for zone in hub.data.zones if zone.rbr][0] + for room in hub.data.rooms: entity = RoomClimate(hub, room, rbr_zone) climates.append(entity) _LOGGER.info("Adding %s climate entities", len(climates)) - async_add_entities(climates, True) + async_add_entities(climates) platform = entity_platform.current_platform.get() platform.async_register_entity_service( @@ -106,9 +106,7 @@ class MultimaticClimate(MultimaticEntity, ClimateEntity, abc.ABC): def __init__(self, hub: ApiHub, comp_name, comp_id, component: Component): """Initialize entity.""" super().__init__(hub, DOMAIN, comp_id, comp_name) - self._system = None - self.component = None - self._refresh(hub.system, component) + self._component = component async def set_quick_veto(self, **kwargs): """Set quick veto, called by service.""" @@ -125,6 +123,11 @@ async def remove_quick_veto(self, **kwargs): def active_mode(self) -> ActiveMode: """Get active mode of the climate.""" + @property + def component(self): + """Return the room or the zone.""" + return self.coordinator.find_component(self._component) + @property def listening(self): """Return whether this entity is listening for system changes or not.""" @@ -133,7 +136,7 @@ def listening(self): @property def available(self): """Return True if entity is available.""" - return self.component is not None + return super().available and self.component is not None @property def temperature_unit(self): @@ -143,7 +146,6 @@ def temperature_unit(self): @property def target_temperature(self): """Return the temperature we try to reach.""" - _LOGGER.debug("Target temp is %s", self.active_mode.target) return self.active_mode.target @property @@ -201,17 +203,6 @@ def target_temperature_low(self) -> Optional[float]: """Return the lowbound target temperature we try to reach.""" return None - async def async_custom_update(self): - """Update specific for multimatic.""" - self._refresh( - self.coordinator.system, self.coordinator.find_component(self.component) - ) - - def _refresh(self, system, component): - """Refresh the entity.""" - self._system = system - self.component = component - class RoomClimate(MultimaticClimate): """Climate for a room.""" @@ -246,11 +237,10 @@ def __init__(self, hub: ApiHub, room: Room, zone: Zone): @property def hvac_mode(self) -> str: """Get the hvac mode based on multimatic mode.""" - active_mode = self.active_mode - hvac_mode = RoomClimate._MULTIMATIC_TO_HA[active_mode.current][0] + hvac_mode = RoomClimate._MULTIMATIC_TO_HA[self.active_mode.current][0] if not hvac_mode: if ( - active_mode.current + self.active_mode.current in (OperatingModes.MANUAL, OperatingModes.QUICK_VETO) and self.hvac_action == CURRENT_HVAC_HEAT ): @@ -280,7 +270,12 @@ def max_temp(self): @property def active_mode(self) -> ActiveMode: """Get active mode of the climate.""" - return self._system.get_active_mode_room(self.component) + return self.coordinator.data.get_active_mode_room(self.component) + + @property + def zone(self): + """Return the zone the current room belongs.""" + return self.coordinator.find_component(self._zone) async def async_set_temperature(self, **kwargs): """Set new target temperature.""" @@ -330,7 +325,7 @@ def hvac_action(self) -> Optional[str]: Need to be one of CURRENT_HVAC_*. """ if ( - self._zone.active_function == ActiveFunction.HEATING + self.zone.active_function == ActiveFunction.HEATING and self.component.temperature < self.active_mode.target ): return _FUNCTION_TO_HVAC_ACTION[ActiveFunction.HEATING] @@ -385,7 +380,7 @@ def __init__(self, hub: ApiHub, zone: Zone): self._supported_presets.remove(PRESET_COOLING_ON) self._supported_presets.remove(PRESET_COOLING_FOR_X_DAYS) - if not hub.system.ventilation: + if not hub.data.ventilation: self._supported_hvac.remove(HVAC_MODE_FAN_ONLY) @property @@ -440,7 +435,7 @@ def target_temperature(self): @property def active_mode(self) -> ActiveMode: """Get active mode of the climate.""" - return self._system.get_active_mode_zone(self.component) + return self.coordinator.data.get_active_mode_zone(self.component) async def async_set_temperature(self, **kwargs): """Set new target temperature.""" diff --git a/multimatic/entities.py b/multimatic/entities.py index 4499154..490c8f0 100644 --- a/multimatic/entities.py +++ b/multimatic/entities.py @@ -1,5 +1,5 @@ """Common entities.""" -from abc import ABC, abstractmethod +from abc import ABC import logging from typing import Optional @@ -15,6 +15,8 @@ class MultimaticEntity(CoordinatorEntity, ABC): """Define base class for multimatic entities.""" + coordinator: ApiHub + def __init__( self, hub: ApiHub, domain, device_id, name, dev_class=None, class_id=True ): @@ -51,26 +53,11 @@ def unique_id(self) -> Optional[str]: """Return a unique ID.""" return self._unique_id - @property - def should_poll(self) -> bool: - """Return True if entity has to be polled for state.""" - return True - - async def async_update(self): - """Update the entity.""" - _LOGGER.debug("Time to update %s", self.entity_id) - await super().async_update() - await self.async_custom_update() - @property def device_class(self): """Return the class of this device, from component DEVICE_CLASSES.""" return self._device_class - @abstractmethod - async def async_custom_update(self): - """Update specific for multimatic.""" - @property def listening(self): """Return whether this entity is listening for system changes or not. diff --git a/multimatic/fan.py b/multimatic/fan.py index 863a379..8130e53 100644 --- a/multimatic/fan.py +++ b/multimatic/fan.py @@ -5,12 +5,7 @@ from pymultimatic.model import OperatingModes, QuickModes -from homeassistant.components.fan import ( - DOMAIN, - SUPPORT_PRESET_MODE, - SUPPORT_SET_SPEED, - FanEntity, -) +from homeassistant.components.fan import DOMAIN, SUPPORT_PRESET_MODE, FanEntity from . import ApiHub from .const import DOMAIN as MULTIMATIC, HUB @@ -24,9 +19,9 @@ async def async_setup_entry(hass, entry, async_add_entities): hub: ApiHub = hass.data[MULTIMATIC][entry.unique_id][HUB] - if hub.system.ventilation: + if hub.data.ventilation: _LOGGER.debug("Adding fan entity") - async_add_entities([MultimaticFan(hub)], True) + async_add_entities([MultimaticFan(hub)]) class MultimaticFan(MultimaticEntity, FanEntity): @@ -35,12 +30,11 @@ class MultimaticFan(MultimaticEntity, FanEntity): def __init__(self, hub: ApiHub): """Initialize entity.""" - self.component = hub.system.ventilation super().__init__( hub, DOMAIN, - self.component.id, - self.component.name, + hub.data.ventilation.id, + hub.data.ventilation.name, None, False, ) @@ -51,9 +45,10 @@ def __init__(self, hub: ApiHub): OperatingModes.NIGHT.name, ] - async def async_custom_update(self): - """Update specific for multimatic.""" - self.component = self.coordinator.system.ventilation + @property + def component(self): + """Return the ventilation.""" + return self.coordinator.data.ventilation async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new preset mode.""" @@ -61,10 +56,6 @@ async def async_set_preset_mode(self, preset_mode: str) -> None: self, OperatingModes.get(preset_mode.upper()) ) - async def async_set_speed(self, speed: str): - """Set the speed of the fan.""" - return await self.async_set_preset_mode(speed) - async def async_turn_on( self, speed: Optional[str] = None, @@ -89,22 +80,19 @@ async def async_turn_off(self, **kwargs: Any): def is_on(self): """Return true if the entity is on.""" return ( - self.coordinator.system.get_active_mode_ventilation().current + self.coordinator.data.get_active_mode_ventilation().current != OperatingModes.NIGHT ) @property def supported_features(self) -> int: """Flag supported features.""" - return SUPPORT_SET_SPEED | SUPPORT_PRESET_MODE + return SUPPORT_PRESET_MODE @property def preset_mode(self) -> Optional[str]: - """Return the current preset mode, e.g., auto, smart, interval, favorite. - - Requires SUPPORT_SET_SPEED. - """ - return self.coordinator.system.get_active_mode_ventilation().current.name + """Return the current preset mode, e.g., auto, smart, interval, favorite.""" + return self.coordinator.data.get_active_mode_ventilation().current.name @property def preset_modes(self) -> Optional[List[str]]: @@ -113,23 +101,13 @@ def preset_modes(self) -> Optional[List[str]]: Requires SUPPORT_SET_SPEED. """ if ( - self.coordinator.system.get_active_mode_ventilation().current + self.coordinator.data.get_active_mode_ventilation().current == QuickModes.VENTILATION_BOOST ): return self._preset_modes + [QuickModes.VENTILATION_BOOST.name] return self._preset_modes @property - def speed_list(self) -> list: - """Get the list of available speeds.""" - return self.preset_modes - - @property - def speed(self) -> Optional[str]: - """Return the current speed.""" - return self.preset_mode - - @property - def percentage(self) -> Optional[int]: - """Return the current speed as a percentage.""" - return None + def available(self): + """Return True if entity is available.""" + return super().available and self.component is not None diff --git a/multimatic/hub.py b/multimatic/hub.py index e9ce825..3298069 100644 --- a/multimatic/hub.py +++ b/multimatic/hub.py @@ -47,7 +47,7 @@ async def check_authentication(hass, username, password, serial): ).login(True) -class ApiHub(DataUpdateCoordinator): +class ApiHub(DataUpdateCoordinator[System]): """multimatic entry point for home-assistant.""" def __init__(self, hass, entry: ConfigEntry): @@ -71,8 +71,6 @@ def __init__(self, hass, entry: ConfigEntry): ) self.serial: str = serial - self.system: System = None - self._hass = hass async def authenticate(self): """Try to authenticate to the API.""" @@ -102,8 +100,9 @@ async def _fetch_data(self): """Fetch multimatic system.""" try: - self.system = await self._manager.get_system() + system = await self._manager.get_system() _LOGGER.debug("fetch_data successful") + return system except ApiError as err: auth_ok = False try: @@ -134,22 +133,19 @@ def find_component(self, comp): """Find a component in the system with the given id, no IO is done.""" if isinstance(comp, Zone): - for zone in self.system.zones: + for zone in self.data.zones: if zone.id == comp.id: return zone if isinstance(comp, Room): - for room in self.system.rooms: + for room in self.data.rooms: if room.id == comp.id: return room if isinstance(comp, HotWater): - if self.system.dhw.hotwater and self.system.dhw.hotwater.id == comp.id: - return self.system.dhw.hotwater + if self.data.dhw.hotwater and self.data.dhw.hotwater.id == comp.id: + return self.data.dhw.hotwater if isinstance(comp, Circulation): - if ( - self.system.dhw.circulation - and self.system.dhw.circulation.id == comp.id - ): - return self.system.dhw.circulation + if self.data.dhw.circulation and self.data.dhw.circulation.id == comp.id: + return self.data.dhw.circulation return None @@ -168,7 +164,7 @@ async def set_hot_water_target_temperature(self, entity, target_temp): touch_system = await self._remove_quick_mode_or_holiday(entity) - current_mode = self.system.get_active_mode_hot_water(hotwater).current + current_mode = self.data.get_active_mode_hot_water(hotwater).current if current_mode == OperatingModes.OFF or touch_system: await self._manager.set_hot_water_operating_mode( @@ -176,7 +172,7 @@ async def set_hot_water_target_temperature(self, entity, target_temp): ) await self._manager.set_hot_water_setpoint_temperature(hotwater.id, target_temp) - self.system.hot_water = hotwater + self.data.hot_water = hotwater await self._refresh(touch_system, entity) async def set_room_target_temperature(self, entity, target_temp): @@ -193,7 +189,7 @@ async def set_room_target_temperature(self, entity, target_temp): touch_system = await self._remove_quick_mode_or_holiday(entity) room = entity.component - current_mode = self.system.get_active_mode_room(room).current + current_mode = self.data.get_active_mode_room(room).current if current_mode == OperatingModes.MANUAL: await self._manager.set_room_setpoint_temperature(room.id, target_temp) @@ -205,7 +201,7 @@ async def set_room_target_temperature(self, entity, target_temp): qveto = QuickVeto(DEFAULT_QUICK_VETO_DURATION, target_temp) await self._manager.set_room_quick_veto(room.id, qveto) room.quick_veto = qveto - self.system.set_room(room.id, room) + self.data.set_room(room.id, room) await self._refresh(touch_system, entity) @@ -223,7 +219,7 @@ async def set_zone_target_temperature(self, entity, target_temp): touch_system = await self._remove_quick_mode_or_holiday(entity) zone = entity.component - current_mode = self.system.get_active_mode_zone(zone).current + current_mode = self.data.get_active_mode_zone(zone).current if current_mode == OperatingModes.QUICK_VETO: await self._manager.remove_zone_quick_veto(zone.id) @@ -232,7 +228,7 @@ async def set_zone_target_temperature(self, entity, target_temp): await self._manager.set_zone_quick_veto(zone.id, veto) zone.quick_veto = veto - self.system.set_zone(zone.id, zone) + self.data.set_zone(zone.id, zone) await self._refresh(touch_system, entity) async def set_hot_water_operating_mode(self, entity, mode): @@ -247,7 +243,7 @@ async def set_hot_water_operating_mode(self, entity, mode): await self._manager.set_hot_water_operating_mode(hotwater.id, mode) hotwater.operating_mode = mode - self.system.dhw.hotwater = hotwater + self.data.dhw.hotwater = hotwater await self._refresh(touch_system, entity) async def set_room_operating_mode(self, entity, mode): @@ -264,13 +260,13 @@ async def set_room_operating_mode(self, entity, mode): if isinstance(mode, QuickMode): await self._manager.set_quick_mode(mode) - self.system.quick_mode = mode + self.data.quick_mode = mode touch_system = True else: await self._manager.set_room_operating_mode(room.id, mode) room.operating_mode = mode - self.system.set_room(room.id, room) + self.data.set_room(room.id, room) await self._refresh(touch_system, entity) async def set_zone_operating_mode(self, entity, mode): @@ -288,7 +284,7 @@ async def set_zone_operating_mode(self, entity, mode): if isinstance(mode, QuickMode): await self._manager.set_quick_mode(mode) - self.system.quick_mode = mode + self.data.quick_mode = mode touch_system = True else: if zone.heating and mode in ZoneHeating.MODES: @@ -298,7 +294,7 @@ async def set_zone_operating_mode(self, entity, mode): await self._manager.set_zone_cooling_operating_mode(zone.id, mode) zone.cooling.operating_mode = mode - self.system.set_zone(zone.id, zone) + self.data.set_zone(zone.id, zone) await self._refresh(touch_system, entity) async def remove_quick_mode(self, entity=None): @@ -318,7 +314,7 @@ async def remove_holiday_mode(self): async def set_holiday_mode(self, start_date, end_date, temperature): """Set holiday mode.""" await self._manager.set_holiday_mode(start_date, end_date, temperature) - self.system.holiday = HolidayMode(True, start_date, end_date, temperature) + self.data.holiday = HolidayMode(True, start_date, end_date, temperature) await self._refresh_entities() async def set_quick_mode(self, mode): @@ -326,7 +322,7 @@ async def set_quick_mode(self, mode): await self._remove_quick_mode_no_refresh() qmode = QuickModes.get(mode) await self._manager.set_quick_mode(qmode) - self.system.quick_mode = qmode + self.data.quick_mode = qmode await self._refresh_entities() async def set_quick_veto(self, entity, temperature, duration=None): @@ -366,20 +362,20 @@ async def set_fan_operating_mode(self, entity, mode: Mode): if isinstance(mode, QuickMode): await self._manager.set_quick_mode(mode) - self.system.quick_mode = mode + self.data.quick_mode = mode touch_system = True else: await self._manager.set_ventilation_operating_mode( - self.system.ventilation.id, mode + self.data.ventilation.id, mode ) - self.system.ventilation.operating_mode = mode + self.data.ventilation.operating_mode = mode await self._refresh(touch_system, entity) async def _remove_quick_mode_no_refresh(self, entity=None): removed = False - if self.system.quick_mode is not None: - qmode = self.system.quick_mode + if self.data.quick_mode is not None: + qmode = self.data.quick_mode if entity: if qmode.is_for(entity.component): @@ -393,15 +389,15 @@ async def _remove_quick_mode_no_refresh(self, entity=None): async def _hard_remove_quick_mode(self): await self._manager.remove_quick_mode() - self.system.quick_mode = None + self.data.quick_mode = None async def _remove_holiday_mode_no_refresh(self): removed = False - if self.system.holiday is not None and self.system.holiday.is_applied: + if self.data.holiday is not None and self.data.holiday.is_applied: removed = True await self._manager.remove_holiday_mode() - self.system.holiday = HolidayMode(False) + self.data.holiday = HolidayMode(False) return removed async def _remove_quick_mode_or_holiday(self, entity): @@ -412,7 +408,7 @@ async def _remove_quick_mode_or_holiday(self, entity): async def _refresh_entities(self): """Fetch multimatic data and force refresh of all listening entities.""" - self._hass.bus.async_fire(REFRESH_ENTITIES_EVENT, {}) + self.hass.bus.async_fire(REFRESH_ENTITIES_EVENT, {}) async def _refresh(self, touch_system, entity): if touch_system: diff --git a/multimatic/manifest.json b/multimatic/manifest.json index 6a02af7..f4cb2c1 100644 --- a/multimatic/manifest.json +++ b/multimatic/manifest.json @@ -14,4 +14,4 @@ "@thomasgermain" ], "version": "1.5.0" -} +} \ No newline at end of file diff --git a/multimatic/sensor.py b/multimatic/sensor.py index 0964ccb..77d882d 100644 --- a/multimatic/sensor.py +++ b/multimatic/sensor.py @@ -28,11 +28,11 @@ async def async_setup_entry(hass, entry, async_add_entities): sensors = [] hub = hass.data[MULTIMATIC][entry.unique_id][HUB] - if hub.system: - if hub.system.outdoor_temperature: + if hub.data: + if hub.data.outdoor_temperature: sensors.append(OutdoorTemperatureSensor(hub)) - for report in hub.system.reports: + for report in hub.data.reports: sensors.append(ReportSensor(hub, report)) _LOGGER.info("Adding %s sensor entities", len(sensors)) @@ -47,32 +47,24 @@ class OutdoorTemperatureSensor(MultimaticEntity): def __init__(self, hub: ApiHub): """Initialize entity.""" super().__init__(hub, DOMAIN, "outdoor", "Outdoor", DEVICE_CLASS_TEMPERATURE) - self._outdoor_temp = hub.system.outdoor_temperature @property def state(self): """Return the state of the entity.""" - return self._outdoor_temp + return self.coordinator.data.outdoor_temperature @property def available(self): """Return True if entity is available.""" - return self._outdoor_temp is not None + return ( + super().available and self.coordinator.data.outdoor_temperature is not None + ) @property def unit_of_measurement(self): """Return the unit of measurement of this entity, if any.""" return TEMP_CELSIUS - async def async_custom_update(self): - """Update specific for multimatic.""" - _LOGGER.debug( - "New / old temperature: %s / %s", - self.coordinator.system.outdoor_temperature, - self._outdoor_temp, - ) - self._outdoor_temp = self.coordinator.system.outdoor_temperature - class ReportSensor(MultimaticEntity): """Report sensor.""" @@ -85,15 +77,12 @@ def __init__(self, hub: ApiHub, report: Report): MultimaticEntity.__init__( self, hub, DOMAIN, report.id, report.name, device_class, False ) - self.report = report self._report_id = report.id - async def async_custom_update(self): - """Update specific for multimatic.""" - self.report = self._find_report() - - def _find_report(self): - for report in self.coordinator.system.reports: + @property + def report(self): + """Get the current report based on the id.""" + for report in self.coordinator.data.reports: if self._report_id == report.id: return report return None @@ -106,7 +95,7 @@ def state(self): @property def available(self): """Return True if entity is available.""" - return self.report is not None + return super().available and self.report is not None @property def unit_of_measurement(self): diff --git a/multimatic/water_heater.py b/multimatic/water_heater.py index 6bdfe47..56095ce 100644 --- a/multimatic/water_heater.py +++ b/multimatic/water_heater.py @@ -38,12 +38,11 @@ async def async_setup_entry(hass, entry, async_add_entities): entities = [] hub = hass.data[MULTIMATIC][entry.unique_id][HUB] - if hub.system and hub.system.dhw and hub.system.dhw.hotwater: + if hub.data and hub.data.dhw and hub.data.dhw.hotwater: entity = MultimaticWaterHeater(hub) entities.append(entity) - _LOGGER.info("Added water heater? %s", len(entities) > 0) - async_add_entities(entities, True) + async_add_entities(entities) return True @@ -52,9 +51,9 @@ class MultimaticWaterHeater(MultimaticEntity, WaterHeaterEntity): def __init__(self, hub: ApiHub): """Initialize entity.""" - self._hotwater = hub.system.dhw.hotwater - super().__init__(hub, DOMAIN, self._hotwater.id, self._hotwater.name) - self._active_mode = None + super().__init__( + hub, DOMAIN, hub.data.dhw.hotwater.id, hub.data.dhw.hotwater.name + ) self._operations = {mode.name: mode for mode in HotWater.MODES} @property @@ -65,7 +64,12 @@ def listening(self): @property def component(self): """Return multimatic component.""" - return self._hotwater + return self.coordinator.data.dhw.hotwater + + @property + def active_mode(self): + """Return multimatic component's active mode.""" + return self.coordinator.data.get_active_mode_hot_water() @property def supported_features(self): @@ -92,14 +96,14 @@ def supported_features(self): is off, but it means the user will be able to change the target temperature only when the heater is ON (which seems odd to me) """ - if self._active_mode.current != QuickModes.HOLIDAY: + if self.active_mode != QuickModes.HOLIDAY: return SUPPORTED_FLAGS return 0 @property def available(self): """Return True if entity is available.""" - return self._hotwater is not None + return super().available and self.component is not None @property def temperature_unit(self): @@ -113,20 +117,18 @@ def state_attributes(self): Adding current temperature """ attrs = super().state_attributes - attrs.update(gen_state_attrs(self.component, self._active_mode)) + attrs.update(gen_state_attrs(self.component, self.active_mode)) return attrs @property def target_temperature(self): """Return the temperature we try to reach.""" - _LOGGER.debug("target temperature is %s", self._active_mode.target) - return self._active_mode.target + return self.active_mode.target @property def current_temperature(self): """Return the current temperature.""" - _LOGGER.debug("current temperature is %s", self._hotwater.temperature) - return self._hotwater.temperature + return self.component.temperature @property def min_temp(self): @@ -141,36 +143,32 @@ def max_temp(self): @property def current_operation(self): """Return current operation ie. eco, electric, performance, ...""" - _LOGGER.debug("current_operation is %s", self._active_mode.current) - return self._active_mode.current.name + return self.active_mode.current.name @property def operation_list(self): """Return current operation ie. eco, electric, performance, ...""" - if self._active_mode.current != QuickModes.HOLIDAY: + if self.active_mode.current != QuickModes.HOLIDAY: return list(self._operations.keys()) return [] @property def is_away_mode_on(self): """Return true if away mode is on.""" - return self._active_mode.current in AWAY_MODES + return self.active_mode.current in AWAY_MODES async def async_set_temperature(self, **kwargs): """Set new target temperature.""" target_temp = float(kwargs.get(ATTR_TEMPERATURE)) - _LOGGER.debug("Trying to set target temp to %s", target_temp) - # HUB will call sync update await self.coordinator.set_hot_water_target_temperature(self, target_temp) async def async_set_operation_mode(self, operation_mode): """Set new target operation mode.""" - _LOGGER.debug("Will set new operation_mode %s", operation_mode) if operation_mode in self._operations.keys(): mode = self._operations[operation_mode] await self.coordinator.set_hot_water_operating_mode(self, mode) else: - _LOGGER.debug("Operation mode is unknown") + _LOGGER.debug("Operation mode %s is unknown", operation_mode) async def async_turn_away_mode_on(self): """Turn away mode on.""" @@ -179,8 +177,3 @@ async def async_turn_away_mode_on(self): async def async_turn_away_mode_off(self): """Turn away mode off.""" await self.coordinator.set_hot_water_operating_mode(self, OperatingModes.AUTO) - - async def async_custom_update(self): - """Update specific for multimatic.""" - self._hotwater = self.coordinator.system.dhw.hotwater - self._active_mode = self.coordinator.system.get_active_mode_hot_water()