Skip to content

feat(import/export): profile-aware power and squelch wire mapping #109

Description

@pskillen

Problem

Several channel fields are stored in the internal model in a vendor-neutral form, but each CPS format (and often each radio within a format) represents them differently on the wire. There is no single deterministic translation without knowing which radio the file came from or is destined for.

Examples today:

Internal (Channel) OpenGD77 wire CHIRP wire
power: 25 (percent) P2 1.0W
power: null (radio default / high) Master / P1 5.0W / 10W / 25W (profile-dependent)
squelch: 75 (percent) 75% (not applicable on CHIRP)
squelch: 0 (open) Disabled (not applicable)

The internal model correctly uses 0–100 percent (or null = radio default) for power and squelch — see Channel. Import/export adapters already contain ad-hoc mappers (parseOpenGd77PowerWire, parseChirpPowerWire, formatChirpPowerWireForProfile, …), but:

  • Import does not ask for a radio profile — CHIRP import uses a generic power ladder that cannot distinguish, e.g., whether 5.0W on a UV-5R Mini means 100% or “default high”.
  • Export has a CHIRP profile picker (#103) used mainly for memory limits and export-time power serialisation, but mapping tables are not centralised or symmetric with import.
  • OpenGD77 export still hard-codes the Baofeng 1701 profile (#72) with no import-side profile selection at all.
  • There is no shared, documented per-profile mapping table that enables a meaningful full path: import → edit in app → export across formats and radios.

Without profile context, operators lose fidelity when crossing format boundaries (e.g. CHIRP → internal → CHIRP on a different radio, or CHIRP → OpenGD77).

Intended outcome

At import and export time, let the operator pick a radio profile (format-specific) that supplies mapping hints for fields that cannot be translated generically.

Scope (v1 fields)

  • Channel.power — wire ↔ percent
  • Channel.squelch — wire ↔ percent (OpenGD77 and other formats that carry squelch; CHIRP N/A)

Future fields (tones, bandwidth steps, etc.) can follow the same pattern — out of scope for this ticket unless trivial to generalise the registry shape.

Behaviour

  • Import UI: when the detected format supports profiles, show a profile picker (or sensible default with override) before/during import. Pass profileId into the import adapter.
  • Export UI: extend existing profile pickers (CHIRP shipped; OpenGD77 #72) to use the same mapping tables, not just cardinality limits.
  • Mapping tables: per format + profile, keyed lookup for wire → percent on import and percent → wire on export. Document in docs/reference/<format>/radios/<profile>.md (tier 3 wire reference).
  • Example: importing a CHIRP file for Baofeng UV-5R Mini maps Power=5.0Wpower: 100; exporting back with the same profile maps power: 1005.0W. Retevis RT95 uses a different ladder (10W / 25W) — already partially handled on export; import should use the same profile tables.
  • Round-trip: system tests should pass using model serialisation alone (no wire stash — see no-wire-stash-roundtrip). Profile-aware mappers are the approved boundary approach.

Architecture notes

  • Mapping tables and profile constants stay at the import/export boundary only — not in src/models/, mutations, validation, or CRUD UI (vendor boundaries).
  • Extend or unify profile registries (src/lib/export/chirp/profiles.ts, future OpenGD77 profiles) to include field mapping config, not just maxMemorySlots.
  • Add ImportOptions (mirror ExportOptions.profileId in types.ts) and thread through importFiles / adapters.
  • CHIRP and OpenGD77 can ship first; DM32 and others adopt the same contract when they gain profiles.

Affected

  • src/lib/import-export/types.tsImportOptions.profileId
  • src/lib/import/<format>/ — profile-aware parse/serialise for power, squelch
  • src/lib/export/<format>/ — centralise mapping tables shared with import
  • Import UI (ImportDropzone or successor) — profile picker when format supports it
  • Export UI — wire existing pickers to mapping tables (#72, CHIRP)
  • docs/reference/<format>/radios/ — per-profile power/squelch ladders
  • src/test/system/*RoundTrip.system.test.ts — profile-scoped assertions

References

Suggested phasing

  1. Registry shape — extend profile types with powerLadder / squelchLadder (or equivalent); add ImportOptions.
  2. CHIRP — symmetric import/export tables for existing three profiles; import UI picker; round-trip tests per profile.
  3. OpenGD77 — Baofeng 1701 profile tables for power + squelch; tie into #72 export picker; import picker on multi-file drop.
  4. Docs — per-radio mapping tables under docs/reference/<format>/radios/.

Out of scope

  • Changing internal percent semantics or CRUD UI.
  • Profile inference from filename or CHIRP driver metadata (manual picker is fine for v1).
  • Power/squelch mapping for formats without profile registries yet.
  • Wire-stash round-trip (wireColumns, replaying imported cells on export).

Manual verify

  1. Import CHIRP UV-5R Mini sample with profile selected → 5.0W channels show 100% power in channel list.
  2. Export same project with same profile → Power column reads 5.0W for those channels.
  3. Switch profile to Retevis RT95 on export → high power serialises to 25W, not 5.0W.
  4. Import OpenGD77 export with 1701 profile → P2 → 25%, P100 → 100%, Disabled squelch → 0%.
  5. Full import → edit power in CRUD → export → re-import with same profile preserves intended wire values.

Metadata

Metadata

Assignees

No one assigned

    Labels

    enhancementNew feature or requestradio-format-supportsupport for 3rd party radio formats - import/export, etc

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions