Problem
OpenGD77 is the only supported CPS interchange format today (src/lib/import/opengd77/, src/lib/export/opengd77/). Operators using analogue-only radios (Baofeng UV-5R, Retevis RT95, etc.) typically manage channel lists in CHIRP — a single CSV per radio with FM/AM channels, CTCSS/DCS tones, duplex/offset, and no DMR concepts.
There is no path to import a CHIRP export into our internal models, edit/visualise it in the app (especially the channel map), or export back to CHIRP for flashing via CHIRP.
CHIRP is deliberately not DMR — no zones file, no contacts, no talk groups, no colour code or timeslot. It is the cleanest litmus test of whether the internal model is truly mode-neutral: vendor specifics must live at the import/export boundary, and analogue channels must round-trip without leaking OpenGD77/DMR assumptions into CRUD, validation, or the store. See format-taxonomy — CHIRP as a litmus test.
Intended outcome
Add CHIRP CSV as an import/export adapter pair, mirroring the OpenGD77/DM32 pattern: wire specifics at the boundary; feature code sees only internal Codeplug models.
Import
- Register
chirp adapter in src/lib/import/registry.ts.
- Classify files by CHIRP header row (parse by header name, not column index):
- Required columns:
Location, Name, Frequency, Duplex, Offset, Tone, rToneFreq, cToneFreq, DtcsCode, DtcsPolarity, RxDtcsCode, CrossMode, Mode, TStep, Skip, Power, Comment (plus optional DMR-ish columns URCALL, RPT1CALL, RPT2CALL, DVCODE — present in wire format but empty on analogue exports).
- Single-file import: one
.csv → Channel[] only (no zones, contacts, talk groups, or RX group lists).
- Map CHIRP wire values → internal model:
| CHIRP column |
Internal target |
Name |
Channel.name (+ preserve as import provenance in meta if needed) |
Frequency |
rxFrequencyHz / txFrequencyHz (derive TX from duplex + offset) |
Duplex / Offset |
split/offset semantics → internal frequency fields |
Mode |
mode (NFM → fm, AM → am, …) |
Tone, rToneFreq, cToneFreq, DtcsCode, DtcsPolarity, RxDtcsCode, CrossMode |
RX/TX tone fields (typed per #52) |
TStep |
channel step / bandwidth (kHz) |
Skip |
scanSkip (and/or CHIRP-specific skip semantics — document) |
Power |
power (radio-specific wire strings like 5.0W / 10W / 25W → percent or null = high) |
Comment |
description or note |
Location |
not stored — assign at export (per #53 pattern; CHIRP memory slot) |
| Empty DMR columns |
drop on import; do not create contacts/talk groups |
- Unmapped CHIRP-only columns →
Channel.vendorExtras (or chirpExtras if we split vendor escape hatches per format).
- Document skipped/lossy fields and classification rules in
docs/features/import-export/chirp/README.md + docs/reference/chirp/.
Export
- Register
chirp adapter in src/lib/export/registry.ts.
- Serialise internal analogue (and optionally mixed) channels → single CHIRP CSV download.
- Map internal values → CHIRP wire format (
fm → NFM, tone enum → Tone/rToneFreq/…, MHz with six decimal places, etc.).
- Assign
Location memory indices at export time (stable ordering policy — document; may follow import order or sort by name).
- Target radio profile at export — CHIRP column availability, power ladder, and memory size vary by radio (filename in sample exports encodes model). Likely needs a radio picker similar in spirit to OpenGD77 #72 but CHIRP-specific (UV-5R vs RT95 vs UV-21ProV2). v1: export using a chosen profile with warnings when channels exceed radio memory or use unsupported power levels.
- Omit or blank DMR columns for analogue-only export; do not synthesise contacts/talk groups.
- Surface warnings when exporting DMR channels to CHIRP (skip, strip digital fields, or block — decide during design).
UI
- Import: extend
ImportDropzone / importFiles to recognise CHIRP CSV alongside OpenGD77 (adapter selection by detected format; error if mixed vendors in one drop).
- Export: add CHIRP section on
/export with CSV download (+ optional target-radio selector).
- Channel map should render imported analogue channels (frequencies, names, tones) without requiring zones.
CHIRP ↔ internal model mapping notes
Key differences from OpenGD77/DM32 (design during implementation):
| Concept |
CHIRP |
Internal model today |
| File shape |
Single CSV per radio |
Multi-entity codeplug (channels + zones + …) |
| Mode |
NFM, AM (analogue) |
fm, am — mode enum shipped (#45) |
| Zones / scan |
No zone file; Skip column only |
Zone[] optional — CHIRP import leaves zones empty |
| Contacts / TG / RX lists |
Wire columns exist but unused on analogue |
DMR entities — leave empty on import; must not be required for analogue CRUD |
| Tones |
Primary analogue feature (CTCSS/DCS/Tone/TSQL/CrossMode) |
Typed tone fields (#52) |
| Power |
5.0W, 10W, 25W, 1.0W strings |
Percent 0–100 or null |
| Memory slot |
Location column |
Not stored — assign at export (#53) |
| Duplex |
``, +, `-`, `off` + `Offset` MHz |
Derive TX frequency from RX + offset rules |
Model / UX gaps (likely sub-tasks)
- Channels-only projects: import creates a valid project with channels but no zones — ensure home/map/CRUD do not assume DMR entities exist.
- Mixed-mode export: if the active project contains DMR channels (from OpenGD77) and the operator exports CHIRP, define behaviour (filter analogue only vs error).
- Target radio profile: CHIRP export is radio-specific — design profile registry under
docs/reference/chirp/radios/ (start with radios represented in sample fixtures).
Reference material
- Sample fixtures (in repo):
sample-exports/Chirp 2026-06-29/ — added in #101:
Baofeng_UV-21ProV2_20251129.csv
Baofeng_UV-5R Mini_20251129.csv
Retevis_RT95 VOX_20251106.csv
- Analogue FM/AM channels: repeaters, PMR446, SAR, FRS, calling channels, airband (
AM row in UV-21ProV2 sample).
- CHIRP project docs: https://chirp.danplanet.com/ (column semantics vary slightly by radio driver — document per profile).
- Mental model: format-taxonomy.md — CHIRP is a sibling format, unrelated to OpenGD77.
Affected
src/lib/import/chirp/ — adapter, parse, columns, tests
src/lib/export/chirp/ — serialise, download, round-trip tests
src/lib/import/registry.ts, src/lib/export/registry.ts
src/lib/import/index.ts — format detection / adapter routing
src/routes/Export.tsx — CHIRP export UI
ImportDropzone hint text + classification
docs/features/import-export/chirp/, docs/reference/chirp/ — mapping tables
- Possibly map/CRUD empty-state copy when project has channels but no zones
Notes / dependencies
- Mirrors #67 (DM32) and #38 (OpenGD77) architecture — read existing adapters as template.
- Distinct from #67 — DM32 is DMR dual-mode stock CPS; CHIRP is analogue-only, single CSV, no zones.
- Builds on #93 pristine model epic — especially typed tones/power (#52), mode enum (#45), and export-time slot assignment (#53).
- Related: #58 (import into active project) — CHIRP partial imports (channels only) should merge cleanly.
- Vendor boundaries: no CHIRP wire strings or radio memory caps in
src/models/, mutations, validation, or CRUD UI.
- All processing client-side; privacy unchanged.
Suggested phasing
- Reference + detect:
docs/reference/chirp/ column tables; header-based classification; parse unit tests against sample fixtures.
- Import: channels-only → internal model; map modes/tones/duplex/power; verify channel map renders.
- Export: serialise back to CHIRP CSV; round-trip tests against committed samples; assign
Location at export.
- Radio profiles: target-radio picker + per-radio limits/warnings; document lossy fields.
- Polish: export page UI, mixed-format drop guards, progress/outstanding docs.
Out of scope
- CHIRP live radio programming (USB) or
.img binary images.
- Modelling CHIRP bank/group abstractions beyond
Location slot assignment.
- DMR digital modes via CHIRP (some radios support DMR in CHIRP — defer until analogue path is solid).
- Guaranteed loss-free round-trip for every CHIRP column on every radio driver — document lossy fields;
vendorExtras for the rest.
Manual verify
- Import
sample-exports/Chirp 2026-06-29/Baofeng_UV-5R Mini_20251129.csv → channels visible on map and in CRUD; no phantom zones/contacts.
- Edit a channel (frequency, tone, name) → export CHIRP CSV → re-import in CHIRP app (or diff against source).
- Mixed OpenGD77 + CHIRP drop → clear error, no partial corrupt state.
- Export project with DMR channels to CHIRP → documented behaviour (filter/warn/error).
- Round-trip all three committed sample files through import → export → byte-compare or field-compare (allowing
Location reorder if policy differs).
Workflow note
Large multi-commit feature: branch from origin/main, atomic conventional commits per layer (detect → parse → import tests → export → UI → docs). Use docs/features/import-export/chirp/chirp-progress.md + outstanding log. PR linking Closes #.
Problem
OpenGD77 is the only supported CPS interchange format today (
src/lib/import/opengd77/,src/lib/export/opengd77/). Operators using analogue-only radios (Baofeng UV-5R, Retevis RT95, etc.) typically manage channel lists in CHIRP — a single CSV per radio with FM/AM channels, CTCSS/DCS tones, duplex/offset, and no DMR concepts.There is no path to import a CHIRP export into our internal models, edit/visualise it in the app (especially the channel map), or export back to CHIRP for flashing via CHIRP.
CHIRP is deliberately not DMR — no zones file, no contacts, no talk groups, no colour code or timeslot. It is the cleanest litmus test of whether the internal model is truly mode-neutral: vendor specifics must live at the import/export boundary, and analogue channels must round-trip without leaking OpenGD77/DMR assumptions into CRUD, validation, or the store. See format-taxonomy — CHIRP as a litmus test.
Intended outcome
Add CHIRP CSV as an import/export adapter pair, mirroring the OpenGD77/DM32 pattern: wire specifics at the boundary; feature code sees only internal
Codeplugmodels.Import
chirpadapter insrc/lib/import/registry.ts.Location,Name,Frequency,Duplex,Offset,Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,RxDtcsCode,CrossMode,Mode,TStep,Skip,Power,Comment(plus optional DMR-ish columnsURCALL,RPT1CALL,RPT2CALL,DVCODE— present in wire format but empty on analogue exports)..csv→Channel[]only (no zones, contacts, talk groups, or RX group lists).NameChannel.name(+ preserve as import provenance inmetaif needed)FrequencyrxFrequencyHz/txFrequencyHz(derive TX from duplex + offset)Duplex/OffsetModemode(NFM→fm,AM→am, …)Tone,rToneFreq,cToneFreq,DtcsCode,DtcsPolarity,RxDtcsCode,CrossModeTStepSkipscanSkip(and/or CHIRP-specific skip semantics — document)Powerpower(radio-specific wire strings like5.0W/10W/25W→ percent ornull= high)CommentdescriptionornoteLocationChannel.vendorExtras(orchirpExtrasif we split vendor escape hatches per format).docs/features/import-export/chirp/README.md+docs/reference/chirp/.Export
chirpadapter insrc/lib/export/registry.ts.fm→NFM, tone enum →Tone/rToneFreq/…, MHz with six decimal places, etc.).Locationmemory indices at export time (stable ordering policy — document; may follow import order or sort by name).UI
ImportDropzone/importFilesto recognise CHIRP CSV alongside OpenGD77 (adapter selection by detected format; error if mixed vendors in one drop)./exportwith CSV download (+ optional target-radio selector).CHIRP ↔ internal model mapping notes
Key differences from OpenGD77/DM32 (design during implementation):
NFM,AM(analogue)fm,am— mode enum shipped (#45)Skipcolumn onlyZone[]optional — CHIRP import leaves zones empty5.0W,10W,25W,1.0WstringsnullLocationcolumn+, `-`, `off` + `Offset` MHzModel / UX gaps (likely sub-tasks)
docs/reference/chirp/radios/(start with radios represented in sample fixtures).Reference material
sample-exports/Chirp 2026-06-29/— added in #101:Baofeng_UV-21ProV2_20251129.csvBaofeng_UV-5R Mini_20251129.csvRetevis_RT95 VOX_20251106.csvAMrow in UV-21ProV2 sample).Affected
src/lib/import/chirp/— adapter, parse, columns, testssrc/lib/export/chirp/— serialise, download, round-trip testssrc/lib/import/registry.ts,src/lib/export/registry.tssrc/lib/import/index.ts— format detection / adapter routingsrc/routes/Export.tsx— CHIRP export UIImportDropzonehint text + classificationdocs/features/import-export/chirp/,docs/reference/chirp/— mapping tablesNotes / dependencies
src/models/, mutations, validation, or CRUD UI.Suggested phasing
docs/reference/chirp/column tables; header-based classification; parse unit tests against sample fixtures.Locationat export.Out of scope
.imgbinary images.Locationslot assignment.vendorExtrasfor the rest.Manual verify
sample-exports/Chirp 2026-06-29/Baofeng_UV-5R Mini_20251129.csv→ channels visible on map and in CRUD; no phantom zones/contacts.Locationreorder if policy differs).Workflow note
Large multi-commit feature: branch from
origin/main, atomic conventional commits per layer (detect → parse → import tests → export → UI → docs). Usedocs/features/import-export/chirp/chirp-progress.md+ outstanding log. PR linkingCloses #.