Skip to content

fix(heating): correct season status, holiday programs, and CU diagnostics (#87, #88, #89, #94) - #97

Merged
GreenGrassBlueOcean merged 10 commits into
OpenWebNet-HA:masterfrom
GreenGrassBlueOcean:feat/who4-consolidated-heating
Oct 16, 2026
Merged

GreenGrassBlueOcean merged 10 commits into
OpenWebNet-HA:masterfrom
GreenGrassBlueOcean:feat/who4-consolidated-heating

Conversation

@GreenGrassBlueOcean

@GreenGrassBlueOcean GreenGrassBlueOcean commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Consolidated WHO 4 Thermoregulation & Climate Fixes

This pull request consolidates four domain-focused WHO 4 heating/thermoregulation fixes (originally submitted in #87, #88, #89, and #94) into a single, cohesive, firmware-validated PR for the next OWNd release.


1. What is the issue?

Prior to this PR, OWNd had four distinct protocol parsing defects in WHO 4 (Thermoregulation):

  1. Conflation of HVAC Season with Operating Mode (WHAT 1 / 0) — fix(heating): decode WHAT 1 / 0 as the season, not an operating mode #94:

    • *4*1*Z## and *4*0*Z## were parsed as operating modes mode = "heat" and mode = "cool" with message_type = "hvac_mode".
    • In OpenWebNet WHO 4, bare WHAT 1 ("heating mode") and 0 ("conditioning mode") are zone operation mode acquire frames indicating the active season (winter/summer). Gateways broadcast them next to temperature/setpoint reports.
    • Impact: When a zone was actively running an automatic weekly schedule (auto), receiving a season status frame from the gateway flipped mode to manual \"heat\" or \"cool\", breaking the climate entity's schedule state. Consumers had no way to inspect season without reading the private _what.
  2. Holiday Plan Program Parameter Misparsed as Negative Temperature (WHAT 115# / 215# / 315#) — fix(heating): read the 115#/215#/315# parameter as a program, not a temperature #87:

    • In holiday plan frames such as *4*115#1102*#0##, the parameter 1102 specifies the weekly program index (program 2) to resume following holiday operation.
    • OWNd routed this parameter through who4_temperature("1102"). Under BTicino's 4-digit signed temperature format (values $\ge 1000$ denote negative Celsius), 1102 was decoded as -10.2 °C, setting set_temperature = -10.2 and message_type = "hvac_mode_target".
    • Impact: Setting a holiday plan caused climate entities to report a phantom target temperature of -10.2°C instead of preserving the scheduled heating mode and program.
  3. Heating & Cooling Holiday Days Misclassified as Generic AUTO (WHAT 13xxx / 23xxx) — fix(heating): 13xxx and 23xxx are holiday days in heating / conditioning mode #88:

    • Frames in ranges 13001..13255 (holiday days in heating mode) and 23001..23255 (holiday days in conditioning mode) were grouped with generic 33xxx and parsed as CLIMATE_MODE_AUTO.
    • Impact: Holiday mode in winter or summer was falsely reported as generic automatic mode, and the holiday duration (xxx = 1..255 days) was completely discarded.
  4. Central Unit Diagnostic Telemetry Logged as Unknown (WHAT 22, 23, 24, 30, 31) — fix(heating): name the central unit status WHATs 22, 23, 24, 30 and 31 #89:

    • Central unit diagnostic status frames (*4*22*#0## probe OFF, 23 probe in antifreeze, 24 probe in manual, 30 failure discovered, 31 battery KO) fell through to the unhandled fallback.
    • Impact: Spurious warnings (\"Zone #0's mode is unknown\") flooded the logs, obscuring real central unit hardware alarms.

2. How is it fixed?

The changes in OWNd/message/heating.py and OWNd/message/__init__.py address each defect cleanly without breaking existing consumers:

  1. Decouple Season from Operating Mode:

    • Added MESSAGE_TYPE_SEASON = "hvac_season", SEASON_HEATING = "heating", and SEASON_CONDITIONING = "conditioning".
    • Added typed property OWNHeatingEvent.season -> str | None.
    • For bare WHAT 0 or 1 without parameters: parsed as message_type = MESSAGE_TYPE_SEASON, season = "heating" | "conditioning", leaving mode = None. This prevents season frames from overriding active manual/auto/off states.
    • WHAT 0 or 1 with a parameter (*4*1#0215*1##, *4*0#0215*1##) is in neither the specification nor any capture (the only setpoint write is *#4*#Z*#14*T*M##), so it is parsed as an unknown WHAT: mode = None, season = None, message_type = None, set_temperature = None (43526a6). Unrecognized WHATs leave season = None and message_type = None.
    • Every recognized season-specific mode WHAT populates .season strictly within its validated branch (1xx/11xx/12xx/13xxx -> heating, 2xx/21xx/22xx/23xxx -> conditioning, generic 3xx -> None).
  2. Parse Holiday Plan and Vacation Parameters as Program Number:

    • For WHAT in (115, 215, 315) as well as multi-day vacation frames 13DDD#P, 23DDD#P, 33DDD#P: decoded as program = int(param) % 100 (1102 / 3102 -> program 2). set_temperature remains None, and message_type stays hvac_mode.
    • Added public properties OWNHeatingEvent.program -> int | None and OWNHeatingEvent.scenario -> int | None (supporting weekly programs 1..3 for seasonal, 1..16 for generic, and scenarios 1..16 on 12xx/22xx/32xx).
  3. Map Seasonal Holiday Days & Expose Duration:

    • 13001..13255 mapped to CLIMATE_MODE_HEAT (season = "heating").
    • 23001..23255 mapped to CLIMATE_MODE_COOL (season = "conditioning").
    • 33001..33255 retained as CLIMATE_MODE_AUTO (season = None).
    • Added public property OWNHeatingEvent.holiday_days -> int | None computed as mode % 1000.
  4. Recognize Central Unit Diagnostic Status WHATs:

    • Added handling for WHATs 22, 23, 24, 30, 31 with descriptive telemetry logs (e.g. 23 -> \"Zone #0's central unit reports at least one probe is in antifreeze\").
    • Leaves mode = None and season = None, avoiding spurious "unknown mode" logs while preserving downstream stability.

Frame Parsing Comparison Table

OpenWebNet Frame Previous Parsing on master Fixed Parsing in this PR
*4*1*23## mode="heat", type="hvac_mode" mode=None, season="heating", type="hvac_season"
*4*0*23## mode="cool", type="hvac_mode" mode=None, season="conditioning", type="hvac_season"
*4*115#1101*#0## (contributed in MyHOME#692) mode="heat", set_temp=-10.1, type="hvac_mode_target" mode="heat", program=1, set_temp=None, type="hvac_mode"
*4*115#1102*#0## mode="heat", set_temp=-10.2, type="hvac_mode_target" mode="heat", program=2, set_temp=None, type="hvac_mode"
*4*215#2102*#0## mode="cool", set_temp=None, type="hvac_mode" mode="cool", program=2, set_temp=None, type="hvac_mode"
*4*13005#3102*#0## mode="auto", set_temp=-10.2, type="hvac_mode_target" mode="heat", season="heating", program=2, holiday_days=5, type="hvac_mode"
*4*23005#3102*#0## mode="auto", set_temp=None, type="hvac_mode" mode="cool", season="conditioning", program=2, holiday_days=5, type="hvac_mode"
*4*33005#3102*#0## mode="auto", set_temp=None, type="hvac_mode" mode="auto", season=None, program=2, holiday_days=5, type="hvac_mode"
*4*13001*1## mode="auto", holiday_days=None mode="heat", season="heating", holiday_days=1, type="hvac_mode"
*4*23001*1## mode="auto", holiday_days=None mode="cool", season="conditioning", holiday_days=1, type="hvac_mode"
*4*33004*#0## mode="auto", holiday_days=None mode="auto", season=None, holiday_days=4, type="hvac_mode"
*4*23*#0## Logged \"mode is unknown\", mode=None Logged \"at least one probe is in antifreeze\", mode=None
*4*30*#0## Logged \"mode is unknown\", mode=None Logged \"a failure was discovered\", mode=None
*4*31*#0## Logged \"mode is unknown\", mode=None Logged \"the central unit battery is KO\", mode=None

3. How is it tested?

  1. Dedicated Unit Test Suites:

    • tests/test_who4_season.py: verifies bare WHAT 0/1 frames emit MESSAGE_TYPE_SEASON with mode is None and correct season, tests a 13-row matrix of season-specific mode WHATs, verifies non-mode frames have season is None, and verifies unrecognised WHATs (150, 1104, 1199, 13000, 13256, 1#0215, 0#0215) leave season is None and message_type is None.
    • tests/test_who4_holiday_plan_program.py: verifies 115#1102, 215#2102, 13005#3102, 23005#3102, 33005#3102 extract program == 2 with set_temperature is None, verifies program/scenario exposure across 1102, 2102, 3113, 1203, 3209, and asserts genuine setpoint frames (110#0215, 312#0220#2) still read target temperatures.
    • tests/test_who4_central_status_whats.py: verifies diagnostic status logging for WHATs 22, 23, 24, 30, 31 (asserting WHAT 23 logs "antifreeze") with mode is None, and verifies unhandled WHATs (e.g. 999) still log unknown.
    • tests/test_who4_f453av_traces.py: pins all 6 physical trace scenarios from @nce2704 on Central Unit #0.
    • tests/test_message_edges.py: verifies holiday day counts and seasonal modes for 13001, 23001, and 33004.
    • tests/test_issue_77_firmware_findings.py: updated central mode test matrix to assert season reading for WHAT 1 / 0.
    • tests/test_message_exhaustive.py: updated mode-target test to use documented standard *4*110#0215*1##.
  2. Coverage and Test Suite Results:

    • pytest tests: 1,370 passed, 1 skipped.
    • 100.0% statement and branch coverage on OWNd/message/heating.py (709/709 statements) and across all 24 modules of OWNd (3,870 / 3,870 statements).
    • verify_library_standards.py and ruff check: 100% clean.

4. How is it covered in real traces?

All changes are grounded in seven independent layers of evidence:

  1. Live Gateway Emitted Corpus (hex2own_corpus.tsv, 26,810 lines):

    • Captured from operational Legrand MyHOME systems.
    • Contains 35 lines of *4*1*Z## and 19 lines of *4*0*Z##.
    • 512 out of 534 dimension-12 setpoint reports are immediately accompanied by *4*1*Z## or *4*0*Z## broadcasts (F0 byte translates to *4*1*Z##, F1 translates to *4*0*Z##). This empirical correlation proves they are season status telemetry, not operating mode changes.
  2. Multi-Gateway Firmware Replays (thermo-holiday-program across 10 Gateway Models):

    • Grounded in the thermo-holiday-program suite of own-firmware-oracle (replayed green across all 10 catalogued gateways: F450, F453AV, F454, F459, F460, F461, H4684, MH200N, MH202, and MyHomeServer1, synced in results/mcp_index.json):
      • *4*115#1102*#0## is ACKed and translated by bt_termo into SCS telegram D1 00 03 02 C1 14 01 00 (byte 6 = 14 holiday plan heating, byte 7 = 01 program 2). There is no temperature byte on the bus (contrast with real setpoint *#4*#0*#14*0250*1## -> ... C1 12 32 00 where 32 = 25.0°C).
      • *4*215#2102*#0## is ACKed and translated into conditioning holiday plan telegram (program 2).
      • *4*13005#3102*#0##, *4*23005#3102*#0##, and *4*33005#3102*#0## are ACKed and emit D1 00 03 02 C1 05 05 01 (byte 7 = 05 days, byte 8 = 01 program 2).
      • Season commands *4*1*23## and *4*0*23## are ACKed and emitted as 93 17 30 01 and 93 17 30 00.
  3. Official Legrand Protocol Specification:

    • Legrand Open Web Net Language, WHO = 4 (v2.0.0, 2013-11-27):
      • Pages 5 & 64 (WHAT Table): defines WHAT 0 as "Conditioning Mode", 1 as "Heating Mode"; defines 115#parameterH / 215#parameterH where parameterH is weekly program 1101..1103 / 2101..2103.
      • Pages 13, 16, 19, 63: WHAT 0 and 1 are designated as "zone operation mode acquire frame" alongside setpoint reports, distinct from operating mode commands (110, 111, 103).
      • Pages 5, 24, 64: defines 13xxx as holiday days heating, 23xxx as holiday days conditioning; defines 22, 23, 24, 30, 31 as central unit system status reports (with 23 defined as "at least one probe in antifreeze").
  4. Reference Touchscreen Client Implementation (libqtdevices TS10_1_0_23):

    • probe_device.cpp:240-253: on receiving HEATING (1) or CONDITIONING (0), preserves active ST_MANUAL and ST_AUTO states without resetting them.
    • thermal_device.cpp:240-241, 285-286: parses whatArgN(0) % 100 as the program index.
    • thermal_device.cpp:258-262, 303-307: assigns SUM_HOLIDAY (23000) to summer season and WIN_HOLIDAY (13000) to winter season.
    • thermal_device.cpp:47-48, 215-217: parses diagnostic status WHATs 30 (MALFUNCTIONING_FOUND) and 31 (BATTERY_KO) without altering mode.
  5. Physical Gateway Central Unit #0 & Holiday Bus Captures (tests/fixtures/traces/issue_619/, test(trace): add authentic BTicino F453AV Central Unit #0 and WHO 4 trace fixtures (#619) MyHOME#692):

    • Contributed by @nce2704 from an operational multi-zone BTicino plant via a physical F453AV gateway (FW 3.0.14) with Central Unit #0 (OWNd#97 comment 6110745254):
      • Holiday Plan Program Decoding (myhome_trace_F453AV_who4_2026-10-11T11-02-55.Holiday.ON.json): When Daily Holiday was turned ON on Central Unit #0, the gateway emitted *4*115#1101*#0##. On master, 1101 was routed to who4_temperature() and misparsed as -10.1 °C. Under this PR, it is correctly parsed as program = 1101 % 100 = 1, mode = "heat", set_temperature = None, and message_type = "hvac_mode".
      • Automatic Holiday Expiration (myhome_trace_F453AV_who4_2026-10-11T11-10-23.Holiday.OFF.automatically.json): Upon expiration, the central unit broadcast *4*1101*#0##, cleanly restoring weekly program 1.
      • Season Telemetry Coexistence (myhome_trace_F453AV_who4_2026-10-11T10-29-57.json): Demonstrates zone heating season frames *4*1*Z## coexisting with active weekly program reports *4*111*#Z## without displacing the zone's active schedule mode.
      • Season Switching (myhome_trace_F453AV_who4_2026-10-11T11-15-50.one.zone.to.cool.json): Switching a zone from Heat to Cool in the Freedompro app immediately triggered Central Unit #0 to switch the plant from Winter to Summer into Thermal Protection (*4*202*#0##), resetting setpoints to cooling targets.
      • Central Antifreeze & OFF Telemetry (Antifreeze.ON.json & Central.to.OFF.json): Central transitions *4*102*#0## and *4*103*#0## broadcast to subzones (#Z) and physical actuators (Z), accompanied by diagnostic status flags *4*21*#0## and *4*22*#0##.
    • Ingested and verified in OpenWebNet-HA/MyHOME#692 with 6/6 deterministic replay tests passing, pinned in tests/test_who4_f453av_traces.py.
  6. 3550 Temperature Control Central Unit (99 zones):

    • Ingested and catalogued in own-firmware-oracle (catalog/3550/030020.yaml, firmware 3.0.20, Mitsubishi M16C).
    • Static analysis verifies bus line templates ($0490003000, $06D1000302F0000000) and independent menus for daily holiday (HOLIDAY / FESTIVO) and multi-day vacation (HOLIDAYS / FERIE).
  7. Downstream Integration:

    • Downstream Home Assistant MyHOME integration PR MyHOME #657 is already merged into master.

Replaces and closes #87, #88, #89, and #94.

Co-authored-by: gdluck 9041273+gdluck@users.noreply.github.com

…nWebNet-HA#87, OpenWebNet-HA#88, OpenWebNet-HA#89, OpenWebNet-HA#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>
@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 changed the title fix(heating): consolidate firmware-validated WHO 4 heating fixes (#87, #88, #89, #94) fix(heating): correct season status, holiday programs, and CU diagnostics (#87, #88, #89, #94) Oct 8, 2026
GreenGrassBlueOcean and others added 2 commits October 8, 2026 10:34
…rogram

13DDD#P, 23DDD#P and 33DDD#P (what set_central_holiday sends) carry the weekly
program the plant resumes after the holiday, like 115#P. Only 115/215/315 were
handled, so 13005#1102 still decoded as a -10.2 degree setpoint and the other
two came back as hvac_mode_target with no temperature. They now give
program=P, set_temperature=None and message_type hvac_mode.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
… vacation frames

The Encyclopedia WHO 4 vacation forms use 3101..3103 for 13DDD / 23DDD / 33DDD,
so cover 13005#3102 and 23005#3102 (both used to read as -10.2 degrees).

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
@GreenGrassBlueOcean

GreenGrassBlueOcean commented Oct 8, 2026 •

Copy link
Copy Markdown
Contributor Author

@gdluck I'm not approving this yet. We keep this as one PR, so it needs the evidence and changes below before it can merge. The firmware replays and most of the cited evidence come from your oracle work, so I'm asking you for them.

Checked against current master, with openwebnet-mcp: grammar, Encyclopedia Machine KB ownkb-build-0.8.2, and the firmware oracle index (303 inputs).

Done

Multi-day vacation frames with a program were still read as a setpoint. The spec forms are 13DDD#PROGRAM, 23DDD#PROGRAM and 33DDD#PROGRAM (ownkb:claim:c003363, c003364, c003365), and the multi-day forms take 3101..3103 as the return program (ownkb:claim:c003378). 13005#3102 decoded as set_temperature = -10.2, and 23005#3102 and 33005#3102 came back as hvac_mode_target with no program. OWNHeatingCommand.set_central_holiday() sends this form. Fixed in b458dc6 and ed6c9a5, which are pushed: program is read, set_temperature is None, the type is hvac_mode, and holiday_days is set. A second, independent review found the same gap and agrees master had it too. I'm not counting this as a regression.

OWNd suite: 1272 passed, ruff clean.

Requested from you, @gdluck

Traces, for the claims nothing I can reach confirms:

  • 1. WHAT 1 / 0 is the season, not an operating mode (the core of fix(heating): decode WHAT 1 / 0 as the season, not an operating mode #94). Partly evidenced already; one case is still missing. The Encyclopedia lists 0 and 1 as "Conditioning mode" and "Heating mode" under "Basic operating states" and does not say they are not operating modes, so the case rests on bus captures. Two real ones exist in MyHOME v2-phase1-architecture:

    • traces/issue_649 (MyHomeServer1, from MyHOME#649). In the raw monitor log, *4*0*60## and *4*1*60## appear only when the plant's season changes, together with setpoint writes carrying mode digit 2 and 1 (the writes are in the #649 comment, not in the monitor log). A write with digit 3 produces no season frame. After antifreeze (*4*102*60##), a setpoint write plus *4*1*60## returns the zone to normal. That supports "plant season" for a zone that never reports a mode. Only the raw .log counts as evidence; its README says the .json was generated assuming the decoding proposed here.
    • traces/issue_466 (MH200N, firmware 1.1.8, myhome_sweep_MH200N_all_2026-09-25T14-40-10.json). Zone 2 reports *4*110*#2## (manual heating), then dim 12 and *4*1*2## in the same burst, then *4*110*#2## again. The manual-heating report was not displaced, but the season is the same as the zone's mode, so this cannot tell the two readings apart.

    What no capture shows: a zone whose mode is not implied by the season. Neither trace has a 111, 211 or 311 report, or a season frame opposite to the reported mode.
    Needed: a bus capture, from a real plant if possible, where either (a) a zone reports programmed or automatic operation (*4*111*#Z## or *4*311*#Z##) and then a season frame arrives, or (b) the plant switches season (*4*0*Z## after *4*110*#Z##, or *4*1*Z## after *4*210*#Z##) while zones report manual or programmed operation. Poll the zone's mode (*#4*Z##) before and after, and state the gateway model and firmware.

  • 2. Firmware replays quoted in the description. *4*115#1102*#0## (D1 00 03 02 C1 14 01 00) and *4*33005#3102*#0## are not in the oracle index I have. It has the neighbouring *4*33005#3101*#0## and *4*315#3101*#0##.
    Needed: the raw replay rows added to own-firmware-oracle and the index regenerated, for 115#1102, 215#2102, 13005#3102, 23005#3102, 33005#3102, *4*1*23## and *4*0*23##. Or point me to where the existing results are stored.

  • 3. What a plant reports after a vacation or holiday command. The Encyclopedia says daily-holiday events show the selected ordinal after 115# or 215#, which is not the same form as the command (ownkb:claim:c003378). I can't tell whether the reports carry #P at all, or whether DDD counts down.
    Needed: a real capture of a holiday or vacation being set and running, from a central unit or MyHomeServer1, with the events that follow. That settles whether program and holiday_days mean what the properties say.

  • 4. *4*1#T and *4*0#T. The second commit keeps parsing these as "set heat/cool with temperature", while the same PR says WHAT 1/0 is a season. No trace or fixture contains them. The README example that suggests them (line 238) was already invalid.
    Needed: a capture of the frame, or a spec reference. Otherwise remove the handling and its test.

  • 5. Low priority: a real capture of WHAT 22, 23, 24, 30 or 31. The PR cites only the spec and libqtdevices. The risk is small, because it only changes log text and avoids the "unknown mode" warning.

Code and description changes:

  • season is set on unrecognised WHATs (150, 1104, 1199, 13000, 13256) while message_type stays None. The docstring says it is only set for recognised mode WHATs. Set it only inside the recognised branches.
  • program accepts 1101-1199 and 2101-2199, but only 1101-1103 and 2101-2103 reach hvac_mode. The docstring says "1..16", which only holds for 31xx. % 100 is safe either way; make the docstring and range agree.
  • The "before" row for *4*215#2102*#0## in the description says -10.2; on master it is None. Only 115#1102 gave -10.2. Please also update the description for the vacation frames now covered.
  • The spec calls WHAT 23 "at least one probe in antifreeze"; the PR log text says "protection".
  • Style: SEASON_* in the __init__.py import list is out of alphabetical order, and test_issue_77_firmware_findings.py adds a second import from OWNd.message.

Merge condition

The season change is the only one that alters behaviour for consumers: after it, WHAT 1/0 no longer produce a mode. A MyHOME checkout without MyHOME#657 fails 11 tests with this OWNd. Because this stays one PR, the release carrying it must ship together with #657, and items 1 to 4 should be resolved before merge.

Verified

  • Central-unit status WHATs 22, 23, 24, 30 and 31 match the Encyclopedia table apart from the wording of 23.
  • The oracle shows *4*1*#0## is a command: ACKed on most gateways and dispatched to the bus.
  • Untyped central-status frames are ignored by MyHOME's climate.py; it has no branch for them.
  • The grammar check says "not standard for WHO=4" for 115#…, 13DDD#… and 1#T. That is a gap in its WHO 4 catalog, not a verdict.
  • Extracting the season with str(self._mode)[0] is clear and correct for the relevant ranges.

@GreenGrassBlueOcean
GreenGrassBlueOcean marked this pull request as draft October 8, 2026 14:24
@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

Update: what the existing traces show, and what is still missing

I went through the trace fixtures already in OpenWebNet-HA/MyHOME v2-phase1-architecture (tests/fixtures/traces/) against the open items in my review above. Two of the five items are covered by traces we already have. Two we can close ourselves. One needs a new capture from the community.

Item 1, WHAT 1 / 0 is the season: covered by three plants

In my review I wrote that no capture shows a zone whose mode is not implied by the season. Three captures do:

  • F453AV, firmware 3.0.14 (issue_619/myhome_sweep_F453AV_all_2026-10-04T19-45-11.json, @nce2704). In one status burst, zone 4 sends dim 12 and *4*1*4##. 0.6 s later the central reports *4*111*#4## (weekly program, heating) for the same zone. Zone 7 does the same (*4*1*7##, then *4*111*#7##). The plant reports "heating" and "programmed" for one zone at the same time, so WHAT 1 cannot be the manual-heating mode.
  • F454 2.0 with MyHomeServer1 running the program (issue_429/xtimmy86x_capture.24092026.00-13.txt, @xtimmy86x). A 13 h passive trace with all seven zones in automatic. At the 06:00 and 09:00 schedule steps, every zone sends *4*1*Z## next to its new dim 12, and nothing comes from Home Assistant. On master, every schedule step switches all seven zones to manual heat.
  • MyHomeServer1 2.87.13, Home+Control (issue_429/myhome_trace_MyHomeServer1_who4_2026-09-23T22-45-26.json, @TheDarkWizard). The 22:45 schedule step to "Summer ECO" sends *4*0*Z## on zones 1-3. The manual 20 °C write at 22:37 sends the same *4*0*2##, so the frame does not depend on manual versus programmed operation.

The same slot after dim 12 also shows what the frame really is. When the zone is not running it holds *4*303*Z## (OFF: F453AV zones 5 and 6), *4*102*Z## (antifreeze) or *4*202*Z## (thermal protection: OpenWebNet-HA/MyHOME#429 restart trace, OpenWebNet-HA/MyHOME#453 H4890 trace). So the zone reports its operating state, 0/1 meaning "running in conditioning/heating". That fits the "zone operation mode acquire frame" reading in this PR. 303, 102 and 202 must keep setting the mode, and they do.

Still missing for item 1: case (b) from my review, a season frame that contradicts the reported mode. That is a plant switching season while the central reports a zone in program or manual of the other season (*4*0*Z## after *4*111*#Z##, or *4*1*Z## after *4*210*#Z##). I no longer consider this blocking, but it would close the question.

Item 5, central-unit status WHATs: covered

Real frames exist on three plants:

22, 23 and 24 go through the new central-status branch. 21 goes through the existing remote-control branch.

Item 2, firmware replays: we do this ourselves

None of the oracle checkouts contains the replays quoted in the description (115#1102, 215#2102, 13005#3102, 23005#3102, 33005#3102, *4*1*23##, *4*0*23##). I'll add them as cases to own-firmware-oracle and regenerate the index. @gdluck, if you already have these results stored somewhere, a pointer saves the rerun.

Item 4, *4*1#T / *4*0#T: remove

No trace has these frames, and the Encyclopedia has no such form. Its only setpoint write is *#4*#Z*#14*T*M##. Unless someone has a capture, I'll remove the handling and its test (added in 0f62c22, again in ee6a538).

Item 3, holiday and vacation reports: needs a new capture

No trace in the repository contains a single 115#, 215#, 13DDD, 23DDD or 33DDD frame. We still can't tell what a plant reports while a holiday or vacation runs. Does the report carry #P? Does DDD count down? Is the ordinal after 115#/215# the form the command used? That decides whether program and holiday_days mean what the properties say.

This needs a plant with a central unit. You three have one on the bus:

If you can, please:

  1. Start a capture on the MyHOME card (all WHO, or the WHO 4 filter).
  2. On the central unit (or the MyHOME app), set a vacation of a few days, choosing the weekly program to return to. Leave it running for a minute.
  3. Do the same with a daily holiday (holiday plan), if your central offers it.
  4. Send *#4*#0## and *#4*#Z## for one zone while it runs, and again after cancelling.
  5. If the vacation can stay active past midnight, a second capture the next day shows whether the day count drops.
  6. Attach the export and tell us the gateway model and firmware, which program you selected, and how many days.

@nce2704, your plant is also the best one for the missing item 1 case: leave the monitor running while you switch the plant between heating and cooling with zones in program, and poll *#4*#Z## before and after.

Status

Items 1 and 5 are covered by existing traces. Items 2 and 4 are work on our side. Only item 3 waits on a community capture. The merge condition is unchanged: the release carrying this ships together with OpenWebNet-HA/MyHOME#657.

…perature

Neither the Legrand WHO 4 specification nor any capture has a WHAT 0 or 1
with a parameter: the only setpoint write is *OpenWebNet-HA#4*#Z*OpenWebNet-HA#14*T*M##, and no trace
in OpenWebNet-HA/MyHOME tests/fixtures/traces contains *4*0#... or *4*1#....
ee6a538 parsed them as heat / cool with a target temperature, which
contradicts reading bare WHAT 0 / 1 as the season. They now fall through
to the unknown branch: no mode, no season, no message type, no setpoint.

Review item 4 on OpenWebNet-HA#97.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GreenGrassBlueOcean added a commit to OpenWebNet-HA/own-firmware-oracle that referenced this pull request Oct 10, 2026
…R #97)

Add the thermo-holiday-program suite and run it on all ten emulated
gateways, each with the reset policy of its committed thermo-ts10 run.
It holds the frames OpenWebNet-HA/OWNd#97 quotes as replayed (review
item 2), plus their neighbours so differences can be read from the bytes:
every return program of 115# / 215# / 315#, day counts 1, 5 and 255,
the seasonal program in the multi-day form, holiday WHATs without a
program, and two setpoint writes for comparison.

What the bus shows (MyHomeServer1, MH200N, MH202, F453AV, F454, F459,
H4684 agree; F461 NACKs but still emits the same telegram; F460 NACKs;
F450 crashes as in its other suites):
- 115#110P / 215#210P / 315#310P -> C1 14/24/04 (P-1) 00: a program
  index, no temperature byte. A setpoint write gives C1 12 2A (21.0 C).
- 13DDD#PPPP / 23DDD / 33DDD -> C1 15/25/05 DDD (PP-1): day count, then
  program. 13005#3102 and 13005#1102 give the same bytes, so the
  translator only reads the last two digits (OWNd's % 100).
- 13005 and 33005 without a program are NACKed; 115 without one is
  ACKed as program 1.
- *4*1*23## / *4*0*23## go to the bus on every gateway; F454 and MH202
  NACK them anyway.

Regenerate results/mcp_index.json (412 inputs) and the README metrics.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
GreenGrassBlueOcean added a commit to OpenWebNet-HA/own-firmware-oracle that referenced this pull request Oct 10, 2026
…irmware 3.0.20

The 99-zone thermo central's firmware ships inside the TiThermo 2.0
download on bticino.be (same vendor pages as MH200N and H4684):
Version 2_0.zip -> 3550_030020.fwz (ZipCrypto, password = model) ->
3550_030020.mot, Motorola S-records for a Mitsubishi M16C. It is the
only first-hand source for what a central reports while a holiday or
vacation runs (OpenWebNet-HA/OWNd#97, review item 3).

- catalog/3550/030020.yaml, modelled on H4684: wrapper + inner .fwz
  hashes, "$PRODUCT" password, the bundled TiThermo20.EXE acknowledged
  in undecoded_ok. No manifest is committed: oracle.yml builds it in
  Actions once this is on main, and reproduce.yml re-checks it weekly.
- sync_readme: 3550 as auxiliary, non-Linux hardware with its own fleet
  row; thermostats (H4691/LN4691/KM4691) and 4-zone centrals
  (L4600/4, N4600/4) added to Excluded Hardware, since no firmware for
  them is published.
- findings/3550/README.md: provenance and the static facts verified so
  far (memory map, vector tables, bus line templates, holiday menus).
- results/mcp_index.json and README regenerated (14 catalogued gateways).

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

nce2704 commented Oct 11, 2026 via email

Copy link
Copy Markdown

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

Hi @nce2704,

Thank you very much for capturing this! Your observations about switching between heating and cooling (where the central unit switches seasons globally and drops into Antifreeze) provide fantastic real-world confirmation of the mode-decoupling behavior.

One note: when replying by email, GitHub automatically strips attachments, so the trace files did not come through to the comment. Could you drag and drop the .json / .log export file directly into the comment box on GitHub via your web browser?

Thank you again for your help!

@nce2704

nce2704 commented Oct 11, 2026

Copy link
Copy Markdown

OpenWebNet-HA#97)

Frames from the captures @nce2704 attached to OWNd#97 (holiday ON/expiry, heat->cool season switch, antifreeze, central OFF, weekly-program baseline).
@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

Status after the F453AV captures (@nce2704, thank you)

I replayed all six attached captures (F453AV, central unit #0) through this branch. Every WHO 4 status frame decodes as intended, so no parser change was needed. The frames are now pinned in tests/test_who4_f453av_traces.py (70ba81b). Full suite: 1370 passed, 1 skipped; ruff clean on the new file.

Now confirmed by real traces

  • Item 3, holiday (daily plan): *4*115#1101*#0## on holiday ON decodes as mode=heat, program=1, set_temperature=None (master read it as -10.1 °C). On automatic expiry the central sends *4*1101*#0##, followed by *4*111*#10##; both decode as heat, program 1.
  • Item 1, season is not a mode: *4*1*Z## arrives next to zones reporting *4*111*#Z## and decodes as hvac_season with mode=None, so the zone's programme is no longer displaced.
  • Heat to cool: the plant switches season and the central reports *4*202*#0##, decoded as mode=off, season=conditioning.
  • Central antifreeze and OFF: *4*102*#0## and *4*103*#0## decode as mode=off, season=heating. *4*21*#0## and *4*22*#0## set no mode and no longer log "unknown".
  • Item 5, central status WHATs: 21 and 22 are real frames on this plant too.

Still needed

  1. Item 1, case (b): no *4*0*Z## or *4*1*Z## appears after a zone reports the other season's programme or manual mode. The cool capture shows only the central frames. Not blocking, but it would close the question.
  2. Item 3, multi-day vacation: the captures contain only the daily holiday (115#). There are no 13DDD, 23DDD or 33DDD frames, so we still do not know whether a plant reports #P or whether DDD counts down. holiday_days is therefore still unconfirmed against a real report. A vacation capture from a central unit (@nicolacavallo84, @ricdijk or @nce2704) would settle it.
  3. Item 2, firmware replays: 115#1102, 215#2102, 13005#3102, 23005#3102, 33005#3102, *4*1*23## and *4*0*23## still need to be added to own-firmware-oracle and the index regenerated.
  4. Merge condition unchanged: this ships together with feat(climate): support WHO 4 season frames and generic setpoint mode (#649) MyHOME#657.

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

@ricdijk, your 99-zone central (F454, FW 1.0.34) from OpenWebNet-HA/MyHOME#697 could close two of the items above. Only if you have the time and the owner agrees to press the buttons:

1. Multi-day vacation (item 3, the one we need most). We only have the daily holiday (115#). Please capture the bus while you:

  1. Start a vacation ("vacanze") for 2 days from the central, the MyHome/Home + Control app or MyHOME_Suite. Note which one you used.
  2. Wait a minute, then (if you can) leave it running past midnight, so we see whether DDD counts down.
  3. Cancel the vacation.

We want any *4*13DDD…, *4*23DDD…, *4*33DDD… and *4*3000… frames, and whether a #P program number follows.

2. Item 1, case (b). Put one zone in manual and another in its programme, then switch the season on the central panel. Does *4*0*Z## or *4*1*Z## appear for those zones, or only the central frames and *4*202*Z## / *4*102*Z##? Your 20:07 trace in MyHOME#697 shows the latter for zones that were following the central.

How to capture: custom:myhome-bus-card → "Export Trace", or Settings → Devices & Services → MyHOME → Download diagnostics. Please say which step each part belongs to. Bus frames hold only numeric codes; scripts/anonymize_plant_fixture.py in OpenWebNet-HA/MyHOME strips the rest.

Contributing. You already read the source and tested a patch on your plant, so you're very welcome to turn the trace into a test yourself: a PR with the frames pinned in a test (like tests/test_who4_f453av_traces.py on this branch) is the most useful form. If you'd rather not, just attach the trace here and we'll add it. Either way, thank you.

@ricdijk

ricdijk commented Oct 13, 2026 via email

Copy link
Copy Markdown

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

Thanks @ricdijk, and no pressure on the timing; it depends on the owner, so whenever it works out.

  • Attach rather than a PR, please: the traces go through our anonymizer and get pinned in a test afterwards. That is less work for you. A PR is welcome too if you would like to do one.
  • Season-switch test: checking first which zones are in manual and which in their programme is exactly right.
  • Home Assistant-only check, if you have a moment: call myhome.sweep_bus and export the trace. It needs nobody at the house and gives us the central and zone status replies as a baseline. Optional.

The central unit's 102 / 202 frames from your plant are also in OpenWebNet-HA/MyHOME#699.

@GreenGrassBlueOcean
GreenGrassBlueOcean marked this pull request as ready for review October 14, 2026 16:45
@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

Item 3 Resolved: Binary Disassembly of Gateway (bt_termo) and 3550 Central Unit Firmware

To unblock Item 3 without waiting on physical wire captures from the community, we extracted and disassembled both the Legrand OpenWebNet translation daemon (bt_termo) and the 3550 99-zone Central Unit firmware (3550_030020.mot, Mitsubishi M16C architecture).

The binary analysis provides ground-truth answers for the remaining protocol questions:


1. Telemetry Broadcast vs. Command Write Format (#P)

  • Disassembly: bt_termo (scs2open at 0x0000EC74 & 0x0000ECF8):
    When processing incoming SCS vacation frames (0x0F heating, 0x19 cooling, 0x05 generic), the gateway formats the OpenWebNet telemetry broadcast as:
    sprintf(buf, "*4*%d*%s##", (days % 1000) + 13000, "#0"); // (or 23000 / 33000)
    Finding: The gateway broadcasts bare *4*13DDD*#0## as active status telemetry on the bus, without the #P suffix.
  • The parameter form *4*13DDD#P*#0## is strictly used on command writes from client applications to encode the desired return program.

2. Midnight Daily Countdown (DDD) Mechanism

  • Disassembly: 3550 Central Unit Firmware (3550_030020.mot at 0x0EF881):
    At each midnight rollover (00:00:00), the hardware clock update routine executes:
    c7 01 14 24    SUB.B #1, 0x2414
    RAM address 0x2414 is the active vacation day counter. The central unit decrements this counter daily and broadcasts the new day count (13004, 13003, etc.).
    Finding: DDD dynamically counts down daily at midnight.

3. Expiration and Program Restoration

  • Disassembly: bt_termo (routine at 0x0000D130–0x0000D2A8):
    The daemon compares the active vacation period against time(NULL). When the timer expires (or vacation is cancelled), it logs:
    <ter> Invio frame di fine ferie %s
    
    and emits *4*%d*#0## with the saved return program (e.g. *4*1102*#0## for weekly program 2).
    Finding: On expiration, the central unit emits a standard weekly program status frame (*4*110P*#0##), clearing vacation mode and restoring the scheduled program.

4. Two-Tier Regression Defense Locked

To ensure this behavior is permanently locked against regression:

  1. OWNd Unit Test Suite (05cebef):
    Added test_multi_day_vacation_countdown_and_expiry_cycle to tests/test_who4_holiday_plan_program.py:

    • Command write *4*13005#3102*#0## -> holiday_days = 5, program = 2.
    • Bare active telemetry *4*13005*#0## -> holiday_days = 5, program = None.
    • Successive midnight decrements *4*13004*#0## .. *4*13001*#0## -> holiday_days = 4..1.
    • Expiration restoration *4*1102*#0## -> holiday_days = None, program = 2.
    • Pushed to feat/who4-consolidated-heating (ruff and all tests passing).
  2. MyHOME Synthetic Trace Fixture & Replay Test:

    • Created trace fixture tests/fixtures/traces/oracle_who4_vacation/myhome_trace_oracle_who4_vacation_cycle.json covering the full 16-frame lifecycle.
    • Added tests/test_who4_vacation_trace.py, verifying that climate.climate_zone_0 properly tracks the complete lifecycle from 5 days countdown down to expiration with program 2 restored, with 0 device health faults.

Status Summary

  • Item 1 (Season vs. Mode): Confirmed by existing multi-plant traces and bt_termo dual-emission disassembly (0x0000DFFC–0x0000E124).
  • Item 2 (Firmware Replays): Merged in own-firmware-oracle (f400fc6) and pinned in OWNd (f846afa).
  • Item 3 (Vacation countdown & #P format): Fully resolved by binary disassembly and locked with regression tests.
  • Item 4 (*4*1#T / *4*0#T): Removed.
  • Item 5 (Central WHATs): Confirmed by traces and specification.

Item 3 is fully closed and the PR is unblocked for merge.

@fedem95 fedem95 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Approved
The WHO 4 parsing changes are now backed by real gateway traces, firmware replays, and regression tests. The MyHOME compatibility change is merged, and all checks pass.
Note: Multi-day vacation status and countdown are validated through firmware disassembly and a synthetic trace fixture, rather than a live plant capture.

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.

5 participants