Skip to content

fix(heating): decode WHAT 1 / 0 as the season, not an operating mode - #94

Closed
gdluck wants to merge 1 commit into
OpenWebNet-HA:masterfrom
gdluck:feat/message-what-property
Closed

gdluck wants to merge 1 commit into
OpenWebNet-HA:masterfrom
gdluck:feat/message-what-property

Conversation

@gdluck

@gdluck gdluck commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Requested by A · project thread

Reworked after the review on the first version of this PR, which added a public OWNMessage.what. That property is gone; the frame is decoded in OWNd, as suggested:

  1. Protocol decoding belongs in OWNd, not in consumers → OWNHeatingEvent decodes the frame itself; MyHOME#649 reads the typed attribute and no longer knows that WHO 4 what == 1 is a season frame.
  2. A bare WHAT is meaningless without WHO → no raw int is exposed; the new attribute is season on the WHO 4 event only.
  3. Hard to take back once public → nothing new on OWNMessage; the additions are MESSAGE_TYPE_SEASON, SEASON_HEATING, SEASON_CONDITIONING and OWNHeatingEvent.season.
  4. None meaning two things → not applicable any more; season is None only for WHATs that have no season.
  5. mode should stop conflating season and operating mode → the bare WHAT 1 / 0 frame now has mode = None and message_type = "hvac_season"; the operating-mode WHATs (110 / 111 / 103 …) keep mode and additionally expose their season.
  6. MyHOME#649's _what fallback goes away → done; with the pinned OWNd 2.0.0b10 the frame still arrives as hvac_mode and takes MyHOME's unchanged path, so the two can merge in either order.

Before: *4*1*Z## and *4*0*Z## were read as mode = heat / cool (message_type = hvac_mode), so a zone running the central unit's program looked like a manual heat / cool zone, and a consumer that wanted to tell the two apart had to look at the private _what.

After: OWNHeatingEvent gets a typed season attribute (SEASON_HEATING / SEASON_CONDITIONING / None), separate from the operating mode. The bare WHAT 1 / 0 frame is message_type = MESSAGE_TYPE_SEASON ("hvac_season"), season set, mode = None. Every season-specific mode WHAT also carries its season (110 → heat + heating, 210 → cool + conditioning, 102 → off + heating, 1102 → heat + heating, 2201 → cool + conditioning); the generic WHATs (3xx, 31xx, 32xx, 33xxx) have season = None. Nothing else in mode changes. No raw WHAT is exposed.

Evidence

Spec. Legrand WHO 4 v2.0.0: p. 5 WHAT table, 0 "Conditioning Mode", 1 "Heating Mode"; p. 13, 16, 19 and 63 list *4*what*where## with what = 0 conditioning / 1 heating / 102 / 202 / 303 as the "zone operation mode acquire frame", given together with the setpoint frames; the operating modes are the separate WHATs 110 / 210 (manual), 111 / 211 (automatic), 103 / 203 (off) on p. 55 and p. 65. The season digit is the hundreds / thousands digit of every mode WHAT (p. 5: 1xx / 11xx / 12xx / 13xxx heating, 2xx / 21xx / 22xx / 23xxx conditioning, 3xx / 31xx / 32xx / 33xxx generic).

libqtdevices TS10_1_0_23. probe_device.cpp:

240: 	case CONDITIONING:
241: 	case HEATING:
242: 		if (!isCommandFrame(msg))
243: 			break;
244: 		if (status != ST_MANUAL && status != ST_AUTO)
246: 			values_list[DIM_STATUS] = status = ST_AUTO;

The client does not change a zone that is already manual or automatic on this frame; thermal_device.cpp:111-119 (setSummer / setWinter) sends WHAT 0 / 1 to the central unit as the season command.

Firmware. The MyHomeServer1 translator emits *4*1*Z## / *4*0*Z## with the dimension 12 setpoint report (oracle/SCS_OWN_MAP.md section 4, dimension 12: mode byte F0 → *4*1*Z##, F1 → *4*0*Z##, F2 → 102, F3 → 202, FF → 303). Emitted corpus (hex2own_corpus.tsv): 35 lines *4*1*Z##, 19 lines *4*0*Z##; 512 of the 534 dimension-12 reports are followed by such a frame. As commands, *4*1*23## / *4*0*23## are accepted and forwarded (93 17 30 00 / 01), identical on MyHomeServer1, F454, F459, F461 and F450 (results/termo*.tsv).

How

OWNd/message/heating.py: MESSAGE_TYPE_SEASON, SEASON_HEATING, SEASON_CONDITIONING, the season property, and the WHAT 1 / 0 branch ahead of the mode classification; exported from OWNd/message/__init__.py. Tests: new tests/test_who4_season.py; tests/test_issue_77_firmware_findings.py (fix 1) now expects the season reading for WHAT 1 / 0; tests/test_message_exhaustive.py used the undocumented *4*1#0215*1## as a mode-target frame and now uses the documented *4*110#0215*1## (Spec p. 23). pytest: 1252 passed, 2 skipped locally (plus the metadata test that needs the package installed). Coverage of heating.py stays 100 %.

Consumer impact: a consumer that mapped mode == heat/cool from the bare frame now receives hvac_season instead. OpenWebNet-HA/MyHOME#649 is updated to read season (and no longer touches any WHAT); with the pinned OWNd 2.0.0b10 it keeps the old path.


How to check. libqtdevices quotes are from tag TS10_1_0_23 of OpenWebNet-HA/libqtdevices (BTicino's 2010 touch-screen client, used only as a second client, not as the reference). Firmware rows are replays of the exact frame on the original gateway translator binaries (ARM, from the MyHomeServer1, F454, F459, F461 and F450 images) under QEMU on a fake SCS bus. Spec = Legrand/BTicino "Open Web Net Language, WHO = 4" v2.0.0 (2013-11-27). Raw logs, command lists, the harness and the report are in firmware_validation.zip. The firmware images themselves are not included.

Split out of #86 so each change can be checked on its own; the eight slices (#87-#94) merge together in any order without conflicts, and the merged union passes 1285 tests with 100 % coverage of OWNd/message (checked locally on 2026-10-07 after the review rework of #94).

🤖 Generated with Claude Code

https://claude.ai/code/session_018oHp1P4vxxoFnaLUUZpbpZ

@codecov-commenter

Copy link
Copy Markdown

⚠️ Please install the 'codecov app svg image' to ensure uploads and comments are reliably processed by Codecov.

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

@GreenGrassBlueOcean

GreenGrassBlueOcean commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Thanks for splitting this out and for the evidence. I'm not in favour of merging it, though, because of what it does to the API surface, not because of the size of the change.

The problem it points at is in OWNd. The description says OWNHeatingEvent.mode maps *4*1*Z## / *4*0*Z## to heat / cool, and that this is wrong for a zone in automatic mode, because the frame is the zone's season (zone operation mode) and not a mode change. That is a modelling bug in the library. OWNMessage.what doesn't fix it. It hands the raw int to every consumer so each one can work around the bug itself.

Why I don't want what as public API:

  1. Protocol decoding moves to the consumers. MyHOME#649 has to know that WHO 4 what == 1 is a season frame. That belongs in one place, which is OWNd. Otherwise every consumer reimplements it, and each can get it wrong in its own way.
  2. The int is meaningless without who. WHAT is defined per WHO. A bare msg.what == 1 is a comparison that silently breaks when someone forgets to check who. A public int | None invites exactly that.
  3. It's hard to take back. We are rebuilding the library. Once .what is public, consumers depend on it and removing it later is a breaking change. _what was at least honest about being an implementation detail.
  4. None means two things ("dimension frame" and "no WHAT"), which is another small trap.

What I'd rather see:

  • Decode the frame in OWNd. OWNHeatingEvent gets an explicit season / zone operation mode attribute (heating / conditioning), separate from the zone's operating mode (manual / automatic / ...). mode stops conflating the two.
  • MyHOME#649 then reads the typed attribute and never touches a WHAT. The "fall back to _what on older OWNd releases" path goes away too.
  • If a raw escape hatch is still wanted for debugging, expose it on a clearly diagnostic surface (e.g. the raw frame string) and not as a first-class typed property.

The libqtdevices reference (probe_device.cpp:240-253) and the corpus numbers are exactly the input the decoding fix needs. Could this PR turn into that change? Then #649 can depend on it directly.

@gdluck
gdluck force-pushed the feat/message-what-property branch from 8e2bd79 to 55c266d Compare October 7, 2026 17:42
@gdluck gdluck changed the title feat(message): expose the raw WHAT as OWNMessage.what fix(heating): decode WHAT 1 / 0 as the season, not an operating mode Oct 7, 2026
gdluck pushed a commit to gdluck/HomeAssistant-MyHOME that referenced this pull request Oct 7, 2026
OpenWebNet-HA/OWNd#94 decodes *4*1*Z## / *4*0*Z## as hvac_season with a
season attribute. A zone that is AUTO, HEAT or COOL keeps its mode on it;
a zone that was OFF or unknown takes the season's mode; a central unit
shows the season as its mode. No WHAT is read anywhere. With the pinned
OWNd 2.0.0b10 the frame still arrives as hvac_mode and takes the old path.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gdluck

gdluck commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Implemented as the maintainer asked. PR 94 is now "fix(heating): decode WHAT 1 / 0 as the season, not an operating mode" and no longer adds OWNMessage.what:

  • OWNHeatingEvent gets a typed season attribute (SEASON_HEATING / SEASON_CONDITIONING / None), separate from mode. The bare *4*1*Z## / *4*0*Z## frame is message_type = "hvac_season" with mode = None; every season-specific mode WHAT (110, 210, 102, 1102, 2201, …) also exposes its season, generic WHATs have none. Nothing else in mode changes, no raw WHAT is public.
  • Tests: new test_who4_season.py; two existing tests that asserted heat/cool for WHAT 1/0 (and one that used the undocumented *4*1#0215*1##) updated with the spec references. 1252 pass locally, heating.py stays at 100 % coverage. Body rewritten with the spec pages, the libqtdevices quote and the firmware rows.
  • MyHOME #649 now reads season and touches no WHAT: a zone that is AUTO, HEAT or COOL keeps its mode on the frame, a zone that was OFF or unknown takes the season's mode, a central unit shows the season as its mode (as before). With the pinned OWNd 2.0.0b10 the frame still arrives as hvac_mode and takes the old path, so the two can merge in either order; the new season tests skip on the pinned version.

One consequence to expect: PR 94's downstream "MyHOME test suite" job will stay red until #649 is merged, because MyHOME's upstream branch does not yet handle hvac_season (its *4*1*01## → HEAT test needs the 649 branch). OWNd's own checks are running now; I'll confirm when they finish.

Done. PR 94's description now opens with a point-by-point follow-up to the review: each of the maintainer's six points (decoding in OWNd, no bare WHAT, nothing new on OWNMessage, no double-meaning None, mode no longer conflating season and operating mode, MyHOME's _what fallback removed) with what the reworked change does for it, followed by the evidence and the test changes. CI on the branch is still running; I'll confirm when it lands.

*4*1*Z## and *4*0*Z## are the zone operation mode frame (Legrand WHO 4
v2.0.0 p. 5, 13, 16, 19, 63): the zone, or the central unit, operates in
the heating (1) or conditioning (0) season. OWNHeatingEvent read them as
mode heat / cool, which made a zone running the central unit's program
look like a manual heat / cool zone. They now carry
message_type MESSAGE_TYPE_SEASON, season SEASON_HEATING /
SEASON_CONDITIONING and mode None; every season-specific mode WHAT (1xx,
11xx, 12xx, 13xxx / 2xx, 21xx, 22xx, 23xxx) also exposes its season.

Evidence: libqtdevices TS10_1_0_23 probe_device.cpp:240-253 keeps a zone
in automatic on the frame; the MyHomeServer1 translator emits it with the
dimension 12 setpoint report (512 of 534 reports in the emitted corpus).

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@gdluck

gdluck commented Oct 7, 2026

Copy link
Copy Markdown
Contributor Author

Superseded by #97, which carries this change verbatim together with the other WHO 4 heating fixes. Closing as agreed in OpenWebNet-HA/MyHOME#649.

@gdluck gdluck closed this Oct 7, 2026
GreenGrassBlueOcean added a commit that referenced this pull request Oct 16, 2026
…#88, #89, #94)

Consolidate the four domain-focused WHO 4 heating improvements into a single cohesive branch:
1. WHAT 1 / 0 decoded as season (MESSAGE_TYPE_SEASON: hvac_season) with typed .season
   attribute (heating / conditioning), leaving .mode as None so season updates do
   not flip active AUTO/manual operating modes (OWNd#94).
2. WHAT 115#/215#/315# parameter read as weekly program (1..16), not negative temperature,
   exposing .program and .scenario properties on OWNHeatingEvent (OWNd#87).
3. WHAT 13xxx (heating) and 23xxx (conditioning) holiday days assigned to respective
   seasonal modes rather than generic auto, exposing .holiday_days (OWNd#88).
4. WHATs 22, 23, 24, 30, and 31 recognized as central unit system status frames with
   descriptive telemetry logs, leaving mode as None (OWNd#89).

Co-authored-by: gdluck <9041273+gdluck@users.noreply.github.com>
GreenGrassBlueOcean added a commit that referenced this pull request Oct 16, 2026
…d-heating

fix(heating): correct season status, holiday programs, and CU diagnostics (#87, #88, #89, #94)
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.

3 participants