Skip to content

Release v5.7.0 - #42

Merged
tis24dev merged 6 commits into
mainfrom
dev
Jun 30, 2026
Merged

Release v5.7.0#42
tis24dev merged 6 commits into
mainfrom
dev

Conversation

@tis24dev

@tis24dev tis24dev commented Jun 30, 2026

Copy link
Copy Markdown
Owner

Automated release PR for v5.7.0.

Summary by CodeRabbit

  • New Features

    • Added new AC fan-direction controls for vertical and horizontal louver positions, including swing and position-based options.
    • Expanded language support for these new select options in English and Italian.
  • Bug Fixes

    • Improved how current appliance states are shown for fridge program selection, especially for edge cases and inactive modes.
    • Tightened option handling so unsupported choices are not shown or sent.

Greptile Summary

This release adds per-axis AC fan-direction (louver position) selects for vertical and horizontal axes, capability-gated live on the per-model settings enum, and tightens the REF program select so that iot_* download presets no longer surface incorrectly as the current option.

  • AC fan-direction selects: Two new capability-gated HonAcDirectionSelect entities per AC (vertical / horizontal), with options drawn from the live device enum, mapped to stable translation keys. Values are normalized in __init__ and sent through async_send_settings (which applies the sanitizer before the requested value wins).
  • REF program select hardening: _REF_ACTIVE_PROGRAM_ATTRS now excludes modeZ1/modeZ2 (engine-synthetic fields) and explicitly documents that active iot_* presets are unobservable from the cloud shadow. New tests cover the modeZ exclusion and the iot_* fall-through to "off".
  • Translations and tests: en/it translations added for both new select entities, and a comprehensive test_ac_fan_direction_select.py validates capability gating, option building, send values, and sanitizer coexistence.

Confidence Score: 5/5

Safe to merge — all changed paths are capability-gated on the live device schema, no hard-coded assumptions about model-specific values, and the sanitizer interaction is proven correct by tests.

The AC fan-direction select follows the same well-tested capability-gating pattern already used by the program-option selects. The REF program select changes are documentation and comment updates only, with new tests that pin the previously undocumented modeZ exclusion. Translation keys are complete and in parity across both languages. No behavioural regression surfaces in any changed code path.

No files require special attention. The most complex new logic is in select.py (HonAcDirectionSelect.init and async_select_option), which is fully exercised by test_ac_fan_direction_select.py.

Important Files Changed

Filename Overview
custom_components/addhon/select.py Adds HonAcDirectionSelect class and wires it into async_setup_entry; adds capability-gating helper _supports_direction_select; minor REF program select docstring and comment updates. Logic is sound.
custom_components/addhon/const.py Adds FAN_DIR_V_LABELS and FAN_DIR_H_LABELS dicts (raw value to option key, superset design, forward-safe). Values are distinct within each map and consistent with the translation files.
tests/test_ac_fan_direction_select.py New comprehensive test file covering capability gating, per-model enum variation, option normalization, current_option mapping, send values, and sanitizer interaction.
tests/test_ref_program_select.py Adds tests pinning the modeZ1/modeZ2 exclusion and the iot_* preset fall-through to off.
custom_components/addhon/translations/en.json Adds fan_direction_vertical and fan_direction_horizontal state blocks; keys match FAN_DIR_V_LABELS / FAN_DIR_H_LABELS values exactly.
custom_components/addhon/translations/it.json Italian translations added with state keys in parity with en.json.
tests/test_entity_translation_keys.py Updated to include _AC_DIRECTION_SELECTS, keeping translation-key coverage consistent with the new entities.
custom_components/addhon/manifest.json Version bumped from 5.6.0-beta to 5.7.0.

Sequence Diagram

