From 8fce77d4d944fcfea0c2fad1d01597bef8f14674 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrico=20Schr=C3=B6der?= Date: Wed, 29 Oct 2025 08:46:53 +0100 Subject: [PATCH 1/4] =?UTF-8?q?Improve=20EMS=20support=20(tested=20on=20Bo?= =?UTF-8?q?sch/Junkers=20Condens=20GC7000i).=20Fixes=20toggling=20between?= =?UTF-8?q?=20pre-selected=20heating=20temp=20and=20flow=20temp=20calculat?= =?UTF-8?q?ed=20by=20SAT.=20Use=20'selburnpow'=20instead=20of=20'burnmaxpo?= =?UTF-8?q?wer'=20to=20set=20maximum=20boiler=20power.=20Adjust=20minimum?= =?UTF-8?q?=20possible=20flow=20temp=20to=2012=C2=B0=20(this=20seems=20to?= =?UTF-8?q?=20be=20the=20lowest=20value=20that=20is=20accepted=20by=20this?= =?UTF-8?q?=20boiler).?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- custom_components/sat/mqtt/ems.py | 16 ++++++++++++---- 1 file changed, 12 insertions(+), 4 deletions(-) diff --git a/custom_components/sat/mqtt/ems.py b/custom_components/sat/mqtt/ems.py index f9400ea2..798f76e5 100644 --- a/custom_components/sat/mqtt/ems.py +++ b/custom_components/sat/mqtt/ems.py @@ -23,7 +23,7 @@ DATA_BOILER_CAPACITY = "nompower" DATA_REL_MIN_MOD_LEVEL = "burnminpower" -DATA_MAX_REL_MOD_LEVEL_SETTING = "burnmaxpower" +DATA_MAX_REL_MOD_LEVEL_SETTING = "selburnpow" _LOGGER: logging.Logger = logging.getLogger(__name__) @@ -108,7 +108,9 @@ def get_tracked_entities(self) -> list[str]: return [DATA_BOILER_DATA] async def async_set_control_setpoint(self, value: float) -> None: - await self._publish_command(f'{{"cmd": "selflowtemp", "value": {0 if value == 10 else value}}}') + # Minimum valid setting for Bosch/Junkers boiler seems to be 12°. Might be different + # for other boilers, do we need a configuration setting for this? + await self._publish_command(f'{{"cmd": "selflowtemp", "value": {max(value, 12)}}}') await super().async_set_control_setpoint(value) @@ -122,12 +124,18 @@ async def async_set_control_thermostat_setpoint(self, value: float) -> None: await super().async_set_control_thermostat_setpoint(value) async def async_set_heater_state(self, state: DeviceState) -> None: - await self._publish_command(f'{{"cmd": "heatingoff", "value": "{DATA_OFF if state == DeviceState.ON else DATA_ON}"}}') + # Do not send 'heatingoff' command, as this leads to EMS toggling the boiler between + # pre-set heating and selected flow temperature. Instead, control on/off solely by setting + # a low flow temperature (SAT already does this). + # (see https://github.com/emsesp/EMS-ESP32/discussions/2641#discussioncomment-14611481) await super().async_set_heater_state(state) async def async_set_control_max_relative_modulation(self, value: int) -> None: - await self._publish_command(f'{{"cmd": "burnmaxpower", "value": {max(value, 20)}}}') + # Do not set 'burnmaxpower' as this is an EEPROM-stored value and will wear out the EEPROM. + # Use 'selburnpow' instead. + # (see https://github.com/emsesp/EMS-ESP32/discussions/2641#discussioncomment-14611481) + await self._publish_command(f'{{"cmd": "selburnpow", "value": {max(value, 20)}}}') await super().async_set_control_max_relative_modulation(value) From 3dd08c65d468dcce79175224c99a5cdccc35a736 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrico=20Schr=C3=B6der?= Date: Wed, 29 Oct 2025 14:01:26 +0100 Subject: [PATCH 2/4] Improve minimum setpoint logic to reflect EMS behavior. --- custom_components/sat/mqtt/ems.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/custom_components/sat/mqtt/ems.py b/custom_components/sat/mqtt/ems.py index 798f76e5..96150fc1 100644 --- a/custom_components/sat/mqtt/ems.py +++ b/custom_components/sat/mqtt/ems.py @@ -108,9 +108,9 @@ def get_tracked_entities(self) -> list[str]: return [DATA_BOILER_DATA] async def async_set_control_setpoint(self, value: float) -> None: - # Minimum valid setting for Bosch/Junkers boiler seems to be 12°. Might be different - # for other boilers, do we need a configuration setting for this? - await self._publish_command(f'{{"cmd": "selflowtemp", "value": {max(value, 12)}}}') + # Minimum valid setting for Bosch/Junkers boiler seems to be 12°. + # Lower values set the boiler to 12° except 0° which sets the boiler to 5°. + await self._publish_command(f'{{"cmd": "selflowtemp", "value": {0 if value < 12 else value}}}') await super().async_set_control_setpoint(value) From 6f49276f7645f342b244cd52ab7306f5aa037391 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrico=20Schr=C3=B6der?= Date: Tue, 18 Nov 2025 10:28:13 +0100 Subject: [PATCH 3/4] Change DATA_MAX_REL_MOD_LEVEL_SETTING to burnmaxpower. Only accept selected flow temperatures >= 12 (otherwise I get random toggling of flow temperatures on my boiler) -> needs to be investigated. --- custom_components/sat/mqtt/ems.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/custom_components/sat/mqtt/ems.py b/custom_components/sat/mqtt/ems.py index 96150fc1..881c71a9 100644 --- a/custom_components/sat/mqtt/ems.py +++ b/custom_components/sat/mqtt/ems.py @@ -23,7 +23,7 @@ DATA_BOILER_CAPACITY = "nompower" DATA_REL_MIN_MOD_LEVEL = "burnminpower" -DATA_MAX_REL_MOD_LEVEL_SETTING = "selburnpow" +DATA_MAX_REL_MOD_LEVEL_SETTING = "burnmaxpower" _LOGGER: logging.Logger = logging.getLogger(__name__) @@ -110,7 +110,7 @@ def get_tracked_entities(self) -> list[str]: async def async_set_control_setpoint(self, value: float) -> None: # Minimum valid setting for Bosch/Junkers boiler seems to be 12°. # Lower values set the boiler to 12° except 0° which sets the boiler to 5°. - await self._publish_command(f'{{"cmd": "selflowtemp", "value": {0 if value < 12 else value}}}') + await self._publish_command(f'{{"cmd": "selflowtemp", "value": {max(value, 12)}}}') await super().async_set_control_setpoint(value) From 1a6ed15a112c12c1153145a74ebe68695c583b7f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Enrico=20Schr=C3=B6der?= Date: Mon, 2 Feb 2026 15:22:26 +0100 Subject: [PATCH 4/4] Disable sending 'heatingactivated' since this interferes with EMS-ESP. --- custom_components/sat/mqtt/ems.py | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/custom_components/sat/mqtt/ems.py b/custom_components/sat/mqtt/ems.py index 7b32a3ae..38c4b4a1 100644 --- a/custom_components/sat/mqtt/ems.py +++ b/custom_components/sat/mqtt/ems.py @@ -125,8 +125,12 @@ async def async_set_control_thermostat_setpoint(self, value: float) -> None: await super().async_set_control_thermostat_setpoint(value) async def async_set_heater_state(self, state: DeviceState) -> None: - await self._publish_command(f'{{"cmd": "heatingactivated", "value": "{DATA_ON if state == DeviceState.ON else DATA_OFF}"}}') - + # Do not send 'heatingoff' command, as this leads to EMS toggling the boiler between + # pre-set heating and selected flow temperature. Instead, control on/off solely by setting + # a low flow temperature (SAT already does this). + # (see https://github.com/emsesp/EMS-ESP32/discussions/2641#discussioncomment-14611481) + # The alternative command 'heatingactivated` also interferes with EMS, so sending nothing + # here seems to be the correct way to handle it. await super().async_set_heater_state(state) async def async_set_control_max_relative_modulation(self, value: int) -> None: