Skip to content

Release v2.4.2 - #7

Merged
telard-pixel merged 4 commits into
mainfrom
dev
Jun 13, 2026
Merged

Release v2.4.2#7
telard-pixel merged 4 commits into
mainfrom
dev

Conversation

@telard-pixel

@telard-pixel telard-pixel commented Jun 13, 2026

Copy link
Copy Markdown
Collaborator

Automated release PR for v2.4.2.

Merge this PR with squash only. The post-merge workflow will move dev to the squash commit, recreate v2.4.2 on that commit, and publish the release.

Summary by CodeRabbit

Release Notes

  • Bug Fixes

    • Removed legacy power entities from Home Assistant registry during setup.
  • New Features

    • Program selection workflow improved: selecting a program now stores it as pending before executing.
    • Start button now applies pending program selections when pressed.

Version: 2.4.2

tis24dev and others added 4 commits June 13, 2026 15:24
Two washer/dryer entities showed as "unavailable" with the orphan "?" badge
after commit c860952 (shipped publicly in 2.4.0): the "Programma" select and
the "Alimentazione" power switch. Both were left in the entity registry but no
longer provided by the integration.

Program select:
- source the option list from startProgram too, so appliances that only expose
  the program via startProgram get the select back (and the existing
  `<id>_program` registry entry rebinds, no longer unavailable).
- decouple selection from start: selecting a program now only records the choice
  (shared store on the coordinator) without sending any command, so picking an
  option never starts the appliance.
- current_option reflects the pending choice, then real device state
  (prCode/program/programName).

Start button:
- "Avvia programma" applies the program chosen in the select before sending
  startProgram, then clears the pending choice so the select reflects device
  state again.

Legacy power entity:
- remove orphan `<id>_power` registry entries on setup so the misleading
  "Alimentazione" switch (intentionally removed) stops showing as unavailable.

Adds tests/test_program_select.py covering select creation for
startProgram-only appliances, no-start-on-select, button applying/clearing the
pending program, device-state fallback, and the power cleanup.
- select.current_option: also match device attributes that expose a human
  program label (e.g. programName) instead of a code, so the current program is
  shown regardless of whether the device reports code or label.
- button "Avvia programma": fail safe when the pending program cannot be
  attached to startProgram (no program/prCode parameter) by raising instead of
  starting a default cycle, and keep the pending selection so it is not lost.
- centralize the pending-store key in const.PROGRAM_PENDING_STORE (was
  duplicated in select.py and button.py).
- tests: restore globally patched entity_registry functions via addCleanup, add
  coverage for human-label current_option and the non-applicable-program
  fail-safe.
@coderabbitai

coderabbitai Bot commented Jun 13, 2026

Copy link
Copy Markdown

Review Change Stack

Caution

Review failed

The pull request is closed.

ℹ️ Recent review info
⚙️ Run configuration

Configuration used: defaults

Review profile: CHILL

Plan: Pro Plus

Run ID: a93c9d1d-3cd9-4b21-a6a6-4fcf19afe41f

📥 Commits

Reviewing files that changed from the base of the PR and between 13da89f and bea2ce1.

📒 Files selected for processing (7)
  • custom_components/haier_hon/__init__.py
  • custom_components/haier_hon/base_entity.py
  • custom_components/haier_hon/button.py
  • custom_components/haier_hon/const.py
  • custom_components/haier_hon/manifest.json
  • custom_components/haier_hon/select.py
  • tests/test_program_select.py

📝 Walkthrough

Walkthrough

The integration refactors program selection for Haier appliances using a pending-program pattern: the select entity now records user choices in coordinator-backed storage without sending commands, while the start button applies the pending selection to the startProgram command before dispatch. Supporting infrastructure adds cross-entity state sharing, legacy registry cleanup during setup, and comprehensive test coverage validating the new workflow and edge cases.

Changes

Program Selection Refactor with Legacy Cleanup

Layer / File(s) Summary
Cross-entity state infrastructure
custom_components/haier_hon/base_entity.py, custom_components/haier_hon/const.py
Added _coordinator_store() helper method for mutable cross-entity state storage independent of coordinator data refresh cycles, and defined PROGRAM_PARAM_NAMES and PROGRAM_PENDING_STORE constants to coordinate pending program handling across select and button entities.
Select component: pending-program storage
custom_components/haier_hon/select.py
Refactored program selection to discover options via startProgram metadata, check PROGRAM_PENDING_STORE in current_option to report pending selections, and store user selections without sending device commands—prior command execution and device refresh were removed.
Button component: apply pending selection
custom_components/haier_hon/button.py
Button now reads pending program from coordinator store when handling startProgram, applies it to the command's first matching parameter from PROGRAM_PARAM_NAMES, validates applicability with error messaging, and clears pending state after successful dispatch.
Legacy entity registry cleanup
custom_components/haier_hon/__init__.py
Added _remove_legacy_entities() function that removes registry entries with _power suffix (e.g., removed "Alimentazione" switch), invoked during setup with error-only logging to prevent setup interruption.
Comprehensive test coverage
tests/test_program_select.py
Added Home Assistant module stubs, fake coordinator/client objects, and tests covering select creation from startProgram, pending selection without device send, button application of pending with parameter validation, stop-button behavior, device state attribute probing, error cases, and legacy entity cleanup.

Sequence Diagram(s)

Program selection end-to-end flow showing how the select entity stores pending choices and the button applies them:

