Skip to content

qdmr: dual-bank zones, fleet contact dedup, tytExtension passthrough - #45

Merged
emuehlstein merged 5 commits into
mainfrom
add-baofeng-dm1701
Oct 8, 2026
Merged

emuehlstein merged 5 commits into
mainfrom
add-baofeng-dm1701

Conversation

@emuehlstein

Copy link
Copy Markdown
Contributor

Summary

Two fixes for the Baofeng DM-1701 / Retevis RT84 qdmr export path, both
hardware-verified on a Baofeng DM-1701 running OEM firmware (2026-10-08).


1 — Dual-bank zone export (zone_banks capability)

Root cause. qdmr models a zone as two channel banks, A and B, each
bound to one VFO. The exporter hardcoded "B": []. On OpenGD77 targets
that is correct. On TyT OEM firmware it is not: the firmware renders the
second display line from bank B and shows "Unprogram." when it is
empty. This appeared on every channel of a 219-channel codeplug across
all 22 zones.

Fix. A new zone_banks field in the capabilities schema (top level,
additionalProperties: false at both levels so it had to be declared).
Default is 1; set to 2 for baofeng_dm1701. When zone_banks >= 2
the exporter mirrors bank A into bank B, giving both VFOs the same
channels. Every OpenGD77 target remains byte-identical to before.

Verification. Written and read back: 219 ch · 22 zones · no empty B.


2 — Deduplicate fleet contacts across radio instances

Root cause. The fleet contact loop emitted one PrivateCall entry
per radio instance, so an operator with three radios registered to the
same DMR ID got that contact triplicated in the output (e.g. KC9MHE
3207125 appearing three times).

Fix. Deduplicate by (name, number) before appending.


3 — tytExtension root extension passthrough

Adds tytExtension to QDMR_ROOT_EXTENSION_KEYS so profiles can carry
TyT OEM-specific settings (button mappings, menu settings) through the
exporter. Without this the exporter had no way to set side buttons on OEM
firmware radios; they always came out Disabled with longPressDuration 0.


Files changed

file change
src/codeplugger/exporters/qdmr_yaml.py zone_banks param, mirror logic, fleet dedup, tytExtension passthrough
src/codeplugger/profile.py pass zone_banks from capabilities to exporter
schemas/capabilities-0.2.schema.json declare zone_banks (integer, 1–2)
radios/baofeng_dm1701/capabilities.json "zone_banks": 2
tests/test_qdmr_yaml.py test_zone_banks_mirrors_channels_into_bank_b

Test results

281 passed, 1 skipped

Relay added 5 commits October 8, 2026 12:43
Two entries, mirroring the RT3S pattern:
- baofeng_dm1701: OEM firmware, dmrconf radio key dm1701 (Retevis RT84 is the
  same hardware, alias key rt84). Limits read from qdmr v0.15.1
  lib/dm1701_limits.cc: 3000 channels, 250 zones (64 ch/zone per A/B bank),
  250 scan lists of 31 channels, 250 RX group lists of 32 TGs, 10000 contacts,
  single DMR ID, 16-char names, 136-174 / 400-480 MHz.
- baofeng_dm1701_opengd77: OpenGD77 firmware, dmrconf key openuv380. Shares the
  MDUV380/DM1701/RT3S/RT84 target, so limits match retevis_rt3s_opengd77
  (1024 ch, 68 zones, 76 group lists, 1024 contacts, UHF capped at 470 MHz,
  no scan-list entity).

Verified: gen_radio_table.py --check clean, 280 passed / 1 skipped, and a
generated qdmr YAML passes dmrconf verify on both dm1701 and rt84. The same
YAML with a 479 MHz channel is correctly rejected on the OpenGD77 key, which
confirms the deliberate 480 vs 470 UHF split between the two files.

Not bench-verified: no RADIO_NAMES entry for dm1701 yet, because those strings
are captured from real detect runs. Device write stays fail-closed until the
radio is on USB. Site rows carry no hw flag.
Captured from a real 'dmrconf detect' run against Eric's radio, which is
the standing rule for this dict -- the value is never guessed from source.

Detected name is 'Baofeng DM-1701', the qdmr display name (_name in
lib/dm1701.cc), not the short --list-radios label 'DM-1701'. This unblocks
the device write path, which was fail-closed on the missing key.

Detect talks to the TyT DFU interface (USB 0483:df11, descriptor 'Digital
Radio in USB mode'), not a serial port. It prints Found: and exits 0, then
emits three benign teardown errors from lib/dfu_libusb.cc. Reproduced
twice; comment added so nobody reads those as a detect failure.

No RT84 entry: same hardware, but its detected string is unverified here.

280 passed, 1 skipped.
Full round trip done on Eric's radio (eam_dm1701_01) 2026-10-08: detect,
binary read, profile write, read back, decode, diff. Read-back matches the
written codeplug exactly -- 219 channels, 22 zones, 15 contacts, 5 group
lists, channel-name lists identical -- and only 22610 of 844093 bytes differ
from the factory image, so the write was targeted rather than a wholesale
reinit.

Confirms on DM-1701 the DM-32 behaviour already in AGENTS.md: the write
progress bar lies. It sat at [ ] 0% and the wrapper never printed an exit
code, yet the write had fully committed. Judge by read-back diff only.

baofeng_dm1701_opengd77 deliberately NOT badged -- that firmware has never
been flashed on this radio.
qdmr models a zone as two channel banks, A and B, each bound to one VFO.
The exporter hardcoded "B": [], which is right for OpenGD77 but wrong for
TyT OEM firmware: that firmware renders the second display line from bank
B and prints "Unprogram." when it is empty.

Hardware-confirmed on a Baofeng DM-1701 2026-10-08. All 22 zones of a
219-channel codeplug wrote and read back correctly while every channel
showed "Unprogram." on line 2, purely because codeplugger models a zone
as one list and only filled A. Mirroring the list into both banks clears
it and gives the second VFO the same channels.

Driven by a new zone_banks capability rather than applied blanket, so
single-bank radios (every OpenGD77 target) keep B empty and are
byte-identical to before. Declared in the 0.2 schema, which is
additionalProperties:false at both levels, and set to 2 only for
baofeng_dm1701.

Also allow tytExtension as a qdmr root extension. The OEM profile has no
settings block (extensions.qdmr.settings models OpenGD77 fields this
firmware lacks), so side buttons could only come out as codeplugger
defaults, i.e. all Disabled. Profiles can now carry the real mapping
instead of it being hardcoded or lost.
Multiple radio instances owned by the same operator share the same
dmr_id and dmr_contact_name. The fleet loop emitted one identical
PrivateCall entry per instance, triplicating KC9MHE in a three-radio
registry. Deduplicate by (name, number) before appending.
@emuehlstein
emuehlstein merged commit 98df03d into main Oct 8, 2026
2 checks passed
@emuehlstein
emuehlstein deleted the add-baofeng-dm1701 branch October 8, 2026 21:18
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.

1 participant