Skip to content

feat(media_player): tuner entity for WHO=16 sound sources (F500) - #427

Merged
GreenGrassBlueOcean merged 4 commits into
OpenWebNet-HA:v2-phase1-architecturefrom
GreenGrassBlueOcean:feat/f500-tuner
Sep 26, 2026
Merged

GreenGrassBlueOcean merged 4 commits into
OpenWebNet-HA:v2-phase1-architecturefrom
GreenGrassBlueOcean:feat/f500-tuner

Conversation

@GreenGrassBlueOcean

@GreenGrassBlueOcean GreenGrassBlueOcean commented Sep 22, 2026 •

Copy link
Copy Markdown
Contributor

Adds full support for WHO=16 sound source tuner entities (F500 / F500N) on the BTicino MyHOME analog sound diffusion matrix (F441 / F441M).

What this adds

A matrix input can hold a tuner (F500 / F500N) rather than an analog line interface (L4561). Tick Source N is a tuner in the integration Options Flow and a dedicated media_player radio entity is registered for that input:

Home Assistant Feature / Service OpenWebNet Frame Result / On-Wire Behavior
Power On / Off (turn_on / turn_off) *16*3*10S## / *16*13*10S## Power on / standby for source input S (WHERE 101–109)
Next / Previous Station (media_next_track / media_previous_track) *16*6001*10S## / *16*6101*10S## Step to next / previous stored station preset
Seek Up / Down (myhome.tuner_seek_up / myhome.tuner_seek_down) *16*5000*10S## / *16*5100*10S## Hardware frequency seek to next / previous receivable FM station
Stored Stations (select_source, Presets 1–15) *#16*10S*#7*<N>## Select stored preset station N (strictly without leading zero)
Tune Frequency (play_media, content type channel) *#16*10S*#6*0*<kHz>## Direct FM frequency tuning in kHz (strictly requires leading zero 0*)
Preset Station Selection (play_media) *#16*10S*#7*<N>## Digits "1"–"15" select preset N
Live Station Title (media_title) *#16*10S*8*<8 ASCII codes>## Dynamic RDS station title (autonomous arrival; blanking frames handled)
State Attributes (frequency / station) DIMENSION 6 / DIMENSION 7 Continuous frequency in MHz (e.g. 96.2) and active preset number (1–15)

