Skip to content

Commit

Permalink
Add experimental convector heater support
Browse files Browse the repository at this point in the history
  • Loading branch information
danielgospodinow authored and Linux User committed Dec 2, 2024
1 parent fa9c029 commit 42f2242
Show file tree
Hide file tree
Showing 6 changed files with 335 additions and 4 deletions.
173 changes: 173 additions & 0 deletions custom_components/eldom/climate.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,173 @@
"""Platform for Eldom water heater integration."""

import logging
from typing import Any

from homeassistant.components.climate import (
ClimateEntity,
ClimateEntityFeature,
HVACMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfTemperature
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity

from .const import (
DEVICE_TYPE_CONVECTOR_HEATER,
DEVICE_TYPE_MAPPING,
DOMAIN,
MANUFACTURER_NAME,
)
from .coordinator import EldomCoordinator
from .eldom_convector import EldomConvectorHeater
from .models import EldomData

SUPPORT_FLAGS_CLIMATE = (
ClimateEntityFeature.TARGET_TEMPERATURE,
ClimateEntityFeature.TURN_ON,
ClimateEntityFeature.TURN_OFF,
)

TEMP_UNIT = UnitOfTemperature.CELSIUS

_LOGGER = logging.getLogger(__name__)


async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Eldom convector heater setup."""

eldom_data: EldomData = hass.data[DOMAIN][entry.entry_id]

await eldom_data.coordinator.async_config_entry_first_refresh()

async_add_entities(
EldomConvectorHeaterEntity(convector_heater, eldom_data.coordinator)
for convector_heater in eldom_data.coordinator.data.get(
DEVICE_TYPE_CONVECTOR_HEATER
).values()
)


class EldomConvectorHeaterEntity(ClimateEntity, CoordinatorEntity):
"""Representation of an Eldom convector heater.
The CoordinatorEntity class provides:
should_poll
async_update
async_added_to_hass
available
"""

def __init__(
self, convector_heater: EldomConvectorHeater, coordinator: EldomCoordinator
) -> None:
"""Initialize an Eldom water heater."""
super().__init__(coordinator)

self._convector_heater = convector_heater

@property
def device_info(self) -> DeviceInfo:
"""Return device information about this convector heater."""
return DeviceInfo(
name=self._convector_heater.name,
identifiers={(DOMAIN, self._convector_heater.device_id)},
manufacturer=MANUFACTURER_NAME,
model=DEVICE_TYPE_MAPPING.get(self._convector_heater.type),
sw_version=self._convector_heater.software_version,
hw_version=self._convector_heater.hardware_version,
)

@property
def unique_id(self) -> str:
"""Return a unique ID."""
return self._convector_heater.device_id

@property
def name(self) -> str:
"""Return the name of the convector heater."""
return self._convector_heater.name

@property
def supported_features(self) -> ClimateEntityFeature:
"""Return the list of supported features."""
return SUPPORT_FLAGS_CLIMATE

@property
def temperature_unit(self) -> str:
"""Return the unit of measurement."""
return TEMP_UNIT

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

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

@property
def current_temperature(self) -> float | None:
"""Return the current temperature."""
return self._convector_heater.current_temperature

@property
def target_temperature(self) -> float | None:
"""Return the temperature we try to reach."""
return self._convector_heater.target_temperature

@property
def hvac_mode(self) -> HVACMode | None:
"""Return current operation ie. Off or Heating."""
return self._convector_heater.current_operation

@property
def hvac_modes(self) -> list[HVACMode]:
"""Return the list of available operation modes."""
return self._convector_heater.operation_modes

async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the convector heater on."""
await self._convector_heater.turn_on()
self.schedule_update_ha_state()
await self.coordinator.async_request_refresh()

async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the convector heater off."""
await self._convector_heater.turn_off()
self.schedule_update_ha_state()
await self.coordinator.async_request_refresh()

async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new target operation mode."""
try:
await self._convector_heater.set_operation_mode(hvac_mode)
self.schedule_update_ha_state()
await self.coordinator.async_request_refresh()
except Exception as e:
_LOGGER.error("Error while setting operation mode: %s", e)
raise HomeAssistantError("Error while setting operation mode") from e

async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
temperature = kwargs.get("temperature")
await self._convector_heater.set_temperature(temperature)
self.schedule_update_ha_state()
await self.coordinator.async_request_refresh()

@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self._convector_heater = self.coordinator.data.get(
self._convector_heater.type
).get(self._convector_heater.id)

self.async_write_ha_state()
2 changes: 2 additions & 0 deletions custom_components/eldom/const.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,10 @@

DEVICE_TYPE_FLAT_BOILER = 7
DEVICE_TYPE_SMART_BOILER = 5
DEVICE_TYPE_CONVECTOR_HEATER = 4

DEVICE_TYPE_MAPPING = {
DEVICE_TYPE_FLAT_BOILER: "Flat Boiler",
DEVICE_TYPE_SMART_BOILER: "Smart Boiler",
DEVICE_TYPE_CONVECTOR_HEATER: "Convector Heater",
}
19 changes: 18 additions & 1 deletion custom_components/eldom/coordinator.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,14 @@
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator

from .const import DEVICE_TYPE_FLAT_BOILER, DEVICE_TYPE_SMART_BOILER, DOMAIN
from .const import (
DEVICE_TYPE_CONVECTOR_HEATER,
DEVICE_TYPE_FLAT_BOILER,
DEVICE_TYPE_SMART_BOILER,
DOMAIN,
)
from .eldom_boiler import FlatEldomBoiler, SmartEldomBoiler
from .eldom_convector import EldomConvectorHeater

_LOGGER = logging.getLogger(__name__)

Expand Down Expand Up @@ -47,7 +53,18 @@ async def _async_update_data(self) -> dict:
if device.deviceType == DEVICE_TYPE_SMART_BOILER
}

