Skip to content

feat: add manual E-Heater assist switch (R4) and refine sterilize profile mapping - #29

Merged
0xAHA merged 2 commits into
0xAHA:mainfrom
tazomatalax:feat/e-heater-control
Jul 31, 2026
Merged

feat: add manual E-Heater assist switch (R4) and refine sterilize profile mapping#29
0xAHA merged 2 commits into
0xAHA:mainfrom
tazomatalax:feat/e-heater-control

Conversation

@tazomatalax

Copy link
Copy Markdown
Contributor

Summary of Changes

Resolves #25 by adding support for triggering the manual E-Heater booster element and refining register profile mappings for EcoSpring / Chromagen / Midea HWS units.

1. Manual E-Heater Assist Trigger (Register 4)

  • Writable Control: Proven via direct Modbus TCP testing that writing 1 to Register 4 triggers the manual E-Heater element.
  • Switch Entity: Added MideaHeaterAssistSwitch (Manual Heater Assist) linked to Register 4.
  • Turn-Off Sequence: Added power-state reset handling (power_state = False -> power_state = True) in async_turn_off() so toggling the switch OFF in Home Assistant clears Register 4 on the hardware.

2. Profile Register Mapping Fix

  • profile_manager.py Fix: Added config["sterilize_register"] = registers.get("sterilize", 3) in apply_profile_to_config() so device profiles (like ecospring_hp300.json) cleanly map sterilize_register into Home Assistant without needing global fallbacks in coordinator.py.

Note: Opened as a Draft PR for live end-to-end validation.

@tazomatalax
tazomatalax force-pushed the feat/e-heater-control branch from ad7c991 to 1dfaa51 Compare July 31, 2026 08:56
@tazomatalax
tazomatalax force-pushed the feat/e-heater-control branch from 1dfaa51 to 857abaf Compare July 31, 2026 08:59
@tazomatalax

Copy link
Copy Markdown
Contributor Author

Sorry mate, using gemini flash for this and it's a bit trigger happy. I didnt see your fix in the last one but went through everything and noticed the hardcoded registers too. I also added a power off function to disable the E-heater, as it latches until the setpoint is reached otherwise.

@0xAHA

0xAHA commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Sorry mate, using gemini flash for this and it's a bit trigger happy. I didnt see your fix in the last one but went through everything and noticed the hardcoded registers too. I also added a power off function to disable the E-heater, as it latches until the setpoint is reached otherwise.

Is it ready to check?

@0xAHA

0xAHA commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Reviewed the latest commit (857abaf) — nice work pulling the coordinator/const wiring in, that resolves the crash risk (CONF_HEATER_ASSIST_TRIGGER_REGISTER import + missing coordinator.heater_assist_trigger_register plumbing) and the entity-gating issue from the earlier version. Confirmed:

  • const.py / coordinator.py now correctly define, poll, and write heater_assist_trigger_register with no unsafe default — matches the pattern used by every other optional diagnostic register.
  • switch.py gates MideaHeaterAssistSwitch on both heater_assist_trigger_register and heater_assist_register being present, so it won't get created for profiles (e.g. Midea 170L/Chromagen) that haven't validated register 4.
  • binary_sensor.py keeps the original unique_id suffixes (heater_assist_raw, sanitize_state_raw) for the two pre-existing sensors, so current EcoSpring installs won't get orphaned entities from the rename — good catch.

One issue remains, and one behavior worth double-checking before merge:

1. sterilize_register is now force-defaulted for every profile

In profile_manager.py, apply_profile_to_config():

config["sterilize_register"] = registers.get("sterilize", 3)

This is unconditional, unlike the "only set if present" pattern used two lines below for heater_assist_register / sanitize_state_register / heater_assist_trigger_register. midea_170l.json has no "sterilize" key in its registers block, so .get("sterilize", 3) silently falls back to 3 for that profile too. Net effect: loading the Midea 170L/Chromagen profile via "Load from Profile" will now force-create a "Sanitize Mode" switch writing to register 3 — a register that's never been declared/validated for that hardware. This reintroduces the same "forced onto unsupported profiles" problem the earlier fix addressed for the heater-assist switch, just on the sterilize register this time.

Suggested fix, matching the conditional style already used right below it:

