55import logging
66from typing import Any
77
8+ from PyViCare .PyViCareRadiatorActuator import RadiatorActuator
89from PyViCare .PyViCareUtils import (
910 PyViCareCommandError ,
1011 PyViCareInvalidDataError ,
2627from homeassistant .config_entries import ConfigEntry
2728from homeassistant .const import (
2829 ATTR_TEMPERATURE ,
30+ PRECISION_HALVES ,
2931 PRECISION_TENTHS ,
3032 PRECISION_WHOLE ,
3133 UnitOfTemperature ,
@@ -120,6 +122,7 @@ async def async_setup_entry(
120122 api = device .asAutoDetectDevice ()
121123
122124 circuits = await hass .async_add_executor_job (get_circuits , api )
125+ # Devices with circuits will get one climate entity per circuit
123126 for circuit in circuits :
124127 suffix = ""
125128 if len (circuits ) > 1 :
@@ -133,6 +136,17 @@ async def async_setup_entry(
133136 )
134137 entities .append (entity )
135138
139+ # RadiatorActuator have no circuits but also create a climate entity
140+ if isinstance (api , RadiatorActuator ):
141+ entity = ViCareThermostat (
142+ f"{ name } RadiatorActuator{ suffix } " ,
143+ api ,
144+ device ,
145+ )
146+ entities .append (entity )
147+
148+
149+
136150 platform = entity_platform .async_get_current_platform ()
137151
138152 platform .async_register_entity_service (
@@ -187,6 +201,7 @@ def __init__(self, name, api, circuit, device_config):
187201 self ._current_temperature = None
188202 self ._current_program = None
189203 self ._current_action = None
204+ self .update ()
190205
191206 @property
192207 def unique_id (self ) -> str :
@@ -406,3 +421,111 @@ def set_vicare_mode(self, vicare_mode):
406421 def set_heating_curve (self , shift , slope ):
407422 """Service function to set vicare heating curve directly."""
408423 self ._circuit .setHeatingCurve (int (shift ), round (float (slope ), 1 ))
424+
425+
426+ class ViCareThermostat (ClimateEntity ):
427+ """Representation of the ViCare heating climate device."""
428+
429+ _attr_precision = PRECISION_TENTHS
430+ _attr_supported_features = (
431+ ClimateEntityFeature .TARGET_TEMPERATURE
432+ )
433+ _attr_temperature_unit = UnitOfTemperature .CELSIUS
434+
435+ def __init__ (self , name , api , device_config ):
436+ """Initialize the climate device."""
437+ self ._name = name
438+ self ._state = None
439+ self ._api = api
440+ self ._device_config = device_config
441+ self ._attributes = {}
442+ self ._target_temperature = None
443+ self ._current_mode = None
444+ self ._current_temperature = None
445+ self .update ()
446+
447+ @property
448+ def unique_id (self ) -> str :
449+ """Return unique ID for this device."""
450+ return get_unique_id (self ._api , self ._device_config , 0 )
451+
452+ @property
453+ def device_info (self ) -> DeviceInfo :
454+ """Return device info for this device."""
455+ return DeviceInfo (
456+ identifiers = {
457+ (
458+ DOMAIN ,
459+ get_unique_device_id (self ._device_config ),
460+ )
461+ },
462+ name = get_device_name (self ._device_config ),
463+ manufacturer = "Viessmann" ,
464+ model = self ._device_config .getModel (),
465+ configuration_url = "https://developer.viessmann.com/" ,
466+ )
467+
468+ def update (self ) -> None :
469+ """Let HA know there has been an update from the ViCare API."""
470+ try :
471+ _room_temperature = None
472+ with suppress (PyViCareNotSupportedFeatureError ):
473+ _room_temperature = self ._api .getTemperature ()
474+ self ._current_temperature = _room_temperature
475+
476+ with suppress (PyViCareNotSupportedFeatureError ):
477+ self ._target_temperature = self ._api .getTargetTemperature ()
478+
479+ # Update the generic device attributes
480+ self ._attributes = {}
481+ self ._attributes ["room_temperature" ] = _room_temperature
482+
483+ except requests .exceptions .ConnectionError :
484+ _LOGGER .error ("Unable to retrieve data from ViCare server" )
485+ except PyViCareRateLimitError as limit_exception :
486+ _LOGGER .error ("Vicare API rate limit exceeded: %s" , limit_exception )
487+ except ValueError :
488+ _LOGGER .error ("Unable to decode data from ViCare server" )
489+ except PyViCareInvalidDataError as invalid_data_exception :
490+ _LOGGER .error ("Invalid data from Vicare server: %s" , invalid_data_exception )
491+
492+ @property
493+ def name (self ):
494+ """Return the name of the climate device."""
495+ return self ._name
496+
497+ @property
498+ def current_temperature (self ):
499+ """Return the current temperature."""
500+ return self ._current_temperature
501+
502+ @property
503+ def target_temperature (self ):
504+ """Return the temperature we try to reach."""
505+ return self ._target_temperature
506+
507+ @property
508+ def hvac_mode (self ) -> HVACMode | None :
509+ """Return current hvac mode."""
510+ return HVACMode .AUTO
511+
512+ @property
513+ def hvac_modes (self ) -> list [HVACMode ]:
514+ """Return the list of available hvac modes."""
515+ return [HVACMode .AUTO ]
516+
517+ @property
518+ def target_temperature_step (self ) -> float :
519+ """Set target temperature step to wholes."""
520+ return PRECISION_HALVES
521+
522+ def set_temperature (self , ** kwargs : Any ) -> None :
523+ """Set new target temperatures."""
524+ if (temp := kwargs .get (ATTR_TEMPERATURE )) is not None :
525+ self ._api .setTargetTemperature (temp )
526+ self ._target_temperature = temp
527+
528+ @property
529+ def extra_state_attributes (self ):
530+ """Show Device Attributes."""
531+ return self ._attributes
0 commit comments