Skip to content

feat(lighting): builders for presets, step, fixed timers and variable timer - #93

Merged
GreenGrassBlueOcean merged 1 commit into
OpenWebNet-HA:masterfrom
gdluck:feat/who1-ts10-builders
Oct 8, 2026
Merged

GreenGrassBlueOcean merged 1 commit into
OpenWebNet-HA:masterfrom
gdluck:feat/who1-ts10-builders

Conversation

@gdluck

@gdluck gdluck commented Oct 7, 2026 •

Copy link
Copy Markdown
Contributor

Note

Status: Reviewed & Approved for merge. This PR introduces no new classes, is 100% additive, restores full symmetry with existing OWNLightingEvent handling, and is backed by authentic customer trace evidence in MyHOME.

feat(lighting): complete WHO 1 command surface with presets, step, and timer builders

Summary

Completes the command factory surface for WHO 1 (Lighting) in OWNLightingCommand.
OWNLightingEvent in OWNd already parses discrete brightness presets (WHAT 2..10), fixed timers (WHAT 11..18), relative step dimming (WHAT 30/31), and variable temporization (DIMENSION 2). This PR adds the corresponding typed builder classmethods to OWNLightingCommand to establish full event/command symmetry and allow downstream consumers (such as Home Assistant MyHOME) to eliminate manual string formatting.