if "sterilize" in registers:
    config["sterilize_register"] = registers["sterilize"]

(ecospring_hp300.json already declares "sterilize": 3 in its registers, so this changes nothing for EcoSpring — it only stops the register from being force-applied to profiles that never opted in.)

2. async_turn_off() power-cycles the whole unit to clear register 4

async def async_turn_off(self, **kwargs: Any) -> None:
    """Turn off E-heater assist by resetting power state to clear R4 trigger."""
    await self.coordinator.write_register("power_state", False)
    await self.coordinator.write_register("power_state", True)

Given writes of 0 to register 4 are rejected by the controller, this may be the only available way to clear it — but as written, switching off "Manual Heater Assist" in Home Assistant will briefly power off and back on the entire heat pump, not just the heater element. That's a bigger side effect than the entity name suggests and could interrupt an in-progress heating cycle. Worth confirming this is intentional/necessary and, if so, maybe noting it in the entity's name/description so it isn't a surprise (e.g. an icon/description that makes the power-cycle explicit, or a confirmation step).

Everything else looks good — happy to take another pass once the sterilize_register gating is adjusted.

…t pattern

Unconditional .get("sterilize", 3) would silently force-create a Sanitize
switch on profiles that never declared the register (e.g. midea_170l.json),
writing to an unvalidated register 3 — same class of bug fixed earlier for
heater_assist_trigger_register.

Consistent with the conditional pattern already used directly below for
heater_assist_register, sanitize_state_register, and
heater_assist_trigger_register. ecospring_hp300.json already declares
"sterilize": 3 so EcoSpring installs are unaffected.
@tazomatalax
tazomatalax marked this pull request as ready for review July 31, 2026 22:33
@tazomatalax

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review — both points are valid.

1. sterilize_register gating — fixed in 01bebc1.
Changed to the conditional pattern matching heater_assist_register / sanitize_state_register / heater_assist_trigger_register:

if "sterilize" in registers:
    config["sterilize_register"] = registers["sterilize"]

ecospring_hp300.json already declares "sterilize": 3 so EcoSpring installs are unaffected. Midea 170L and other profiles that never declared the key will no longer get an unvalidated switch on register 3.

2. async_turn_off() power-cycle — intentional, but noted.
Confirmed via direct Modbus testing that writing 0 to register 4 returns Exception Code 3 (Illegal Data Value) — the controller simply does not accept it. The power-cycle (R0 = 0 → R0 = 1) is the only confirmed way to clear the latch, and it does bring the unit back within a couple of seconds in normal operation.

Agreed it is a larger side effect than the entity name implies. Tested it in HA — the unit came back cleanly after the reset. Happy to add a more explicit description or entity name if you want to signal the behaviour to users (e.g. "Manual E-Heater Boost" with a note in the entity description). Open to your preference on that before merge.

@0xAHA

0xAHA commented Jul 31, 2026

Copy link
Copy Markdown
Owner

01bebc1 looks correct — confirmed the sterilize_register gating now matches the conditional pattern used for the other optional registers, ecospring_hp300.json is unaffected, and Midea 170L/Chromagen profiles will no longer get a switch on an unvalidated register.

Full re-check of the branch tip:

  • const.py/coordinator.py: heater_assist_trigger_register correctly defined, polled, and written with no unsafe default.
  • switch.py: heater-assist switch gated on both heater_assist_trigger_register and heater_assist_register.
  • profile_manager.py: all four optional registers (sterilize, heater_assist_register, sanitize_state_register, heater_assist_trigger_register) now use the same "only if declared in profile" pattern.
  • binary_sensor.py: original unique_ids preserved for the two pre-existing sensors.
  • All five changed files compile cleanly.

No naming preference on the "Manual Heater Assist" entity / power-cycle behavior from our end — that's your call. Good to merge as-is.

@0xAHA
0xAHA merged commit 690f0ca into 0xAHA:main Jul 31, 2026
0xAHA added a commit that referenced this pull request Jul 31, 2026
Manual E-Heater assist switch, Sanitize Cycle Scheduled sensor, and
refined R108/R109 decoding from PR #29. Released as a pre-release
pending further live end-to-end validation on EcoSpring HP300.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
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.

📋 Device Profile Submission

2 participants