Conversation
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 reviews are paused for this user.Troubleshooting steps vary by plan Learn more → On a Teams plan? Using GitHub Enterprise Server, GitLab Self-Managed, or Bitbucket Data Center? |
📝 WalkthroughWalkthroughAdds ChangesAC Fan-Direction Selects
REF Program Select modeZ Tests
Estimated code review effort🎯 3 (Moderate) | ⏱️ ~25 minutes Possibly related PRs
Poem
🚥 Pre-merge checks | ✅ 4 | ❌ 1❌ Failed checks (1 warning)
✅ Passed checks (4 passed)
✨ Finishing Touches📝 Generate docstrings
🧪 Generate unit tests (beta)
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. Comment |
There was a problem hiding this comment.
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
📒 Files selected for processing (8)
custom_components/addhon/const.pycustom_components/addhon/manifest.jsoncustom_components/addhon/select.pycustom_components/addhon/translations/en.jsoncustom_components/addhon/translations/it.jsontests/test_ac_fan_direction_select.pytests/test_entity_translation_keys.pytests/test_ref_program_select.py
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).
Automated release PR for
v5.7.0.Summary by CodeRabbit
New Features
Bug Fixes
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.
HonAcDirectionSelectentities per AC (vertical / horizontal), with options drawn from the live device enum, mapped to stable translation keys. Values are normalized in__init__and sent throughasync_send_settings(which applies the sanitizer before the requested value wins)._REF_ACTIVE_PROGRAM_ATTRSnow excludesmodeZ1/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".test_ac_fan_direction_select.pyvalidates 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
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"%%{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"Reviews (2): Last reviewed commit: "HonAcDirectionSelect: normalize enum cod..." | Re-trigger Greptile