Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add support for AtlanticHeatRecoveryVentilation #379

Merged
merged 36 commits into from
Jun 14, 2022
Merged
Show file tree
Hide file tree
Changes from 15 commits
Commits
Show all changes
36 commits
Select commit Hold shift + click to select a range
00293be
First try for AtlanticHeatRecoveryVentilation
iMicknl Feb 11, 2021
dd5bd2c
Bugfix
iMicknl Feb 11, 2021
4d0b816
Bugfix for sensor
iMicknl Mar 10, 2021
dc9ff4f
Bugfix
iMicknl Mar 10, 2021
7cebb62
Merge branch 'master' into feature/AtlanticHeatRecoveryVentilation
iMicknl Mar 10, 2021
619762d
Fix merge conflict
iMicknl Mar 10, 2021
66a200d
Merge branch 'master' into feature/AtlanticHeatRecoveryVentilation
iMicknl Mar 10, 2021
72529fb
Bugfix
iMicknl Mar 10, 2021
907074d
Add support for temperature
iMicknl Mar 10, 2021
dc04002
Merge branch 'master' into feature/AtlanticHeatRecoveryVentilation
iMicknl Apr 25, 2021
2102846
fixes for "Add support for AtlanticHeatRecoveryVentilation" (#438)
tillstaff May 27, 2021
da8785d
Merge branch 'master' into feature/AtlanticHeatRecoveryVentilation
iMicknl May 28, 2021
d541632
Clean up code
iMicknl May 28, 2021
1aae48c
Small improvements
iMicknl May 28, 2021
af7e54f
Bugfixes
iMicknl May 28, 2021
e2325ec
Merge branch 'master' into feature/AtlanticHeatRecoveryVentilation
iMicknl Aug 2, 2021
0cbc603
Merge branch 'master' into feature/AtlanticHeatRecoveryVentilation
vlebourl Feb 9, 2022
22d9e7d
fix linter error
vlebourl Feb 9, 2022
2ea0197
Merge branch 'master' into feature/AtlanticHeatRecoveryVentilation
iMicknl Feb 20, 2022
a1b0d48
Bring back up to speed
iMicknl Feb 20, 2022
85fccd3
Bugfixes
iMicknl Feb 20, 2022
34504c0
More fixes
iMicknl Feb 20, 2022
83f55b7
Use constants
iMicknl Feb 20, 2022
083af52
New standards
iMicknl Feb 20, 2022
8a1eeb0
Bugfix
iMicknl Feb 21, 2022
1737700
Merge branch 'master' into feature/AtlanticHeatRecoveryVentilation
iMicknl Feb 21, 2022
10b395a
Merge branch 'master' into feature/AtlanticHeatRecoveryVentilation
iMicknl Feb 21, 2022
39f7716
Update atlantic_heat_recovery_ventilation.py
iMicknl Feb 22, 2022
f392e21
Black fix
iMicknl Feb 22, 2022
f0aff74
Merge branch 'master' into feature/AtlanticHeatRecoveryVentilation
vlebourl Feb 23, 2022
6de75b1
Update custom_components/tahoma/climate_devices/atlantic_heat_recover…
vlebourl Feb 23, 2022
c2b98a6
Bugfix
iMicknl Feb 23, 2022
afc401f
Update custom_components/tahoma/climate_devices/atlantic_heat_recover…
iMicknl Apr 2, 2022
af47b3e
Merge branch 'master' into feature/AtlanticHeatRecoveryVentilation
iMicknl Apr 2, 2022
eb4621d
Update atlantic_heat_recovery_ventilation.py
iMicknl Apr 2, 2022
3636a56
Merge branch 'master' into feature/AtlanticHeatRecoveryVentilation
iMicknl Jun 14, 2022
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions custom_components/tahoma/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
from homeassistant.components.climate import DOMAIN as CLIMATE

from .climate_devices.atlantic_electrical_heater import AtlanticElectricalHeater
from .climate_devices.atlantic_heat_recovery_ventilation import (
AtlanticHeatRecoveryVentilation,
)
from .climate_devices.atlantic_pass_apcdhw import AtlanticPassAPCDHW
from .climate_devices.dimmer_exterior_heating import DimmerExteriorHeating
from .climate_devices.evo_home_controller import EvoHomeController
Expand All @@ -16,6 +19,7 @@

TYPE = {
"AtlanticElectricalHeater": AtlanticElectricalHeater,
"AtlanticHeatRecoveryVentilation": AtlanticHeatRecoveryVentilation,
"HitachiAirToWaterHeatingZone": HitachiAirToWaterHeatingZone,
"SomfyThermostat": SomfyThermostat,
"DimmerExteriorHeating": DimmerExteriorHeating,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,247 @@
"""Support for AtlanticHeatRecoveryVentilation."""
import logging
from typing import List, Optional

from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
FAN_AUTO,
HVAC_MODE_FAN_ONLY,
SUPPORT_FAN_MODE,
SUPPORT_PRESET_MODE,
)
from homeassistant.const import EVENT_HOMEASSISTANT_START, STATE_UNKNOWN, TEMP_CELSIUS
from homeassistant.core import callback
from homeassistant.helpers.event import async_track_state_change

from ..coordinator import TahomaDataUpdateCoordinator
from ..tahoma_entity import TahomaEntity
iMicknl marked this conversation as resolved.
Show resolved Hide resolved

FAN_BOOST = "home_boost"
FAN_KITCHEN = "kitchen_boost"
FAN_AWAY = "away"
FAN_BYPASS = "bypass_boost"

PRESET_AUTO = "auto"
PRESET_PROG = "prog"
PRESET_MANUAL = "manual"

COMMAND_SET_AIR_DEMAND_MODE = "setAirDemandMode"
COMMAND_SET_VENTILATION_CONFIGURATION_MODE = "setVentilationConfigurationMode"
COMMAND_SET_VENTILATION_MODE = "setVentilationMode"
COMMAND_REFRESH_VENTILATION_STATE = "refreshVentilationState"
COMMAND_REFRESH_VENTILATION_CONFIGURATION_MODE = "refreshVentilationConfigurationMode"

IO_AIR_DEMAND_MODE_STATE = "io:AirDemandModeState"
IO_VENTILATION_MODE_STATE = "io:VentilationModeState"
IO_VENTILATION_CONFIGURATION_MODE_STATE = "io:VentilationConfigurationModeState"
iMicknl marked this conversation as resolved.
Show resolved Hide resolved

TAHOMA_TO_FAN_MODES = {
"auto": FAN_AUTO,
"away": FAN_BOOST,
"boost": FAN_KITCHEN,
"high": FAN_AWAY,
None: FAN_BYPASS,
}

FAN_MODES_TO_TAHOMA = {v: k for k, v in TAHOMA_TO_FAN_MODES.items()}

PRESET_MODES = [PRESET_AUTO, PRESET_PROG, PRESET_MANUAL]

_LOGGER = logging.getLogger(__name__)


class AtlanticHeatRecoveryVentilation(TahomaEntity, ClimateEntity):
"""Representation of a AtlanticHeatRecoveryVentilation device."""

def __init__(self, device_url: str, coordinator: TahomaDataUpdateCoordinator):
iMicknl marked this conversation as resolved.
Show resolved Hide resolved
"""Init method."""
super().__init__(device_url, coordinator)

self._temp_sensor_entity_id = None
self._current_temperature = None

async def async_added_to_hass(self):
"""Register temperature sensor after added to hass."""
await super().async_added_to_hass()

base_url = self.get_base_device_url()
entity_registry = await self.hass.helpers.entity_registry.async_get_registry()
self._temp_sensor_entity_id = next(
(
entity_id
for entity_id, entry in entity_registry.entities.items()
if entry.unique_id == f"{base_url}#4"
),
None,
)

if self._temp_sensor_entity_id:
async_track_state_change(
self.hass, self._temp_sensor_entity_id, self._async_temp_sensor_changed
)

else:
_LOGGER.warning(
"Temperature sensor could not be found for entity %s", self.name
)

@callback
def _async_startup(event):
"""Init on startup."""
if self._temp_sensor_entity_id:
temp_sensor_state = self.hass.states.get(self._temp_sensor_entity_id)
if temp_sensor_state and temp_sensor_state.state != STATE_UNKNOWN:
self.update_temp(temp_sensor_state)

self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_START, _async_startup)

