From 301bc2d7067e69eb23b88e302999c02b68441e3b Mon Sep 17 00:00:00 2001 From: TLongstride Date: Sun, 19 Nov 2023 14:47:43 +0000 Subject: [PATCH 1/7] Add Output_status binary_sensor --- .../danfoss_ally/binary_sensor.py | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/custom_components/danfoss_ally/binary_sensor.py b/custom_components/danfoss_ally/binary_sensor.py index 63b1921..92ad6fa 100644 --- a/custom_components/danfoss_ally/binary_sensor.py +++ b/custom_components/danfoss_ally/binary_sensor.py @@ -166,6 +166,21 @@ async def async_setup_entry( ) ] ) + if "output_status" in ally.devices[device]: + _LOGGER.debug( + "Found output_status_detector for %s", ally.devices[device]["name"] + ) + entities.extend( + [ + AllyBinarySensor( + ally, + ally.devices[device]["name"], + device, + "output_status", + ally.devices[device]["model"], + ) + ] + ) if "adaptation_runstatus" in ally.devices[device]: _LOGGER.debug( "Found adaptation_runstatus for %s", ally.devices[device]["name"] @@ -261,6 +276,11 @@ def __init__(self, ally, name, device_id, device_type, model): self.entity_description = BinarySensorEntityDescription( key=4, icon="mdi:gas-burner", entity_registry_enabled_default=False ) + elif self._type == "output_status": + self._state = self._device["output_status"] + self.entity_description = BinarySensorEntityDescription( + key=4, icon="mdi:pipe-valve", entity_registry_enabled_default=False + ) elif self._type == "adaptation run status": self._state = bool(int(self._device["adaptation_runstatus"]) & 0x01) self.entity_description = BinarySensorEntityDescription( @@ -323,6 +343,8 @@ def device_class(self): return BinarySensorDeviceClass.HEAT elif self._type == "adaptation run status": return BinarySensorDeviceClass.RUNNING + elif self._type == "output_status": + return BinarySensorDeviceClass.OPENING return None @callback @@ -361,6 +383,8 @@ def _async_update_data(self): self._state = self._device["heat_supply_request"] elif self._type == "boiler relay": self._state = self._device["boiler_relay"] + elif self._type == "output_status": + self._state = self._device["output_status"] elif self._type == "adaptation run status": self._state = bool(int(self._device["adaptation_runstatus"]) & 0x01) elif self._type == "adaptation run valve characteristic found": From 57b8dc194619521d5dd70d95b31dd6c83e7b31da Mon Sep 17 00:00:00 2001 From: TLongstride Date: Sun, 19 Nov 2023 14:48:23 +0000 Subject: [PATCH 2/7] Correct Havc Action for Icon --- custom_components/danfoss_ally/climate.py | 12 ++++++++---- 1 file changed, 8 insertions(+), 4 deletions(-) diff --git a/custom_components/danfoss_ally/climate.py b/custom_components/danfoss_ally/climate.py index 73b09fe..b63a803 100644 --- a/custom_components/danfoss_ally/climate.py +++ b/custom_components/danfoss_ally/climate.py @@ -9,6 +9,7 @@ ATTR_HVAC_MODE, ATTR_PRESET_MODE, CURRENT_HVAC_HEAT, + CURRENT_HVAC_COOL, CURRENT_HVAC_IDLE, HVAC_MODE_AUTO, HVAC_MODE_HEAT, @@ -494,10 +495,13 @@ def hvac_action(self): """Return the current running hvac operation if supported. Need to be one of CURRENT_HVAC_*. """ - if "work_state" in self._device: - if self._device["work_state"] == "heat_active": - return CURRENT_HVAC_HEAT - elif self._device["work_state"] == "Heat": + if "output_status" in self._device: + if self._device["output_status"] == True: + if self._device["work_state"] == "Heat" or "heat_active": + return CURRENT_HVAC_HEAT + elif self._device["work_state"] == "Cool" or "cool_active": + return CURRENT_HVAC_COOL + elif self._device["output_status"] == False: return CURRENT_HVAC_IDLE From 015ca78dfcf436071e1c64982b8dca3291e9ad87 Mon Sep 17 00:00:00 2001 From: TLongstride Date: Tue, 21 Nov 2023 22:21:41 +0000 Subject: [PATCH 3/7] Climate enhancement --- custom_components/danfoss_ally/climate.py | 99 ++++++++++++++++++++++- 1 file changed, 96 insertions(+), 3 deletions(-) diff --git a/custom_components/danfoss_ally/climate.py b/custom_components/danfoss_ally/climate.py index b63a803..b99ffeb 100644 --- a/custom_components/danfoss_ally/climate.py +++ b/custom_components/danfoss_ally/climate.py @@ -13,6 +13,8 @@ CURRENT_HVAC_IDLE, HVAC_MODE_AUTO, HVAC_MODE_HEAT, + HVAC_MODE_COOL, + HVAC_MODE_OFF, PRESET_AWAY, PRESET_HOME, ClimateEntityFeature, @@ -490,6 +492,37 @@ def __init__( support_flags, ) + @property + def hvac_mode(self): + """Return hvac operation ie. heat, cool mode. + Need to be one of HVAC_MODE_*. + """ + if "mode" in self._device: + if ( + self._device["mode"] == "at_home" + or self._device["mode"] == "leaving_home" + or self._device["mode"] == "holiday_sat" + or self._device["mode"] == "holiday" + or self._device["mode"] == "pause" + ): + return HVAC_MODE_AUTO + elif ( + self._device["work_state"] == "Heat" + or self._device["work_state"] == "heat_active" + ): + if self._device["manual_mode_fast"] == self._device["lower_temp"]: + return HVAC_MODE_OFF + else: + return HVAC_MODE_HEAT + elif ( + self._device["work_state"] == "Cool" + or self._device["work_state"] == "cool_active" + ): + if self._device["manual_mode_fast"] == self._device["upper_temp"]: + return HVAC_MODE_OFF + else: + return HVAC_MODE_COOL + @property def hvac_action(self): """Return the current running hvac operation if supported. @@ -497,9 +530,15 @@ def hvac_action(self): """ if "output_status" in self._device: if self._device["output_status"] == True: - if self._device["work_state"] == "Heat" or "heat_active": + if ( + self._device["work_state"] == "Heat" + or self._device["work_state"] == "heat_active" + ): return CURRENT_HVAC_HEAT - elif self._device["work_state"] == "Cool" or "cool_active": + elif ( + self._device["work_state"] == "Cool" + or self._device["work_state"] == "cool_active" + ): return CURRENT_HVAC_COOL elif self._device["output_status"] == False: return CURRENT_HVAC_IDLE @@ -540,6 +579,50 @@ async def async_setup_entry( if entities: async_add_entities(entities, True) + @callback + def _async_update_callback(self): + """Load data and update state.""" + self._async_update_data() + self.async_write_ha_state() + + def set_hvac_mode(self, hvac_mode): + """Set new target hvac mode.""" + + _LOGGER.debug("Setting hvac mode to %s", hvac_mode) + + if hvac_mode == HVAC_MODE_AUTO: + mode = "at_home" # We have to choose either at_home or leaving_home + manual_set = self._device["at_home_setting"] + elif hvac_mode == HVAC_MODE_HEAT: + mode = "manual" + manual_set = self._device["leaving_home_setting"] + elif hvac_mode == HVAC_MODE_COOL: + mode = "manual" + manual_set = self._device["at_home_setting"] + elif hvac_mode == HVAC_MODE_OFF: + mode = "manual" + if ( + self._device["work_state"] == "heat_active" + or self._device["work_state"] == "Heat" + ): + manual_set = self._device["lower_temp"] + elif ( + self._device["work_state"] == "cool_active" + or self._device["work_state"] == "Cool" + ): + manual_set = self._device["upper_temp"] + + if mode is None: + return + + self._device["mode"] = mode # Update current copy of device data + self._device["manual_mode_fast"] = manual_set + self._ally.set_mode(self._device_id, mode) + self._ally.set_temperature(self._device_id, manual_set) + + # Update UI + self.async_write_ha_state() + def _generate_entities(ally: AllyConnector): """Create all climate entities.""" @@ -566,7 +649,17 @@ def create_climate_entity(ally, name: str, device_id: str, model: str) -> AllyCl support_flags = ( ClimateEntityFeature.TARGET_TEMPERATURE | ClimateEntityFeature.PRESET_MODE ) - supported_hvac_modes = [HVAC_MODE_AUTO, HVAC_MODE_HEAT] + + if model == "Icon RT": + supported_hvac_modes = [ + HVAC_MODE_AUTO, + HVAC_MODE_HEAT, + HVAC_MODE_COOL, + HVAC_MODE_OFF, + ] + else: + supported_hvac_modes = [HVAC_MODE_AUTO, HVAC_MODE_HEAT] + heat_min_temp = 4.5 heat_max_temp = 35.0 heat_step = 0.5 From ca19585b3f93b5aeb225f45c62e28a59d0056341 Mon Sep 17 00:00:00 2001 From: TLongstride Date: Wed, 22 Nov 2023 17:24:35 +0000 Subject: [PATCH 4/7] Clean Thermal Actuator Sensor --- custom_components/danfoss_ally/binary_sensor.py | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/custom_components/danfoss_ally/binary_sensor.py b/custom_components/danfoss_ally/binary_sensor.py index 92ad6fa..da08386 100644 --- a/custom_components/danfoss_ally/binary_sensor.py +++ b/custom_components/danfoss_ally/binary_sensor.py @@ -176,7 +176,7 @@ async def async_setup_entry( ally, ally.devices[device]["name"], device, - "output_status", + "Thermal actuator", ally.devices[device]["model"], ) ] @@ -276,10 +276,10 @@ def __init__(self, ally, name, device_id, device_type, model): self.entity_description = BinarySensorEntityDescription( key=4, icon="mdi:gas-burner", entity_registry_enabled_default=False ) - elif self._type == "output_status": + elif self._type == "Thermal actuator": self._state = self._device["output_status"] self.entity_description = BinarySensorEntityDescription( - key=4, icon="mdi:pipe-valve", entity_registry_enabled_default=False + key=4, icon="mdi:pipe-valve" ) elif self._type == "adaptation run status": self._state = bool(int(self._device["adaptation_runstatus"]) & 0x01) @@ -343,7 +343,7 @@ def device_class(self): return BinarySensorDeviceClass.HEAT elif self._type == "adaptation run status": return BinarySensorDeviceClass.RUNNING - elif self._type == "output_status": + elif self._type == "Thermal actuator": return BinarySensorDeviceClass.OPENING return None @@ -383,7 +383,7 @@ def _async_update_data(self): self._state = self._device["heat_supply_request"] elif self._type == "boiler relay": self._state = self._device["boiler_relay"] - elif self._type == "output_status": + elif self._type == "Thermal actuator": self._state = self._device["output_status"] elif self._type == "adaptation run status": self._state = bool(int(self._device["adaptation_runstatus"]) & 0x01) From 80fb0a0eefe6faf2b1492800184aebef81dc312c Mon Sep 17 00:00:00 2001 From: TLongstride <74832134+TLongstride@users.noreply.github.com> Date: Thu, 23 Nov 2023 13:24:57 +0100 Subject: [PATCH 5/7] Update README.md --- README.md | 1 - 1 file changed, 1 deletion(-) diff --git a/README.md b/README.md index 4d7cb6d..1070a52 100644 --- a/README.md +++ b/README.md @@ -43,4 +43,3 @@ See: [Usage.md](Usage.md) ## Known issues - Inconsistency between API and app, devices are not updating correctly between API and app and sometimes the devices renders offline in the app - this is a Danfoss issue and not this integration -- Floorheating modules (Icon) are not reporting the actual room temperature in the API - this is a Danfoss issue and not this integration From 430eb603480c748fb6dead676535c6b6212086c6 Mon Sep 17 00:00:00 2001 From: TLongstride <74832134+TLongstride@users.noreply.github.com> Date: Thu, 23 Nov 2023 13:28:31 +0100 Subject: [PATCH 6/7] Update Usage.md Add Thermal Actuator Sensor --- Usage.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Usage.md b/Usage.md index 4d68504..5d41d91 100644 --- a/Usage.md +++ b/Usage.md @@ -181,4 +181,11 @@ Possible values:   +#### Thermal Actuator _(\*2)_ + +With this binary sensor, you can know if the Thermal Actuator is open or closed. The HVAC Action is base one this state. + +  + _(\*1): Radiator only_ +_(\*2): Icon only_ From 74513769d4ca673d06372ae25e892ba738f95667 Mon Sep 17 00:00:00 2001 From: TLongstride <74832134+TLongstride@users.noreply.github.com> Date: Thu, 23 Nov 2023 13:37:01 +0100 Subject: [PATCH 7/7] Update climate.py --- custom_components/danfoss_ally/climate.py | 88 +++++++++++------------ 1 file changed, 43 insertions(+), 45 deletions(-) diff --git a/custom_components/danfoss_ally/climate.py b/custom_components/danfoss_ally/climate.py index b99ffeb..ba9ebc5 100644 --- a/custom_components/danfoss_ally/climate.py +++ b/custom_components/danfoss_ally/climate.py @@ -522,6 +522,49 @@ def hvac_mode(self): return HVAC_MODE_OFF else: return HVAC_MODE_COOL + @callback + def _async_update_callback(self): + """Load data and update state.""" + self._async_update_data() + self.async_write_ha_state() + + def set_hvac_mode(self, hvac_mode): + """Set new target hvac mode.""" + + _LOGGER.debug("Setting hvac mode to %s", hvac_mode) + + if hvac_mode == HVAC_MODE_AUTO: + mode = "at_home" # We have to choose either at_home or leaving_home + manual_set = self._device["at_home_setting"] + elif hvac_mode == HVAC_MODE_HEAT: + mode = "manual" + manual_set = self._device["leaving_home_setting"] + elif hvac_mode == HVAC_MODE_COOL: + mode = "manual" + manual_set = self._device["at_home_setting"] + elif hvac_mode == HVAC_MODE_OFF: + mode = "manual" + if ( + self._device["work_state"] == "heat_active" + or self._device["work_state"] == "Heat" + ): + manual_set = self._device["lower_temp"] + elif ( + self._device["work_state"] == "cool_active" + or self._device["work_state"] == "Cool" + ): + manual_set = self._device["upper_temp"] + + if mode is None: + return + + self._device["mode"] = mode # Update current copy of device data + self._device["manual_mode_fast"] = manual_set + self._ally.set_mode(self._device_id, mode) + self._ally.set_temperature(self._device_id, manual_set) + + # Update UI + self.async_write_ha_state() @property def hvac_action(self): @@ -579,51 +622,6 @@ async def async_setup_entry( if entities: async_add_entities(entities, True) - @callback - def _async_update_callback(self): - """Load data and update state.""" - self._async_update_data() - self.async_write_ha_state() - - def set_hvac_mode(self, hvac_mode): - """Set new target hvac mode.""" - - _LOGGER.debug("Setting hvac mode to %s", hvac_mode) - - if hvac_mode == HVAC_MODE_AUTO: - mode = "at_home" # We have to choose either at_home or leaving_home - manual_set = self._device["at_home_setting"] - elif hvac_mode == HVAC_MODE_HEAT: - mode = "manual" - manual_set = self._device["leaving_home_setting"] - elif hvac_mode == HVAC_MODE_COOL: - mode = "manual" - manual_set = self._device["at_home_setting"] - elif hvac_mode == HVAC_MODE_OFF: - mode = "manual" - if ( - self._device["work_state"] == "heat_active" - or self._device["work_state"] == "Heat" - ): - manual_set = self._device["lower_temp"] - elif ( - self._device["work_state"] == "cool_active" - or self._device["work_state"] == "Cool" - ): - manual_set = self._device["upper_temp"] - - if mode is None: - return - - self._device["mode"] = mode # Update current copy of device data - self._device["manual_mode_fast"] = manual_set - self._ally.set_mode(self._device_id, mode) - self._ally.set_temperature(self._device_id, manual_set) - - # Update UI - self.async_write_ha_state() - - def _generate_entities(ally: AllyConnector): """Create all climate entities.""" _LOGGER.debug("Setting up Danfoss Ally climate entities")