Repository navigation
fix(energy): parse the WHO 18 dimension 1200 automatic-update reply - #92
GreenGrassBlueOcean merged 2 commits into
Conversation
*OpenWebNet-HA#18*W*1200#type*time## is the meter's answer to the automatic power update request *OpenWebNet-HA#18*W*#1200#type*time##: time > 0 confirms the stream, time = 0 says it stopped. OWNd left it untyped. libqtdevices TS10_1_0_23 names the dimension (_DIM_STATE_UPDATE_INTERVAL = 1200, energy_device.cpp:111), re-arms the stream when time is 0 (:289-322, :719-726) and defines type 1 electricity, 2 gas, 3 heat, 4 water (:50-56). The gateway firmware (bt_supervisione on MyHomeServer1, F454, F459, F461) accepts the write forms #1200#1*255, #1200#1*0 and #1200#2*255 with distinct bus frames, and refuses the bare request *OpenWebNet-HA#18*W*1200## with no bus frame. OWNEnergyEvent gains message type auto_update_interval with update_interval and energy_type. Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com> Claude-Session: https://claude.ai/code/session_018oHp1P4vxxoFnaLUUZpbpZ
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Review: WHO 18 Dimension 1200 Auto-Update Reply (OWNEnergyEvent)
Verdict: Approve with Structural Prerequisites & Code Fixes (Protocol-accurate and eliminates silent unhandled frames, but requires __all__ export fix, trace fixture backing, and a companion integration PR in MyHOME).
Thanks @gdluck! This PR correctly addresses the incoming confirmation and stop replies for automatic energy streaming (*#18*W*1200#type*time##).
Before merging into OWNd, we have two structural prerequisites and two code fixes:
1. Authentic Bus Trace in MyHOME (PR Required)
We audited the trace corpus in MyHOME/tests/fixtures/traces/ across all gateway captures (F454, MH200, MH201, MyHomeServer1):
- Existing traces: In customer logs (
issue_629), we have authentic traces ofMyHOMEPowerSensorsending invalid bare read requests*#18*WHERE*1200##which gateway firmware drops with NACK (Could not send message *#18*74#0*1200##. No more retries.). Inissue_649, we have empirical confirmation that*#18*51*#1200#1*255##ACKs while*#18*51*1200##NACKs. - Prerequisite: We currently lack an authentic customer on-wire bus capture in
tests/fixtures/traces/showing a meter emitting the incoming reply frames*#18*51*1200#1*255##and*#18*51*1200#1*0##. To protect against regressions and ground this in empirical data, we need a trace fixture PR inMyHOMEwith these frames.
2. Companion PR in MyHOME (Downstream Consumer)
OWNEnergyEvent serves downstream consumers—specifically MyHOMEPowerSensor in custom_components/myhome/sensor.py:
- Eliminate Dead Polling Frame:
MyHOMEPowerSensor.async_updatecurrently sends*#18*{where}*1200##for periodic polling. Gateway firmware (bt_supervisione) always NACKs this request because Dimension 1200 is strictly a write/event dimension. A companion PR inMyHOMEis needed to eliminate this dead polling frame. - Handle Auto-Update Telemetry:
MyHOMEPowerSensor.handle_eventcurrently ignores non-power events. The companion PR will interceptMESSAGE_TYPE_AUTO_UPDATE_INTERVALto track active streaming timers and detect when the meter halts streaming (update_interval == 0).
3. Firmware Oracle & Binary Evidence
-
Firmware Verification (
bt_supervisione):
ARM binary reverse engineering and QEMU execution acrossMyHomeServer1(028206),F454(020051),F459, andF461confirm exact SCS bus telegram dispatch:-
*#18*51*#1200#1*255##$\rightarrow$ ACK$\rightarrow$ SCSD1 A1 02 32 00 02 1D FF(Type1D= active power, IntervalFF= 255) -
*#18*51*#1200#1*0##$\rightarrow$ ACK$\rightarrow$ SCSD1 A1 02 32 00 02 1D 00(Stream stop) -
*#18*51*#1200#2*255##$\rightarrow$ ACK$\rightarrow$ SCSD1 A1 02 32 00 02 82 FF(Type82= gas, IntervalFF) -
*#18*51*1200##$\rightarrow$ NACK(*#*0##, dropped at gateway router boundary; never reaches SCS bus)
-
-
Encyclopedia Machine KB:
functional/who-18-energy-management/dimensions.md(ownkb:chunk:r000421):- Start/update form:
*#18*WHERE*#1200#Type*Time##(Type 1= active power,Time = 1..255) - Stop form:
*#18*WHERE*#1200#Type*0##(Time = 0) - Reply form:
*#18*WHERE*1200#Type*Time##
- Start/update form:
4. Code Fixes in This PR (OWNd)
A. Missing from __all__ in OWNd/message/__init__.py (Must Fix)
MESSAGE_TYPE_AUTO_UPDATE_INTERVAL is imported on line 43 of OWNd/message/__init__.py, but is omitted from __all__ (lines 198–207). Please add it to __all__:
# Energy (WHO 18)
"MESSAGE_TYPE_ACTIVE_POWER",
"MESSAGE_TYPE_ENERGY_TOTALIZER",
"MESSAGE_TYPE_HOURLY_CONSUMPTION",
"MESSAGE_TYPE_DAILY_CONSUMPTION",
"MESSAGE_TYPE_MONTHLY_CONSUMPTION",
"MESSAGE_TYPE_CURRENT_DAY_CONSUMPTION",
"MESSAGE_TYPE_CURRENT_MONTH_CONSUMPTION",
"MESSAGE_TYPE_AUTO_UPDATE_INTERVAL", # <-- Add
"_integer_value",
"OWNEnergyEvent",
"OWNEnergyCommand",B. Symmetrical Command Builder in OWNEnergyCommand
OWNEnergyCommand implements start_sending_instant_power, but lacks a companion method to stop streaming (duration = 0) or select energy types 2–4:
@classmethod
def stop_sending_instant_power(
cls, where: str | int, energy_type: int = 1
) -> OWNEnergyCommand:
"""Stop automatic power updates (interval 0)."""
where = f"{where}#0" if str(where).startswith("7") else str(where)
message = cls(f"*#18*{where}*#1200#{energy_type}*0##")
message._human_readable_log = f"Stopping instant power updates from sensor {where}."
return messageC. Log Message Duration Units
In OWNEnergyCommand.start_sending_instant_power (line 218), the log states "for {duration} minutes", whereas in OWNEnergyEvent.__init__ (line 173), it states "every {self._update_interval} s". Per Legrand's SCS specification WHO_18.pdf, Time is minutes (the ZigBee variant defines seconds). Clarifying the units in the event log or docstring prevents user confusion.
|
Both structural prerequisites in
Once the minor code fixes in this PR (exporting |
…e the unit Review follow-ups on PR 92: MESSAGE_TYPE_AUTO_UPDATE_INTERVAL is listed in __all__; OWNEnergyCommand.stop_sending_instant_power(where, energy_type) builds *OpenWebNet-HA#18*W*#1200#type*0## (firmware: ACK, bus D1 A1 02 32 00 02 1D 00) and start_sending_instant_power takes the energy type too (default 1, frames unchanged); the event log and docstring say the Time field is in minutes, as the start builder already did (Legrand WHO 18). Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
|
Thanks. The two code fixes are in 8463824:
I read MyHOME #660 and #661 as the prerequisites; nothing else is needed on this side for them. |
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Blocked: Missing Authentic On-Wire Customer Bus Traces
Per OpenWebNet-HA repository and protocol verification standards, PR #92 cannot be merged in its current state.
1. Mandatory Trace Gate
- All protocol parsers and event types in
OWNdmust be grounded in authentic, untouched on-wire customer plant bus traces. - A thorough audit of the entire trace corpus across all gateway captures (
MyHomeServer1,F454,F459,MH200,MH201,MH202,H4890) confirms that we do not have an authentic customer on-wire bus capture of an energy meter emitting the*#18*WHERE*1200#Type*Time##incoming reply frame on a live plant. - A proposed companion trace PR (
MyHOME#660) was withdrawn and closed because it contained synthesized Dimension 1200 frames, which violates the foundational rule: "They are facts: never edit or synthesize a frame."
2. What We Know Empirically
- Firmware Verification (
bt_supervisione): We have verified via binary execution that*#18*WHERE*#1200#1*Time##start/stop write frames produce valid SCS telegrams (Opcode 1D) and ACK, while bare reads*#18*WHERE*1200##NACK. - Telemetry: Physical plant captures (
issue_466) show continuous passive active power frames*#18*WHERE*113*P##. - Missing Evidence: The exact on-wire behavior of a physical energy meter (e.g. BTicino F520) emitting incoming
*#18*WHERE*1200#Type*Time##replies on the bus has not been captured in an untouched customer log.
3. Conditions to Unblock
This PR remains strictly blocked until:
- An authentic, unedited on-wire customer trace capturing physical meter responses for
*#18*WHERE*1200#Type*Time##is contributed toMyHOME/tests/fixtures/traces/. - The code fixes noted in the earlier review (adding
MESSAGE_TYPE_AUTO_UPDATE_INTERVALtoOWNd/message/__init__.py::__all__and symmetricalstop_sending_instant_powerbuilder) are applied.
…and replay test (#466) Adds an authentic customer on-wire bus trace and deterministic replay test suite for the complete WHO 18 Dimension 1200 automatic instant power stream lifecycle (*#18*WHERE*#1200#Type*Time## start command, meter interval confirmation, periodic active power telemetry, and zero-duration interval notification emitted upon 2-minute stream lease expiry). Captured and contributed by @xtimmy86x on PR #661 (#issuecomment-6067730589) from a physical BTicino F454 gateway (firmware 2.0) and BTicino F520 energy meters on a live plant. Companion to: - OWNd PR OpenWebNet-HA/OWNd#92 - Downstream companion PR #661 Co-authored-by: xtimmy86x <76836856+xtimmy86x@users.noreply.github.com>
GreenGrassBlueOcean
left a comment
There was a problem hiding this comment.
Mandatory trace gate satisfied by authentic customer on-wire bus capture in OpenWebNet-HA/MyHOME#664. Code review follow-ups (exporting MESSAGE_TYPE_AUTO_UPDATE_INTERVAL in __all__, stop_sending_instant_power builder, minutes duration unit) verified in commit 8463824. Approving.
fedem95
left a comment
There was a problem hiding this comment.
Approving. Checked against Legrand's WHO_18.pdf (v1.0.0, 2011-07-15): start *#18*<Where>*#1200#<Type>*<Time>## with Time in minutes (1–255), event *#18*<Where>*1200#<Type>*<Time>##, stop with Time 0. On xtimmy86x's F454 trace (MyHOME#664; F520 meters per his comment) the parser turns all four 1200 replies into auto_update_interval with the right value and type, and the other 61 received frames (53 of them *113*) parse exactly as on master. The stop builder gives *#18*56*#1200#1*0## (and the 74#0 form). Suite passes locally (1240, test_metadata excluded for my env), CI green.
Notes:
- Unit in the description. The Before/After line says
update_interval = 255 (seconds, …); the code, docstrings and the PDF say minutes. In the trace*#18*56*#1200#1*2##is sent at 19:27:55.541 and*#18*56*1200#1*0##arrives at 19:30:01.726, 126.2 s later. - Log wording. It reads "automatic updates every 2 minutes". In the same trace meter 56 sends
*113*about every 30 s, already before the request (19:27:40), and the 0 reply comes about 2 minutes after a request with Time = 2. "every N minutes" doesn't match that; maybe "automatic updates for N minutes"? - Energy type. The PDF only defines
Type 1 = active power; 2/3/4 (gas, heat, water) come from libqtdevices alone. - The description cites "WHO = 4 v2.0.0 (2013-11-27)" as the spec; the WHO 18 document is
WHO_18.pdfv1.0.0.
Requested by A · project thread
Before:
*#18*51*1200#1*255##produced anOWNEnergyEventwith no message type and no value.After:
message_type = auto_update_interval,update_interval = 255(minutes duration,0= stream stopped),energy_type = 1(1 electricity, 2 gas, 3 heat, 4 water).Evidence
Legrand WHO 18 Specification. Legrand/BTicino "Open Web Net Language, WHO = 18 Energy Management" v1.0.0 (2011-07-15):
*#18*<Where>*#1200#<Type>*<Time>##whereTimeis stream duration in minutes (1..255).*#18*<Where>*1200#<Type>*<Time>##.*#18*<Where>*#1200#<Type>*0##(Time = 0indicates streaming stopped).Type 1 = active power; energy types 2 (gas), 3 (heat), and 4 (water) are sourced from BTicino's touch screen clientlibqtdevices.libqtdevices TS10_1_0_23.
energy_device.cpp:The client sends
*#18*W*#1200#type*time##to start the updates and parses the1200#type*timereply, re-arming the stream whentimeis 0.Firmware. bt_supervisione, identical on MyHomeServer1, F454, F459 and F461 (
results/spv*.tsv):*#18*51*#1200#1*255##D1 A1 02 32 00 02 1D FF*#18*51*#1200#1*0##D1 A1 02 32 00 02 1D 00*#18*51*#1200#2*255##D1 A1 02 32 00 02 82 FF*#18*51*1200##*#18*51*1200#1##The write form carries the type (
1Delectricity,82gas) and the duration (FF/00) on the bus; the bare request forms never leave the gateway, which is why OWNd should expect the reply only as an answer to the write (and why MyHOME's*#18*W*1200##poll is dead, resolved in MyHOME companion PR #661).On-wire Customer Bus Capture. Grounded in physical F454 trace capture contributed by @xtimmy86x in OpenWebNet-HA/MyHOME#664 (
tests/fixtures/traces/issue_466/myhome_trace_F454_who18_2026-10-08T19-30-16.json). In the live trace,*#18*56*#1200#1*2##is sent at 19:27:55.541 and*#18*56*1200#1*0##arrives at 19:30:01.726 (~126 s later), empirically confirming the duration-in-minutes lifecycle.How
OWNd/message/energy.py:elif self._dimension == 1200branch inOWNEnergyEventparsingupdate_interval(minutes duration) andenergy_type.MESSAGE_TYPE_AUTO_UPDATE_INTERVALinOWNd/message/__init__.py::__all__.OWNEnergyCommand.stop_sending_instant_power(where, energy_type=1)(*#18*W*#1200#type*0##).energy_typeinOWNEnergyCommand.start_sending_instant_power(where, duration=65, energy_type=1).tests/test_who18_auto_update_reply.py.pytest: 1240 passed, 1 skipped.How to check.
libqtdevicesquotes are from tagTS10_1_0_23of 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:ACK=*#*1##,NACK=*#*0##, the bus body is what the program put on the SCS bus (hex, without the A8 / checksum / A3 framing). Spec = Legrand/BTicino "Open Web Net Language, WHO = 18 Energy Management" v1.0.0 (2011-07-15). Raw logs, command lists, the harness and the report are in firmware_validation.zip (results/<program>[_<image>].tsv,results/*.cl/*.stub/*.mon,REPORT.md). 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