Skip to content

fix(gateway): WHO 13 clock echoes with timezone 0530/124 no longer raise - #144

Draft
gdluck wants to merge 2 commits into
OpenWebNet-HA:masterfrom
gdluck:fix/who13-clock-echo-timezones
Draft

gdluck wants to merge 2 commits into
OpenWebNet-HA:masterfrom
gdluck:fix/who13-clock-echo-timezones

Conversation

@gdluck

@gdluck gdluck commented Oct 24, 2026 •

Copy link
Copy Markdown
Contributor

Requested by A

Summary

*#13**#0*14*30*00*0530## and ...*124## are clock writes whose timezone value is not the documented SHH form: a four-digit value with a sign digit (0530) and a three-digit negative offset of 24 h or more (124). Most gateways ACK such writes and echo them on the event session, and the F450 reports the stored 0530 on later reads. _gateway_timezone raised ValueError: Invalid gateway timezone: '0530' on the first and, for 124, datetime.timezone refused a 24 h offset; the exception propagated out of the event-session parse.

What the values mean is not verified by any capture (an ACK and an echo prove that the gateway stores the value, not what it means), so this PR decodes them as unknown (timezone is None), never as local time (""), and keeps raw_timezone as sent. Split out of #138.

Changes

  • OWNd/message/gateway.py: _UNMAPPED_TIMEZONE_REGEX = [01][0-9]{3}|1(?:2[4-9]|[3-9][0-9]), the two classes the measured values belong to, bounded by the sign digit and the length (0000..1999) and by the first negative offset datetime.timezone cannot hold (124..199; 123 is still -23:00). _gateway_timezone returns None for them (type now str | None); the four fromisoformat call sites use self._timezone or "", so the parsed time / datetime are naive. raw_timezone keeps the value; is_timezone_unconfigured stays False (that is the 999 sentinel). The timezone property docstring says what None means. This covers both the echo of a write (OWNGatewayCommand, *#13**#0*...## and *#13**#22*...##) and the read reply (OWNGatewayEvent, dimensions 0 and 22).
  • Everything else still raises as before: 200, 099, 024, 2, 12, 2000, 9999, 2400, 05300, 00530. (Revision da77a53: _gateway_timezone itself refuses a positive SHH with hour 24..99 and is ASCII-bound; the first revision's docstring said the helper raised for 099 while it returned +99:00 and the error came from time.fromisoformat in the caller.) MyHOME's tests/test_gateway_regressions.py:41 (test_malformed_gateway_clock_fields_are_rejected) requires *#13**0*15*00*00*200## and ...*099## to keep raising; they do.
  • CHANGELOG.md: entry under [Unreleased] / Fixed.

MyHOME does not read OWNGatewayEvent.timezone or raw_timezone (custom_components/myhome at v2-phase1-architecture: no consumer), so None reaches no integration code today; a future consumer gets "unknown", not "local".

State Diagram

stateDiagram-v2
    direction TB

    state "_gateway_timezone(values[3]) for *#13**0*HH*MM*SS*T## and the #0 / #22 / 22 forms" as TZ {
        [*] --> Classify
        Classify --> Local : or 999\ntimezone , unchanged
        Classify --> Offset : SHH, S in 0/1, HH 00..23\n+02 -00 / -23 -00 , unchanged
        Classify --> Unknown : 0000..1999 , sign + three digits, 0530 measured\nor 124..199 ,24 h or more, 124 measured\ntimezone None, raw_timezone kept, naive time , new - before - ValueError out of the event session
        Classify --> Rejected : 200, 099, 024, 2000, 05300, ...\nValueError , unchanged
    }
Loading

Where are the traces

tests/fixtures/traces/f461_who13_clock_tz_live_2026-10-26/ in MyHOME (OpenWebNet-HA/MyHOME#823, commit e0426cc): the live F461 2.0.11 capture of 2026-10-26 asked for in the review, 16 event frames / 79 rows, README.md + myhome_trace_F461_who13_clock_tz_live_2026-10-26.json with every row decoded by both OWNd master 050dc1d (ownd_master_* / ownd_master_error) and this head da77a53 (ownd_*), and tests/test_f461_who13_clock_tz_live_2026_10_26_trace.py (22 tests, passing on master, on this head and on the 2.0.0b11 pin; the fail-before / pass-after of the written strings is pinned as the two recorded outcomes). The earlier live reads (*#13**0*...*999## on the F461 and MyHomeServer1, trace PR OpenWebNet-HA/MyHOME#799) parsed before and after.

Hardware Verification (2026-10-26)

Captured live on the F461 2.0.11 (event-only listener for the whole run + a probe command session, both on this head; the probe refuses to send what its OWNd cannot parse, so master could not have sent these writes). Every write with the current local time and today's date: *#13**#0*HH*MM*SS*0530##, *#13**#0*HH*MM*SS*124##, *#13**#22*HH*MM*SS*0530*W*DD*MM*YYYY## and the 124 variant were each ACKed in 9-18 ms with no reply, and nothing was echoed on the event session (not within the 3 s after each write, not anywhere in the 13-minute window). Every *#13**0## / *#13**22## read-back, before and after each write, answered 999 (also delivered on the event session). The spontaneous 10-minute broadcast at 20:50:00 UTC, with 124 the last value written, was *#13**#1*01*26*10*2026## + *#13**#0*22*50*00*##: empty time-zone field. The restore *#13**#0*HH*MM*SS*999## was ACKed in 12 ms and not echoed either (the oracle's emulated F461 020011 is ack / out on these writes; the physical one is ack / silent), read-backs 999. So the real F461 behaves like the F454 / F459 "ACK, keep the old value" column rather than its own emulation: it neither stores nor echoes a non-standard offset, the read-back path (OWNGatewayEvent with 0530) is live only on the emulated F450, and master's ValueError cannot escape the event session on a physical F461 echo because the gateway never emits one. Where master does raise is on the written command string itself (*#13**#0*22*46*45*0530## -> Invalid gateway timezone: '0530'; the 124 forms -> the timedelta range error); this head parses them as OWNGatewayCommand with timezone None and raw_timezone kept. The same sequence on the MyHomeServer1 3.87.13 (the user allowed the writes for this test): dimension 0 writes (0530, 124, even the 999 restore) get no reply at all (10 s timeout, no ACK / NACK, no echo) and the next command is answered only after ~7 s; dimension 22 writes ACK in 0.01 s without an echo; read-backs 999; clock unchanged, as the 028206 emulation says, so it adds nothing beyond confirming the emulation (its frames stay out of the repository: the listener holds the house's own traffic).

What does the Oracle say

Suite who13-clock, results/<P>/<V>/oracle/full/who13-clock.tsv:

Frame ack / out (echoed on the event session) ack / silent nack / silent - / silent
*#13**#0*14*30*00*0530## F450 020010, F453 010100, F453AV 030014, F460 020012, F461 020011, H4684 020054, MH200 020102, MH200N 010108, MH202 010024 (9) none F454 020051, F459 020105 MyHomeServer1 028206
*#13**#0*14*30*00*124## the same nine plus F459 020105 (10) F454 020051 (ACK, no echo) none MyHomeServer1 028206
*#13**#22*14*30*00*0530*03*21*10*2026## the same nine (9) MyHomeServer1 028206 F454, F459 none

F453, F453AV, H4684, MH200 and MH200N also put the clock on the bus (bus: columns). Suite who13-clock-readback (findings/who13-clock.md, "Read-back after a write"): after *#13**#0*14*30*00*0530## the F450 reports T=0530 on later reads, "the only firmware that echoes a non-standard offset back"; F454 and F459 keep 102 because they NACKed 0530. So both the echo path (OWNGatewayCommand) and the reply path (OWNGatewayEvent) need the tolerance. No row says what the gateway does with the value, hence None.

Testing

  • tests/test_who13_clock_echo_timezones.py (new, 43 cases): two helper-level tests pin where 024..099, 200, 2000, +02, 02 and a Unicode-digit value raise and that 000/023/123 still decode; the #0 and #22 echoes with 0530 / 124 parse as OWNGatewayCommand with timezone is None, raw_timezone as sent, is_timezone_unconfigured False, a naive 14:30:00 and the date kept; the dimension 0 and 22 replies with 0530 / 124 parse as OWNGatewayEvent the same way; five unmeasured members of the two classes (0000, 1999, 1240, 125, 199) are None too; eleven values outside them still raise (200, 099, 024, 2, 12, 2000, 9999, 2400, 05300, 00530); whole-hour offsets 000..123 still decode (123 is -23:00); 999 and empty still mean local time.
  • Fail before / pass after: on master 14 fail, 18 pass (0530 raises Invalid gateway timezone; 124 raises the timedelta range error); on this branch all 32 pass.
  • Full suite (head da77a53 on master 6c3930d): 2082 passed, 1 skipped; 100 % statement coverage, mypy clean (str | None end to end), ruff clean, scripts/sync_documentation.py --check and scripts/verify_library_standards.py pass.

Live retest (2026-10-24 and 2026-10-26)

Not exercised live: both plants report 999; no 0530 / 124 was written to a real gateway (clock writes on a real home were out of scope).

Exercised live on 2026-10-26 on the F461 2.0.11 and the MyHomeServer1 3.87.13 with the 0530 / 124 writes themselves: see Hardware Verification above and OpenWebNet-HA/MyHOME#823.

Companion

None. Split from #138 (#-prefixed dimension 14 setpoints).

🤖 Generated with Claude Code

@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!

own-firmware-oracle who13-clock: `*OpenWebNet-HA#13**#0*14*30*00*0530##` is ACKed and
echoed on the event session by nine gateways (F454, F459 NACK;
MyHomeServer1 silent), `…*124##` by eleven, and the F450 reports the
stored `0530` on later reads (who13-clock-readback). `_gateway_timezone`
raised ValueError on both and the exception left the event session.

The two measured values define two bounded classes, a four-digit value
with a sign digit and a three-digit negative offset of 24 h or more.
Their layout and meaning are unverified, so `timezone` is None (unknown),
never "" (local time), `raw_timezone` keeps the value, and the time /
datetime are naive. `200`, `099`, `2000`, `05300` and the like still
raise (MyHOME tests/test_gateway_regressions.py pins `099` and `200`).

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gdluck
gdluck force-pushed the fix/who13-clock-echo-timezones branch from d93484d to 5b588fc Compare October 26, 2026 08:56
@gdluck

gdluck commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

Rebased on master 87cafc3 (after #136): the CHANGELOG [Unreleased] entry and the heating.py regex block now sit beside the merged dimension-22 guard; content unchanged, suite and 100 % coverage green.

… only

The helper's docstring said it raises ValueError for 099, but it returned
"+99:00" and the error came from time.fromisoformat in the caller with a
timedelta message. It now refuses a positive SHH whose hour is 24 or more
(024..099) with its own message, and the shape check is ASCII-bound so a
Unicode digit such as "0٢٣" no longer slips through to fromisoformat.
Whole-frame behaviour is unchanged (those frames already raised); the
docstring states the three classes exactly and two helper-level tests pin
them.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
@gdluck

gdluck commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

Self-correction in da77a53 (no review asked for it). @GreenGrassBlueOcean a quick confirmation would be welcome.

Context. This PR makes clock echoes with the unmapped timezone classes 0530 and 124 parse with timezone None instead of raising, and keeps 099 / 200 invalid as MyHOME's regression test requires.

How it was found. Reading the helper's docstring against what it returns: it said _gateway_timezone "raises ValueError for everything else (200, 099, 2000)", but for 024..099 the helper returned +24:00..+99:00 and the ValueError came from time.fromisoformat in the caller with a timedelta message. Whole frames did raise, so the tests passed; the text described a path that did not exist. The same read showed the shape regex used \d, which also matches Unicode digits.

Live check (2026-10-26 16:45 UTC). *#13**0## answers …*999## on both the F461 2.0.11 and a MyHomeServer1 3.87.13: timezone unconfigured, decoded as local time. 024..099 never come from a real gateway; this only moves the error to the helper.

What changed. The helper refuses a positive SHH whose hour is 24 or more and is ASCII-bound ([01][0-9]{2}, re.ASCII); the docstring names the three classes exactly; two helper-level tests pin them. Suite 2082 passed on master 6c3930d, ruff / mypy clean.

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor

@gdluck could you capture a live trace for this on the F461 2.0.11? "Where are the traces" currently says 0530 / 124 were only written by the oracle harness, so the echo path (OWNGatewayCommand) and the read-back path (OWNGatewayEvent) have no physical evidence yet.

Plant: F461 only. Per the oracle (who13-clock) the MyHomeServer1 028206 drops #0 without a reply and ACKs #22 with no echo, so it adds nothing here.

Setup: an event-only listener running for the whole capture, plus a command session for the writes. Use the current local time and today's date in every write rather than 14:30:00, so the clock doesn't jump.

Sequence:

  1. Baseline: *#13**0##, *#13**22## (expected 999).
  2. *#13**#0*HH*MM*SS*0530##: ACK? echoed on the event session? Then read back with *#13**0## / *#13**22##. Does the F461 store 0530 like the emulated F450, or keep 999?
  3. *#13**#0*HH*MM*SS*124##, then the same read-back.
  4. *#13**#22*HH*MM*SS*0530*W*DD*MM*YYYY## (and, optionally, the 124 variant), then the same read-back.
  5. If possible, keep listening through one spontaneous 10-minute clock broadcast while a non-standard value is stored. Does *#13**#0*...*## stay empty, or does it carry 0530 / 124?
  6. Restore: *#13**#0*HH*MM*SS*999##. The oracle says F461 020011 ACKs it and echoes it. Then read *#13**0## until it reports 999 again.

Deliverable: a MyHOME trace archive (tests/fixtures/traces/f461_who13_clock_tz_live_<date>/, in the same layout as f461_actuators_live_2026-10-25) with ownd_class / ownd_log from both OWNd master (it should raise on the 0530 / 124 echoes: fail before) and this PR's head da77a53 (timezone is None, raw_timezone kept: pass after). Please link it from "Where are the traces" and update "Live retest".

This would settle three things: whether the real F461 behaves as its emulation does, whether the read-back path is live on an F461 or only on the F450, and whether master really lets the ValueError escape the event session on a physical echo.

@gdluck

gdluck commented Oct 26, 2026

Copy link
Copy Markdown
Contributor Author

Captured as asked, on the F461 2.0.11 with the sequence you specified (current local time in every write, listener for the whole run, one spontaneous 10-minute broadcast, restore last), plus the same run on a MyHomeServer1 3.87.13 since the user allowed the writes for this test. Archive: OpenWebNet-HA/MyHOME#823 (tests/fixtures/traces/f461_who13_clock_tz_live_2026-10-26/, every row with both the master and the da77a53 decode, 22 tests passing on both and on the 2.0.0b11 pin). "Where are the traces", a Hardware Verification section and "Live retest" are updated in the description.

The three things it settles:

  1. The real F461 does not behave as its emulation. All four writes (0530 / 124 on dimension 0 and 22) were ACKed in 9-18 ms with no reply and nothing was echoed on the event session, not within 3 s and not anywhere in the 13-minute window; the oracle's 020011 is ack / out on them. The 999 restore was ACKed and not echoed either. The physical F461 sits in the F454 / F459 "ACK, keep the old value" column.
  2. The read-back path is not live on an F461. Every *#13**0## / *#13**22## read-back before and after each write answered 999, and the 20:50:00 broadcast, with 124 the last value written, carried the usual empty time-zone field. The OWNGatewayEvent path with 0530 exists only on the emulated F450.
  3. master cannot be made to raise by an F461 echo, because there is none. It raises on the written command string itself (*#13**#0*22*46*45*0530## -> Invalid gateway timezone: '0530', the 124 forms -> the timedelta range error), which is why the probe had to run on this head to send the writes at all; this head parses them with timezone None and raw_timezone kept. Both outcomes are pinned in the archive's test_written_strings_fail_before_pass_after.

MyHomeServer1: dimension 0 writes (0530, 124, even the 999 restore) get no reply at all and delay the next command by ~7 s; dimension 22 writes ACK with no echo; read-backs 999; clock unchanged. Exactly its 028206 emulation, so it indeed adds nothing; its frames stay out of the repository (the listener holds the house's own traffic).

This branch has not been deployed

No deployments
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