Skip to content

Fix invalid climate min/max range and non-functional heat setpoint on default-mapping devices - #124

Open
ChrisReini wants to merge 3 commits into
radical-squared:Custom-componentfrom
ChrisReini:fix-invalid-min-max-temperature-range
Open

ChrisReini wants to merge 3 commits into
radical-squared:Custom-componentfrom
ChrisReini:fix-invalid-min-max-temperature-range

Conversation

@ChrisReini

Copy link
Copy Markdown

Context

This PR addresses two related bugs affecting devices that fall back to the generic mapping.default.json because they have no dedicated mapping.<product_id>.json file. It was developed and tested against product ID 1548963836789501952 (a Planet Pool-type heat pump), but the underlying issue is not specific to that one unit — see #90 ("Bad mapping of parameters for PLANET POOL TEP0001"), which reports a very similar mapping discrepancy between the vendor app and this integration on a different, also-unlisted Planet Pool device. This suggests the vendor's register layout is not consistent enough across models for mapping.default.json to safely assume any single fixed layout, and that a defensive/validating approach (rather than trying to hand-craft a "correct" mapping for every unlisted model) is the more robust way to handle this family of devices.

Both fixes are scoped as tightly as possible: they only ever engage for devices using the default mapping, and only for behavior that was demonstrably broken. Devices with their own dedicated mapping file (currently product IDs 1245226668902080512 and 1442284873216843776) are not touched by either fix — their code paths are unchanged.


Fix 1: invalid min/max temperature range in heat mode

Problem: climate.warmepumpe reported min_temp=35.0 and max_temp=27.0 in heat mode — an invalid range (minimum greater than maximum). The app showed a valid heating range for the same device at the same time.

Root cause: In heat mode, mapping.default.json reads the minimum temperature from register R10 ("Min. heat") and the maximum from register R11 ("Max. heat"). On this device, those two registers are effectively swapped relative to what the generic mapping assumes, so R10 ends up holding the larger value and R11 the smaller one.

Fix: Added _get_device_temperature_range() in custom_components/aqua_temp/managers/aqua_temp_api.py. It now fetches the minimum and maximum temperature together (instead of via two independent, unrelated method calls as before), and if minimum > maximum, it swaps the two values and logs a _LOGGER.warning so the correction is visible in the log. This is a purely defensive check — it only changes behavior when the reported range is already invalid, and it works for any device regardless of whether its product ID is recognized, so it doesn't depend on correctly identifying the device model.

Tested: Confirmed on a real HA instance. After the fix, climate.warmepumpe reports a valid range (min_temp=27.0, max_temp=35.0) instead of the previous invalid min_temp=35.0, max_temp=27.0.


Fix 2: heat-mode setpoint changes never reach the physical device

Problem: Changing the target temperature in heat mode from Home Assistant had no effect on the physical unit at all. The Home Assistant entity would show the newly-set value, but the official vendor app kept showing the old, unchanged setpoint — i.e. the command was silently not applied by the device.

Root cause: mapping.default.json uses register R02 ("Heating set") as the heat-mode target-temperature register. On this device, R02 is writable and reads back whatever was last written to it (so it looks like it's working from the integration's point of view), but it has no effect whatsoever on the device's actual behavior. The register that actually drives the device — both for what the vendor app displays and for real setpoint changes — is R01 ("Cooling set"), and this appears to hold true regardless of which HVAC mode is currently active.

