Summary
Radios that expose get_sub_devices() — dual-band mobiles and zoned commercial sets — are unsupported end to end. Their images load without error and present an empty channel list, because their channels live in the sub-devices rather than on the parent radio object.
17 of the 357 upstream test images are affected; 15 of those contain real channel data that CHIRP displays and webchirp does not.
| Image |
Sub-devices |
Channels |
Parent get_memory() errors |
| Yaesu_FT-8800 |
Left [1-512], Right [1-512] |
70 |
512 |
| Retevis_RA87 |
Left [0-99], Right [0-99] |
68 |
100 |
| Kenwood_TK-3180K2 |
Zone FRS/GMRS, Zone Channels, Zone GMRS Wide |
65 |
512 |
| Baojie_BJ-9900 |
Left [1-128], Right [1-128] |
50 |
128 |
| Yaesu_FTM-350 |
Left [0-500], Right [0-500] |
34 |
501 |
| Kenwood_TK-880 |
1 [1-250], 2 [1-250] |
31 |
250 |
| LUITON_LT-725UV |
Upper [1-128], Lower [1-128] |
8 |
128 |
| Yaesu_VX-1 |
CG1 [1-52], CG2 [1-142], BC [1-10] |
5 |
260 |
| Kenwood_TK-481 |
1, 2 |
3 |
250 |
| Kenwood_TK-981 |
1, System 2, System 3 |
3 |
250 |
| Kenwood_TK-7160K |
Zone 1 [1-128] |
3 |
128 |
| Yaesu_FT-8100 |
VHF [1-99], UHF [1-99] |
3 |
99 |
| Kenwood_TK-3140K |
Group 1, Group 2 |
2 |
250 |
| Kenwood_TK-280 |
1 [1-250] |
1 |
250 |
| Kenwood_TK-8180 |
Zone 1 [1-250] |
1 |
512 |
| BTECH_UV-50X3 |
Left [0-499], Right [0-499] |
0 (image is blank) |
500 |
| Jetstream_JT270MH |
A Band [1-99], B Band [1-99] |
0 (image is blank) |
0 |
Root cause
_radio_rows_from_instance (web/python/runtime_bridge.py:572) iterates only the parent radio's memory_bounds and calls radio.get_memory(n) on the parent. For these drivers the parent holds no memories, so every call raises — NotImplementedError, or bitwise-struct errors like AttributeError: No attribute system-1 in struct (anonymous).
The failure is invisible because the loop swallows it:
try:
mem = radio.get_memory(number)
except Exception:
continue
512 consecutive exceptions become "no channels", with nothing logged. That also runs against the "preserve debug visibility" rule in CLAUDE.md — full errors should reach the debug panel.
The write path has the same gap: _apply_rows_to_radio_instance (web/python/runtime_bridge.py:591) also walks only the parent, so these radios cannot be written either. get_radio_column_metadata likewise derives editability and bounds from the parent's features.
For reference, upstream handles this by indexing sub-devices explicitly — see SUB_DEVICE in chirp/tests/base.py and the per-sub-device test generation in chirp/tests/__init__.py.
What full support needs
- Read — walk
get_sub_devices() when rf.has_sub_devices, tagging each row with the sub-device it came from.
- Write — route rows back to the right sub-device in
_apply_rows_to_radio_instance.
- Column metadata / settings — resolve features per sub-device rather than from the parent.
- UI — sub-devices need to be selectable or otherwise distinguished. Note their memory numbering overlaps: FT-8800 exposes
Left [1-512] and Right [1-512], so Location alone is no longer a unique row key. CHIRP's desktop UI presents each sub-device as its own tab, which is probably the model to follow.
- Stop swallowing per-memory exceptions — surface them in the debug panel rather than returning an empty list, so a regression like this is visible rather than silent.
Open questions
- One tab per sub-device, or a single table with a sub-device column? The overlapping
Location ranges push toward tabs.
- Does the export/import
.img round trip need to preserve sub-device identity, or is reconstructing it from the driver enough?
Verification
chirp/tests/images/ gives direct coverage: each affected image should load with the channel count in the table above, and a load → export round trip should be byte-identical to the source where the driver allows.
Found while measuring image-load coverage for #28.
Summary
Radios that expose
get_sub_devices()— dual-band mobiles and zoned commercial sets — are unsupported end to end. Their images load without error and present an empty channel list, because their channels live in the sub-devices rather than on the parent radio object.17 of the 357 upstream test images are affected; 15 of those contain real channel data that CHIRP displays and webchirp does not.
get_memory()errorsRoot cause
_radio_rows_from_instance(web/python/runtime_bridge.py:572) iterates only the parent radio'smemory_boundsand callsradio.get_memory(n)on the parent. For these drivers the parent holds no memories, so every call raises —NotImplementedError, or bitwise-struct errors likeAttributeError: No attribute system-1 in struct (anonymous).The failure is invisible because the loop swallows it:
512 consecutive exceptions become "no channels", with nothing logged. That also runs against the "preserve debug visibility" rule in CLAUDE.md — full errors should reach the debug panel.
The write path has the same gap:
_apply_rows_to_radio_instance(web/python/runtime_bridge.py:591) also walks only the parent, so these radios cannot be written either.get_radio_column_metadatalikewise derives editability and bounds from the parent's features.For reference, upstream handles this by indexing sub-devices explicitly — see
SUB_DEVICEinchirp/tests/base.pyand the per-sub-device test generation inchirp/tests/__init__.py.What full support needs
get_sub_devices()whenrf.has_sub_devices, tagging each row with the sub-device it came from._apply_rows_to_radio_instance.Left [1-512]andRight [1-512], soLocationalone is no longer a unique row key. CHIRP's desktop UI presents each sub-device as its own tab, which is probably the model to follow.Open questions
Locationranges push toward tabs..imground trip need to preserve sub-device identity, or is reconstructing it from the driver enough?Verification
chirp/tests/images/gives direct coverage: each affected image should load with the channel count in the table above, and a load → export round trip should be byte-identical to the source where the driver allows.Found while measuring image-load coverage for #28.