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 basic support for AtlanticPassAPCHeatingAndCoolingZone #380

Merged
merged 14 commits into from
Jul 14, 2021
6 changes: 6 additions & 0 deletions custom_components/tahoma/climate.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,10 @@
from .climate_devices.atlantic_electrical_towel_dryer import (
AtlanticElectricalTowelDryer,
)
from .climate_devices.atlantic_pass_apc_heating_and_cooling_zone import (
AtlanticPassAPCHeatingAndCoolingZone,
)
from .climate_devices.atlantic_pass_apc_zone_control import AtlanticPassAPCZoneControl
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 @@ -25,6 +29,8 @@
"AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint": AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint,
"AtlanticElectricalTowelDryer": AtlanticElectricalTowelDryer,
"AtlanticPassAPCDHW": AtlanticPassAPCDHW,
"AtlanticPassAPCHeatingAndCoolingZone": AtlanticPassAPCHeatingAndCoolingZone,
"AtlanticPassAPCZoneControl": AtlanticPassAPCZoneControl,
"DimmerExteriorHeating": DimmerExteriorHeating,
"EvoHomeController": EvoHomeController,
"HeatingSetPoint": HeatingSetPoint,
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,203 @@
"""Support for Atlantic Pass APC Heating And Cooling Zone."""
import logging
from typing import List, Optional

