From f62970e8319a3a6aa698741f7f53a0a836f89df7 Mon Sep 17 00:00:00 2001 From: Thomas Germain <12560542+thomasgermain@users.noreply.github.com> Date: Sun, 8 Oct 2023 10:59:16 +0200 Subject: [PATCH] feat: Climate presets --- README.md | 151 ++++++++++----- custom_components/multimatic/__init__.py | 25 ++- custom_components/multimatic/climate.py | 121 +++++------- custom_components/multimatic/config_flow.py | 19 +- custom_components/multimatic/const.py | 12 +- custom_components/multimatic/manifest.json | 20 +- custom_components/multimatic/services.yaml | 24 --- custom_components/multimatic/strings.json | 112 +++++++++++ .../multimatic/translations/en.json | 112 +++++++++++ .../multimatic/translations/fr.json | 112 +++++++++++ custom_components/multimatic/water_heater.py | 177 ------------------ 11 files changed, 521 insertions(+), 364 deletions(-) delete mode 100644 custom_components/multimatic/water_heater.py diff --git a/README.md b/README.md index 95440b7..54c46a3 100644 --- a/README.md +++ b/README.md @@ -10,51 +10,67 @@ Ideas are welcome ! Don't hesitate to create issue to suggest something, it will **This integration is also compatible with sensoAPP and has been tested with the vr920 and vr921 devices.** ## Installations + - Through HACS [custom repositories](https://hacs.xyz/docs/faq/custom_repositories/) ! -- Otherwise, download the zip from the latest release and copy `multimatic` folder and put it inside your `custom_components` folder. +- Otherwise, download the zip from the latest release and copy `multimatic` folder and put it inside + your `custom_components` folder. You can configure it through the UI using integration. -You have to provide your username and password (same as multimatic or senso app), if you have multiple serial numbers, you can choose for which number serial number you want the integration. +You have to provide your username and password (same as multimatic or senso app), if you have multiple serial numbers, +you can choose for which number serial number you want the integration. You can create multiple instance of the integration with different serial number (**This is still a beta feature**). **It is strongly recommended using a dedicated user for HA**, for 2 reasons: + - As usual for security reason, if your HA got compromised somehow, you know which user to block - I cannot confirm it, but it seems multimatic and senso API only accept the same user to be connected at the same time ## Changelog + See [releases details](https://github.com/thomasgermain/vaillant-component/releases) + ## 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.` - 1 climate entity per room `climate.` -- 1 fan entity `fan.` +- 1 fan entity `fan.` - 1 binary_sensor entity `binary_sensor.control_dhw` reflecting if the circulation is on or off -- 1 binary_sensor entity `climate._window` per room reflecting the state of the "open window" in a room (this is a feature of the multimatic API, if the temperature is going down pretty fast, the API assumes there is an open window and heating stops) +- 1 binary_sensor entity `climate._window` per room reflecting the state of the "open window" in a room (this + is a feature of the multimatic API, if the temperature is going down pretty fast, the API assumes there is an open + window and heating stops) - 1 binary_sensor entity `climate._lock`per device reflecting if valves are "child locked" or not -- 1 binary_sensor entity `binary_sensor._battery` reflecting battery level for each device (VR50, VR51) in the system -- 1 binary_sensor entity `binary_sensor._battery` reflecting connectivity for each device (VR50, VR51) in the system +- 1 binary_sensor entity `binary_sensor._battery` reflecting battery level for each device (VR50, VR51) in the + system +- 1 binary_sensor entity `binary_sensor._battery` reflecting connectivity for each device (VR50, VR51) in the + system - 1 binary_sensor entity `binary_sensor.multimtic_system_update`to know if there is an update pending - 1 binary_sensor entity `binary_sensor.multimtic_system_online` to know if the vr900/920 is connected to the internet -- 1 binary_sensor entity `binary_sensor.` to know if there is an error at the boiler. **Some boiler does not provide this information, so entity won't be available.** +- 1 binary_sensor entity `binary_sensor.` to know if there is an error at the boiler. **Some boiler does + not provide this information, so entity won't be available.** - 1 temperature sensor `sensor.outdoor_temperature` for outdoor temperature - 1 sensor for each report in live_report (boiler temperature, boiler water pressure, etc.) - 1 binary sensor `binary_sensor.multimtic_quick_mode` to know a quick mode is running on - 1 binary sensor ` binary_sensor.multimtic_holiday` to know the holiday mode is on/off -- 1 binary sensor `binary_sensor.multimatic_errors`indicating if there are errors coming from the API (if `on`, details are in `state_attributes`) +- 1 binary sensor `binary_sensor.multimatic_errors`indicating if there are errors coming from the API (if `on`, details + are in `state_attributes`) ## Provided devices + - 1 device per VR50 or VR51 -- 1 device for the boiler (if supported). Some boilers don't provide enough information to be able to create a device in HA. +- 1 device for the boiler (if supported). Some boilers don't provide enough information to be able to create a device in + HA. - 1 device for the gateway (like VR920) - 1 "multimatic" (VRC700) device (the water pressure is linked to the VRC 700 inside the multimatic API) - hot water circuit - heating circuit +For the climate and water heater entities, you can also find -For the climate and water heater entities, you can also find - the 'real multimatic mode' running on (AUTO, MANUAL, DAY, etc) -For the boiler error entity, you can also find +For the boiler error entity, you can also find + - the last update (this is not the last HA update, this is the last time multimatic checks the boiler) - the status code (these can be found in your documentation) - the title (human-readable description of the status code) @@ -63,56 +79,97 @@ For the `binary_sensor.multimtic_quick_mode`, when on, you have the current quic For the `binary_sensor.multimtic_holiday`, when on, you have the start date, end date and target temperature ## Provided services + - `multimatic.set_holiday_mode` to set the holiday mode (see services in HA ui to get the params) - `multimatic.remove_holiday_mode` .. I guess you get it - `multimatic.set_quick_mode` to set a quick mode -- `multimatic.remove_quick_mode` don't tell me you don't get it +- `multimatic.remove_quick_mode` don't tell me you don't get it - `multimatic.set_quick_veto` to set a quick veto for a climate entity - `multimatic.remove_quick_veto` to remove a quick veto for a climate entity -- `multimatic.request_hvac_update` to tell multimatic API to fetch data from your installation and made them available in the API +- `multimatic.request_hvac_update` to tell multimatic API to fetch data from your installation and made them available + in the API - `multimatic.set_ventilation_day_level` to set ventilation day level - `multimatic.set_ventilation_night_level` to set ventilation night level - `multimatic.set_datetime` to set the current date time of the system This will allow you to create some buttons in UI to activate/deactivate quick mode or holiday mode with a single click - ## Expected behavior -On **room** climate: +### Room climate + +#### Changing temperature -Changing temperature while ... - `MANUAL` mode -> it simply changes target temperature -- other modes -> it creates a quick_veto (duration = 3 hours) (it's also removing holiday or quick mode) - -Modes mapping: -- `AUTO` -> `HVAC_MODE_AUTO` & `PRESET_COMFORT` -- `OFF` -> `HVAC_MODE_OFF` & no preset -- `QUICK_VETO` -> hvac depends on state & `PRESET_QUICK_VETO` (custom) -- `QM_SYSTEM_OFF` -> `HVAC_MODE_OFF` & `PRESET_SYSTEM_OFF` (custom) -- `HOLIDAY` -> `HVAC_MODE_OFF` & `PRESET_HOLIDAY` (custom) -- `MANUAL` -> no hvac & `PRESET_MANUAL` (custom) - -On **zone** climate: -- Changing temperature will lead to a quick veto with selected temperature for 6 hours (quick veto duration is not configurable for a zone) - -Modes mapping: - -| Vaillant Mode | HA Mode | -| ------------- |-----------------------------------------------------| -| AUTO | `HVAC_MODE_AUTO` & `PRESET_COMFORT` | -| DAY | no hvac & `PRESET_DAY` (custom) | -| NIGHT | no hvac & `PRESET_SLEEP` | -| OFF | `HVAC_MODE_OFF` & no preset | -| ON (= cooling ON) | no hvac & `PRESET_COOLING_ON` (custom) | -| QUICK_VETO | depends on the state & `PRESET_QUICK_VETO` (custom) | -| QM_ONE_DAY_AT_HOME | HVAC_MODE_AUTO & `PRESET_HOME` | -| QM_PARTY | no hvac & `PRESET_PARTY` (custom) | -| QM_VENTILATION_BOOST | `HVAC_MODE_FAN_ONLY` & no preset | -| QM_ONE_DAY_AWAY | `HVAC_MODE_OFF` & `PRESET_AWAY` | -| QM_SYSTEM_OFF | `HVAC_MODE_OFF` & `PRESET_SYSTEM_OFF` (custom) | -| HOLIDAY | `HVAC_MODE_OFF` & `PRESET_HOLIDAY` (custom) | -| QM_COOLING_FOR_X_DAYS | no hvac & `PRESET_COOLING_FOR_X_DAYS` | +- other modes -> it creates a quick_veto (duration = 3 hours) and it removes holiday or quick mode. + +#### Modes mapping + +| Multimatic mode | HA HVAC | HA preset | +|-------------------------|---------|----------------------------| +| AUTO | AUTO | COMFORT | +| OFF | OFF | / | +| QUICK_VETO | HEAT | PRESET_QUICK_VETO (custom) | +| SYSTEM_OFF (quick mode) | OFF | PRESET_SYSTEM_OFF (custom) | +| HOLIDAY (quick mode) | OFF | PRESET_AWAY | +| MANUAL | HEAT | PRESET_HOME | + +#### Available HVAC mode + +| HVAC mode | Multimatic mode | +|-----------|-----------------| +| AUTO | AUTO | +| OFF | OFF | + +#### Available preset mode + +| preset mode | Multimatic mode | +|----------------|-----------------| +| PRESET_COMFORT | AUTO | +| PRESET_HOME | MANUAL | + +### Zone climate + +#### Changing temperature + +Changing temperature will lead to a quick veto with selected temperature for 6 hours (quick veto duration is not +configurable for a zone) + +#### Modes mapping + +| Vaillant Mode | HA HVAC | HA preset | +|---------------------------------|------------------------|----------------------------| +| AUTO / TIMED CONTROLLED | AUTO | PRESET_COMFORT | +| DAY | HEAT | PRESET_HOME | +| NIGHT | OFF | PRESET_SLEEP | +| MANUAL | Based on zone function | / | +| OFF | OFF | / | +| ON | Based on zone function | / | +| QUICK_VETO | HEAT | PRESET_QUICK_VETO (custom) | +| ONE_DAY_AT_HOME (quick mode) | AUTO | PRESET_HOME | +| PARTY (quick mode) | OFF | PRESET_HOME | +| VENTILATION_BOOST (quick mode) | FAN_ONLY | / | +| ONE_DAY_AWAY (quick mode) | OFF | PRESET_AWAY | +| SYSTEM_OFF (quick mode) | OFF | PRESET_SYSTEM_OFF (custom) | +| HOLIDAY (quick mode) | OFF | PRESET_AWAY | +| COOLING_FOR_X_DAYS (quick mode) | COOL | / | + +#### Available HVAC mode + +| HVAC mode | Multimatic mode | +|-----------|---------------------------------| +| AUTO | AUTO | +| OFF | OFF | +| FAN_ONLY | VENTILATION_BOOST (quick mode) | +| COOL | COOLING_FOR_X_DAYS (quick mode) | + +#### Available preset mode + +| preset mode | Multimatic mode | +|----------------|------------------------------| +| PRESET_COMFORT | AUTO | +| PRESET_HOME | ONE_DAY_AT_HOME (quick mode) | +| PRESET_AWAY | ONE_DAY_AWWAY (quick mode) | ### DHW climate @@ -125,7 +182,7 @@ Modes mapping: | SYSTEM_OFF (quick mode) | OFF | PRESET_SYSTEM_OFF | | HOTWATER_BOOST (quick mode) | HEAT | PRESET_BOOST | | PARTY (quick mode) | OFF | PRESET_HOME | -| ON | HEAT | PRESET_NONE | +| ON | EAT | PRESET_NONE | #### Available HVAC mode diff --git a/custom_components/multimatic/__init__.py b/custom_components/multimatic/__init__.py index dfc45ca..2800eda 100644 --- a/custom_components/multimatic/__init__.py +++ b/custom_components/multimatic/__init__.py @@ -4,10 +4,17 @@ import logging from pymultimatic.api import ApiError, defaults +import voluptuous as vol from homeassistant.config_entries import ConfigEntry -from homeassistant.const import CONF_SCAN_INTERVAL, EVENT_HOMEASSISTANT_STOP +from homeassistant.const import ( + CONF_PASSWORD, + CONF_SCAN_INTERVAL, + CONF_USERNAME, + EVENT_HOMEASSISTANT_STOP, +) from homeassistant.core import HomeAssistant +import homeassistant.helpers.config_validation as cv from homeassistant.helpers.event import async_track_time_interval from homeassistant.helpers.typing import ConfigType @@ -19,8 +26,10 @@ DEFAULT_SCAN_INTERVAL, DOMAIN, FORCE_RELOGIN_TIMEDELTA, + MULTIMATIC, PLATFORMS, RELOGIN_TASK_CLEAN, + SENSO, SERVICES_HANDLER, ) from .coordinator import MultimaticApi, MultimaticCoordinator @@ -28,6 +37,20 @@ _LOGGER = logging.getLogger(__name__) +DATA_SCHEMA = vol.Schema( + { + vol.Required(CONF_USERNAME): cv.string, + vol.Required(CONF_PASSWORD): cv.string, + vol.Optional(CONF_SERIAL_NUMBER): cv.string, + vol.Required(CONF_APPLICATION, default=MULTIMATIC): vol.In([MULTIMATIC, SENSO]), + } +) + +CONFIG_SCHEMA = vol.Schema( + {DOMAIN: DATA_SCHEMA}, + extra=vol.ALLOW_EXTRA, +) + async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: """Set up the multimatic integration.""" diff --git a/custom_components/multimatic/climate.py b/custom_components/multimatic/climate.py index 4b88b97..61499a4 100644 --- a/custom_components/multimatic/climate.py +++ b/custom_components/multimatic/climate.py @@ -44,12 +44,6 @@ DEFAULT_QUICK_VETO_DURATION, DHW, DOMAIN as MULTIMATIC, - PRESET_COOLING_FOR_X_DAYS, - PRESET_COOLING_ON, - PRESET_DAY, - PRESET_HOLIDAY, - PRESET_MANUAL, - PRESET_PARTY, PRESET_QUICK_VETO, PRESET_SYSTEM_OFF, ROOMS, @@ -70,6 +64,12 @@ ActiveFunction.STANDBY: HVACAction.IDLE, } +_FUNCTION_TO_HVAC_MODE: dict[ActiveFunction, HVACMode] = { + ActiveFunction.COOLING: HVACMode.COOL, + ActiveFunction.HEATING: HVACMode.HEAT, + ActiveFunction.STANDBY: HVACMode.OFF, +} + async def async_setup_entry( hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback @@ -279,10 +279,10 @@ class RoomClimate(MultimaticClimate): _MULTIMATIC_TO_HA: dict[Mode, list] = { OperatingModes.AUTO: [HVACMode.AUTO, PRESET_COMFORT], OperatingModes.OFF: [HVACMode.OFF, PRESET_NONE], - OperatingModes.QUICK_VETO: [None, PRESET_QUICK_VETO], + OperatingModes.QUICK_VETO: [HVACMode.HEAT, PRESET_QUICK_VETO], QuickModes.SYSTEM_OFF: [HVACMode.OFF, PRESET_SYSTEM_OFF], - QuickModes.HOLIDAY: [HVACMode.OFF, PRESET_HOLIDAY], - OperatingModes.MANUAL: [None, PRESET_MANUAL], + QuickModes.HOLIDAY: [HVACMode.OFF, PRESET_AWAY], + OperatingModes.MANUAL: [HVACMode.HEAT, PRESET_HOME], } _HA_MODE_TO_MULTIMATIC = { @@ -292,8 +292,7 @@ class RoomClimate(MultimaticClimate): _HA_PRESET_TO_MULTIMATIC = { PRESET_COMFORT: OperatingModes.AUTO, - PRESET_MANUAL: OperatingModes.MANUAL, - PRESET_SYSTEM_OFF: QuickModes.SYSTEM_OFF, + PRESET_HOME: OperatingModes.MANUAL, } def __init__( @@ -335,16 +334,7 @@ def component(self) -> Room: @property def hvac_mode(self) -> HVACMode: """Get the hvac mode based on multimatic mode.""" - hvac_mode = RoomClimate._MULTIMATIC_TO_HA[self.active_mode.current][0] - if not hvac_mode: - if self.active_mode.current in ( - OperatingModes.MANUAL, - OperatingModes.QUICK_VETO, - ): - if self.hvac_action == HVACAction.HEATING: - return HVACMode.HEAT - return HVACMode.OFF - return hvac_mode + return RoomClimate._MULTIMATIC_TO_HA[self.active_mode.current][0] @property def min_temp(self) -> float: @@ -378,8 +368,9 @@ async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None: async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new target preset mode.""" - mode = RoomClimate._HA_PRESET_TO_MULTIMATIC[preset_mode] - await self.coordinator.api.set_room_operating_mode(self, mode) + if preset_mode != "": + mode = RoomClimate._HA_PRESET_TO_MULTIMATIC[preset_mode] + await self.coordinator.api.set_room_operating_mode(self, mode) @property def hvac_action(self) -> HVACAction: @@ -421,8 +412,6 @@ def __init__( super().__init__(coordinator, zone.id) if not zone.cooling: - self._supported_presets.remove(PRESET_COOLING_ON) - self._supported_presets.remove(PRESET_COOLING_FOR_X_DAYS) self._supported_hvac.remove(HVACMode.COOL) if not ventilation: @@ -448,26 +437,10 @@ def component(self) -> Zone: @property def hvac_mode(self) -> HVACMode: """Get the hvac mode based on multimatic mode.""" - current_mode = self.active_mode.current - hvac_mode = self._multimatic_mode()[current_mode][0] - if not hvac_mode: - if ( - current_mode - in [ - OperatingModes.DAY, - OperatingModes.NIGHT, - QuickModes.PARTY, - OperatingModes.QUICK_VETO, - ] - and self.hvac_action == HVACAction.HEATING - ): - return HVACMode.HEAT - if ( - self.preset_mode in (PRESET_COOLING_ON, PRESET_COOLING_FOR_X_DAYS) - and self.hvac_action == HVACAction.COOLING - ): - return HVACMode.COOL - return hvac_mode if hvac_mode else HVACMode.OFF + hvac_mode = self._multimatic_mode()[self.active_mode.current][0] + if not hvac_mode: # if no hvac mode defined, guess it from zone function + return _FUNCTION_TO_HVAC_MODE[self.component.active_function] + return hvac_mode @property def min_temp(self) -> float: @@ -504,8 +477,9 @@ def hvac_action(self) -> HVACAction | None: async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new target preset mode.""" - mode = self._ha_preset()[preset_mode] - await self.coordinator.api.set_zone_operating_mode(self, mode) + if preset_mode != "": + mode = self._ha_preset()[preset_mode] + await self.coordinator.api.set_zone_operating_mode(self, mode) class ZoneClimate(AbstractZoneClimate): @@ -513,18 +487,21 @@ class ZoneClimate(AbstractZoneClimate): _MULTIMATIC_TO_HA: dict[Mode, list] = { OperatingModes.AUTO: [HVACMode.AUTO, PRESET_COMFORT], - OperatingModes.DAY: [None, PRESET_DAY], - OperatingModes.NIGHT: [None, PRESET_SLEEP], + OperatingModes.DAY: [HVACMode.HEAT, PRESET_HOME], + OperatingModes.NIGHT: [HVACMode.OFF, PRESET_SLEEP], OperatingModes.OFF: [HVACMode.OFF, PRESET_NONE], - OperatingModes.ON: [None, PRESET_COOLING_ON], - OperatingModes.QUICK_VETO: [None, PRESET_QUICK_VETO], + OperatingModes.ON: [ + None, + PRESET_NONE, + ], # can be heating or cooling, this is determined at runtime + OperatingModes.QUICK_VETO: [HVACMode.HEAT, PRESET_QUICK_VETO], QuickModes.ONE_DAY_AT_HOME: [HVACMode.AUTO, PRESET_HOME], - QuickModes.PARTY: [None, PRESET_PARTY], + QuickModes.PARTY: [HVACMode.OFF, PRESET_HOME], QuickModes.VENTILATION_BOOST: [HVACMode.FAN_ONLY, PRESET_NONE], QuickModes.ONE_DAY_AWAY: [HVACMode.OFF, PRESET_AWAY], QuickModes.SYSTEM_OFF: [HVACMode.OFF, PRESET_SYSTEM_OFF], - QuickModes.HOLIDAY: [HVACMode.OFF, PRESET_HOLIDAY], - QuickModes.COOLING_FOR_X_DAYS: [None, PRESET_COOLING_FOR_X_DAYS], + QuickModes.HOLIDAY: [HVACMode.OFF, PRESET_AWAY], + QuickModes.COOLING_FOR_X_DAYS: [HVACMode.COOL, PRESET_NONE], } _HA_MODE_TO_MULTIMATIC = { @@ -536,14 +513,8 @@ class ZoneClimate(AbstractZoneClimate): _HA_PRESET_TO_MULTIMATIC = { PRESET_COMFORT: OperatingModes.AUTO, - PRESET_DAY: OperatingModes.DAY, - PRESET_SLEEP: OperatingModes.NIGHT, - PRESET_COOLING_ON: OperatingModes.ON, PRESET_HOME: QuickModes.ONE_DAY_AT_HOME, - PRESET_PARTY: QuickModes.PARTY, PRESET_AWAY: QuickModes.ONE_DAY_AWAY, - PRESET_SYSTEM_OFF: QuickModes.SYSTEM_OFF, - PRESET_COOLING_FOR_X_DAYS: QuickModes.COOLING_FOR_X_DAYS, } def _ha_mode(self): @@ -561,18 +532,17 @@ class ZoneClimateSenso(AbstractZoneClimate): _SENSO_TO_HA: dict[Mode, list] = { OperatingModes.TIME_CONTROLLED: [HVACMode.AUTO, PRESET_COMFORT], - OperatingModes.DAY: [None, PRESET_DAY], - OperatingModes.NIGHT: [None, PRESET_SLEEP], + OperatingModes.DAY: [HVACMode.HEAT, PRESET_HOME], + OperatingModes.NIGHT: [HVACMode.OFF, PRESET_SLEEP], OperatingModes.OFF: [HVACMode.OFF, PRESET_NONE], - OperatingModes.MANUAL: [None, PRESET_COOLING_ON], - OperatingModes.QUICK_VETO: [None, PRESET_QUICK_VETO], - QuickModes.ONE_DAY_AT_HOME: [HVACMode.AUTO, PRESET_HOME], - QuickModes.PARTY: [None, PRESET_PARTY], - QuickModes.VENTILATION_BOOST: [HVACMode.FAN_ONLY, PRESET_NONE], + OperatingModes.MANUAL: [ + None, + PRESET_NONE, + ], # can be heating or cooling, this is determined at runtime QuickModes.ONE_DAY_AWAY: [HVACMode.OFF, PRESET_AWAY], QuickModes.SYSTEM_OFF: [HVACMode.OFF, PRESET_SYSTEM_OFF], - QuickModes.HOLIDAY: [HVACMode.OFF, PRESET_HOLIDAY], - QuickModes.COOLING_FOR_X_DAYS: [None, PRESET_COOLING_FOR_X_DAYS], + QuickModes.HOLIDAY: [HVACMode.OFF, PRESET_AWAY], + QuickModes.COOLING_FOR_X_DAYS: [HVACMode.COOL, PRESET_NONE], } _HA_MODE_TO_SENSO = { HVACMode.AUTO: OperatingModes.TIME_CONTROLLED, @@ -583,14 +553,8 @@ class ZoneClimateSenso(AbstractZoneClimate): _HA_PRESET_TO_SENSO = { PRESET_COMFORT: OperatingModes.TIME_CONTROLLED, - PRESET_DAY: OperatingModes.DAY, - PRESET_SLEEP: OperatingModes.NIGHT, - PRESET_COOLING_ON: OperatingModes.MANUAL, PRESET_HOME: QuickModes.ONE_DAY_AT_HOME, - PRESET_PARTY: QuickModes.PARTY, PRESET_AWAY: QuickModes.ONE_DAY_AWAY, - PRESET_SYSTEM_OFF: QuickModes.SYSTEM_OFF, - PRESET_COOLING_FOR_X_DAYS: QuickModes.COOLING_FOR_X_DAYS, } def _ha_mode(self): @@ -686,9 +650,10 @@ def hvac_mode(self) -> HVACMode | None: async def async_set_preset_mode(self, preset_mode: str) -> None: """Set new target preset mode.""" - mode = DHWClimate._HA_PRESET_TO_MULTIMATIC[preset_mode] - _LOGGER.info("Will set %s operation mode to hot water", mode) - await self.coordinator.api.set_hot_water_operating_mode(self, mode) + if preset_mode != "": + mode = DHWClimate._HA_PRESET_TO_MULTIMATIC[preset_mode] + _LOGGER.info("Will set %s operation mode to hot water", mode) + await self.coordinator.api.set_hot_water_operating_mode(self, mode) async def async_set_temperature(self, **kwargs: Any) -> None: """Set new target temperature.""" diff --git a/custom_components/multimatic/config_flow.py b/custom_components/multimatic/config_flow.py index 66b3fcb..1f23532 100644 --- a/custom_components/multimatic/config_flow.py +++ b/custom_components/multimatic/config_flow.py @@ -13,26 +13,11 @@ from homeassistant.helpers.aiohttp_client import async_create_clientsession import homeassistant.helpers.config_validation as cv -from .const import ( - CONF_APPLICATION, - CONF_SERIAL_NUMBER, - DEFAULT_SCAN_INTERVAL, - DOMAIN, - MULTIMATIC, - SENSO, -) +from . import DATA_SCHEMA +from .const import CONF_APPLICATION, DEFAULT_SCAN_INTERVAL, DOMAIN, SENSO _LOGGER = logging.getLogger(__name__) -DATA_SCHEMA = vol.Schema( - { - vol.Required(CONF_USERNAME): str, - vol.Required(CONF_PASSWORD): str, - vol.Optional(CONF_SERIAL_NUMBER): str, - vol.Required(CONF_APPLICATION, default=MULTIMATIC): vol.In([MULTIMATIC, SENSO]), - } -) - async def validate_input(hass: core.HomeAssistant, data): """Validate the user input allows us to connect. diff --git a/custom_components/multimatic/const.py b/custom_components/multimatic/const.py index d226aac..5c2b6f9 100644 --- a/custom_components/multimatic/const.py +++ b/custom_components/multimatic/const.py @@ -10,17 +10,11 @@ ENTITIES = "entities" # list of platforms into entity are created -PLATFORMS = ["binary_sensor", "sensor", "water_heater", "climate", "fan"] +PLATFORMS = ["binary_sensor", "sensor", "climate", "fan"] # climate custom presets -PRESET_DAY = "day" -PRESET_COOLING_ON = "cooling_on" -PRESET_MANUAL = "manual" -PRESET_SYSTEM_OFF = "system_off" -PRESET_PARTY = "party" -PRESET_HOLIDAY = "holiday" -PRESET_QUICK_VETO = "quick_veto" -PRESET_COOLING_FOR_X_DAYS = "cooling_for_x_days" +PRESET_SYSTEM_OFF = "System off" +PRESET_QUICK_VETO = "Quick Veto" # default values for configuration diff --git a/custom_components/multimatic/manifest.json b/custom_components/multimatic/manifest.json index a27a6ae..8fa7c0c 100644 --- a/custom_components/multimatic/manifest.json +++ b/custom_components/multimatic/manifest.json @@ -1,18 +1,16 @@ { "domain": "multimatic", "name": "Multimatic", + "codeowners": ["@thomasgermain"], "config_flow": true, + "dependencies": [], "documentation": "https://github.com/thomasgermain/vaillant-component", - "issue_tracker": "https://github.com/thomasgermain/vaillant-component/issues", - "requirements": [ - "pymultimatic==0.7.3" - ], - "ssdp": [], - "zeroconf": [], "homekit": {}, - "dependencies": [], - "codeowners": ["@thomasgermain"], - "version": "1.16.3", + "integration_type": "hub", "iot_class": "cloud_polling", - "integration_type": "hub" -} + "issue_tracker": "https://github.com/thomasgermain/vaillant-component/issues", + "requirements": ["pymultimatic==0.7.3"], + "ssdp": [], + "version": "1.17.0b1", + "zeroconf": [] +} \ No newline at end of file diff --git a/custom_components/multimatic/services.yaml b/custom_components/multimatic/services.yaml index bb58cfd..2008a05 100644 --- a/custom_components/multimatic/services.yaml +++ b/custom_components/multimatic/services.yaml @@ -1,14 +1,10 @@ remove_quick_mode: - description: Remove quick mode remove_holiday_mode: - description: Remove holiday mode set_quick_mode: - description: Set a quick mode to multimatic system. fields: quick_mode: - description: Name of the quick mode (required) example: QM_HOTWATER_BOOST, QM_VENTILATION_BOOST, QM_ONE_DAY_AWAY, QM_SYSTEM_OFF, QM_ONE_DAY_AT_HOME, QM_PARTY selector: select: @@ -20,7 +16,6 @@ set_quick_mode: - QM_ONE_DAY_AT_HOME - QM_PARTY duration: - description: (int) number of days the quick mode should last example: 3 selector: number: @@ -29,20 +24,16 @@ set_quick_mode: mode: box set_holiday_mode: - description: Set holiday mode fields: start_date: - description: Start date of the holiday mode YYYY-MM-DD format (required) example: "2019-11-25" selector: date: end_date: - description: End date of the holiday mode, YYYY-MM-DD format (required) example: "2019-11-26" selector: date: temperature: - description: temperature to maintin while holiday mode is active (required) example: 15 selector: number: @@ -51,17 +42,14 @@ set_holiday_mode: mode: box set_quick_veto: - description: Set a quick veto for a climate entity fields: entity_id: - description: Entity id from where to set a quick veto example: climate.bathroom selector: entity: integration: multimatic domain: climate temperature: - description: Target temperature to be applied while quick veto is running on example: 25 selector: number: @@ -69,7 +57,6 @@ set_quick_veto: max: 30 mode: box duration: - description: Duration (in minutes) of the quick veto. Min 30min, max 1440 (24 hours). If not specified, the default (configured) duration is applied. example: 60 selector: number: @@ -78,10 +65,8 @@ set_quick_veto: mode: box remove_quick_veto: - description: Remove a quick veto for a climate entity fields: entity_id: - description: Entity id from where to remove quick veto example: climate.bathroom selector: entity: @@ -89,20 +74,16 @@ remove_quick_veto: domain: climate request_hvac_update: - description: Ask multimatic API to get data from your installation. set_ventilation_day_level: - description: Set day level ventilation fields: entity_id: - description: Entity id of the fan example: fan.bathroom selector: entity: integration: multimatic domain: fan level: - description: Level to set (required) example: 1 selector: number: @@ -111,17 +92,14 @@ set_ventilation_day_level: mode: box set_ventilation_night_level: - description: Set night level ventilation fields: entity_id: - description: Entity id of the fan example: fan.bathroom selector: entity: integration: multimatic domain: fan level: - description: Level to set (required) example: 2 selector: number: @@ -130,10 +108,8 @@ set_ventilation_night_level: mode: box set_datetime: - description: Set multimatic system datetime fields: datetime: - description: datetime to set example: 2022-11-06T11:11:38 selector: datetime: diff --git a/custom_components/multimatic/strings.json b/custom_components/multimatic/strings.json index 06f665f..7d9d764 100644 --- a/custom_components/multimatic/strings.json +++ b/custom_components/multimatic/strings.json @@ -28,5 +28,117 @@ } } } + }, + "services": { + "remove_quick_mode": { + "name": "Remove quick mode", + "description": "Remove quick mode for the whole system." + }, + "remove_holiday_mode": { + "name": "Remove holiday mode", + "description": "Remove holiday mode." + }, + "set_quick_mode": { + "name": "Set quick mode", + "description": "Set quick mode for the while system.", + "fields": { + "quick_mode": { + "name": "Quick mode", + "description": "Name of the quick mode to set." + }, + "duration": { + "name": "Duration", + "description": "Number of days the quick mode should last." + } + } + }, + "set_holiday_mode": { + "name": "Set holiday mode", + "description": "Set holiday mode.", + "fields": { + "start_date": { + "name": "Start date", + "description": "Start date of the holiday mode YYYY-MM-DD format." + }, + "end_date": { + "name": "End date", + "description": "End date of the holiday mode, YYYY-MM-DD format." + }, + "temperature": { + "name": "Target temperature", + "description": "temperature to maintain while holiday mode is active." + } + } + }, + "set_quick_veto": { + "name": "Set a quick veto", + "description": "Set a quick veto for a climate entity.", + "fields": { + "entity_id": { + "name": "Entity id", + "description": "Climate entity id to which quick veto should be set." + }, + "temperature": { + "name": "Target temperature", + "description": "Target temperature to be applied while quick veto is running on." + }, + "duration": { + "name": "Duration", + "description": "Duration (in minutes) of the quick veto. Min 30min, max 1440 (24 hours). If not specified, the default (configured) duration is applied." + } + } + }, + "remove_quick_veto": { + "name": "Remove quick veto", + "description": "Remove a quick veto for a climate entity.", + "fields": { + "entity_id": { + "name": "Entity id", + "description": "Climate entity id to which quick veto should be removed." + } + } + }, + "request_hvac_update": { + "name": "Request HVAC update", + "description": "Ask multimatic API to get data from your installation." + }, + "set_ventilation_day_level": { + "name": "Set day level ventilation", + "description": "Set day level ventilation.", + "fields": { + "entity_id": { + "name": "Entity id", + "description": "Entity id of the fan." + }, + "level": { + "name": "level", + "description": "Level to set." + } + } + }, + "set_ventilation_night_level": { + "name": "Set night level ventilation", + "description": "Set night level ventilation.", + "fields": { + "entity_id": { + "name": "Entity id", + "description": "Entity id of the fan." + }, + "level": { + "name": "level", + "description": "Level to set." + } + } + }, + "set_datetime": { + "name": "Set datetime", + "description": "Set multimatic system datetime.", + "fields": { + "datetime": { + "name": "datetime", + "description": "The datetime to set." + } + } + } } } diff --git a/custom_components/multimatic/translations/en.json b/custom_components/multimatic/translations/en.json index 3e0c7bf..98e79ae 100644 --- a/custom_components/multimatic/translations/en.json +++ b/custom_components/multimatic/translations/en.json @@ -28,5 +28,117 @@ } } } + }, + "services": { + "remove_quick_mode": { + "name": "Remove quick mode", + "description": "Remove quick mode for the whole system." + }, + "remove_holiday_mode": { + "name": "Remove holiday mode", + "description": "Remove holiday mode." + }, + "set_quick_mode": { + "name": "Set quick mode", + "description": "Set quick mode for the while system.", + "fields": { + "quick_mode": { + "name": "Quick mode", + "description": "Name of the quick mode to set." + }, + "duration": { + "name": "Duration", + "description": "Number of days the quick mode should last." + } + } + }, + "set_holiday_mode": { + "name": "Set holiday mode", + "description": "Set holiday mode.", + "fields": { + "start_date": { + "name": "Start date", + "description": "Start date of the holiday mode YYYY-MM-DD format." + }, + "end_date": { + "name": "End date", + "description": "End date of the holiday mode, YYYY-MM-DD format." + }, + "temperature": { + "name": "Target temperature", + "description": "temperature to maintain while holiday mode is active." + } + } + }, + "set_quick_veto": { + "name": "Set a quick veto", + "description": "Set a quick veto for a climate entity.", + "fields": { + "entity_id": { + "name": "Entity id", + "description": "Climate entity id to which quick veto should be set." + }, + "temperature": { + "name": "Target temperature", + "description": "Target temperature to be applied while quick veto is running on." + }, + "duration": { + "name": "Duration", + "description": "Duration (in minutes) of the quick veto. Min 30min, max 1440 (24 hours). If not specified, the default (configured) duration is applied." + } + } + }, + "remove_quick_veto": { + "name": "Remove quick veto", + "description": "Remove a quick veto for a climate entity.", + "fields": { + "entity_id": { + "name": "Entity id", + "description": "Climate entity id to which quick veto should be removed." + } + } + }, + "request_hvac_update": { + "name": "Request HVAC update", + "description": "Ask multimatic API to get data from your installation." + }, + "set_ventilation_day_level": { + "name": "Set day level ventilation", + "description": "Set day level ventilation.", + "fields": { + "entity_id": { + "name": "Entity id", + "description": "Entity id of the fan." + }, + "level": { + "name": "level", + "description": "Level to set." + } + } + }, + "set_ventilation_night_level": { + "name": "Set night level ventilation", + "description": "Set night level ventilation.", + "fields": { + "entity_id": { + "name": "Entity id", + "description": "Entity id of the fan." + }, + "level": { + "name": "level", + "description": "Level to set." + } + } + }, + "set_datetime": { + "name": "Set datetime", + "description": "Set multimatic system datetime.", + "fields": { + "datetime": { + "name": "datetime", + "description": "The datetime to set." + } + } } + } } \ No newline at end of file diff --git a/custom_components/multimatic/translations/fr.json b/custom_components/multimatic/translations/fr.json index ae92124..56e97f0 100644 --- a/custom_components/multimatic/translations/fr.json +++ b/custom_components/multimatic/translations/fr.json @@ -28,5 +28,117 @@ } } } + }, + "services": { + "remove_quick_mode": { + "name": "Supprimer le mode de fonctionnement spécial", + "description": "Supprimer le mode de fonctionnement spécial" + }, + "remove_holiday_mode": { + "name": "Annuler mode vacance", + "description": "Annuler mode vacance." + }, + "set_quick_mode": { + "name": "Activer le mode de fonctionnement spécial", + "description": "Activer le mode de fonctionnement spécial.", + "fields": { + "quick_mode": { + "name": "Mode de fonctionnement spécial", + "description": "Nom du fonctionnement spécial à activer." + }, + "duration": { + "name": "Durée", + "description": "Durée, en jours, du fonctionnement spécial." + } + } + }, + "set_holiday_mode": { + "name": "Activer mode vacance", + "description": "Activer mode vacance", + "fields": { + "start_date": { + "name": "Date de début", + "description": "Date de début du mode vacance, au format YYYY-MM-DD." + }, + "end_date": { + "name": "Date de fin", + "description": "Date de fin du mode vacance, au format YYYY-MM-DD." + }, + "temperature": { + "name": "Température désirée", + "description": "Température désirée pendant que le mode vacance est actif." + } + } + }, + "set_quick_veto": { + "name": "Activer quick veto", + "description": "Activer un quick veto pour une zone ou pièce.", + "fields": { + "entity_id": { + "name": "Entity id", + "description": "Entity id de la zone ou de la pièce pour laquelle le quick veto est souhaité." + }, + "temperature": { + "name": "Température désirée", + "description": "Température désirée pour le quick veto." + }, + "duration": { + "name": "Durée", + "description": "Durée (en minutes) du quick veto. Min 30min, max 1440 (24 hours). Si non spécifié, la durée par défaut est appliquée." + } + } + }, + "remove_quick_veto": { + "name": "Supprimer quick veto", + "description": "Supprimer un quick veto pour une zone ou pièce.", + "fields": { + "entity_id": { + "name": "Entity id", + "description": "Entity id de la zone ou de la pièce pour laquelle le quick veto doit être supprimé." + } + } + }, + "request_hvac_update": { + "name": "Demande de mise à jour HVAC", + "description": "Rafraichit les données du système." + }, + "set_ventilation_day_level": { + "name": "Modifier la ventilation en mode jour", + "description": "Modifier la ventilation en mode jour.", + "fields": { + "entity_id": { + "name": "Entity id", + "description": "Entity id de la ventilation." + }, + "level": { + "name": "Niveau", + "description": "Le niveau souhaité." + } + } + }, + "set_ventilation_night_level": { + "name": "Modifier la ventilation en mode nuit", + "description": "Modifier la ventilation en mode nuit.", + "fields": { + "entity_id": { + "name": "Entity id", + "description": "Entity id de la ventilation." + }, + "level": { + "name": "Niveau", + "description": "Le niveau souhaité." + } + } + }, + "set_datetime": { + "name": "Modifier la date et de l'heure", + "description": "Modifier la date et de l'heure", + "fields": { + "datetime": { + "name": "Date & heure", + "description": "Date & heure souhaitée." + } + } + } } } \ No newline at end of file diff --git a/custom_components/multimatic/water_heater.py b/custom_components/multimatic/water_heater.py deleted file mode 100644 index 3850f4d..0000000 --- a/custom_components/multimatic/water_heater.py +++ /dev/null @@ -1,177 +0,0 @@ -"""Interfaces with multimatic water heater.""" -import logging -from typing import Any - -from pymultimatic.model import HotWater, OperatingModes, QuickModes - -from homeassistant.components.water_heater import ( - DOMAIN, - UnitOfTemperature, - WaterHeaterEntity, - WaterHeaterEntityFeature, -) -from homeassistant.config_entries import ConfigEntry -from homeassistant.const import ATTR_TEMPERATURE -from homeassistant.core import HomeAssistant -from homeassistant.helpers.entity_platform import AddEntitiesCallback - -from .const import DHW -from .coordinator import MultimaticCoordinator -from .entities import MultimaticEntity -from .utils import get_coordinator - -_LOGGER = logging.getLogger(__name__) - -SUPPORTED_FLAGS = ( - WaterHeaterEntityFeature.TARGET_TEMPERATURE - | WaterHeaterEntityFeature.OPERATION_MODE - | WaterHeaterEntityFeature.AWAY_MODE -) -ATTR_CURRENT_TEMPERATURE = "current_temperature" -ATTR_TIME_PROGRAM = "time_program" - -AWAY_MODES = [ - OperatingModes.OFF, - QuickModes.HOLIDAY, - QuickModes.ONE_DAY_AWAY, - QuickModes.SYSTEM_OFF, -] - - -async def async_setup_entry( - hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback -) -> None: - """Set up water_heater platform.""" - entities = [] - coordinator = get_coordinator(hass, DHW, entry.entry_id) - - if coordinator.data and coordinator.data.hotwater: - entities.append(MultimaticWaterHeater(coordinator)) - - _LOGGER.info("Adding %s water heater entities", len(entities)) - - async_add_entities(entities) - - -class MultimaticWaterHeater(MultimaticEntity, WaterHeaterEntity): - """Represent the multimatic water heater.""" - - def __init__(self, coordinator: MultimaticCoordinator) -> None: - """Initialize entity.""" - super().__init__(coordinator, DOMAIN, coordinator.data.hotwater.id) - self._operations = {mode.name: mode for mode in HotWater.MODES} - self._name = coordinator.data.hotwater.name - - @property - def name(self) -> str: - """Return the name of the entity.""" - return self._name - - @property - def component(self): - """Return multimatic component.""" - return self.coordinator.data.hotwater - - @property - def active_mode(self): - """Return multimatic component's active mode.""" - return self.coordinator.api.get_active_mode(self.component) - - @property - def supported_features(self) -> WaterHeaterEntityFeature: - """Return the list of supported features. - - !! It could be misleading here, since when heater is not heating, - target temperature is fixed (5 °C) - The API doesn't allow to change - this setting. It means if the user wants to change the target - temperature, it will always be the target temperature when the - heater is on function. See example below: - - 1. Target temperature when heater is off is 5 (this is a fixed - setting) - 2. Target temperature when heater is on is for instance 50 (this is a - configurable setting) - 3. While heater is off, user changes target_temperature to 45. It will - actually change the target temperature from 50 to 45 - 4. While heater is off, user will still see 5 in UI - (even if he changes to 45 before) - 5. When heater will go on, user will see the target temperature he set - at point 3 -> 45. - - Maybe I can remove the SUPPORT_TARGET_TEMPERATURE flag if the heater - 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) - """ - return SUPPORTED_FLAGS - - @property - def available(self) -> bool: - """Return True if entity is available.""" - return super().available and self.component is not None - - @property - def temperature_unit(self) -> str: - """Return the unit of measurement used by the platform.""" - return UnitOfTemperature.CELSIUS - - @property - def target_temperature(self) -> float: - """Return the temperature we try to reach.""" - return self.active_mode.target - - @property - def current_temperature(self) -> float: - """Return the current temperature.""" - return self.component.temperature - - @property - def min_temp(self) -> float: - """Return the minimum temperature.""" - return HotWater.MIN_TARGET_TEMP - - @property - def max_temp(self) -> float: - """Return the maximum temperature.""" - return HotWater.MAX_TARGET_TEMP - - @property - def current_operation(self) -> str: - """Return current operation ie. eco, electric, performance, ...""" - return self.active_mode.current.name - - @property - def operation_list(self) -> list[str]: - """Return current operation ie. eco, electric, performance, ...""" - if self.active_mode.current != QuickModes.HOLIDAY: - return list(self._operations.keys()) - return [] - - @property - def is_away_mode_on(self) -> bool: - """Return true if away mode is on.""" - return self.active_mode.current in AWAY_MODES - - async def async_set_temperature(self, **kwargs: Any) -> None: - """Set new target temperature.""" - target_temp = kwargs.get(ATTR_TEMPERATURE) - await self.coordinator.api.set_hot_water_target_temperature(self, target_temp) - - async def async_set_operation_mode(self, operation_mode: str) -> None: - """Set new target operation mode.""" - if operation_mode in self._operations: - mode = self._operations[operation_mode] - await self.coordinator.api.set_hot_water_operating_mode(self, mode) - else: - _LOGGER.debug("Operation mode %s is unknown", operation_mode) - - async def async_turn_away_mode_on(self) -> None: - """Turn away mode on.""" - await self.coordinator.api.set_hot_water_operating_mode( - self, OperatingModes.OFF - ) - - async def async_turn_away_mode_off(self) -> None: - """Turn away mode off.""" - await self.coordinator.api.set_hot_water_operating_mode( - self, OperatingModes.AUTO - )