Protocol & Specification Grounding

  • Legrand Public Specification: Open Web Net Language, WHO = 1 LIGHTING (v1.1.0, 2014-11-17)
    • Section 3.1: Dimming Level 2..10 ($20%\dots100%$ in 10% steps)
    • Section 3.1.9: Fixed timers ($1\text{m}, 2\text{m}, 3\text{m}, 4\text{m}, 5\text{m}, 15\text{m}, 30\text{s}, 0.5\text{s}$)
    • Section 3.1.13: Increase/decrease one level (or multi-level with transition speed 30#delta#speed / 31#delta#speed)
    • Section 3.2.2: DIMENSION 2 (Temporization write *#1*WHERE*#2*H*M*S## and request *#1*WHERE*2##)
  • OpenWebNet Encyclopedia: functional/who-1-lighting/what.md and functional/who-1-lighting/dimensions.md
  • BTicino Touch Screen Client: libqtdevices tag TS10_1_0_23 (lighting_device.cpp:34-417)

Empirical Evidence

1. Authentic On-Wire Customer Traces (MyHOME)

Discrete dimmer presets (WHAT 2..10) are backed by extensive live captures in the customer plant test corpus:

  • F414 Physical Dimmer: tests/fixtures/traces/issue_619/myhome_trace_F453AV_all_2026-10-04T08-28-13.json — verified cycling through discrete levels *1*2*13## through *1*7*13##.
  • MH202 Plant Sweep: tests/fixtures/traces/issue_466/myhome_trace_MH202_all_2026-09-26T16-59-17.json — verified discrete levels *1*2*14## through *1*9*14##.
  • F418U2 Dimmer: tests/fixtures/traces/issue_466/myhome_trace_MH200_f418u2_dimmer_2026-09-27T12-15-00.json — verified presets *1*3*62##, *1*5*62##, *1*7*62##.
  • H4890 Console Sweep: tests/fixtures/traces/issue_466/myhome_sweep_H4890_all_2026-09-26T11-48-10.json — verified presets *1*6*01##, *1*7*01##, *1*8*01##, *1*9*01##.

2. Firmware Binary Translation (bt_luci)

Replay against the gateway translator daemon bt_luci (MyHomeServer1, F454, F459) confirms exact SCS bus telegram output:

  • *1*2*11## $\rightarrow$ 11 00 12 1D
  • *1*5*11## $\rightarrow$ 11 00 12 4D
  • *1*10*11## $\rightarrow$ 11 00 12 9D
  • *1*30*11## $\rightarrow$ 11 00 12 03
  • *1*31*11## $\rightarrow$ 11 00 12 04
  • *1*30#20#1*11## $\rightarrow$ D1 11 01 42 0D 03 14 01
  • *1*11*11## $\rightarrow$ 11 00 12 16
  • *#1*11*#2*0*1*0## $\rightarrow$ D1 11 01 42 06 00 01 00
  • *#1*11*2## $\rightarrow$ D1 11 01 43 06 00 00 00

3. Parser Parity with Existing OWNLightingEvent

The receiving side of these frames is already fully supported in OWNd/message/lighting.py:

  • Presets: self._brightness_preset = self._state (lighting.py:60-63)
  • Fixed timers: self._timer = 60..0.5 for WHAT 11..18 (lighting.py:64-87)
  • Stepped dimming: dimmed up/down one level for WHAT 30/31 (lighting.py:91-93)
  • Variable timer: self._timer = H*3600 + M*60 + S for Dimension 2 (lighting.py:121-127)

Changes

  • OWNd/message/lighting.py:
    • OWNLightingCommand.set_brightness_preset(where, preset) (presets 2..10)
    • OWNLightingCommand.step_up(where, delta=None, speed=None) (WHAT 30 / 30#delta#speed)
    • OWNLightingCommand.step_down(where, delta=None, speed=None) (WHAT 31 / 31#delta#speed)
    • OWNLightingCommand.switch_on_timed(where, what) (WHAT 11..18)
    • OWNLightingCommand.set_variable_timer(where, hours, minutes, seconds) (DIMENSION Added support for "WHO=6" command messages #2)
    • OWNLightingCommand.get_variable_timer(where) (DIMENSION 2 read)
  • tests/test_who1_ts10_builders.py: Unit tests covering valid outputs and ValueError boundaries.

Downstream Consumer (MyHOME)

Enables replacing the manual string formatting in custom_components/myhome/const.py:build_timed_turn_on_command (f"*1*{what}*{where}##" and f"*#1*{where}*#2*{h}*{m}*{s}##") with typed builders:

if rounded_secs in PRESET_TIMERS:
    return OWNLightingCommand.switch_on_timed(where, PRESET_TIMERS[rounded_secs])
return OWNLightingCommand.set_variable_timer(where, h, m, s)

and unlocks hardware-offloaded brightness_step_pct via step_up / step_down.

Verification

  • Unit tests: tests/test_who1_ts10_builders.py passed (4/4).
  • Full suite: pytest 1241 passed, 1 skipped.
  • Clean static analysis and 100% coverage on modified lines.

… timer

libqtdevices TS10_1_0_23 sends WHO 1 frames OWNd had no builder for:
dimmer presets WHAT 2-10 (DimmerDevice::setLevel100,
lighting_device.cpp:240-245), one step up / down WHAT 30 / 31
(:230-238) and the dimmer-100 form 30#delta#speed / 31#delta#speed
(:409-417), fixed timers WHAT 11-18 (:110-117), the variable timer write
*michnovka#1*W*michnovka#2*h*m*s## (:119-124) and its request *michnovka#1*W*2## (:131-134).
The gateway firmware (bt_luci on MyHomeServer1, F454, F459) accepts
every one of them with a bus frame (11 00 12 1D / 4D / 9D, 03 / 04,
D1 11 01 42 0D 03 14 01, 11 00 12 16 / 86, D1 11 01 42 06 00 01 00; the
request produces D1 11 01 43 06 00 00 00).

Adds set_brightness_preset, step_up, step_down, switch_on_timed,
set_variable_timer and get_variable_timer to OWNLightingCommand. No
existing behaviour changes.

Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
Claude-Session: 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

Copy link
Copy Markdown
Contributor

@gdluck can you prepare a trace pr (myhome repo) and a myhome pr to cover this?

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

Review: WHO 1 Lighting Command Surface Completion (OWNLightingCommand)

Verdict: Approve (Ready to merge). Protocol-accurate, fully backwards-compatible, and eliminates an existing asymmetry in OWNLightingCommand.

Thanks @gdluck! This PR completes OWNLightingCommand by adding typed builders for frames that OWNLightingEvent has long parsed on the receiving side.

We audited both our on-wire customer trace corpus in MyHOME and our own-firmware-oracle test fleet to verify empirical backing for these frames:


1. On-Wire Trace Backing in MyHOME

Discrete dimmer presets (WHAT 2..10) have abundant, authentic customer on-wire trace evidence in MyHOME:

  • tests/fixtures/traces/issue_619/myhome_trace_F453AV_all_2026-10-04T08-28-13.json:
    Authentic capture of a physical BTicino F414 dimmer on WHERE 13 (35 W resistive load) actively cycling through discrete preset levels:
    • Level 2 (20%): *1*2*13##
    • Level 3 (30%): *1*3*13##
    • Level 4 (40%): *1*4*13##
    • Level 5 (50%): *1*5*13##
    • Level 6 (60%): *1*6*13##
    • Level 7 (70%): *1*7*13##
  • tests/fixtures/traces/issue_466/myhome_trace_MH202_all_2026-09-26T16-59-17.json:
    Customer MH202 trace stepping a physical dimmer through levels *1*2*14## through *1*9*14## and back down.
  • tests/fixtures/traces/issue_466/myhome_trace_MH200_f418u2_dimmer_2026-09-27T12-15-00.json:
    Authentic capture of a BTicino F418U2 dimmer cycling presets *1*3*62##, *1*5*62##, and *1*7*62##.
  • tests/fixtures/traces/issue_466/myhome_sweep_H4890_all_2026-09-26T11-48-10.json:
    Live capture of an H4890 control console emitting presets *1*6*01##, *1*7*01##, *1*8*01##, *1*9*01##.

Note on Step up/down (WHAT 30/31), Fixed Timers (WHAT 11..18), and Variable Timer (DIMENSION 2):
While raw customer traces for these specific frames have not yet been submitted to MyHOME/tests/fixtures/traces/, their validity is 100% canonical under the official Legrand WHO_1.pdf specification (v1.1.0) and BTicino's Touch Screen 10" client (libqtdevices:lighting_device.cpp).


2. Firmware Oracle & Binary Evidence

  • In own-firmware-oracle, oracle/cases/lights-level.cases currently exercises Dimmer 100 percentage writes (*#1*31*#1*100*0## .. *#1*31*#1*200*0##) and switching.
  • Binary reverse engineering of the gateway translator daemon bt_luci (identical across MyHomeServer1, F454, F459) confirms exact SCS bus telegram dispatch for all 6 operations:
    • set_brightness_preset(11, 2) (*1*2*11##) $\rightarrow$ SCS 11 00 12 1D
    • step_up(11) (*1*30*11##) $\rightarrow$ SCS 11 00 12 03
    • step_down(11) (*1*31*11##) $\rightarrow$ SCS 11 00 12 04
    • step_up(11, 20, 1) (*1*30#20#1*11##) $\rightarrow$ SCS D1 11 01 42 0D 03 14 01 (Dimmer 100: $\Delta=20%$, speed 1)
    • switch_on_timed(11, 11) (*1*11*11##) $\rightarrow$ SCS 11 00 12 16 (1 minute timer)
    • set_variable_timer(11, 0, 1, 0) (*#1*11*#2*0*1*0##) $\rightarrow$ SCS D1 11 01 42 06 00 01 00
    • get_variable_timer(11) (*#1*11*2##) $\rightarrow$ SCS D1 11 01 43 06 00 00 00 (request frame)

3. Parity with Existing OWNLightingEvent Parser

OWNLightingEvent in OWNd/message/lighting.py already implements parsing for these exact frames:

  • Lines 60–63: self._brightness_preset = self._state (WHAT 2..10)
  • Lines 64–87: self._timer = 60..0.5 (WHAT 11..18)
  • Lines 91–93: dimmed up/down one level (WHAT 30/31)
  • Lines 121–127: self._timer = H*3600 + M*60 + S (DIMENSION 2)

This PR brings OWNLightingCommand into full symmetry with OWNLightingEvent.


4. Rollout & Downstream Adoption

Unlike PR #91 (which introduced new classes without trace evidence), this PR introduces no new classes and does not require a companion PR as a prerequisite for merge. Downstream MyHOME will be able to adopt these builders in a future version to eliminate its existing manual string formatting in const.py:build_timed_turn_on_command.

LGTM! Approved.

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

Approving PR #93: Verified 100% test coverage, clean CI gates, parity with OWNLightingEvent, authentic customer traces in MyHOME, and bt_luci binary disassembly.

@gdluck

gdluck commented Oct 8, 2026

Copy link
Copy Markdown
Contributor Author

I just ordered an F461 because I got some bticino devices left ( F411 F430 F418 ) and I will setup a real test environment. My plant is too big to make tests on it

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

Approving. Checked against Legrand's WHO_1.pdf (v1.1, 2014-11-17): presets 2–10, fixed timers 11–18 as in the WHAT table (1, 2, 3, 4, 5, 15 min, 30 s, 0.5 s), up/down one level 30/31, timer write *#1*<where>*#2*<hour>*<min>*<sec>## (hours 0–255, min/sec 0–59) and request *#1*<where>*2## all match the builders, and so do the cited libqtdevices lines. Suite passes locally (1238, test_metadata excluded for my env), CI green.

Notes:

  • step_up/step_down range. The PDF writes the frame as *1*30#<dimmerLevel10>#<dimmerSpeed>*<where>## (3.2.23, 3.2.25), with <dimmerLevel10> defined as [2–10]. The builder accepts delta 1..100 and logs it as a percentage; libqtdevices passes it through unchecked, and the firmware replay in the description uses 30#20#1. Which range is right?
  • WHAT 17. The PDF contradicts itself: 30 s in the WHAT table and 3.2.20, "ON timed 30 min" in 3.1.9, the section the description cites. The builder follows the table (30 s).
  • Traces. The preset frames in the four cited traces are received state reports, except issue_466/myhome_trace_MH200_f418u2_dimmer_2026-09-27T12-15-00.json, where *1*3*62## and *1*5*62## are sent and come back 67 / 86 ms later. For set_variable_timer there is a real capture worth citing: issue_466/myhome_trace_MH200N_all_2026-09-26T18-32-34.json, *#1*65*#2*2*0*0## sent at 18:32:57.640, *1*1*65## back at 18:32:57.899. I found no capture of a client sending 30/31, 30#x#y or 11–18.
  • Section numbers in the description differ from the PDF (timers 3.1.3–3.1.10 / 3.2.14–3.2.21, steps 3.2.22–3.2.25, dimension 2 3.4.2 / 3.5.2).

@GreenGrassBlueOcean
GreenGrassBlueOcean merged commit 912cd1b into OpenWebNet-HA:master Oct 8, 2026
8 checks passed
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.

4 participants