%%{init: {'theme': 'neutral'}}%%
sequenceDiagram
    participant HA as Home Assistant
    participant Setup as async_setup_entry
    participant Gate as _supports_direction_select
    participant Entity as HonAcDirectionSelect
    participant Cmd as async_send_settings
    participant San as sanitize_wind_direction

    HA->>Setup: "setup AC appliance"
    Setup->>Gate: "settings_param(appliance, windDirectionVertical)"
    Gate-->>Setup: "param (typology=enum, len>1)"
    Setup->>Entity: "HonAcDirectionSelect(coordinator, id, desc, client)"
    Note over Entity: "Build _raw_to_key from live enum, _attr_options = option keys"

    HA->>Entity: "async_select_option(position_6)"
    Entity->>Entity: "_key_to_raw[position_6] = 6"
    Entity->>Cmd: "async_send_settings({windDirectionVertical: 6})"
    Cmd->>San: "pre_send(command_params)"
    San-->>Cmd: "other params sanitized if invalid"
    Cmd->>Cmd: "param.value = 6 (wins over sanitizer)"
    Cmd->>Cmd: "command.send()"
    Cmd-->>Entity: "done"
    Entity->>Entity: "_async_request_command_refresh()"

    HA->>Entity: "current_option (on refresh)"
    Entity->>Entity: "_get_attr(settings.windDirectionVertical) = 6"
    Entity-->>HA: "position_6"
Loading
%%{init: {'theme': 'base', 'themeVariables': {"darkMode": true, "background": "#0d1117", "primaryColor": "#21262d", "primaryTextColor": "#e6edf3", "primaryBorderColor": "#8b949e", "lineColor": "#8b949e", "textColor": "#e6edf3", "edgeLabelBackground": "#161b22", "actorBkg": "#21262d", "actorBorder": "#8b949e", "actorTextColor": "#e6edf3", "actorLineColor": "#8b949e", "signalColor": "#8b949e", "signalTextColor": "#e6edf3", "noteBkgColor": "#373320", "noteBorderColor": "#d4a72c", "noteTextColor": "#f0e6c0", "labelBoxBkgColor": "#21262d", "labelBoxBorderColor": "#8b949e", "labelTextColor": "#e6edf3", "loopTextColor": "#e6edf3", "activationBkgColor": "#30363d", "activationBorderColor": "#8b949e"}}}%%
sequenceDiagram
    participant HA as Home Assistant
    participant Setup as async_setup_entry
    participant Gate as _supports_direction_select
    participant Entity as HonAcDirectionSelect
    participant Cmd as async_send_settings
    participant San as sanitize_wind_direction

    HA->>Setup: "setup AC appliance"
    Setup->>Gate: "settings_param(appliance, windDirectionVertical)"
    Gate-->>Setup: "param (typology=enum, len>1)"
    Setup->>Entity: "HonAcDirectionSelect(coordinator, id, desc, client)"
    Note over Entity: "Build _raw_to_key from live enum, _attr_options = option keys"

    HA->>Entity: "async_select_option(position_6)"
    Entity->>Entity: "_key_to_raw[position_6] = 6"
    Entity->>Cmd: "async_send_settings({windDirectionVertical: 6})"
    Cmd->>San: "pre_send(command_params)"
    San-->>Cmd: "other params sanitized if invalid"
    Cmd->>Cmd: "param.value = 6 (wins over sanitizer)"
    Cmd->>Cmd: "command.send()"
    Cmd-->>Entity: "done"
    Entity->>Entity: "_async_request_command_refresh()"

    HA->>Entity: "current_option (on refresh)"
    Entity->>Entity: "_get_attr(settings.windDirectionVertical) = 6"
    Entity-->>HA: "position_6"
Loading

Reviews (2): Last reviewed commit: "HonAcDirectionSelect: normalize enum cod..." | Re-trigger Greptile

tis24dev and others added 5 commits June 29, 2026 21:08
The earlier modeZ1/modeZ2 addition to _REF_ACTIVE_PROGRAM_ATTRS was a no-op:
modeZ1/modeZ2 are engine-synthetic (client/engine/appliances/ref.py rewrites
them by value from the boost flags), so every offered value they can carry is
already resolved by the FLAG path first, and an active iot_* preset is clobbered
to "no_mode" before the select ever sees it. Restore the matcher to
("programName", "prStr", "prCode") and document the reality instead of reading a
dead field.

Honest iot_* gap: on the only REF we have live evidence for (roberglezz,
HCW58F18EWMP) the raw cloud shadow carries no program-identity field for an
active iot_* download preset (no prCode/prStr/programName; activity empty); its
only footprint is the user-settable tempSel setpoint triple, which is not
identity-safe, so matching it would false-pin. An active iot_* therefore
correctly reads "off". programName/prStr/prCode stay as the forward-correct
handler for any REF model that does expose a raw program-identity key.

