Repository navigation
fix(cover): pass the yaml class: through to WHO 2 covers (#39) - #796
GreenGrassBlueOcean merged 2 commits into
Conversation
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
e7826ce to
7277805
Compare
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Code review
The change is correct and narrowly scoped to the WHO 2 path (build() used by PlatformDiscovery for who="2").
cover_schema / MyHomeDeviceSchema already folds a yaml device_class: into CONF_DEVICE_CLASS and defaults an absent class to "switch".
The new logic in build() turns a valid CoverDeviceClass into the value passed to the entity and treats the schema default (or any unknown string) as unset via the try/except ValueError.
MyHOMECover.init stores it in _configured_device_class and sets _attr_device_class only when it is present.
_enable_slat_tilt now sets CoverDeviceClass.BLIND only when no class was configured, so an explicit class: shade (or curtain, awning, etc.) is preserved.
WHO 1 impulse covers already had their own handling and are untouched.
Tests (tests/test_cover_device_class.py) exercise the important cases (default shutter, explicit classes, device_class: alias, slat_tilt alone, and slat_tilt + explicit class). Docs and the b16 release notes are updated.
No functional issues found. The implementation matches the description and the #39 request.
PR description
The body already contains the main required sections, but it does not fully match the current .github/pull_request_template.md and the checklist itself is stale:
The PR Description checklist item still lists old section names (Summary, Changes, …, Companion) instead of the template’s actual headings (What is the problem, What is the solution, State Diagram, Hardware Verification, How is it tested, Where are the traces, What does the Firmware Oracle / Encyclopedia say).
Companion is not part of the template (consider dropping it).
An explicit Fixes #39 (or Closes #39) near the top would let GitHub auto-link/close the issue.
Requested change: please update the description so the checklist matches the real sections and the template, and add the Fixes #39 line.
|
Thanks. Description updated: |
…t-HA#39) `class:` / `device_class:` in myhome.yaml was validated by the cover schema but never handed to the entity, so every WHO 2 cover was a `shutter` (or a `blind` with slat_tilt). The configured class now sets the entity's device class and wins over the slat-tilt default. Verified on a physical F461 2.0.11 with an F411/2 at WHERE 05: `class: curtain` gives `device_class: curtain`. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
7277805 to
823a2de
Compare
|
@gdluck the pr description is still not rendering ... State Diagram Unable to render rich display Lexical error on line 6. Unrecognized text. For more information, see https://docs.github.com/get-started/writing-on-github/working-with-advanced-formatting/creating-diagrams#creating-mermaid-diagrams stateDiagram-v2 |
|
Sorry about that. The diagram did not go through a mermaid parser before posting; I have now run every state diagram of this batch through the mermaid 11 parser locally (19 of the 24 failed on colons, semicolons or parentheses inside labels, and this one also on a state named |
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Thanks @gdluck for updating the PR template sections, adding Fixes #39, and validating the batch's Mermaid diagrams!
The code, typing, test coverage, standards checks, and rebase onto v2-phase1-architecture all look solid and verified cleanly. There is just one remaining bug in the PR description's Mermaid diagram to fix before we can approve and merge, plus a few polite, non-blocking polish suggestions if you'd like to include them here or in a small follow-up.
Requested change (blocking): Mermaid NoWithClass typo in ## State Diagram
When Class was renamed to WithClass to avoid Mermaid's reserved class keyword, a substring replacement accidentally turned NoClass into NoWithClass on the first transition line:
NoWithClass --> Shutter : before and after
Because stateDiagram-v2 implicitly creates undeclared state identifiers at the root level, the Mermaid 11 parser accepts this without throwing a syntax error, but renders a broken graph: NoWithClass appears as a disconnected orphan node outside "myhome.yaml cover entry", and NoClass loses its "before and after" transition arrow to Shutter.
Replacing NoWithClass with NoClass fixes the rendered diagram:
stateDiagram-v2
direction LR
state "myhome.yaml cover entry" as Yaml {
NoClass : no yaml key - schema default switch
WithClass : yaml key class curtain - a device_class key is folded into it
}
state "MyHOMECover device_class" as Entity {
Shutter
Blind
Configured : curtain / awning / garage / shade ...
}
[*] --> NoClass
[*] --> WithClass
NoClass --> Shutter : before and after
NoClass --> Blind : slat_tilt true, before and after
WithClass --> Shutter : before , value dropped
WithClass --> Configured : after , slat_tilt true keeps it
(Worth a quick grep across the other PR descriptions in the batch too, just in case a similar Class → WithClass replacement touched another NoClass node.)
Non-blocking follow-up suggestions (optional)
Left three small inline notes on docs/configuration/covers.md, custom_components/myhome/cover.py, and tests/test_cover_device_class.py (all non-blocking — happy to take them either in this PR or a follow-up):
docs/configuration/covers.mdstructure & cross-references: PromotingDevice class (class)out of the Venetian BlindsH2section so general cover users find it easily, qualifying theslat_tilt"sets device class toblind" lines right above it ("unlessclassis set"), and mentioning Home Assistant's UI Settings → Entities → Show as option for v2 UI-configured covers (matchingswitches.md/binary-sensors.md).cover.pyraw-dict parity: Keepingor cfg.get("device_class")inbuild()for symmetry withbuild_impulse()right below it when callers bypasscover_schema.- Optional extra test case & Encyclopedia note: Optionally adding a
MyHOMEScopeCover(area/group) orstrategy: "venetian"+class: "shade"test case; and if helpful in the PR's Encyclopedia section, noting thatWHO = 2wire frames never carry actuator loadSUBTYPE(2= Shutter,3= Curtain,4= Gate,5= Garage door only exists in offline MyHOME_Suite catalogue metadataOWN-DEV-0003), which is why device class must come from HA configuration or UI "Show as".
|
|
||
| Higher tilt is assumed to be *open* (an opening pulse raises the angle, verified on an F401 roller shutter with tilting slats in [discussion #770](https://github.com/orgs/OpenWebNet-HA/discussions/770#discussioncomment-19144521) with `slat_time: 1.4`). Dedicated Venetian actuators (such as the F411U2 in Venetian mode) still await a keypad / app capture (#492). | ||
|
|
||
| ### Device class (`class`) |
There was a problem hiding this comment.
Non-blocking doc polish: Would you mind considering three small tweaks here (either in this PR or a quick follow-up)?
- Section level / placement:
### Device class (\class`)is currently nested as anH3inside## 🪟 Venetian Blinds & Slat Tilt Positioning (Timed Slat Inching), whereasclass:applies to all WHO 2 covers (curtains, awnings, shutters, garage doors, gates, windows, shades). Promoting it to its own##` section (or placing it with general cover YAML configuration) would make it much easier to spot. - Consistency with Venetian bullets above: The bullet and
slat_tiltparameter table row just above still say "Sets the entity device class toblind" without "unlessclassis set". - v2 UI pointer: Like
docs/configuration/switches.mdanddocs/configuration/binary-sensors.md, a short note that UI-configured covers (withoutmyhome.yaml) can change their device class in Home Assistant via Settings → Devices & services → Entities → Show as would be helpful for v2 UI users.
| # with myhome.set_cover_travel_time wins until it is reset. | ||
| kwargs["calibration"] = {CONF_SLAT_TIME: stored[CONF_SLAT_TIME]} | ||
| # cover_schema already folds a yaml `device_class:` into CONF_DEVICE_CLASS. | ||
| raw_dev_class = cfg.get(CONF_DEVICE_CLASS) |
There was a problem hiding this comment.
Non-blocking nit: myhome.yaml is completely fine here because MyHomeDeviceSchema folds "device_class" into CONF_DEVICE_CLASS (and defaults omitted entries to "switch"), as the comment notes. Still, build_impulse() right below (line 266), as well as switch.py, binary_sensor.py, and sensor.py, writes cfg.get(CONF_DEVICE_CLASS) or cfg.get("device_class") — keeping that fallback here would stay symmetric if any caller ever pre-seeds hass.data without running cover_schema.
| ({CONF_DEVICE_CLASS: "garage"}, CoverDeviceClass.GARAGE), | ||
| ({CONF_SLAT_TILT: True}, CoverDeviceClass.BLIND), | ||
| ({CONF_SLAT_TILT: True, CONF_DEVICE_CLASS: "shade"}, CoverDeviceClass.SHADE), | ||
| ], |
There was a problem hiding this comment.
Non-blocking test suggestion: Nice, concise parametrized coverage — this already hits 100% of the changed lines in cover.py (including the "switch" ValueError path). If you happen to touch this file, adding a quick case for an area/group MyHOMEScopeCover (e.g. where: "1") or strategy: "venetian" + class: "shade" could be nice extra defense-in-depth, though both paths work cleanly as written.
|
Good catch, thank you: |
…back, add scope and venetian test cases (review follow-up) Review follow-up on OpenWebNet-HA#796 (OpenWebNet-HA#39), the three non-blocking items: - docs/configuration/covers.md: "Device class (`class`)" is a section of its own next to the general cover YAML configuration instead of a subsection of the Venetian section; the Venetian bullet and the `slat_tilt` parameter row say "blind unless `class` is set"; UI covers are pointed to Settings -> Devices & Services -> Entities -> Show As, as switches.md and binary-sensors.md do. - cover.py build(): `cfg.get(CONF_DEVICE_CLASS) or cfg.get("device_class")` again, for symmetry with build_impulse(), switch.py, binary_sensor.py and sensor.py when a caller seeds hass.data without cover_schema. - tests/test_cover_device_class.py: `strategy: venetian` with and without `class: shade`, an area `where: "1"` MyHOMEScopeCover with `class: curtain`, and a general `where: "0"` scope cover. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Polish pushed as 7696194: |
|
@Interstellar0verdrive raising your awaereness because of the sidepanel and maybe you also have some input for this |
Interstellar0verdrive
left a comment
There was a problem hiding this comment.
Approving. My shutters have no class: set, so this is code review plus my own probes.
The schema only accepts the ten HA cover classes, so a typo fails validation instead of silently becoming a shutter, and treating the switch default as unset matches the impulse covers. A configured class survives slat tilt, promotion and demotion (I probed curtain through all three), scope covers get it through the same path, and covers without class are unchanged: still shutter, or blind with slat_tilt. My #702 probes give identical output.
On the side panel, which is @xtimmy86x's area: nothing here conflicts with it. Covers set up from the panel rely on "Show As" until the panel offers a class of its own, which the new docs section already explains; whether and when to add that is his call.
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
What was requested vs. current state
Description updated to match the template + Fixes #39
Mermaid diagram fixed (NoClass restored) and validated
Polish items added in the latest commit:
Device class section promoted in docs/configuration/covers.md + “unless class is set” wording + UI “Show As” note
cfg.get(CONF_DEVICE_CLASS) or cfg.get("device_class") fallback in build()
Extra tests (venetian, venetian+shade, area/general scope covers)
Encyclopedia note about SUBTYPE only existing in offline catalogue metadata
All review threads are either outdated or non-blocking; the author addressed the blocking Mermaid issue and the optional polish.
31ea9fd
into
OpenWebNet-HA:v2-phase1-architecture
Requested by A
Fixes #39
What is the problem
class:(ordevice_class:) on a WHO 2 cover inmyhome.yamlis accepted by the cover schema but never reaches the entity:async_setup_entrybuildsMyHOMECoverwithout it, so every WHO 2 cover is ashutter, or ablindwhenslat_tilt: trueis set. A curtain or awning configured as such is shown with the shutter icon and wording.This is the yaml path of #39.
Rebased onto the current
v2-phase1-architecture(ddc6d60): the one conflict with the merged #778 was the coverbuild()kwargs, resolved by keeping bothmin_position_delta=anddevice_class=.What is the solution
cover.pyasync_setup_entry: the configured class is turned into aCoverDeviceClassand passed to the entity. The device schema defaults an absent class to"switch", which is not a cover class, so that value (or any unknown one) is treated as "not configured", the same way the impulse covers already handle it. The lookup readscfg.get(CONF_DEVICE_CLASS) or cfg.get("device_class"):cover_schemafolds a yamldevice_class:intoclass, and the raw-key fallback stays for symmetry withbuild_impulse(),switch.py,binary_sensor.pyandsensor.pyshould a caller ever seedhass.datawithout running the schema (review follow-up).MyHOMECover.__init__: newdevice_classkwarg; a configured class sets_attr_device_class._enable_slat_tiltsetsBLINDonly when no class is configured, soslat_tilt: truewithclass: shadestays a shade.class)" section of its own indocs/configuration/covers.md, next to the general cover YAML configuration (review follow-up: it was nested under the Venetian section). It says the class applies to point-to-point and to general / area / group covers, the Venetian bullet and theslat_tiltparameter row now read "sets device class toblindunlessclassis set", and UI-configured covers are pointed to Settings → Devices & Services → Entities → Show As, asswitches.mdandbinary-sensors.mddo. Release-notes delta line.State Diagram
stateDiagram-v2 direction LR state "myhome.yaml cover entry" as Yaml { NoClass : no yaml key - schema default switch WithClass : yaml key class curtain - a device_class key is folded into it } state "MyHOMECover device_class" as Entity { Shutter Blind Configured : curtain / awning / garage / shade ... } [*] --> NoClass [*] --> WithClass NoClass --> Shutter : before and after NoClass --> Blind : slat_tilt true, before and after WithClass --> Shutter : before , value dropped WithClass --> Configured : after , slat_tilt true keeps itHardware Verification
Live F461 2.0.11 plant (local Home Assistant, combined build of all our PRs, 2026-10-24) and, for status reads, the MyHomeServer1 3.87.13 plant; captures in MyHOME #799 (merged).
Live retest (2026-10-24)
Consensus label: confirmed for the tested case (combined build, F461 2.0.11, HA 2026.10.2). A yaml variant with
class: curtainon cover 05 gavecover.shutterdevice_class: curtain; the plant yaml without the key gaveshutter. No bus frame is involved, so none was exercised.How is it tested
tests/test_cover_device_class.py::test_who2_cover_uses_the_configured_class(10 cases, from acover_schema-validated yaml dict throughasync_setup_entry): default →shutter,class: curtain,device_class: awning,class: garage,slat_tilt→blind,slat_tilt+class: shade→shade,strategy: venetian→blind,strategy: venetian+class: shade→shade, areawhere: "1"+class: curtain→curtainon aMyHOMEScopeCover, generalwhere: "0"→shutteron aMyHOMEScopeCover(the last four added in the review follow-up).v2-phase1-architecture(d8bac15) (curtain, awning, garage, the two shade cases, the area curtain); the shutter and blind cases pass before and after.mastercheckout (f04d8aa) onPYTHONPATH, HA 2026.10.2, Python 3.14,-n auto: 4489 passed, 1 skipped.scripts/verify_ownd_coverage.py: 100 % statement coverage (16,052 / 16,052).ruff check .,scripts/typing_ratchet.py(mypy --strict, 0 errors),scripts/verify_ha_standards.py,scripts/sync_documentation.py --check,scripts/manage_translations.py check,tests/test_fixture_privacy.pypass.Where are the traces
WHERE 05(where the yaml check below was made)What does the Firmware Oracle / Encyclopedia say
No oracle stimulus exists for this change: no frame is sent or parsed differently.
WHO 2 wire frames never carry the actuator's load SUBTYPE: a status is
*2*<WHAT>*<WHERE>##and a level is*#2*<WHERE>*10*<LEVEL>*<STATUS>##, with no field that tells a shutter from a curtain, a gate or a garage door. The SUBTYPE values (2 = Shutter, 3 = Curtain, 4 = Gate, 5 = Garage door) exist only in the offline MyHOME_Suite catalogue metadata (Encyclopedia OWN-DEV-0003), which the gateway does not expose on the bus. The device class therefore has to come from the Home Assistant configuration (class:inmyhome.yaml) or from the UI "Show As" setting; it cannot be discovered.Checklist
What is the problem,What is the solution,State Diagram,How is it tested,Where are the traces,What does the Firmware Oracle / Encyclopedia say).MyHOMECoverkeeps itshandle_event()path; noasync_dispatcher_connectadded.pytest,ruff, andscripts/verify_ha_standards.pypass.🤖 Generated with Claude Code