Skip to content

fix(energy): align auto-update telemetry log wording and stream lifecycle properties - #102

Merged
GreenGrassBlueOcean merged 7 commits into
masterfrom
fix/who18-dim1200-review-followups
Oct 11, 2026
Merged

GreenGrassBlueOcean merged 7 commits into
masterfrom
fix/who18-dim1200-review-followups

Conversation

@GreenGrassBlueOcean

@GreenGrassBlueOcean GreenGrassBlueOcean commented Oct 9, 2026 •

Copy link
Copy Markdown
Contributor

WHO 18 Dimension 1200 Auto-Update Telemetry Refinements

Follow-up to #92 addressing review observations from @fedem95 and @GreenGrassBlueOcean: refines human-readable log wording to accurately reflect session duration semantics, clamps non-negative protocol boundaries, and adds explicit stream_duration and is_streaming_active properties on OWNEnergyEvent.


1. What is the problem?

  1. Misleading Telemetry Log Wording:

    • In PR fix(energy): parse the WHO 18 dimension 1200 automatic-update reply #92, incoming Dimension 1200 events (*#18*WHERE*1200#Type*Time##) were logged as:
      Sensor <where> automatic updates every <time> minutes (0 = stopped).
    • In physical plant bus captures (and per Legrand WHO_18.pdf specification), Time specifies the duration of the streaming session in minutes, not the sampling frequency.
    • While streaming is active, physical energy meters (e.g. BTicino F520) emit passive active power frames (*#18*WHERE*113*P##) approximately every 30 seconds or on power draw changes.
    • When Time = 0 arrives to indicate the stream has finished, formatting the message as "automatic updates every 0 minutes (0 = stopped)" was unnatural and confusing.
  2. Semantic Gap in Event Properties:

    • OWNEnergyEvent exposed update_interval to store the decoded Time parameter.
    • Downstream consumers (like Home Assistant MyHOME) had to manually check if event.update_interval > 0 vs == 0 to determine streaming state.
    • No direct semantic duration alias or active streaming boolean indicator was provided.
  3. Boundary Value Handling:

    • Negative duration values or malformed parameters were not defensively clamped to 0, which could lead to nonsensical negative durations or logs if unexpected values were parsed.
    • Calling start_sending_instant_power(where, 0) previously emitted a log saying "for 0 minutes" rather than "Stopping".
  4. Documentation Discrepancy:

    • energy_type docstring did not explicitly delineate official specification boundaries (Type 1 = active power in Legrand WHO_18.pdf) from extended media types (2 gas, 3 heat, 4 water from BTicino touch screen libqtdevices).
    • update_interval docstring did not note that stream_duration is the preferred property name.

2. How is it solved?

  1. Human-Readable Log Formatting:

    • When update_interval > 0: Sensor <sensor> automatic updates for <update_interval> minutes.
    • When update_interval == 0: Sensor <sensor> automatic updates stopped.
  2. Added Typed Properties to OWNEnergyEvent:

    • stream_duration: Typed property alias returning int | None representing the stream duration in minutes (0 when stopped). Preserves update_interval for 100% backward compatibility with existing callers.
    • is_streaming_active: Typed boolean property returning:
      • True if updates are streaming (update_interval > 0),
      • False if stream has stopped (update_interval == 0),
      • None if the event is not a Dimension 1200 event.
  3. Defensive Clamping & Command Logging:

    • Parsed _update_interval and _energy_type are clamped with max(0, ...) to ensure duration is strictly non-negative.
    • start_sending_instant_power(where, duration) clamps duration = 255 if duration > 255 else max(0, int(duration)) and formats log as "Stopping..." when duration == 0.
  4. Clarified Docstrings:

    • update_interval documented as retained for backward compatibility with preference for stream_duration.
    • stream_duration documented as stream duration in minutes (0..255 per Legrand WHO 18).
    • energy_type documented with explicit provenance (Type 1 per Legrand WHO 18, Types 2–4 per libqtdevices).

WHO 18 Dimension 1200 Event Telemetry Summary

Frame message_type update_interval / stream_duration is_streaming_active human_readable_log
*#18*51*1200#1*255## auto_update_interval 255 True Sensor 1 automatic updates for 255 minutes.
*#18*51*1200#1*2## auto_update_interval 2 True Sensor 1 automatic updates for 2 minutes.
*#18*51*1200#1*0## auto_update_interval 0 False Sensor 1 automatic updates stopped.
*#18*51*113*1234## active_power None None Sensor 1 is reporting an active power draw of 1234 W.

3. How is it tested?

  1. Unit Test Assertions (tests/test_who18_auto_update_reply.py):

    • test_auto_update_confirmation_is_parsed: asserts stream_duration == 255, is_streaming_active is True, and exact log string "Sensor 1 automatic updates for 255 minutes.".
    • test_auto_update_stop_is_parsed: asserts stream_duration == 0, is_streaming_active is False, and exact log string "Sensor 1 automatic updates stopped.".
    • test_other_dimensions_are_untouched: verifies non-1200 events retain stream_duration is None and is_streaming_active is None.
    • test_builder_duration_zero_or_negative: asserts start_sending_instant_power with 0 or negative duration produces *#18*51*#1200#1*0## with "Stopping" log.
    • test_negative_interval_clamped_to_stopped: verifies corrupted/negative values clamp safely to 0 and report stopped.
  2. Full Regression & Standards Verification:

    • Full test suite: 1,288 passed (pytest -p no:homeassistant).
    • Standards suite: scripts/verify_library_standards.py passed with 0 errors.
    • Coverage: scripts/verify_coverage.py reports 100.0% coverage across all 24 modules (3,946 / 3,946 statements).
    • Linters: ruff check and mypy pass with 0 errors.

4. Where are the real traces?

All duration and lifecycle semantics are grounded in authentic, unedited on-wire bus captures from live customer installations:

  1. On-Wire Customer Plant Capture (MyHOME#664):

    • Fixture: tests/fixtures/traces/issue_466/myhome_trace_F454_who18_2026-10-08T19-30-16.json
    • Hardware: Physical Legrand F454 gateway with BTicino F520 energy meters on SCS bus.
    • Real-world sequence:
      • 19:27:40: Meter 56 broadcasts passive power: *#18*56*113*...##.
      • 19:27:55.541: Controller requests automatic streaming for 2 minutes: *#18*56*#1200#1*2##.
      • 19:27:55.612: Meter confirms duration: *#18*56*1200#1*2##.
      • Active period: Meter broadcasts periodic active power *113* frames ~every 30 seconds.
      • 19:30:01.726: Meter emits stream stop frame: *#18*56*1200#1*0## (exactly 126.2 seconds later).
    • Demonstrates empirically that the Time parameter represents the stream active duration, and 0 signifies session termination.
  2. Official Protocol Specification:

    • Legrand/BTicino Open Web Net Language, WHO = 18 Energy Management (WHO_18.pdf v1.0.0, 2011-07-15):
      • Section 3.1: Start/update command *#18*WHERE*#1200#Type*Time## (Time 1..255 minutes).
      • Section 3.2: Stop command *#18*WHERE*#1200#Type*0## (Time = 0).
      • Event response *#18*WHERE*1200#Type*Time##.
  3. Gateway Translator Binary Analysis (bt_supervisione):

    • Replay under QEMU on MyHomeServer1, F454, F459, and F461 firmwares confirms *#18*51*#1200#1*255## ACKs with SCS telegram D1 A1 02 32 00 02 1D FF, and stop frame *#18*51*#1200#1*0## ACKs with D1 A1 02 32 00 02 1D 00.

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

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

Thanks, this looks good. The WHO 18 telemetry now reflects stream duration and stop semantics, and the actuator #0 suffix is preserved. CI is green. Approved.
Non-blocking note: test_negative_interval_clamped_to_stopped currently checks a local clamp after mutating the event, rather than exercising the code path that sets _update_interval. Consider adjusting it to assert the parsed event properties and log after passing through that path.

@GreenGrassBlueOcean
GreenGrassBlueOcean merged commit 4980b0c into master Oct 11, 2026
16 checks passed
@GreenGrassBlueOcean
GreenGrassBlueOcean deleted the fix/who18-dim1200-review-followups branch October 11, 2026 10:24
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