convector_heaters: dict[str, EldomConvectorHeater] = {
device.id: EldomConvectorHeater(
device.id,
await self.api.get_convector_heater_status(device.id),
self.api,
)
for device in devices
if device.deviceType == DEVICE_TYPE_CONVECTOR_HEATER
}

return {
DEVICE_TYPE_FLAT_BOILER: flat_boilers,
DEVICE_TYPE_SMART_BOILER: smart_boilers,
DEVICE_TYPE_CONVECTOR_HEATER: convector_heaters,
}
2 changes: 1 addition & 1 deletion custom_components/eldom/eldom_boiler.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"""Eldom objects."""
"""Eldom boiler objects."""

from abc import ABC, abstractmethod
import logging
Expand Down
139 changes: 139 additions & 0 deletions custom_components/eldom/eldom_convector.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,139 @@
"""Eldom convector heater objects."""

import logging

from eldom.client import Client as EldomClient
from eldom.models import ConvectorHeaterDetails

from homeassistant.components.climate import HVACMode

OPERATION_MODES = {0: HVACMode.OFF, 1: HVACMode.HEAT}

MAX_TEMP = 35
MIN_TEMP = 5

_LOGGER = logging.getLogger(__name__)


class EldomConvectorHeater:
"""An Eldom convector heater representation object."""

def __init__(
self,
id: int,
convector_heater_details: ConvectorHeaterDetails,
eldom_client: EldomClient,
) -> None:
"""Initialize the heater."""
self._id = id
self._convector_heater_details = convector_heater_details
self._eldom_client = eldom_client

@property
def id(self) -> int:
"""Retrieve the heater's ID."""
return self._id

@property
def device_id(self) -> str:
"""Retrieve the heater's device ID."""
return self._convector_heater_details.DeviceID

@property
def name(self) -> str:
"""Retrieve the heater's name."""
return f"Convector Heater ({self._convector_heater_details.DeviceID[-4:]})"

@property
def type(self) -> int:
"""Retrieve the heater's type."""
return self._convector_heater_details.Type

@property
def software_version(self) -> str:
"""Retrieve the heater's software version."""
return self._convector_heater_details.SoftwareVersion

@property
def hardware_version(self) -> str:
"""Retrieve the heater's hardware version."""
return self._convector_heater_details.HardwareVersion

@property
def operation_modes(self) -> list[HVACMode]:
"""Retrieve the heater's operation modes. Modes are: Off, Heat."""
return list(OPERATION_MODES.values())

@property
def max_temperature(self) -> float:
"""Retrieve the heater's maximum temperature."""
return MAX_TEMP

@property
def min_temperature(self) -> float:
"""Retrieve the heater's minimum temperature."""
return MIN_TEMP

@property
def current_temperature(self) -> float:
"""Retrieve the heater's current temperature."""
return self._convector_heater_details.AmbientTemp

@property
def target_temperature(self) -> float:
"""Retrieve the heater's target temperature."""
return self._convector_heater_details.SetTemp

@property
def powerful_enabled(self) -> bool:
"""Retrieve whether the heater's powerful mode is enabled."""
return self._convector_heater_details.BoostHeating

@property
def day_energy_consumption(self) -> float:
"""Retrieve the heater's day energy consumption."""
return self._convector_heater_details.EnergyD

@property
def night_energy_consumption(self) -> float:
"""Retrieve the heater's night energy consumption."""
return self._convector_heater_details.EnergyN

@property
def current_operation(self) -> HVACMode:
"""Return current operation ie. Off or Heat."""
return OPERATION_MODES.get(self._convector_heater_details.State, "Unknown")

@property
def power_level(self) -> int:
"""Retrieve the heating level of the heater."""
return self._convector_heater_details.Power

async def turn_on(self) -> None:
"""Turn the heater on."""
await self.set_operation_mode(HVACMode.HEAT)

async def turn_off(self) -> None:
"""Turn the heater off."""
await self.set_operation_mode(HVACMode.OFF)

async def set_operation_mode(self, operation_mode: HVACMode) -> None:
"""Set new target operation mode."""
if operation_mode not in OPERATION_MODES.values():
raise ValueError("Operation mode not supported")

operation_mode_id = {v: k for k, v in OPERATION_MODES.items()}[operation_mode]

self._convector_heater_details.State = operation_mode_id

await self._eldom_client.set_convector_heater_state(
self.device_id, operation_mode_id
)

async def set_temperature(self, temperature: float) -> None:
"""Set the temperature of the heater."""
self._convector_heater_details.SetTemp = temperature

await self._eldom_client.set_convector_heater_temperature(
self.device_id, temperature
)
4 changes: 2 additions & 2 deletions custom_components/eldom/manifest.json
Original file line number Diff line number Diff line change
Expand Up @@ -8,8 +8,8 @@
"issue_tracker": "https://github.com/qbaware/homeassistant-eldom/issues",
"homekit": {},
"iot_class": "cloud_polling",
"requirements": ["pyeldom==0.3.0"],
"requirements": ["pyeldom==0.4.1"],
"ssdp": [],
"zeroconf": [],
"version": "2.2.10"
"version": "3.0.0"
}

0 comments on commit 42f2242

Please sign in to comment.