from homeassistant.components.climate import SUPPORT_TARGET_TEMPERATURE, ClimateEntity
from homeassistant.components.climate.const import (
HVAC_MODE_AUTO,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
)
from homeassistant.const import (
ATTR_TEMPERATURE,
EVENT_HOMEASSISTANT_START,
STATE_UNAVAILABLE,
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

_LOGGER = logging.getLogger(__name__)

COMMAND_REFRESH_OPERATING_MODE = "refreshOperatingMode"
COMMAND_REFRESH_PASS_APC_HEATING_PROFILE = "refreshPassAPCHeatingProfile"
COMMAND_REFRESH_TARGET_TEMPERATURE = "refreshTargetTemperature"
COMMAND_SET_HEATING_LEVEL = "setHeatingLevel"
COMMAND_SET_HEATING_ON_OFF_STATE = "setHeatingOnOffState"
COMMAND_SET_HEATING_TARGET_TEMPERATURE = "setHeatingTargetTemperature"
COMMAND_SET_OPERATING_MODE = "setOperatingMode"
COMMAND_SET_PASS_APC_HEATING_MODE = "setPassAPCHeatingMode"
COMMAND_SET_TARGET_TEMPERATURE = "setTargetTemperature"

CORE_HEATING_ON_OFF_STATE = "core:HeatingOnOffState"
CORE_HEATING_TARGET_TEMPERATURE_STATE = "core:HeatingTargetTemperatureState"
CORE_MINIMUM_HEATING_TARGET_TEMPERATURE_STATE = (
"core:MinimumHeatingTargetTemperatureState"
)
CORE_MAXIMUM_HEATING_TARGET_TEMPERATURE_STATE = (
"core:MaximumHeatingTargetTemperatureState"
)
CORE_ON_OFF_STATE = "core:OnOffState"
CORE_OPERATING_MODE_STATE = "core:OperatingModeState"
CORE_TARGET_TEMPERATURE_STATE = "core:TargetTemperatureState"

IO_PASS_APC_HEATING_MODE_STATE = "io:PassAPCHeatingModeState"
IO_TARGET_HEATING_LEVEL_STATE = "io:TargetHeatingLevelState"

# Map TaHoma HVAC modes to Home Assistant HVAC modes
TAHOMA_TO_HVAC_MODE = {
"stop": HVAC_MODE_OFF, # fallback
"off": HVAC_MODE_OFF,
"manu": HVAC_MODE_HEAT,
"auto": HVAC_MODE_AUTO, # fallback
"internalScheduling": HVAC_MODE_AUTO, # prog
}

HVAC_MODE_TO_TAHOMA = {v: k for k, v in TAHOMA_TO_HVAC_MODE.items()}


class AtlanticPassAPCHeatingAndCoolingZone(TahomaEntity, ClimateEntity):
"""Representation of Atlantic Pass APC Heating and Cooling Zone."""

def __init__(self, device_url: str, coordinator: TahomaDataUpdateCoordinator):
"""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()

# The linked temperature sensor uses subsystem_id + 1
new_subsystem_id = int(self.device_url.split("#", 1)[1]) + 1

self._temp_sensor_entity_id = next(
(
entity_id
for entity_id, entry in entity_registry.entities.items()
if entry.unique_id == f"{base_url}#{str(new_subsystem_id)}"
),
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 in [STATE_UNKNOWN, STATE_UNAVAILABLE]:
return

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

@property
def min_temp(self) -> float:
"""Return the minimum temperature."""
return self.select_state(CORE_MINIMUM_HEATING_TARGET_TEMPERATURE_STATE)

@property
def max_temp(self) -> float:
"""Return the maximum temperature."""
return self.select_state(CORE_MAXIMUM_HEATING_TARGET_TEMPERATURE_STATE)

@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:
"""Return the list of supported features."""
supported_features = 0
supported_features |= SUPPORT_TARGET_TEMPERATURE

return supported_features

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

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

if self.select_state(CORE_HEATING_ON_OFF_STATE) == "off":
return HVAC_MODE_OFF

return TAHOMA_TO_HVAC_MODE[self.select_state(IO_PASS_APC_HEATING_MODE_STATE)]

async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""

if hvac_mode == HVAC_MODE_OFF:
await self.async_execute_command(COMMAND_SET_HEATING_ON_OFF_STATE, "off")
else:
if self.hvac_mode == HVAC_MODE_OFF:
await self.async_execute_command(COMMAND_SET_HEATING_ON_OFF_STATE, "on")

await self.async_execute_command(
COMMAND_SET_PASS_APC_HEATING_MODE, HVAC_MODE_TO_TAHOMA[hvac_mode]
)

await self.async_execute_command(COMMAND_REFRESH_PASS_APC_HEATING_PROFILE)
await self.async_execute_command(COMMAND_REFRESH_OPERATING_MODE)

@property
def target_temperature(self) -> None:
"""Return the temperature."""
return self.select_state(CORE_HEATING_TARGET_TEMPERATURE_STATE)

async def async_set_temperature(self, **kwargs) -> None:
"""Set new temperature."""
temperature = kwargs.get(ATTR_TEMPERATURE)

await self.async_execute_command(
COMMAND_SET_HEATING_TARGET_TEMPERATURE, temperature
)
await self.async_execute_command(COMMAND_REFRESH_TARGET_TEMPERATURE)
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
"""Support for Atlantic Pass APC Zone Control."""
import logging
from typing import List

from homeassistant.components.climate import ClimateEntity
from homeassistant.components.climate.const import (
HVAC_MODE_COOL,
HVAC_MODE_DRY,
HVAC_MODE_HEAT,
HVAC_MODE_OFF,
)
from homeassistant.const import TEMP_CELSIUS

from ..tahoma_entity import TahomaEntity

_LOGGER = logging.getLogger(__name__)

COMMAND_SET_PASS_APC_OPERATING_MODE = "setPassAPCOperatingMode"

IO_PASS_APC_OPERATING_MODE_STATE = "io:PassAPCOperatingModeState"

# Map TaHoma HVAC modes to Home Assistant HVAC modes
TAHOMA_TO_HVAC_MODE = {
"heating": HVAC_MODE_HEAT,
"drying": HVAC_MODE_DRY,
"cooling": HVAC_MODE_COOL,
"stop": HVAC_MODE_OFF,
}

HVAC_MODE_TO_TAHOMA = {v: k for k, v in TAHOMA_TO_HVAC_MODE.items()}


class AtlanticPassAPCZoneControl(TahomaEntity, ClimateEntity):
"""Representation of Atlantic Pass APC Zone Control."""

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

@property
def supported_features(self) -> int:
"""Return the list of supported features."""
supported_features = 0

return supported_features

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

@property
def hvac_mode(self) -> str:
"""Return hvac operation ie. heat, cool mode."""
return TAHOMA_TO_HVAC_MODE[self.select_state(IO_PASS_APC_OPERATING_MODE_STATE)]

async def async_set_hvac_mode(self, hvac_mode: str) -> None:
"""Set new target hvac mode."""
await self.async_execute_command(
COMMAND_SET_PASS_APC_OPERATING_MODE, HVAC_MODE_TO_TAHOMA[hvac_mode]
)
2 changes: 2 additions & 0 deletions custom_components/tahoma/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -51,6 +51,8 @@
"AtlanticElectricalHeaterWithAdjustableTemperatureSetpoint": CLIMATE, # widgetName, uiClass is HeatingSystem (not supported)
"AtlanticElectricalTowelDryer": CLIMATE, # widgetName, uiClass is HeatingSystem (not supported)
"AtlanticPassAPCDHW": CLIMATE, # widgetName, uiClass is WaterHeatingSystem (not supported)
"AtlanticPassAPCHeatingAndCoolingZone": CLIMATE, # widgetName, uiClass is HeatingSystem (not supported)
"AtlanticPassAPCZoneControl": CLIMATE, # widgetName, uiClass is HeatingSystem (not supported)
"Awning": COVER,
"CarButtonSensor": BINARY_SENSOR,
"ConsumptionSensor": SENSOR,
Expand Down