Architecture & Design Decisions

  • Tuners are declared, not guessed: On the OpenWebNet SCS bus, a silent tuner in standby is indistinguishable from an unused or line input until it transmits. To prevent phantom entities or discovery flicker, users declare tuner inputs via the integration Options Flow.
  • Source frame routing: Source events carry source addresses (101–109) rather than amplifier zone addresses. The platform supplies routing keys (<source>#16) so source status and dimension events are routed directly to their sound source entity without interfering with zone discovery.
  • Dedicated Home Assistant Services: Exposes myhome.tuner_seek_up and myhome.tuner_seek_down entity actions targeting tuner sources, fully localized across all 4 supported languages (en, fr, it, nl) with icons in icons.json. Standard zone amplifier entities guard against misuse by rejecting seek calls with a descriptive translated error (seek_not_supported).
  • Dynamic preset expansion (F500 vs F500N): Standard F500 tuners store 5 presets, whereas F500N models support up to 15. The entity defaults to 5 stations in source_list and dynamically expands up to 15 whenever higher presets are selected or reported on the bus.
  • Preset state hygiene on frequency tuning: When a frequency is tuned directly or reached via hardware seek, DIMENSION 7 is only reported by hardware if the frequency matches an active preset. The entity automatically clears stale station presets and source attributes whenever frequency is changed without a station confirmation.
  • RDS handling & blanking: Tuners emit an autonomous blanking frame (*8*32*...##, eight spaces) immediately after tuning, followed by the dynamic station title. The entity correctly suppresses the blank title and updates media_title as soon as printable ASCII codes arrive.

Live Hardware Verification

Fully verified against authentic live bus captures from an MH200N gateway connected to an F500N tuner with antenna, contributed by @manfredgittmaier-afk (#427 comment 5847535313 and comment 5848039616):

  1. Station write asymmetry: Confirmed that the station write requires omitting the leading zero (*#16*101*#7*<N>##), while the status report carries a leading zero (*#16*101*7*0*<N>##). Passing a leading zero in write syntax is treated as a status read by the tuner.
  2. Frequency write syntax: Confirmed that the frequency write strictly requires the leading zero (*#16*101*#6*0*<kHz>##), while writing without zero is completely ignored by hardware.
  3. Frequency units: Confirmed in kHz (96200 = 96.2 MHz, 88800 = 88.8 MHz, 103500 = 103.5 MHz).
  4. Hardware seek up / down: Confirmed that *16*5000*101## and *16*5100*101## lock onto receivable stations and trigger status reports.
  5. Autonomous RDS: Confirmed that DIMENSION 8 RDS text frames arrive spontaneously after tuning events without needing polling.
  6. WHO=22 Mirroring: Confirmed that WHO=16 tuner events are mirrored on WHO=22 with compound source address 5#2#1.
  7. Collective Sound Status Query: Confirmed that *#16*0*5## responds with the full status of all sources and amplifiers on MH200N (#427 comment 5848181845), settling WHO 16 audio support for MH200N in OWNd (OpenWebNet-HA/OWNd#63).

Authentic on-wire bus captures are committed under tests/fixtures/traces/f500_tuner/ (myhome_trace_MH200N_f500n_tuner.json and myhome_trace_MH200N_f500n_tuner_seek_and_frequency.json) and replayed in continuous CI.

Testing & Quality Gates

  • 2,356 passing tests across the entire integration suite (100% pass rate).
  • 100.0% coverage maintained across all 43 modules (8,128 / 8,128 statements covered with 0 missing lines).
  • Replay tests against authentic MH200N + F500N hardware captures in test_sound_source_trace_replay.py.
  • Complete documentation of services in docs/configuration/services.md and tuner usage in docs/configuration/media_player.md.
  • Quality scale criteria updated in quality_scale.yaml and verified via scripts/verify_ha_standards.py.

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

@wave68runner — you are the only person we know of with a working tuner on this bus, so if you have the appetite, your captures would take this out of draft.

Your earlier trace already gave us the one piece of hardware evidence in this PR: *#16*101*8*32*81*109*117*115*105*99*32##, the RDS text as eight ASCII codes. Everything else here is read off WHO_16.pdf and has never been tried against a real radio.

The three captures that would settle it

All passive: use your wall control or the iOS app as you normally would, with the bus monitor running. Nothing to install, nothing sent by us.

  1. Change the station on the tuner a couple of times.
    Looking for: *#16*10S*7*0*<N>##, and whether the controller writes *#16*10S*#7*<N>## or reaches the station some other way. The specification has the write carry its parameter directly while the report prefixes a zero, which is odd enough that I would like to see it on a wire.

  2. Tune to a different frequency manually, if your controller can.
    Looking for: *#16*10S*6*0*<six digits>##. The specification calls this field Hz while every example in the same document uses kHz. Your capture would tell us which one hardware means, and that is the difference between tuning to 107.00 MHz and tuning to nothing.

  3. Whatever your controller sends when you open the radio page.
    Looking for: *16*101*10S## (start RDS). This PR sends that when the entity is added, on the assumption that a tuner stays silent until asked. If your controller never sends it and RDS arrives anyway, that assumption is wrong and the frame should go.

Two questions that need no capture

  • F500 or F500N? The F500 stores 5 stations, the F500N 15. This PR offers 5, which is wrong for an F500N.
  • Does your tuner do AM as well as FM, and does the controller expose it? This PR ignores modulation completely, and refuses anything outside the FM band, which would be wrong on an AM-capable plant.

If you want to go further

Installing this branch and trying the radio entity would be the only real test of the write path: media_player.play_media with content type channel and "107.5" should produce *#16*101*#6*0*107500##. A capture of that, and whether the radio actually moved, is the last thing standing between this and a normal PR.

No pressure on any of it. The passive captures are the valuable part; the rest is a bonus.

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

@wave68runner

Copy link
Copy Markdown

@wave68runner — you are the only person we know of with a working tuner on this bus, so if you have the appetite, your captures would take this out of draft.

Your earlier trace already gave us the one piece of hardware evidence in this PR: *#16*101*8*32*81*109*117*115*105*99*32##, the RDS text as eight ASCII codes. Everything else here is read off WHO_16.pdf and has never been tried against a real radio.

The three captures that would settle it

All passive: use your wall control or the iOS app as you normally would, with the bus monitor running. Nothing to install, nothing sent by us.

  1. Change the station on the tuner a couple of times.
    Looking for: *#16*10S*7*0*<N>##, and whether the controller writes *#16*10S*#7*<N>## or reaches the station some other way. The specification has the write carry its parameter directly while the report prefixes a zero, which is odd enough that I would like to see it on a wire.
  2. Tune to a different frequency manually, if your controller can.
    Looking for: *#16*10S*6*0*<six digits>##. The specification calls this field Hz while every example in the same document uses kHz. Your capture would tell us which one hardware means, and that is the difference between tuning to 107.00 MHz and tuning to nothing.
  3. Whatever your controller sends when you open the radio page.
    Looking for: *16*101*10S## (start RDS). This PR sends that when the entity is added, on the assumption that a tuner stays silent until asked. If your controller never sends it and RDS arrives anyway, that assumption is wrong and the frame should go.

Two questions that need no capture

  • F500 or F500N? The F500 stores 5 stations, the F500N 15. This PR offers 5, which is wrong for an F500N.
  • Does your tuner do AM as well as FM, and does the controller expose it? This PR ignores modulation completely, and refuses anything outside the FM band, which would be wrong on an AM-capable plant.

If you want to go further

Installing this branch and trying the radio entity would be the only real test of the write path: media_player.play_media with content type channel and "107.5" should produce *#16*101*#6*0*107500##. A capture of that, and whether the radio actually moved, is the last thing standing between this and a normal PR.

No pressure on any of it. The passive captures are the valuable part; the rest is a bonus.

I will try to test that. I Hope that my latency problems will be over

@manfredgittmaier-afk

Copy link
Copy Markdown

@GreenGrassBlueOcean – here's a capture from a working tuner.

Setup: MH200N gateway, F500N tuner at source 101, antenna connected. Integration v2.0.0b13, OWNd 2.0.0b8.

Works:

  • *16*6001*101## → next station (sent via the Bus Monitor card "Transmit frame" and via myhome.send_message). The tuner answers right away:
  *#16*101*6*0*96200##
  *#22*5#2#1*5*1*9620##
  *#16*101*7*0*2##
  *#22*5#2#1*11*1*9620*2##
  *22*2#1*6*2##
  *#16*101*8*75*82*79*78*69*72*73*84##      (RDS "KRONEHIT")
  *#22*5#2#1*10*75*82*79*78*69*72*73*84##
  • *16*6101*101## → previous station (via myhome.send_message), same kind of reply.
  • *#16*101*#7*3## → selects stored station 3 (via myhome.send_message). So the spec form of the station write without the leading zero is correct.

Observations:

  • The same station write with the leading zero (*#16*101*#7*0*3##) is not accepted as a write – the tuner only reports its current state. The write/report asymmetry in WHO_16.pdf is real.
  • Frequency field is kHz (96200 = 96.2 MHz, 88800 = 88.8 MHz, 103500 = 103.5 MHz), matching the spec examples.
  • The station report carries the leading zero (*#16*101*7*0*<N>##), as in the spec.
  • RDS text (dimension 8) arrives without an explicit request after a station change. A second RDS frame with different content follows shortly after (e.g. *#16*101*8*107*114*111*110*101*104*105*116##).
  • Every WHO=16 event is mirrored as WHO=22 with source 5#2#1.
  • The wall control's "next station" button puts *22*9*5#3#2#1## on the bus. Sending that same frame from the gateway is ACKed (*#*1##) but has no effect.
  • Although this is an F500N (15 memories), 5 stored stations are in use here; earlier wall-control captures show wrap-around 5 → 1.

Not tested yet: frequency write (*#16*101*#6*…) and seek (WHAT 5000 / 5100).

Happy to capture more if useful.

A matrix input can hold a tuner such as the F500 rather than a line
interface. This adds a radio entity for those inputs: stations as the source
list, next and previous station as track controls, the frequency and the
stored station as attributes, and the RDS text as the media title.

Which inputs are tuners is declared in the options rather than detected. A
source device that has not spoken is indistinguishable on the bus from one
that is not there, and a tuner in standby says nothing at all, so detection
would mean creating entities on a guess and removing them again.

Source frames carry no zone address, so they were dropped before reaching any
entity. The platform now routes them by source key, which leaves the existing
zone discovery untouched.

The entity asks its tuner to start reporting RDS when it is added (WHAT 101),
because a tuner does not broadcast station text unless asked. Frequencies are
written as six digits in kHz and refused outside the FM band; the
specification calls the field Hz while every example in the same document uses
kHz, and the examples are what hardware follows. The station write carries its
parameter directly while the report prefixes it with a zero: that asymmetry is
in the specification and is preserved rather than normalised.

Scope, stated in the module and the documentation: only the RDS report shape
is confirmed against real hardware, by a capture contributed on OpenWebNet-HA#422. Every
other frame comes from WHO_16.pdf v1.0.1 and has not been exercised against a
tuner with an antenna connected.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
@GreenGrassBlueOcean

GreenGrassBlueOcean commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor Author

@manfredgittmaier-afk — this is tremendous! Thank you so much for testing this against live hardware with an antenna connected.

Your capture settles three long-standing questions:

  1. Station write asymmetry: *#16*101*#7*<N>## (without the leading zero) is the true write syntax, while with zero *#16*101*#7*0*<N>## is rejected as a write. This PR was already implemented following that exact write form without the zero, so your hardware test validates our implementation!
  2. Frequency unit: Confirmed in kHz (e.g. 96200 = 96.2 MHz), validating our parser and attribute calculations.
  3. Autonomous RDS: Dimension 8 frames arrive spontaneously after tuning events without needing an explicit poll, which means we can avoid unnecessary polling loops.
  4. WHO=22 Mirroring: Compound address 5#2#1 confirms our routing expectations.

I have just rebased this PR cleanly onto v2-phase1-architecture with all tests passing.


The Final Captures to Settle PR #427 and OWNd #63 📻

Since your plant is running v2.0.0b13 (where the automated sweep_bus skips WHO 16 on the MH200N profile), testing these frames manually via the Bus Monitor card ("Transmit frame") or myhome.send_message would allow us to finalize the tuner implementation here and permanently verify the MH200N audio profile in OWNd:

1. Direct Frequency Tuning Syntax (Dimension 6)

In the current branch, we send frequency writes with a 0* prefix (*#16*101*#6*0*<kHz>##), but given that Dimension 7 rejected the leading zero, we need to verify if Dimension 6 behaves the same way.

Could you test sending both of these manually (e.g. to 96.2 MHz or any strong local station):

  • Option A (with zero): *#16*101*#6*0*96200##
  • Option B (without zero): *#16*101*#6*96200##

Which one does the tuner ACK (*#*1##) and actually tune to?

2. Frequency Seek

Could you test the hardware seek commands manually:

  • Seek Up: *16*5000*101##
  • Seek Down: *16*5100*101##

Does the tuner seek and report the new frequency (DIM 6), station (DIM 7), and RDS (DIM 8) once locked?

3. F500N Memory Presets (1–15)

The basic F500 stores 5 presets, while the F500N datasheet specifies 15.

  • If you select a higher station manually, e.g. *#16*101*#7*6## (or up to 15), does the tuner accept it or return NACK (*#*0##)?

4. Manual Collective Audio Status Query (*#16*0*5##)

Could you test transmitting this single frame manually:

  • *#16*0*5##

Does your MH200N answer with the tuner/amplifier status frames (*16*...##)?
Because v2.0.0b13's automated sweep_bus skips WHO 16 on the MH200N profile, sending this frame manually via the Transmit tool will capture the exact on-wire evidence needed to permanently enable supports_audio on the MH200NProfile in the underlying OWNd library (OpenWebNet-HA/OWNd#63)!

5. Bus Monitor Trace Export (.json)

After transmitting those frames, please click "Export Trace" (or "Copy Trace") on the <myhome-bus-card> and drag/drop or paste the .json file here.

Having authentic on-wire frames from an F500N on an MH200N allows us to add a golden trace replay test directly into tests/fixtures/traces/, cementing permanent CI test coverage for the F500/F500N tuner and the MH200N audio profile! 🚀

…dware traces (OpenWebNet-HA#427)

- Support dynamic preset expansion up to 15 stations for F500N tuners
- Replay authentic hardware traces captured on MH200N + F500N with antenna
- Update sound_source and media_player documentation with tested hardware boundaries
- Maintain 100.0% statement coverage on sound_source.py
@manfredgittmaier-afk

Copy link
Copy Markdown

@GreenGrassBlueOcean – here are the remaining captures from the F500N (MH200N gateway, antenna connected). All frames sent via myhome.send_message; both Bus Monitor traces are attached below.

1. Frequency write (dimension 6)

  • *#16*101*#6*0*96200## (with zero) → works, tunes to 96.2 MHz. Reply: *#16*101*6*0*96200##, then RDS.
  • *#16*101*#6*96200## (without zero) → no effect, no reply at all.

So dimension 6 is the opposite of dimension 7: the frequency write needs the leading zero – your current branch is right.

2. Seek

  • *16*5000*101## → works. From 88.8 it locked on 89.5 MHz (*#16*101*6*0*89500##).
  • *16*5100*101## → works. From 89.5 back down to 88.8 MHz.

3. Presets above 5

  • *#16*101*#7*6## and *#16*101*#7*15## → no audible change and no reply on the bus (no status report, no NACK).
  • Caveat: only 5 stations are stored in this tuner, so this doesn't tell whether memories 6–15 are rejected or just empty.

Further observations from the traces

  • Dimension 7 is only reported when the tuned frequency matches a stored station, and not always then. Seek down to 88.8 MHz reported *7*0*1. Seek up to 89.5 MHz (not stored) reported no dim 7. The direct frequency write to 96.2 MHz reported no dim 7 either, although 96.2 is stored on memory 2. The station attribute should therefore not be expected after every tune.
  • RDS goes blank first: after a frequency change the first dim 8 frame is eight spaces (*8*32*32*32*32*32*32*32*32##); the real text follows 1–8 s later.
  • RDS can be stale for a moment: after seek down, the first dim 8 still carried the previous station's text ("Bayern 2") before switching to the new one ~2 s later.
  • RDS text changes on its own: Ö3 sends "HITRADIO" and ~2 s later " OE 3 ", so dim 8 should always overwrite the title.
  • Every WHO=16 event is mirrored as WHO=22 on source 5#2#1; a preset change also sends *#22*2#1*6*<N>##.

Summary of the write syntax on real hardware (F500N)

Function Frame Result
Next / previous station *16*6001*101## / *16*6101*101## ✅
Select stored station *#16*101*#7*<N>## (no zero) ✅
Select stored station *#16*101*#7*0*<N>## (with zero) ❌ read as status query
Tune frequency *#16*101*#6*0*<kHz>## (with zero) ✅
Tune frequency *#16*101*#6*<kHz>## (no zero) ❌ ignored, no reply
Seek up / down *16*5000*101## / *16*5100*101## ✅
Memory 6 / 15 (only 5 stored) *#16*101*#7*6## ignored, no reply

Happy to test the branch itself once it's out of draft.

MyHOME Diagnostic Bundle

Environment:

  • Home Assistant Version: 2026.9.3
  • Integration Version: 2.0.0b13
  • OWNd Protocol Engine: 2.0.0b8
  • Timestamp: 2026-09-26T16:40:49.412Z

Active Gateway Configuration:

  • Model: MH200N (BTicino S.p.A.)
  • Firmware: 1.0
  • Connection: Ethernet TCP
  • MAC Prefix: 00:03:50
  • Queue Pacing: 0.15s
  • Worker Count: 1
  • Connection Status: Connected

Buffer Telemetry:

  • Total RX Frames: 44
  • Total TX Frames: 4
  • Total Captured: 48
  • Buffer Depth: 48 / 200
  • Capture Kind: Passive trace
  • Gateway Queue Depth: 0
  • Active Card Filter: None (All frames)
OpenWebNet Bus Trace

[18:39:53.753] [RX] #4100198##
[18:39:53.881] [RX] #411402003##
[18:39:54.273] [RX] #42
00215##
[18:39:54.446] [RX] #42
1402003##
[18:39:54.803] [RX] #4300217##
[18:39:54.939] [RX] #431402003##
[18:39:55.175] [TX] #16101
#71##
[18:39:55.312] [RX] #16101
6088800##
[18:39:55.323] [RX] #225#2#1518880##
[18:39:55.335] [RX] #16101
701##
[18:39:55.340] [RX] #225#2#111188801##
[18:39:55.373] [RX] #4400221##
[18:39:55.404] [RX] #222#161##
[18:39:55.559] [RX] #1610187273848265687379##
[18:39:55.563] [RX] #225#2#1
107273848265687379##
[18:39:55.871] [RX] #441402203##
[18:39:55.936] [RX] #45
00225##
[18:39:56.121] [RX] #45
1401803##
[18:39:56.376] [RX] #4600233##
[18:39:56.518] [RX] #461401803##
[18:39:56.646] [RX] #41#1
201##
[18:39:56.903] [RX] #47
00231##
[18:39:57.004] [RX] #13**2208
45030010420042000##
[18:39:57.059] [RX] #471401803##
[18:39:58.903] [RX] #43#1
200##
[18:40:03.363] [RX] #47#1
200##
[18:40:03.643] [RX] #45#1
200##
[18:40:05.364] [RX] #42#1
200##
[18:40:06.074] [RX] #46#1
200##
[18:40:09.390] [TX] #16101
#6096200##
[18:40:09.571] [RX] #161016096200##
[18:40:09.582] [RX] #225#2#1
519620##
[18:40:09.747] [RX] #1610183232323232323232##
[18:40:09.752] [RX] #225#2#1
103232323232323232##
[18:40:10.781] [RX] #161018107114111110101104105116##
[18:40:10.793] [RX] #225#2#1
10107114111110101104105116##
[18:40:14.452] [TX] #16101*#71##
[18:40:14.572] [RX] #16101
6088800##
[18:40:14.582] [RX] #225#2#1518880##
[18:40:14.594] [RX] #16101
701##
[18:40:14.624] [RX] #225#2#111188801##
[18:40:14.629] [RX] #222#161##
[18:40:14.767] [RX] #1610187273848265687379##
[18:40:14.772] [RX] #225#2#1
107273848265687379##
[18:40:16.968] [RX] #1610183232796932513232##
[18:40:16.973] [RX] #225#2#1
103232796932513232##
[18:40:20.857] [TX] #16101*#696200##
[18:40:29.979] [RX] #13**2208
45330010420042000##

MyHOME Diagnostic Bundle

Environment:

  • Home Assistant Version: 2026.9.3
  • Integration Version: 2.0.0b13
  • OWNd Protocol Engine: 2.0.0b8
  • Timestamp: 2026-09-26T16:43:16.646Z

Active Gateway Configuration:

  • Model: MH200N (BTicino S.p.A.)
  • Firmware: 1.0
  • Connection: Ethernet TCP
  • MAC Prefix: 00:03:50
  • Queue Pacing: 0.15s
  • Worker Count: 1
  • Connection Status: Connected

Buffer Telemetry:

  • Total RX Frames: 24
  • Total TX Frames: 4
  • Total Captured: 28
  • Buffer Depth: 28 / 200
  • Capture Kind: Passive trace
  • Gateway Queue Depth: 0
  • Active Card Filter: None (All frames)
OpenWebNet Bus Trace

[18:42:45.795] [TX] #16101*#71##
[18:42:45.920] [RX] #16101
6088800##
[18:42:45.929] [RX] #225#2#1518880##
[18:42:45.940] [RX] #16101
701##
[18:42:45.961] [RX] #225#2#111188801##
[18:42:45.977] [RX] #222#161##
[18:42:46.115] [RX] #1610187273848265687379##
[18:42:46.120] [RX] #225#2#1
107273848265687379##
[18:42:48.044] [RX] #1610183232796932513232##
[18:42:48.049] [RX] #225#2#1
103232796932513232##
[18:42:53.004] [TX] 165000101##
[18:42:53.304] [RX] #16101
6089500##
[18:42:53.309] [RX] #225#2#1518950##
[18:42:53.459] [RX] #16101
83232323232323232##
[18:42:53.469] [RX] #225#2#1103232323232323232##
[18:42:57.165] [RX] #13**2208
48030010420042000##
[18:43:01.752] [RX] #16101866971211011141103250##
[18:43:01.753] [RX] #225#2#1
1066971211011141103250##
[18:43:01.868] [TX] 165100101##
[18:43:02.174] [RX] #16101
6088800##
[18:43:02.178] [RX] #225#2#1518880##
[18:43:02.191] [RX] #16101
701##
[18:43:02.197] [RX] #225#2#111188801##
[18:43:02.325] [RX] #16101866971211011141103250##
[18:43:02.330] [RX] #225#2#1
1066971211011141103250##
[18:43:04.059] [RX] #1610183232796932513232##
[18:43:04.071] [RX] #225#2#1
103232796932513232##
[18:43:09.550] [TX] #16101*#7*6##

…k on live bus (OpenWebNet-HA#427)

- Hardware-verify direct frequency write syntax (*OpenWebNet-HA#16*10S*OpenWebNet-HA#6*0*<KHZ>##; leading zero required)

- Add async_seek_up and async_seek_down hardware seek commands (*16*5000*10S## / *16*5100*10S##)

- Clear stored station preset and source when tuning frequency or when unstored frequency is reported

- Add authentic MH200N + F500N seek & frequency tuning trace fixture and replay tests

- Update media_player documentation to mark tuner controls fully hardware-verified
@GreenGrassBlueOcean
GreenGrassBlueOcean marked this pull request as ready for review September 26, 2026 17:00
@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

@manfredgittmaier-afk — this is fantastic news! Thank you so much for capturing and documenting these remaining behaviors.

Your findings close the loop on every single outstanding question for the tuner implementation:

  1. Dimension 6 Frequency Write: Verified on live hardware that *#16*101*#6*0*<kHz>## (with the leading zero) tunes to the desired frequency (e.g. 96.2 MHz), while without zero it is ignored. This confirms the write syntax in this branch is 100% correct!
  2. Hardware Seek Up / Down: Verified that *16*5000*101## and *16*5100*101## trigger hardware seek and lock onto the next/previous station. We have also exposed direct async_seek_up() and async_seek_down() methods on the entity.
  3. Station Preset Behavior: Verified that Dimension 7 is only reported when the tuned frequency matches a stored preset. We have now updated the entity to cleanly reset the preset attribute/source when manually tuning or seeking to an unstored frequency.
  4. RDS Blanking: Verified that the tuner emits a blanking frame (8*32*...##) upon tuning before the dynamic station title arrives.

PR #427 is Now Out of Draft! 🚀

I have just pushed the updates to feat/f500-tuner, incorporated your latest seek and frequency frames into our trace fixtures and replay tests, and marked this pull request as Ready for review.

You are warmly invited to test this branch on your system whenever convenient!


The Last Piece for OWNd PR #63 (MH200NProfile) 📻

As you may have seen in OWNd PR #63, enabling native WHO 16 sound support in the underlying daemon for the MH200NProfile was held on a specific condition: waiting for live verification of whether the MH200N responds to the general collective audio status query:

  • *#16*0*5##

If you have a quick moment, could you transmit *#16*0*5## (via the Bus Monitor card "Transmit frame" or myhome.send_message) and report what the bus monitor shows?

If the MH200N answers with the status frames for your tuner/amplifiers, that single capture will settle OpenWebNet-HA/OWNd#63 and permanently enable the WHO 16 audio profile for all MH200N users!

@manfredgittmaier-afk

Copy link
Copy Markdown

@GreenGrassBlueOcean – great, thanks for getting #427 out of draft!

Here's the capture for OWNd#63: the MH200N answers *#16*0*5## (sent via myhome.send_message, no NACK). Two runs, full traces below.

Run 1 – all amplifiers off, tuner on (reply within ~1.2 s):

Frame Meaning
*16*3*101## source 101 (F500N tuner) ON
*16*13*41## amplifier 41 OFF
*16*13*21## amplifier 21 OFF
*16*13*11## amplifier 11 OFF
*16*13*31## amplifier 31 OFF

Run 2 – amplifier 41 playing the tuner:

Frame Meaning
*16*3*101## source 101 ON
*16*3*41## amplifier 41 ON
*16*13*31## / *16*13*11## / *16*13*21## amplifiers 31, 11, 21 OFF
*#22*3#4#1*1## WHO=22 only, right after the amplifier list
*#16*41*1*10## ~4 s later: volume of amplifier 41 (dimension 1 = 10)

Every WHO=16 frame is followed by its WHO=22 mirror (*#22*2#1*12*1*4## for the source, *#22*3#<A>#1*12*<1|0>*<4|10>## for the amplifiers – 1*4 when on, 0*10 when off). The order of the amplifiers in the reply is not fixed (41, 21, 11, 31 in run 1; 41, 31, 11, 21 in run 2).

So the MH200N supports the general sound status query and lists every source and amplifier with its on/off state.

MyHOME Diagnostic Bundle

Environment:

  • Home Assistant Version: 2026.9.3
  • Integration Version: 2.0.0b13
  • OWNd Protocol Engine: 2.0.0b8
  • Timestamp: 2026-09-26T17:05:12.574Z

Active Gateway Configuration:

  • Model: MH200N (BTicino S.p.A.)
  • Firmware: 1.0
  • Connection: Ethernet TCP
  • MAC Prefix: 00:03:50
  • Queue Pacing: 0.15s
  • Worker Count: 1
  • Connection Status: Connected

Buffer Telemetry:

  • Total RX Frames: 10
  • Total TX Frames: 1
  • Total Captured: 11
  • Buffer Depth: 11 / 200
  • Capture Kind: Passive trace
  • Gateway Queue Depth: 0
  • Active Card Filter: None (All frames)
OpenWebNet Bus Trace

[19:05:08.141] [TX] #1605##
[19:05:08.272] [RX] 163
101##
[19:05:08.277] [RX] #222#11214##
[19:05:08.964] [RX] 1613
41##
[19:05:08.968] [RX] #223#4#112010##
[19:05:09.074] [RX] 1613
21##
[19:05:09.079] [RX] #223#2#112010##
[19:05:09.213] [RX] 1613
11##
[19:05:09.217] [RX] #223#1#112010##
[19:05:09.313] [RX] 1613
31##
[19:05:09.318] [RX] #223#3#1120*10##

MyHOME Diagnostic Bundle

Environment:

  • Home Assistant Version: 2026.9.3
  • Integration Version: 2.0.0b13
  • OWNd Protocol Engine: 2.0.0b8
  • Timestamp: 2026-09-26T17:07:25.832Z

Active Gateway Configuration:

  • Model: MH200N (BTicino S.p.A.)
  • Firmware: 1.0
  • Connection: Ethernet TCP
  • MAC Prefix: 00:03:50
  • Queue Pacing: 0.15s
  • Worker Count: 1
  • Connection Status: Connected

Buffer Telemetry:

  • Total RX Frames: 13
  • Total TX Frames: 1
  • Total Captured: 14
  • Buffer Depth: 14 / 200
  • Capture Kind: Passive trace
  • Gateway Queue Depth: 0
  • Active Card Filter: None (All frames)
OpenWebNet Bus Trace

[19:07:21.720] [TX] #1605##
[19:07:21.849] [RX] 163
101##
[19:07:21.853] [RX] #222#11214##
[19:07:22.698] [RX] 163
41##
[19:07:22.710] [RX] #223#4#11214##
[19:07:22.714] [RX] 1613
31##
[19:07:22.718] [RX] #223#3#112010##
[19:07:22.778] [RX] 1613
11##
[19:07:22.782] [RX] #223#1#112010##
[19:07:22.819] [RX] 1613
21##
[19:07:22.824] [RX] #223#2#112010##
[19:07:22.908] [RX] #223#4#1
1##
[19:07:26.589] [RX] #1641110##
[19:07:26.591] [RX] #223#4#1110##

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

@manfredgittmaier-afk Fantastic capture! This is a massive help — your *#16*0*5## trace was the exact missing piece of hardware evidence needed to settle WHO 16 support for the MH200N on OWNd!

I've already updated OWNd PR #63 with supports_audio = True and cited your traces, so this unblocks automated bus sweep discovery for the entire sound subsystem on MH200N gateways in the upcoming library release.

Regarding #427:
Just to clarify, PR #427 is actually still in Draft! What you noticed was a rebase and push where we:

  • Ingested your earlier F500N trace into our golden test corpus (tests/fixtures/traces/f500_tuner/).
  • Added dynamic preset expansion up to 15 stations for the F500N (while keeping 1–5 as the default for basic F500 units).
  • Achieved 100% test coverage with full authentic frame replay.

The PR is intentionally remaining in draft until we can confirm the last few open specification ambiguities on physical hardware (whenever you have a spare moment, no rush at all!):

  1. Direct Frequency Write Syntax:
    • *#16*101*#6*0*96200## (with leading zero) vs *#16*101*#6*96200## (without leading zero).
    • Question: Does sending either one tune the radio to 96.2 MHz, or does one of them return NACK (*#*0##)?
  2. Hardware Seek Commands:
    • Seek up: *16*5000*101##
    • Seek down: *16*5100*101##
    • Question: Does the tuner initiate automatic station seeking when these frames are sent?
  3. Extended Presets (Stations 6–15):
    • Selecting a preset above 5: *#16*101*#7*6##
    • Question: Does the tuner accept station 6 (or does it only accept 1–5)?

Thank you again for all the incredible testing and traces!

@manfredgittmaier-afk

Copy link
Copy Markdown

@GreenGrassBlueOcean – thanks, great to hear OWNd#63 is unblocked!

The three open points for #427 are already answered in my earlier comment (with both traces attached), here's the short version from the F500N on the MH200N:

  1. Frequency write: *#16*101*#6*0*96200## (with zero) tunes to 96.2 MHz. *#16*101*#6*96200## (without zero) is silently ignored – no tuning, no reply, no NACK.
  2. Seek: *16*5000*101## and *16*5100*101## both work – the tuner seeks and locks on the next/previous station, then reports dim 6 (and dim 7 only if the frequency is a stored preset), followed by RDS.
  3. Preset 6: *#16*101*#7*6## (and …#7*15##) → no reaction and no reply on the bus. Caveat: only 5 stations are stored in this tuner, so this can't tell whether 6–15 are unsupported or just empty.

Happy to run anything else you need.

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

@manfredgittmaier-afk — thank you so much! Your three answers and the *#16*0*5## trace closed the loop on every single open question for both PR #427 and OWNd #63!

PR #427 is now officially out of draft and ready for review, with all your on-wire captures committed to our test corpus. You are warmly invited to test this branch directly on your system whenever convenient!


Other MH200N Hardware Blind Spots 📡

Since you offered to run anything else we might need, we maintain a community Hardware Trace Availability Matrix (#466) to verify which subsystems work across each gateway model.

If you have a couple of minutes and want to help us check off the last few blanks for the MH200N, here are a few quick checks:

1. Burglar Alarm (WHO 5)

Do you happen to have a BTicino / Legrand burglar alarm system on this bus (e.g. 3485 / 3486 central unit or keypads)?

  • If yes (or to test if one responds), transmitting:
    *#5*0## (central status) or *#5*#1## (partition 1)
  • If an alarm is present, any capture of arming/disarming or a zone opening would be incredible. (If no alarm is installed, *#5*0## will just return NACK, which is also helpful confirmation!)

2. Gateway Diagnostic Object Model (WHO 1013)

We use WHO 1013 to probe gateway hardware object models. Does your MH200N respond to:

  • *#1013*0*1##

3. CEN+ Scenario Buttons (WHO 25) or Auxiliary (WHO 9)

  • Do you have any CEN+ scenario pushbuttons (e.g., 4-button scenario controls) or auxiliary channels (*#9*0##) on your installation? If so, any trace of a button press or query would fill those remaining columns!

No pressure at all on any of these — your audio and tuner captures already solved our biggest blind spot! 🚀

@manfredgittmaier-afk

Copy link
Copy Markdown

@GreenGrassBlueOcean – posted the MH200N captures (WHO 5 / 1013 / 9 / 25 + trace) in #466.

@GreenGrassBlueOcean
GreenGrassBlueOcean merged commit a0dc4a4 into OpenWebNet-HA:v2-phase1-architecture Sep 26, 2026
16 checks passed
@manfredgittmaier-afk

Copy link
Copy Markdown

@GreenGrassBlueOcean – installed the merged branch (MH200N + F500N, OWNd 2.0.0b8). The radio entity sends correctly: station select and next/previous tune the tuner.

But tuner reports never reach the entity. Trace attached – at 22:00:15 media_player.media_next_track on the entity:

TX 166001101##
RX #1610160103500##
RX #16101704##
RX #1610186578846978786932## ("ANTENNE ")

Afterwards the entity still shows station: 2 / source: Station 2 (set locally by an earlier select_source), and frequency and media_title never appear at all. On/off (*16*3*101##) does reach it. So dimension frames 6/7/8 of source 101 seem to be dropped before handle_event – maybe OWNd 2.0.0b8 doesn't parse *#16*10S*<dim>*…## as an OWNSoundEvent with a dimension, or the route key for these frames doesn't match 101#16.

Two more things from the same trace:

  • Some stations rotate their RDS text every ~5 s (Radio OÖ: *RADIO* / **OOE** ), and occasionally a malformed dim 8 frame with 12 codes instead of 8 arrives, e.g. *#16*101*8*42*42*79*79*69*42*79*79*69*42*42*32##.
  • The entity's source should probably follow the reported station after next/previous, not only after select_source.
    myhome_trace_MH200N_all_2026-09-26T20-00-28.json

@GreenGrassBlueOcean

GreenGrassBlueOcean commented Sep 26, 2026 •

Copy link
Copy Markdown
Contributor Author

i will check this tomorrow @manfredgittmaier-afk

@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

@manfredgittmaier-afk — thank you so much for the detailed bug report and trace capture!

We diagnosed the exact issue and resolved it in PR #497:

  1. OWNSoundEvent dimension values attribute: In OWNd.message.OWNSoundEvent, incoming dimension values are stored in _dimension_value rather than a public dimension_value attribute. Unit tests previously mocked dimension_value=..., masking the mismatch on real hardware. handle_event now extracts dimension values falling back across dimension_value, _dimension_value, and dimension_values.
  2. Preset & source tracking: With dimension values properly decoded, incoming Dimension 7 reports (*#16*101*7*0*<station>##) and Dimension 6 reports (*#16*101*6*0*<kHz>##) immediately update frequency, station, and source (Station N) when advancing stations (media_next_track / media_previous_track) or tuning.
  3. Malformed 12-code RDS frames: When stations rotate RDS Program Service names every ~5s (such as Radio OÖ: *RADIO* / **OOE** ), rare hardware buffer glitches occasionally emitted malformed 12-code frames (e.g. *#16*101*8*42*42*79*79*69*42*79*79*69*42*42*32##). Dimension 8 handling now strictly validates len(values) == 8, gracefully ignoring corrupted frames without altering or blanking the current station title.
  4. Frequency state hygiene: Switching frequencies directly or via seek now clears media_title so stale titles from prior stations are not retained while waiting for new RDS text.

Your full live trace capture (myhome_trace_MH200N_all_2026-09-26T20-00-28.json) has been added to our authentic test fixtures under tests/fixtures/traces/f500_tuner/ with complete replay coverage!

GreenGrassBlueOcean added a commit to GreenGrassBlueOcean/MyHOME that referenced this pull request Sep 27, 2026
…formed RDS frames

Resolves issue reported in PR OpenWebNet-HA#427 comment 5849429368:
- Read dimension values from _dimension_value on OWNSoundEvent instances when dimension_value property is not present
- Ensure tuner frequency, station, source and media_title update from incoming bus reports
- Gracefully ignore malformed RDS frames with length != 8 (e.g. transient 12-code glitch frames)
- Clear media_title on frequency change
- Add authentic trace capture and replay test covering rotating RDS, 12-code glitch, and preset advance sequence
@GreenGrassBlueOcean

Copy link
Copy Markdown
Contributor Author

@manfredgittmaier-afk Thank you so much for capturing and sharing this detailed trace!

Diagnostics & Root Cause

  1. Dimension reports dropping:
    In OWNd 2.0.0b8, OWNSoundEvent stores parsed dimension payload items in _dimension_value rather than a public dimension_value attribute. handle_event evaluated getattr(message, "dimension_value", None) or [], which returned an empty list on real OWNSoundEvent instances. Because values was empty, all dimension 6 (frequency), 7 (station preset), and 8 (RDS title) reports were silently skipped. Unit tests had set dimension_value on mocks, masking the attribute name difference.
  2. Transient 12-code RDS frames:
    During fast RDS rotations (such as Radio OÖ alternating every ~5 s), occasional hardware buffer glitches emit malformed 12-code dimension 8 frames (*#16*101*8*42*42*79*79*69*42*79*79*69*42*42*32##). We now validate that dimension 8 has strictly 8 codes, and ignore malformed frames so existing media titles are preserved without corruption or flicker.
  3. Preset source tracking:
    _set_station_from_bus already updates _attr_source = f"Station {station}", which now executes cleanly when the bus confirms preset changes after media_next_track / media_previous_track.

We have tracked this in #498 and opened PR #497 with the fix. Your full 200-frame trace myhome_trace_MH200N_all_2026-09-26T20-00-28.json has been added as a regression fixture in CI (test_f500n_station_advance_and_rds_rotation_replay).

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