self.schedule_update_ha_state(True)

async def _async_temp_sensor_changed(self, entity_id, old_state, new_state) -> None:
"""Handle temperature changes."""
if new_state is None or old_state == new_state:
return

self.update_temp(new_state)
self.schedule_update_ha_state()

@callback
def update_temp(self, state):
"""Update thermostat with latest state from sensor."""
if state is None or state.state == STATE_UNKNOWN:
return

try:
self._current_temperature = float(state.state)
except ValueError as ex:
_LOGGER.error("Unable to update from sensor: %s", ex)

@property
def current_temperature(self) -> Optional[float]:
"""Return the current temperature."""
return self._current_temperature

@property
def temperature_unit(self) -> str:
"""Return the unit of measurement used by the platform."""
return TEMP_CELSIUS

@property
def supported_features(self) -> int:
"""Flag supported features."""
return SUPPORT_PRESET_MODE | SUPPORT_FAN_MODE

@property
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode."""
return HVAC_MODE_FAN_ONLY

@property
def hvac_modes(self) -> List[str]:
"""Return the list of available hvac operation modes."""
return [HVAC_MODE_FAN_ONLY]

async def async_set_hvac_mode(self, hvac_mode: str) -> None:
iMicknl marked this conversation as resolved.
Show resolved Hide resolved
"""Not implemented since there is only one hvac_mode."""

@property
def preset_mode(self) -> Optional[str]:
"""Return the current preset mode, e.g., auto, smart, interval, favorite."""
state_ventilation_configuration = self.select_state(
IO_VENTILATION_CONFIGURATION_MODE_STATE
)
state_ventilation_mode = self.select_state(IO_VENTILATION_MODE_STATE)
state_prog = state_ventilation_mode.get("prog")

if state_prog == "on":
return PRESET_PROG

if state_ventilation_configuration == "comfort":
return PRESET_AUTO

if state_ventilation_configuration == "standard":
return PRESET_MANUAL

return None

@property
def preset_modes(self) -> Optional[List[str]]:
"""Return a list of available preset modes."""
return PRESET_MODES

async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode of the fan."""
if preset_mode == PRESET_AUTO:
await self.async_execute_command(
COMMAND_SET_VENTILATION_CONFIGURATION_MODE, "comfort"
)
await self._set_ventilation_mode(prog="off")