Add regression guards (modeZ synthetic field not surfaced; iot_* preset not
surfaced on the current engine; flag priority) pinning this behavior. No
runtime behavior change. The real iot_* surfacing is blocked on a real
iot_*-active diagnostics dump.
…clusion

Comment/docstring only. The decompile dig (apk/analysis/deep/ref-active-program-
detection.md + refrigeration.md section 6) supersedes the earlier "blocked on a real
iot_*-active dump" framing: an active iot_* download preset is provably NOT recoverable
from the cloud shadow. It leaves no program-identity field on either observed REF model
(HDPW5620CNPK, HCW58F18EWMP), and the official hOn app itself does not read one nor
reverse-match setpoints; it only shows the running preset as a client-local "Last used"
label in AsyncStorage (@quickset). So "off" is the permanently-correct current_option for
download presets, not a provisional fallback. No runtime behavior change.
#37

Users could only toggle vertical swing on/off; horizontal was not exposed at all.
Add two capability-gated select entities per AC that expose the manual louver
positions, mirroring the fridge program-select pattern: options come from the
device's LIVE per-model windDirectionVertical/windDirectionHorizontal enum (never
hard-coded), bilingual en/it labels, immediate send.

- const.py: FAN_DIR_V_LABELS / FAN_DIR_H_LABELS raw-value -> option-key maps
  (2/4/5/6/7 = louver angles, vertical 8 = swing; horizontal 0/3/4/5/6/7), cited to
  the decompiled label fns. Superset maps intersected with the live enum.
- select.py: HonAcDirectionSelect + descriptors + _supports_direction_select gate
  (param present, typology != "fixed", live enum >1 option) + AC branch in
  async_setup_entry. Options read live via param_allowed_values; current_option maps
  the live value through normalize_code and returns None on an out-of-enum reading;
  writes route through async_send_settings (same sanitizer-guarded path as swing).
- translations/en.json + it.json: bilingual names + per-position state labels.
- The existing climate swing_mode (on/off) entity is unchanged; vertical value 8
  (swing) coexists by design (both write windDirectionVertical).

Gating validated against live diagnostics from 3 distinct AC models (Mik + Roberto):
AS68PDAHRA (horizontal typology=fixed -> no horizontal select), AS35RBAHRA-3
(vertical enum lacks 7 -> not offered), AS35PBPHRA-PRE (full, confirmed identical on
two instances). New test_ac_fan_direction_select.py drives async_setup_entry
end-to-end (21 tests); test_entity_translation_keys.py extended for the new keys.
Full suite 961 passed, 1 skipped.
Roberto's live test (AS35PBPHRA-PRE) showed the horizontal louver kept
oscillating with no discoverable way to stop it. Root cause: windDirectionHorizontal
value 7 is the SWING value, symmetric with the vertical axis (8 = swing). The app's
getHorizontalFanDirectionLabel returns HORIZONTAL_SWING iff value === '7' and
HORIZONTAL_FIXED for every other value (decomp.txt:2532971-2532978, control flow
re-verified). We had mislabeled 7 as "position_7", hiding the swing control so the
fixed positions were indistinguishable from oscillation.

Relabel FAN_DIR_H_LABELS: 7 -> swing; 0,3,4,5,6 stay fixed position_N. Update the
en/it state translations to match. The read/write paths were already mechanically
correct (verified live on Roberto's AC: the shadow tracks every commanded value with
no revert, even under active vertical swing). Tests updated; full suite green.
@qodo-code-review

Copy link
Copy Markdown

Qodo reviews are paused for this user.

Troubleshooting steps vary by plan Learn more →

On a Teams plan?
Reviews resume once this user has a paid seat and their Git account is linked in Qodo.
Link Git account →

Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center?
These require an Enterprise plan - Contact us
Contact us →

@coderabbitai

coderabbitai Bot commented Jun 30, 2026

Copy link
Copy Markdown

Review Change Stack

📝 Walkthrough

Walkthrough

Adds HonAcDirectionSelect entity supporting vertical and horizontal AC louver control, including FAN_DIR_V_LABELS/FAN_DIR_H_LABELS constants, capability gating via live settings enums, en/it translations, and a comprehensive test suite. Also expands HonRefProgramSelect inline comments and adds seven modeZ/iot-preset behavioral tests. Manifest bumped to 5.7.0.

Changes

AC Fan-Direction Selects

