CHIRP CSV: emit importable Duplex values; RX-only as simplex - #14
Conversation
CHIRP's CSV parser (chirp_common.really_from_csv, kk7ds/chirp @ a229fae) accepts only "+", "-" or empty for Duplex. The exporter emitted "off" for receive-only channels and "split" for >30 MHz spreads; both raise InvalidDataError, so any file containing such a row failed to import. "off" was reachable today via any RX-only assignment (e.g. NOAA weather), and the export still exited 0, so the breakage was silent. Receive-only channels now export as plain simplex. This matches how they are actually stored in our reference codeplugs: the CPD/CFD receive-only blocks in muehlstein-codeplugger-profiles (BF-888_CPDCFD.img ch11-16 and TYT_TH-9800_20240124.img) hold tx == rx with no transmit inhibit, carrying the intent in the channel name. No channels are dropped. Wide splits now emit the true +/- offset instead of "split", so the TX frequency survives rather than being replaced by an unparseable literal. Also zero-pad DTCS codes to three digits to match CHIRP's own "%03i" output and the existing reference exports; code 23 previously serialized as "23". Caveat worth reviewing: a receive-only channel exported this way is transmit-capable in CHIRP. Nothing in a CHIRP CSV can express a TX inhibit (Power and BCL are not read back by the parser), so callers that need transmit actually blocked must enforce it outside the CSV.
Loose end closed: BF-888 byte 12 fully decodedThe PR body flags flag byte Two independent bit assignments, 16/16 agreement with the CPS's own CSV:
So Byte 12 has nothing to do with transmit. No bit in it gates TX on any of these 16 channels, including the CPD dispatch frequencies. This is corroborated independently by This strengthens rather than weakens the PR's reasoning: the receive-only intent genuinely is not encoded anywhere in the codeplug, so exporting RX-only as plain simplex loses nothing that was ever there. The reviewer caveat stands as written, now verified across 32 channels in two configs rather than inferred from 16. One correction to the PR bodyThe PR describes the CPD block as
The substantive finding is unaffected — every channel in Incidental: sparse slots are native to the format. |
|
The BF-888 image-format findings from this thread now have a permanent home: https://github.com/Chicago-Offline/bf888-info Moved so they don't stay buried in a merged-PR comment. Also carries the |
Closes #13.
What was broken
really_from_csv()(chirp_common.py:515,kk7ds/chirp@a229fae) accepts only+,-, or empty forDuplex:We emitted
offfor receive-only channels andsplitfor >30 MHz spreads. Both raise, so the whole row fails to import — and the export still exits 0, so it fails silently.offwas reachable today via any RX-only assignment (NOAA weather being the live case).Why RX-only became simplex, not skipped
This is the part worth arguing about, so here's the evidence rather than a preference.
Option 2 from the issue (skip + warn) drops channels. Before choosing, I decoded the reference codeplugs Eric pushed to
muehlstein-codeplugger-profiles/reference/cps_exports. The receive-only blocks are stored as plain simplex withtx == rx:Baofeng_BF-888_CPDCFD.imgch11–16 — CPD 453.650 / .675 / .725 / .775 / .825 / .975, every onetx == rxTYT_TH-9800_20240124.img— same CPD set, plus 453.1 / 453.3447 / 453.5 / 453.55No transmit inhibit anywhere. No
off, no split, no offset redirect. The receive-only intent lives in the channel name. So exporting RX-only as simplex reproduces existing practice and keeps every channel.(
Duplex=offappears in zero rows across all three reference CSVs — the value we were emitting has no precedent in the real exports.)Changes
_duplex_and_offset()returns("", 0.000000)fortx_permitted=Falseinstead ofoff+/-offset instead ofsplit, so the TX frequency survives (145.000/440.000 →+295.000000) rather than being replaced by an unparseable literal"%03i"and the reference exports (code 23 was serializing as23, should be023)offfrom CHIRP's in-memoryvalid_duplexes, which does allow itSample output
Tests
uv run pytest -q→ 24 passed (was 21).off{+, -, ""}across RX-only, wide-split, and repeater channels — the parser contract, asserted directlyA receive-only channel exported this way is transmit-capable in CHIRP. Nothing in a CHIRP CSV can express a TX inhibit:
Power(col 16) andComment(col 17) are written byto_csv()but never read back byreally_from_csv(), which stops atvals[14].BCLisn't a CSV field at all.So this is not a safety regression versus the reference codeplugs — they don't block TX either — but it is a real property: nothing stops a TX on 453.775 except the operator reading the name. If we want enforced RX-only, it has to happen outside the CSV path (radio-side via the driver, or by refusing to include those channels).
One loose end I did not resolve: flag byte
[12]in the BF-888 structs varies (0x07on all 460.x;0x03/0x0bon 453.x, bit3 set only on 453.650 and 453.775). I read it as bandwidth or busy-lock rather than TX inhibit since it doesn't track the band split — but that's an inference from 16 channels, not a verified decode. If it is a TX inhibit, the precedent argument above gets stronger, not weaker.