You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Those are the numbers the CPS will accept. They are not the numbers that produce a working codeplug. Every DM-32UV constraint that has actually cost us debugging time
is currently unmodeled, so a profile can validate clean and still generate a plug that
is silently broken on hardware.
This issue proposes extending the capabilities file (and the validator that reads it)
with the constraints below. All of them are field-verified or read directly out of
NeonPlug's DM-32UV codec — sources cited per item.
Proposed constraint block
Illustrative shape, not a final schema — naming/nesting is up for discussion:
1. max_scanlist_members: 15 — highest value, hardest won
The on-radio scanlist record stores its channel list at +0x1A, and the next field
begins at +0x38. That is 30 bytes = 15 x uint16. NeonPlug's own codec agrees:
its comment reads "30 bytes, uint16 array LE, 0x0000 terminated, max 15 channels", channelCount at +0x0B is documented 0-15, and both encoder and decoder hard-loop i < 15.
Worth stating explicitly because it is a real point of confusion: the OEM CPS shows
16 entries. It adds a "Current Channel" entry at index 0 on top of your 15 defined
members. So a "16" in vendor-facing documentation is not wrong, it is describing the
UI. Storage is 15. A generator must target 15.
Failure mode when exceeded: not a rejected import. On a real plug, an oversized list
made every scanlist unselectable on the radio, not just the offending one.
2. The full-15 edge case (document, do not silently rely on)
NeonPlug clamps writes to Math.min(len, 15), but only emits the 0x0000 terminator if (channelCount < 15). At exactly 15 the 30-byte field is full and no
terminator is written, so the radio's parse can run past the end of the record into
the adjacent field.
This is the suspected mechanism behind the all-scanlists-unselectable failure above.
It is not yet verified on hardware. Recommendation: treat 15 as the hard cap for
validation, but consider a warning at exactly 15, and note in radios/README that
dropping the largest list to 14 is the first diagnostic if scan selection misbehaves.
3. channelCount must equal the exact member count — never +1
A separate, already-fixed defect in our own generator: writing channelCount = len + 1
(a "1-based" misreading) causes the radio to read one entry past a correctly sized
array. Same class of bug as #2, different cause. Any exporter writing this field needs
a test asserting channelCount == len(members).
4. Name length limits
Channel name: 16 chars
Zone name: 16 chars
Scanlist name: 10 chars — NeonPlug encodes it as 11 bytes null-terminated, name.slice(0, 10), so an 11th character is silently truncated, not rejected.
Contact name: 16 chars
The scanlist case matters most for a generator: truncation is silent, and two
scanlists whose names differ only after character 10 collide into the same on-radio
name. Since profiles reference things by stable ID this will not corrupt selection,
but it will produce a confusing radio UI.
Suggested validator behavior
profile.py already reads limits and raises ProfileValidationError for zones and
channels. The same pattern extends naturally:
Hard error: any constraint exceeded (> max_scanlist_members, name over limit).
Scanlists are exactly where the DM32 pain was concentrated, so it is worth having
these constraints written down before0.2 designs around them, rather than
rediscovering them on hardware.
Happy to open a PR with the capabilities block and validator changes if the shape
above looks right.
Summary
radios/baofeng_dm32/capabilities.jsoncurrently models the CPS ceiling, not the radio:Those are the numbers the CPS will accept. They are not the numbers that produce a
working codeplug. Every DM-32UV constraint that has actually cost us debugging time
is currently unmodeled, so a profile can validate clean and still generate a plug that
is silently broken on hardware.
This issue proposes extending the capabilities file (and the validator that reads it)
with the constraints below. All of them are field-verified or read directly out of
NeonPlug's DM-32UV codec — sources cited per item.
Proposed constraint block
Illustrative shape, not a final schema — naming/nesting is up for discussion:
{ "id": "baofeng_dm32", "name": "Baofeng DM-32", "limits": { "max_channels": 4000, "max_zones": 250, "max_channels_per_zone": 250, "max_scanlists": 250, "max_scanlist_members": 15, "max_zone_name_chars": 16, "max_channel_name_chars": 16, "max_scanlist_name_chars": 10, "max_contact_name_chars": 16 } }The constraints, and why each one matters
1.
max_scanlist_members: 15— highest value, hardest wonThe on-radio scanlist record stores its channel list at
+0x1A, and the next fieldbegins at
+0x38. That is 30 bytes = 15 x uint16. NeonPlug's own codec agrees:its comment reads "30 bytes, uint16 array LE, 0x0000 terminated, max 15 channels",
channelCountat+0x0Bis documented0-15, and both encoder and decoder hard-loopi < 15.Source:
src/radios/dm32uv/structures.tsWorth stating explicitly because it is a real point of confusion: the OEM CPS shows
16 entries. It adds a "Current Channel" entry at index 0 on top of your 15 defined
members. So a "16" in vendor-facing documentation is not wrong, it is describing the
UI. Storage is 15. A generator must target 15.
Failure mode when exceeded: not a rejected import. On a real plug, an oversized list
made every scanlist unselectable on the radio, not just the offending one.
2. The full-15 edge case (document, do not silently rely on)
NeonPlug clamps writes to
Math.min(len, 15), but only emits the0x0000terminatorif (channelCount < 15). At exactly 15 the 30-byte field is full and noterminator is written, so the radio's parse can run past the end of the record into
the adjacent field.
This is the suspected mechanism behind the all-scanlists-unselectable failure above.
It is not yet verified on hardware. Recommendation: treat 15 as the hard cap for
validation, but consider a warning at exactly 15, and note in
radios/READMEthatdropping the largest list to 14 is the first diagnostic if scan selection misbehaves.
3.
channelCountmust equal the exact member count — never+1A separate, already-fixed defect in our own generator: writing
channelCount = len + 1(a "1-based" misreading) causes the radio to read one entry past a correctly sized
array. Same class of bug as #2, different cause. Any exporter writing this field needs
a test asserting
channelCount == len(members).4. Name length limits
name.slice(0, 10), so an 11th character is silently truncated, not rejected.The scanlist case matters most for a generator: truncation is silent, and two
scanlists whose names differ only after character 10 collide into the same on-radio
name. Since profiles reference things by stable ID this will not corrupt selection,
but it will produce a confusing radio UI.
Suggested validator behavior
profile.pyalready readslimitsand raisesProfileValidationErrorfor zones andchannels. The same pattern extends naturally:
> max_scanlist_members, name over limit).value, the limit, and the offending entity name.
Scope note
Profile
0.1deliberately excludes scanlists and contacts, so items #1-#3 are notenforceable end-to-end yet. Filing now anyway because:
0.1today — channels and zones are alreadygenerated, and
max_*_name_charsis enforceable immediately.these constraints written down before
0.2designs around them, rather thanrediscovering them on hardware.
Happy to open a PR with the capabilities block and validator changes if the shape
above looks right.