Layer / File(s) Summary
Fan-direction label maps and axis descriptions
custom_components/addhon/const.py, custom_components/addhon/select.py
Defines FAN_DIR_V_LABELS/FAN_DIR_H_LABELS raw-value-to-stable-key dicts, HonAcDirectionSelectDescription dataclass, _AC_DIRECTION_SELECTS axis configs, and _supports_direction_select() capability gate (rejects fixed typology, requires >1 allowed value).
HonAcDirectionSelect entity and setup wiring
custom_components/addhon/select.py
Implements HonAcDirectionSelect with current_option (returns None for out-of-enum values) and async_select_option (validates key, sends raw value via async_send_settings, triggers refresh, raises HomeAssistantError on invalid input). Wires entity creation into async_setup_entry for APPLIANCE_AC.
Translations
custom_components/addhon/translations/en.json, custom_components/addhon/translations/it.json
Adds fan_direction_vertical and fan_direction_horizontal select state labels in English and Italian.
AC direction select tests
tests/test_ac_fan_direction_select.py, tests/test_entity_translation_keys.py
Tests option construction from live enums, current_option mapping, async_select_option payloads, error paths, en/it i18n key parity; extends translation key coverage collection to include _AC_DIRECTION_SELECTS.

REF Program Select modeZ Tests

Layer / File(s) Summary
HonRefProgramSelect comment updates
custom_components/addhon/select.py
Rewrites inline comments for current_option derivation and _active_program_code() double-gate rules; no logic changes.
modeZ and iot-preset behavioral tests
tests/test_ref_program_select.py
Adds seven async test methods: iot-preset non-surfacing via modeZ, no_mode/empty fallback to "off", live-enum gating, flag precedence over modeZ, and load-bearing iot-preset scenario.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

  • tis24dev/addhOn#38: Prior expansion of test_entity_translation_keys.py select-platform state-key coverage that this PR extends for AC direction selects.
  • tis24dev/addhOn#41: Introduced the writable ref_program select and startProgram command flow that the new modeZ tests exercise.

Poem

🐇 Hop left, hop right, the louver obeys,
Up, down, or swing — the AC displays!
Vertical, horizontal, each axis in line,
position_4 or swing, the options align.
With tests green and translations in two,
This bunny's quite proud of the feature debut! 🌬️

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 2.63% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title matches the release-focused nature of the PR and correctly identifies the version being shipped.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.
Description Check ✅ Passed Check skipped - CodeRabbit’s high-level summary is enabled.
✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch dev

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.

@coderabbitai coderabbitai Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

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

Actionable comments posted: 1

🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@custom_components/addhon/select.py`:
- Around line 867-890: `HonAcDirectionSelect` builds `_raw_to_key` from
`param_allowed_values()` using unnormalized raw strings, but `current_option()`
looks up `normalize_code(raw)`, so valid values like `13.0` or `4,5` can miss
and appear unknown. Update the option mapping in `__init__` to normalize each
raw schema value before storing it in `_raw_to_key` and `_key_to_raw`, keeping
the constructor’s mapping symmetric with `current_option()`’s lookup.
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Path: .coderabbit.yaml

Review profile: CHILL

Plan: Pro

Run ID: a23d341c-0b34-48a8-8f50-d6dac87c54d0

📥 Commits

Reviewing files that changed from the base of the PR and between 8e5f154 and 6a0c256.

📒 Files selected for processing (8)
  • custom_components/addhon/const.py
  • custom_components/addhon/manifest.json
  • custom_components/addhon/select.py
  • custom_components/addhon/translations/en.json
  • custom_components/addhon/translations/it.json
  • tests/test_ac_fan_direction_select.py
  • tests/test_entity_translation_keys.py
  • tests/test_ref_program_select.py

Comment thread custom_components/addhon/select.py Outdated
CodeRabbit (PR #42): the option map was built from param_allowed_values() (raw,
unnormalized) while current_option() looks it up via normalize_code(), so an enum
advertised non-canonically (e.g. "13.0") would build a key the normalized read-back
never matches and would surface as unknown. Normalize each raw to its canonical code
when building _raw_to_key/_key_to_raw, matching the twin HonProgramOptionSelect /
option_value_set path which already normalize. No-op for the real per-model enums
(clean integers): read, write (sent value) and label now share one canonical form.
Read-side regression test added; full suite green (964).
@tis24dev
tis24dev merged commit 5482f57 into main Jun 30, 2026
9 checks passed
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