Evidence: To confirm this before writing the fix, a new setpoint was written from Home Assistant using the pre-fix code. R02 updated immediately to the new value (i.e. the write "succeeded" from the integration's perspective), but the official app — even after restarting the app — kept showing the previous, unchanged setpoint. After applying the fix so that writes go to R01 instead, the official app (after an app restart, since it appears to cache the last known value) correctly showed the new setpoint. This demonstrates conclusively that R01 is the register that actually controls the device, and that R02 is non-functional for this class of device — not just occasionally stale or out of range, which means a plausibility/range check on R02's value alone could never have reliably caught this.

Fix: Added AquaTempConfigManager.is_default_mapping() in aqua_temp_config_manager.py, and _get_effective_target_temperature_pc() in aqua_temp_api.py, wired into the existing _get_target_temperature_protocol_code() (which is used by both get_device_target_temperature, for reading, and set_temperature, for writing — so both directions automatically stay consistent with each other through this single method). Whenever the current HVAC mode is HEAT and the device is using the default mapping, this now unconditionally uses the cool-mode target register (R01) instead of the heat-mode target register (R02) for both reading and writing the heat setpoint, and logs a _LOGGER.warning each time it does so.

Tested: Confirmed on a real HA instance. Writing a new setpoint from Home Assistant in heat mode is now correctly reflected in the official vendor app.


⚠️ Cooling mode: untested, please verify before merging

The reporter's physical unit is a heat-only pool heat pump with no cooling function, so cool mode could not be tested at any point during this work. Both fixes were deliberately kept as conservative as possible for exactly this reason:

  • Fix 1 (min/max swap) is mode-agnostic by design, but it is purely defensive: it only ever changes behavior when the reported range is already invalid (minimum > maximum). For a device whose cool-mode range is already valid, this fix is a complete no-op.
  • Fix 2 (register redirection) is explicitly gated on hvac_mode == HVACMode.HEAT. The cool-mode code path is never entered by this change and behaves exactly as it did before this PR, on every device.

That said, maintainers or users with a device that actually supports cooling should verify cool mode still behaves correctly after this change, since it could not be verified by the reporter. If anything about the cool-mode assumptions here turns out to be wrong, please flag it — happy to adjust.


Scope / risk for other users

  • Both fixes are gated behind the new AquaTempConfigManager.is_default_mapping() check, which is True only for devices without their own mapping.<product_id>.json file.
  • Devices with a dedicated, verified mapping file (currently product IDs 1245226668902080512 and 1442284873216843776) are completely unaffected by both fixes — their code paths are unchanged.
  • Fix 2 additionally only activates for hvac_mode == HVACMode.HEAT, so cool and auto modes are unaffected even on default-mapping devices.
  • Neither fix modifies mapping.default.json or any other mapping/entity-description JSON file — both are implemented as runtime validation/fallback logic in aqua_temp_api.py / aqua_temp_config_manager.py, so they apply automatically to any current or future unlisted device without needing a dedicated mapping file to be authored and shipped for it first.

Version

This PR spans manifest versions 3.0.373.0.40 (three commits, each bumping the version and adding a CHANGELOG.md entry, per this repo's existing convention):

  • 3.0.38 — Fix 1 (invalid min/max range)
  • 3.0.39 — Fix 2, first iteration (conditional fallback based on a plausibility/range check)
  • 3.0.40 — Fix 2, final iteration (unconditional fallback for HEAT + default mapping, after real-device testing showed the plausibility check couldn't reliably detect the non-functional register — see "Evidence" above)

Testing

Both fixes were tested against a real, physical device (product ID 1548963836789501952) via a live Home Assistant instance, not just by code review:

  • Fix 1: verified via the entity's reported min_temp/max_temp becoming a valid range, and via the new warning log line appearing when the swap is applied.
  • Fix 2: verified by writing a new heat setpoint from Home Assistant and confirming it was reflected in the official vendor app (see "Evidence" above for the full before/after comparison).

Cool mode, as noted above, remains untested since the reporter's hardware cannot cool.

ChrisReini and others added 3 commits August 13, 2026 11:18
For devices without a dedicated product ID mapping file (falling back to
mapping.default.json), the min/max temperature registers can be reported
the wrong way round for a given HVAC mode (minimum > maximum), which
produces an invalid range for the climate entity (e.g. min_temp=35.0,
max_temp=27.0 for product ID 1548963836789501952).

Instead of relying on a per-product mapping file, add a validation in
AquaTempAPI: minimum and maximum are now fetched together and, if
minimum > maximum, automatically swapped, with a warning logged. This
protects any device regardless of whether its product ID is recognized.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
On devices without a dedicated product ID mapping file (falling back to
mapping.default.json), the heat-mode target register (e.g. R02) can be
stale/unused on the physical device, so changing the setpoint from Home
Assistant never reaches the unit even though the entity itself updates.
On the reported device, the real live setpoint is exposed through the
cool-mode target register (e.g. R01) regardless of the active HVAC mode.

Add AquaTempConfigManager.is_default_mapping() and a new
_get_effective_target_temperature_pc() in AquaTempAPI, wired into
_get_target_temperature_protocol_code() (used by both
get_device_target_temperature and set_temperature, so reading and
writing stay consistent). It falls back from the heat target register to
the cool target register only when: HVAC mode is HEAT, the device uses
the generic default mapping, and the heat register's own value is
missing or outside the device's validated min/max range for heat mode.

Cool mode is never touched by this change, and devices with a dedicated
mapping file are unaffected. This is deliberately conservative since the
reporting user's hardware is heat-only and cool mode could not be tested.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ping devices

Real-device testing showed that the heat-mode target register (e.g. R02)
on devices using the generic default mapping isn't just occasionally
stale: the device accepts and stores writes to it, but they have no
effect on the physical unit at all. A plausibility/range check on the
register's value can't catch this, since the device happily stores an
in-range value there that still does nothing.

Replace the previous conditional (missing-or-out-of-range) fallback in
_get_effective_target_temperature_pc() with an unconditional one: for
HVAC mode HEAT on devices using the default mapping, always use the
cool-mode target register (e.g. R01) instead, which is the one that
actually drives the device and matches what the vendor app shows.

Cool mode and devices with a dedicated mapping file remain completely
unaffected, same as before.

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.

1 participant