sequenceDiagram
  participant User
  participant SelectEntity
  participant Button
  participant CoordinatorStore as Coordinator<br/>Store
  participant Command
  participant Device

  User->>SelectEntity: Select program option
  SelectEntity->>CoordinatorStore: Store program code in<br/>PROGRAM_PENDING_STORE
  SelectEntity->>SelectEntity: Update UI (async_write_ha_state)
  
  Note over CoordinatorStore: Pending selection persists<br/>across coordinator refreshes
  
  User->>Button: Press "Avvia programma"
  Button->>CoordinatorStore: Read pending program
  Button->>Command: Apply pending code to<br/>startProgram parameter
  Command->>Device: Send command with program
  Device-->>Button: Command executed
  Button->>CoordinatorStore: Clear pending program
  Button->>Button: Refresh coordinator state
Loading

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Possibly related PRs

Poem

A rabbit refactors with careful design,
Pending programs stored, in sync and in line,
Select waits patiently, button applies with grace,
Legacy swept clean from the registry's place! 🐰✨

✨ 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

Comment @coderabbitai help to get the list of available commands and usage tips.

@telard-pixel
telard-pixel merged commit 43eb5f3 into main Jun 13, 2026
2 of 3 checks passed
This was referenced Jun 13, 2026
tis24dev added a commit that referenced this pull request Jun 23, 2026
…2xx)

Audit findings #15 and #7. refresh() read data["id_token"]/data["access_token"]
directly, raising KeyError on a 2xx response missing them (#7), and never
persisted a rotated refresh_token from the response (#15), so a rotating IdP
would invalidate the stored token and force a full re-login.

Read tokens defensively: a 2xx without id_token/access_token now returns False
(caller falls back to authenticate()) without touching _expires; a rotated
refresh_token in the response is persisted.

Tests: rotation, no-rotation, malformed-2xx->False, 4xx->False.
tis24dev added a commit that referenced this pull request Jun 23, 2026
…2xx)

Audit findings #15 and #7. refresh() read data["id_token"]/data["access_token"]
directly, raising KeyError on a 2xx response missing them (#7), and never
persisted a rotated refresh_token from the response (#15), so a rotating IdP
would invalidate the stored token and force a full re-login.

Read tokens defensively: a 2xx without id_token/access_token now returns False
(caller falls back to authenticate()) without touching _expires; a rotated
refresh_token in the response is persisted.

Tests: rotation, no-rotation, malformed-2xx->False, 4xx->False.
tis24dev added a commit that referenced this pull request Jun 23, 2026
…2xx)

Audit findings #15 and #7. refresh() read data["id_token"]/data["access_token"]
directly, raising KeyError on a 2xx response missing them (#7), and never
persisted a rotated refresh_token from the response (#15), so a rotating IdP
would invalidate the stored token and force a full re-login.

Read tokens defensively: a 2xx without id_token/access_token now returns False
(caller falls back to authenticate()) without touching _expires; a rotated
refresh_token in the response is persisted.

Tests: rotation, no-rotation, malformed-2xx->False, 4xx->False.
tis24dev added a commit that referenced this pull request Jun 25, 2026
)

- oauth._remote_descriptor: defensive parse (bad JSON, non-dict payload, a
  non-numeric or overflowing `ver`) so a malformed Salesforce descriptor stays
  inert -- detection falls back to None instead of raising a hard login failure (#8).
- oauth.detect_progressive_otp: return None when the ViewState id ("vid") is
  absent, mirroring the csrf/authorization guard; an empty vid is sent in every
  /apexremote ctx and would be rejected with a confusing error (#7).
- config_flow._async_close_mfa_client: also clear _mfa_data (the cached plaintext
  password) and _mfa_reauth_entry, so credentials/state are not left reachable on
  the flow object after success, abort, or async_remove (#9).
- hon_client: thread the recorded phase into classify(err, phase=...) at the
  setup / MFA-verify / MFA-send failure sites (defensive; no auth phase is
  timeout-mapped today, so it is future-proofing of the diagnostics wiring) (#10).
- tests: RUF059 unused-unpack rename; mutation-proven coverage for each fix.
tis24dev added a commit that referenced this pull request Jun 28, 2026
…ges, #7 label)

- CodeRabbit #5: add the empty-string dryLevel sentinel to DRY_LEVEL_SENTINELS
  ('' / '0' / '11' are all "no dry level" per the app's hasDryLevelValue), so a blank
  code is dropped from the select options and does not count toward the gate.
- CodeRabbit #6: the range materializer no longer overshoots the max. option_choices
  used `current <= hi + step/2`, which could emit a value beyond the declared max on a
  step that overshoots (0..10 step 20 -> ["0","20"]); it now uses a tight `+1e-9` bound
  (float-drift only). is_settable_option for a drop-free range now requires
  `lo + step <= hi` (>= 2 reachable values) instead of `max > min`, so a single-real-
  value range is not offered as a control. (A defensive step<=0 guard is added too;
  param_range already coerces 0->1 and rejects negatives, so it is unreachable in
  practice.) No real Haier range is affected (all integer, on-grid max).
- CodeRabbit #7: give anti_crease_time a label distinct from anticrease
  (en "Anti-crease time", it "Tempo antipiega") so a WD exposing both does not show two
  identical switches.

Tests added (mutation-proven): uneven-range no-overshoot + gate, '' sentinel drop,
anti_crease_time/anticrease label distinctness. 3-lens refuter pool: HOLD. 907 passed,
1 skipped (local + CI-clean).
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.

2 participants