Repository navigation
feat(sound): add WHO 16 media transport commands and empirical conformance fixtures - #95
Conversation
|
Codecov Report✅ All modified and coverable lines are covered by tests. 📢 Thoughts on this report? Let us know! |
- Add OWNSoundCommand builders: next_track, previous_track, seek_up, seek_down, select_track, request_track, set_frequency, request_frequency, start_rds, stop_rds, request_rds - Add OWNSoundEvent properties: frequency_khz, track, rds_text, is_source_busy, track_step_forward, track_step_backward, is_seek_up, is_seek_down - Add unit test coverage in tests/test_sound.py (100% statement and branch coverage)
75f9d47 to
78a96dc
Compare
- Validate track >= 1 in select_track and OWNSoundEvent with spec docstrings on station presets vs. CD tracks - Validate kilohertz > 0 in set_frequency and OWNSoundEvent with usage docstrings - Clarify 8-byte RDS PS constraint in OWNSoundEvent and add tests for short/long/blank RDS frames
fedem95
left a comment
There was a problem hiding this comment.
Parsing checks out against the real F500N captures in MyHOME (tests/fixtures/traces/f500_tuner/, MyHOME#427): frequency (*#16*101*6*0*96200## → 96200), preset (*7*0*2 → 2) and RDS (KRONEHIT, Bayern 2, …) all decode, and the two 12-code glitch frames give None. select_track, next_track, previous_track, seek_up and seek_down build exactly the frames sent in those traces. Suite passes locally (1261), CI green.
set_frequency pads to 6 digits ({khz:06d}): set_frequency("101", 96200) builds *#16*101*#6*0*096200##. In myhome_trace_MH200N_f500n_tuner_seek_and_frequency.json the frame that worked is *#16*101*#6*0*96200## (tx 16:40:09.390, *#16*101*6*0*96200## back at 16:40:09.571). No capture or oracle row uses the padded form (oracle#25 also sends *#16*101*#6*0*98500##), and the test only covers 107000, where padding does nothing. Could it send the plain number?
Sources in the description don't match what they contain:
- "live recordings from physical L4561N":
findings/MyHomeServer1/sound-who22.mdrecords a QEMU emulation of WHO 22;sound-who16-source.tsvexists only in open oracle#25, F454 emulation. - "ACKed and translated" on F454 and MyHomeServer1: that TSV has no MyHomeServer1 rows, and on the F454
*16*6001*,*16*6015*,*16*6101*,*16*6115*,*16*5000*,*16*5100*arenack/out. - "Official Legrand WHO 16 specification":
developer.legrand.com/uploads/2019/12/WHO_16.pdfreturns 404 (as you noted on #80). Which document is this? - libqtdevices:
media_device.cppis WHO 22 only (device("22", …); next/prev track WHAT 9/10, frequency up/down 5/6, RDS 31/32, frequency in dimension 5 as MHz×100). It has none of the WHO 16 codes here.
The F500N captures (MyHOME#427) are the real evidence for 6001/6101/5000/5100 and dimensions 6/7/8; I'd cite those. WHAT 100, RDS start/stop 101/102 and multi-step 6002–6015 have no capture that I could find.
set_frequency formatted the value as {khz:06d}, so 96.2 MHz became
*OpenWebNet-HA#16*101*OpenWebNet-HA#6*0*096200##. The F500N capture on an MH200N (MyHOME#427)
shows the plain *OpenWebNet-HA#16*101*OpenWebNet-HA#6*0*96200## accepted, and no capture or
oracle row uses the padded form. The "6 digits" wording in the WHO 16
spec only comes with a 107000 example, where padding changes nothing.
Adds a test below 100 MHz.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
@fedem95 Thanks for checking this against the captures. You were right on every point. Both are fixed now. Padding. The document. It is OpenWebNet_Community_4_soundsystem_v1_0_1_EN, v1.0.1, 2011-11-24. The Legrand URL is gone; the same file (same git blob) is at https://github.com/sxpert/myhomepy/blob/master/lgg-docs/WHO_16.pdf. It is the only source for WHAT 100, 101/102 and the ±n steps. Sources. The description now has an evidence table per code. The L4561N trace I meant is now in OpenWebNet-HA/MyHOME#662 ( Reading the F454 TSV again: next to the |
|
Replayed
The PR description evidence table has been updated accordingly; this open point is closed. |
fedem95
left a comment
There was a problem hiding this comment.
Approving. set_frequency now sends the plain value (96200 → *#16*101*#6*0*96200##, tested), matching the F500N capture. Checked the cited document (sxpert/myhomepy/lgg-docs/WHO_16.pdf, "OpenWebNet_Community_4_soundsystem_v1_0_1_EN", v1.0.1, 11/24/2011): it defines WHAT 100 (source busy), 101/102 (RDS start/stop), 5000/5100, 6001–6015 / 6101–6115, the 6-digit frequency write with 107000, and the station write *#16*where*#7*parameter##. Oracle#30 confirms *#16*101*#7*1..5## on the emulated F454 (nack/out, presets 1..5 on the bus). Suite passes locally (1261, test_metadata excluded for my env), CI green.
Two notes:
- Preset range.
select_trackaccepts anytrack >= 1(select_track("101", 6)builds*#16*101*#7*6##). The document givesparameter = [1–5]for the station write, and on manfredgittmaier-afk's F500N*#16*101*#7*6##and*#7*15##gave "no audible change and no reply on the bus" (MyHOME#427, comment 5848039616). - The fixture cited as a live capture.
myhome_trace_MH200N_f500n_tuner.jsonhas rounded timestamps (15:35:00.000000, .100000, .200000, …) andexported_at15:37:15.000Z, the time of manfred's comment 5847535313. The frames come from his report, but the timing doesn't. The timed evidence for*#7*Nis in his comment 5848039616:[18:39:55.175] TX *#16*101*#7*1##,[18:39:55.335] RX *#16*101*7*0*1##.
WHO 16 Media Source Transport & Tuner Control
Adds command builders and event decoders for WHO 16 (Sound System) source transport, tuner frequency, presets and RDS text.
1. What is the issue?
Until now OWNd only built and parsed zone power and volume for WHO 16 (
*16*1*where##,*16*10*where##,*#16*where*#1*vol##). It had nothing for source transport or the FM tuner:No builders for source and tuner commands:
*16*600N*where##) and back (*16*610N*where##), 1..15 steps.*16*5000*where##) and down (*16*5100*where##).*#16*where*#7*N##/*#16*where*7##).*#16*where*#6*0*F##/*#16*where*6##).*16*101*where##,*16*102*where##,*#16*where*8##).Source events not decoded:
100(source busy),5000/5100and6001..6015/6101..6115were logged as unhandled states.2. How is it fixed?
OWNd/message/sound.py(exported viaOWNd/message/__init__.py):OWNSoundCommandfactory methods:next_track(where, step=1):*16*600N*where##,1 <= step <= 15.previous_track(where, step=1):*16*610N*where##,1 <= step <= 15.seek_up(where)/seek_down(where):*16*5000*where##/*16*5100*where##.select_track(where, track):*#16*where*#7*N##,track >= 1(tuner preset or media track).request_track(where):*#16*where*7##.set_frequency(where, kilohertz):*#16*where*#6*0*F##,kilohertz > 0. The value is sent without zero padding (96200, not096200). See section 4.request_frequency(where):*#16*where*6##.start_rds(where)/stop_rds(where):*16*101*where##/*16*102*where##.request_rds(where):*#16*where*8##.OWNSoundEventproperties:is_source_busy: WHAT 100.is_seek_up/is_seek_down: WHAT 5000 / 5100.track_step_forward/track_step_backward: the step 1..15 from WHAT 6001..6015 / 6101..6115.frequency_khz: Dimension 6 value in kHz (*#16*where*6*0*F##);Nonefor non-positive or non-numeric values.track: Dimension 7 preset/track (*#16*where*7*0*N##);Nonefor non-positive or non-numeric values.rds_text: Dimension 8, exactly 8 ASCII codes (32..126) to text; any other length, or non-printable codes, giveNone.Nonewithout raising; out-of-range builder arguments raiseValueError.WHO 16 frame summary
*16*6001*101##track_step_forward=1/OWNSoundCommand.next_track("101")stepin 1..15*16*6103*101##track_step_backward=3/OWNSoundCommand.previous_track("101", 3)stepin 1..15*16*5000*101##is_seek_up=True/OWNSoundCommand.seek_up("101")*16*5100*101##is_seek_down=True/OWNSoundCommand.seek_down("101")*16*100*101##is_source_busy=True*#16*101*#7*4##OWNSoundCommand.select_track("101", 4)track >= 1*#16*101*7*0*4##track=4/OWNSoundCommand.request_track("101")0*#16*101*#6*0*96200##OWNSoundCommand.set_frequency("101", 96200)*#16*101*6*0*96200##frequency_khz=96200/OWNSoundCommand.request_frequency("101")*16*101*101##/*16*102*101##OWNSoundCommand.start_rds("101")/stop_rds("101")*#16*101*8*75*82*79*78*69*72*73*84##rds_text="KRONEHIT"/OWNSoundCommand.request_rds("101")3. How is it tested?
tests/test_sound.py:test_who16_sound_source_commands: every builder's frame. Range checks:step0/16,track0/negative,kilohertz0/negative.set_frequencyis tested at 107000 and at 96200, below 100 MHz, where padding would show.test_who16_sound_source_events: busy (*16*100*101##), steps (*16*6001*101##,*16*6005*101##,*16*6101*101##), seek (*16*5000*101##,*16*5100*101##), frequency, preset and RDS text.Results: 1264 passed, 1 skipped.
OWNd/message/sound.pyhas 100% statement and branch coverage over the suite.ruff checkandmypyare clean.4. Evidence
Each code, with where its evidence comes from:
6001,6101(step ±1)nack, frame still put on the bus6002..6015,6102..6115(step ±n)6003,6005,6102,6104); no reply6015,6115nack, still on the bus5000,5100(seek)nack, still on the bus100(busy)nack, silent101/102(RDS start/stop)ack, on the bus*#16*101*#6*0*96200##, reports88800..103500ack, on the bus (107000,98500,101500)*#16*101*#7*3##, reports*7*0*1..*7*0*4; L4561N: writes 1/2/5, no replynack/out, translates*#16*101*#7*1..5to SCS$0493018FC0..C4(presets 1..5);*#7*0*Nwas an oracle test artifact that sent preset 0 ($0493018FCF) (own-firmware-oracle#30)Sources:
Live captures:
tests/fixtures/traces/f500_tuner/in MyHOME. These carry the frequency, preset and RDS reports and the 12-code glitch frames.tests/fixtures/traces/mh200_l4561n_source/(MyHOME#662), also in own-firmware-oracle#25. The capture is filtered on WHO 16 and WHERE 102/21, so ACK/NACK are missing. None of the commands changed what was reported, and the L4561N is an aux input, not a tuner. It only shows that these frames went out on a real plant.Specification: OpenWebNet_Community_4_soundsystem_v1_0_1_EN, v1.0.1 of 2011-11-24 (Legrand R&D; v1.0.0 BTicino, 2006). The Legrand URL is gone; the same file is at sxpert/myhomepy
lgg-docs/WHO_16.pdf. WHAT table on pp. 4–5; FREQUENCY WRITE in §1.5.7 (p. 15). That section says "composed by 6 digits", and its only example is107000, where padding changes nothing. It also says Hz where the value is kHz. The F500N capture shows the unpadded96200working, soset_frequencyfollows the capture.Firmware emulation: own-firmware-oracle#25 and own-firmware-oracle#30, F454 020051 only (
results/F454/020051/oracle/full/sound-who16-source.tsv). It has no MyHomeServer1 rows.nack/outmeans the gateway answered NACK but still put a telegram on the bus.Not used: libqtdevices
media_device.cppis WHO 22 only and has none of these codes.5. Downstream Consumer Integration
This PR powers the Home Assistant integration MyHOME (OpenWebNet-HA/MyHOME#651), so
MyHOMESoundSourceentities can:OWNSoundCommandfactory methods for all media and tuner controls.track_step_forward/track_step_backwardevents.6. Release & Rollout Order
graph TD A["Maintainer approves & merges OWNd PR #95"] --> B["Merge OWNd PR #98 (bump to 2.0.0-b11)"] B --> C["Publish GitHub Release v2.0.0b11 -> PyPI"] C --> D["Re-run MyHOME PR #651 CI (all 16 checks turn green)"] D --> E["Merge MyHOME PR #651"]2.0.0-b11.v2.0.0b11: GitHub Actions builds and pushes the release wheel to PyPI.OWNd==2.0.0b11resolves from PyPI, turning all matrix jobs green.