if preset_mode == PRESET_PROG:
await self.async_execute_command(
COMMAND_SET_VENTILATION_CONFIGURATION_MODE, "standard"
)
await self._set_ventilation_mode(prog="on")

if preset_mode == PRESET_MANUAL:
await self.async_execute_command(
COMMAND_SET_VENTILATION_CONFIGURATION_MODE, "standard"
)
await self._set_ventilation_mode(prog="off")

await self.async_execute_command(
COMMAND_REFRESH_VENTILATION_STATE,
)
await self.async_execute_command(
COMMAND_REFRESH_VENTILATION_CONFIGURATION_MODE,
)

@property
def fan_mode(self) -> Optional[str]:
"""Return the fan setting."""
ventilation_mode_state = self.select_state(IO_VENTILATION_MODE_STATE)
cooling = ventilation_mode_state.get("cooling")

if cooling == "on":
return FAN_BYPASS
else:
return TAHOMA_TO_FAN_MODES[self.select_state(IO_AIR_DEMAND_MODE_STATE)]

@property
def fan_modes(self) -> Optional[List[str]]:
"""Return the list of available fan modes."""
return [*FAN_MODES_TO_TAHOMA]

async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
if fan_mode == FAN_BYPASS:
await self.async_execute_command(COMMAND_SET_AIR_DEMAND_MODE, "auto")
iMicknl marked this conversation as resolved.
Show resolved Hide resolved
else:
await self.async_execute_command(
COMMAND_SET_AIR_DEMAND_MODE, FAN_MODES_TO_TAHOMA[fan_mode]
iMicknl marked this conversation as resolved.
Show resolved Hide resolved
)

await self.async_execute_command(
COMMAND_REFRESH_VENTILATION_STATE,
)
await self.async_execute_command(
COMMAND_REFRESH_VENTILATION_CONFIGURATION_MODE,
)

async def _set_ventilation_mode(
self,
cooling=None,
prog=None,
):
"""Execute ventilation mode command with all parameters."""
ventilation_mode_state = self.select_state(IO_VENTILATION_MODE_STATE)

if cooling:
ventilation_mode_state["cooling"] = cooling

if prog:
ventilation_mode_state["prog"] = prog

await self.async_execute_command(
COMMAND_SET_VENTILATION_MODE, ventilation_mode_state
)
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Support for AtlanticPassAPCDHWComponement."""
"""Support for AtlanticPassAPCDHW."""
from typing import List, Optional

from homeassistant.components.climate import (
Expand Down
1 change: 1 addition & 0 deletions custom_components/tahoma/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@
"AirSensor": SENSOR,
"Alarm": ALARM_CONTROL_PANEL,
"AtlanticElectricalHeater": CLIMATE, # widgetName, uiClass is HeatingSystem (not supported)
"AtlanticHeatRecoveryVentilation": CLIMATE, # widgetName, uiClass is VentilationSystem (not supported)
"AtlanticPassAPCDHW": CLIMATE, # widgetName, uiClass is WaterHeatingSystem (not supported)
"Awning": COVER,
"CarButtonSensor": BINARY_SENSOR,
Expand Down