Skip to content

Improve/fix support for Bosch Junkers boilers through EMS-ESP#154

Open
enricoschroeder wants to merge 7 commits intoAlexwijn:developfrom
enricoschroeder:improve-ems-bosch-junkers
Open

Improve/fix support for Bosch Junkers boilers through EMS-ESP#154
enricoschroeder wants to merge 7 commits intoAlexwijn:developfrom
enricoschroeder:improve-ems-bosch-junkers

Conversation

@enricoschroeder
Copy link

@enricoschroeder enricoschroeder commented Feb 2, 2026

Improve/fix support for Bosch Junkers boilers through EMS-ESP.

See #109

Fixes flow temperature behaving erratically because of wrong commands send to EMS-ESP. Changed commands according to feedback from EMS-ESP developers (emsesp/EMS-ESP32#2641 (reply in thread)).

Changes running reliably with my Bosch Junkers Condens GC7000i for a couple of weeks.

Pull Request Template

Summary

  • [ x] I have added a clear, concise title and description.
  • [ x] The description includes what has changed and why it matters.

Checklist

  • [ x] I followed the coding style guidelines (PEP8, Home Assistant patterns).
  • [na ] All new code is typed with type hints where appropriate.
  • [ na] I added or updated unit tests that cover my changes.
  • [na ] Tests run locally and on CI without errors.
  • [ na] Documentation updates (README, docs, comments) are included if relevant.

Screenshots / Visuals

(Add screenshots if the PR introduces UI changes.)

Additional Notes

(Any extra information for reviewers.)

Summary by CodeRabbit

  • Improvements
    • Enforced a minimum boiler control setpoint (now clamped to 12) for safer operation.
    • Enforced a minimum for maximum relative modulation (now clamped to 20) to reduce wear and ensure stable performance.
    • Stopped sending an explicit heating activation command; heating is now managed via flow-temperature behavior for clearer state handling.

enricoschroeder and others added 6 commits October 29, 2025 08:46
Fixes toggling between pre-selected heating temp and flow temp calculated by SAT.
Use 'selburnpow' instead of 'burnmaxpower' to set maximum boiler power.
Adjust minimum possible flow temp to 12° (this seems to be the lowest value that is accepted by this boiler).
Only accept selected flow temperatures >= 12 (otherwise I get random toggling of flow temperatures on my boiler) -> needs to be investigated.
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Feb 2, 2026

Walkthrough

Updates to the EMS MQTT handler to clamp control setpoint values and switch the max modulation command; inline comments added explaining min values and rationale.

Changes

Cohort / File(s) Summary
EMS MQTT Handler
custom_components/sat/mqtt/ems.py
Clamp selflowtemp setpoint to minimum 12 (max(value, 12)); replace burnmaxpower with selburnpow and clamp to minimum 20 (max(value, 20)); remove sending explicit heatingactivated command; add explanatory comments and reference links.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~20 minutes

Suggested reviewers

  • Alexwijn
🚥 Pre-merge checks | ✅ 2 | ❌ 1
❌ Failed checks (1 warning)
Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (2 passed)
Check name Status Explanation
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
Title check ✅ Passed The title 'Improve/fix support for Bosch Junkers boilers through EMS-ESP' directly and clearly summarizes the main objective of the PR—enhancing compatibility with Bosch/Junkers boilers via EMS-ESP integration improvements.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 2

🤖 Fix all issues with AI agents
In `@custom_components/sat/mqtt/ems.py`:
- Around line 136-142: The method async_set_control_max_relative_modulation
clamps the published value but still passes the original unclamped value to the
superclass, causing a desync; change it to compute a single clamped variable
(e.g., clamped = max(value, 20)), use that clamped value in the JSON sent to
selburnpow, and call super().async_set_control_max_relative_modulation(clamped)
so both the device and the superclass state stay consistent.
- Around line 111-116: Compute a single clamped value once (e.g. clamped =
max(value, 12)) inside async_set_control_setpoint and use that clamped variable
for both the _publish_command payload and the call to
super().async_set_control_setpoint so the internal state/UI stays in sync with
the actual command sent (update references in async_set_control_setpoint to use
the clamped value for both the publish and the super call).

Comment on lines 111 to 116
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°.
# Lower values set the boiler to 12° except 0° which sets the boiler to 5°.
await self._publish_command(f'{{"cmd": "selflowtemp", "value": {max(value, 12)}}}')

await super().async_set_control_setpoint(value)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Clamp once and pass the clamped value to super() for state consistency.

You publish a clamped value but forward the original value to the superclass, which can leave the internal state/UI out of sync with the actual boiler command. Consider clamping once and using that value for both calls.

🔧 Proposed fix
-        await self._publish_command(f'{{"cmd": "selflowtemp", "value": {max(value, 12)}}}')
+        clamped_value = max(value, 12)
+        await self._publish_command(f'{{"cmd": "selflowtemp", "value": {clamped_value}}}')
 
-        await super().async_set_control_setpoint(value)
+        await super().async_set_control_setpoint(clamped_value)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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°.
# Lower values set the boiler to 12° except 0° which sets the boiler to 5°.
await self._publish_command(f'{{"cmd": "selflowtemp", "value": {max(value, 12)}}}')
await super().async_set_control_setpoint(value)
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°.
clamped_value = max(value, 12)
await self._publish_command(f'{{"cmd": "selflowtemp", "value": {clamped_value}}}')
await super().async_set_control_setpoint(clamped_value)
🤖 Prompt for AI Agents
In `@custom_components/sat/mqtt/ems.py` around lines 111 - 116, Compute a single
clamped value once (e.g. clamped = max(value, 12)) inside
async_set_control_setpoint and use that clamped variable for both the
_publish_command payload and the call to super().async_set_control_setpoint so
the internal state/UI stays in sync with the actual command sent (update
references in async_set_control_setpoint to use the clamped value for both the
publish and the super call).

Comment on lines 136 to 142
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)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

Keep super() in sync with the clamped modulation value.

The published value is clamped, but the superclass receives the unclamped input, which can desync reported vs. applied max modulation.

🔧 Proposed fix
-        await self._publish_command(f'{{"cmd": "selburnpow", "value": {max(value, 20)}}}')
+        clamped_value = max(value, 20)
+        await self._publish_command(f'{{"cmd": "selburnpow", "value": {clamped_value}}}')
 
-        await super().async_set_control_max_relative_modulation(value)
+        await super().async_set_control_max_relative_modulation(clamped_value)
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
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)
async def async_set_control_max_relative_modulation(self, value: int) -> None:
# 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)
clamped_value = max(value, 20)
await self._publish_command(f'{{"cmd": "selburnpow", "value": {clamped_value}}}')
await super().async_set_control_max_relative_modulation(clamped_value)
🤖 Prompt for AI Agents
In `@custom_components/sat/mqtt/ems.py` around lines 136 - 142, The method
async_set_control_max_relative_modulation clamps the published value but still
passes the original unclamped value to the superclass, causing a desync; change
it to compute a single clamped variable (e.g., clamped = max(value, 20)), use
that clamped value in the JSON sent to selburnpow, and call
super().async_set_control_max_relative_modulation(clamped) so both the device
and the superclass state stay consistent.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant