Skip to content

fix(hacs): resolve model names reported in long form ("AquaClean Mera Classic") - #40

Open
chriguschneider wants to merge 1 commit into
jens62:mainfrom
chriguschneider:fix/model-name-resolution
Open

chriguschneider wants to merge 1 commit into
jens62:mainfrom
chriguschneider:fix/model-name-resolution

Conversation

@chriguschneider

Copy link
Copy Markdown

The problem

On a Mera Classic, firmware RS30.0 TS206, the integration creates the entities of every model — 149 in total. About 50 never receive a value, and ~19 belong to hardware the Classic does not have. A few of those report plausible-looking values for absent hardware, e.g. number.…_wc_seat_heat = 3.0 on a device without seat heating — easy to mistake for a real reading.

Root cause

The device reports its model via proc 0x82 description as:

AquaClean Mera Classic

PROC82_DESCRIPTION_TO_MODEL only lists the short form AcMeraClassic. The lookup misses → _device_model stays None → it is never persisted to the config entry ("device_type": null) → get_feature_sets() falls back to _FS_FULL.

The miss is silent — there is no log line, so the only symptom is the entity count. This should affect every manual-MAC-entry install; devices discovered via advertisement go through ADV_DEVICE_TYPE_TO_MODEL, which uses a third spelling again (Geberit Mera Classic).

Evidence from the affected instance: sensor.…_model (the raw description, passed through unchanged by AquaCleanClient) reads AquaClean Mera Classic, and the config entry has "device_type": null.

The fix

normalize_model_name() + resolve_device_model() in const.py. The normalizer lower-cases, drops non-alphanumerics and strips the vendor/product prefixes, so all three known spellings collapse onto the model key:

reported normalized
AcMeraClassic meraclassic
AquaClean Mera Classic meraclassic
Geberit Mera Classic meraclassic

Deliberate choices:

  • Exact tables are tried first, normalized lookup only as a fallback → behaviour for every spelling that already worked is bit-for-bit unchanged.
  • The normalized table is derived from DEVICE_MODEL_FEATURE_SETS, so adding a model still means touching one place only.
  • An unmapped description now logs a warning naming the string, so the next unknown spelling surfaces instead of silently inflating the entity list.
  • Both call sites (coordinator.py proc-0x82 path, config_flow.py advertisement path) use the same resolver.

Tests

tests/test_device_model_resolution.py — standalone, no HA and no BLE needed (const.py has no imports, so it is loaded from its file path), matching the style of the existing test scripts. Covers:

  • normalized keys are unique across all 9 models
  • all 17 spellings from both existing tables still resolve (regression guard)
  • the long form resolves, including mixed case, extra whitespace and a Geberit AquaClean … double prefix
  • unknown/empty input still returns None — no false positives
  • get_feature_sets() keeps its _FS_FULL fallback for unknown models
  • a resolved Mera Classic no longer gets MERA_COMFORT_ONLY, WITH_SEAT_HEATER, WITH_WATER_HEATER, ALBA_ONLY or SELA_ONLY — while keeping WITH_ODOUR_EXTRACTION
$ python tests/test_device_model_resolution.py
all checks passed

Notes

  • Verified against 3.1.3b1; the affected instance runs 3.1.2.
  • I only have one device, so the long-form spellings for models other than Mera Classic are inferred from the pattern, not observed. If Geberit is inconsistent there, the warning added in this PR will surface it.
  • Existing installs that already hit this keep their inflated entity set until the model is re-detected, since device_type is only refined while it is None — that path is untouched here.

A Mera Classic on firmware RS30.0 TS206 reports its model via proc 0x82
`description` as "AquaClean Mera Classic", while PROC82_DESCRIPTION_TO_MODEL
only lists the short form "AcMeraClassic". The lookup misses, _device_model
stays None and is therefore never persisted to the config entry, and
get_feature_sets() falls back to _FS_FULL — so every model's entities are
created (149 on this device, ~50 of which never receive a value).

The miss was silent: no log entry, the only symptom being the entity count.

Add normalize_model_name() / resolve_device_model() in const.py. The
normalizer lower-cases, drops non-alphanumerics and strips the vendor and
product prefixes, so all three known spellings collapse onto the model key:

    "AcMeraClassic"           -> meraclassic
    "AquaClean Mera Classic"  -> meraclassic
    "Geberit Mera Classic"    -> meraclassic

resolve_device_model() tries the two existing exact tables first and only
then the normalized lookup, so behaviour for every spelling that already
worked is unchanged. The normalized table is derived from
DEVICE_MODEL_FEATURE_SETS, so a new model still only needs adding in one
place.

Both call sites now use the resolver, and an unmapped description logs a
warning naming the string, so the next unknown spelling surfaces instead of
silently inflating the entity list.

Tests cover: unique normalized keys, all 17 spellings from the existing
tables still resolving, the long form resolving, unknown input still
returning None, the _FS_FULL fallback, and a resolved Mera Classic no longer
receiving Comfort/Alba-only features.
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