Skip to content

Generate NeonPlug exports from ResolvedCodeplug #4

Description

@emuehlstein

Summary

Implement codeplugger's first exporter: consume an exporter-neutral ResolvedCodeplug and produce a .neonplug file accepted by NeonPlug for the Baofeng DM-32UV.

SSRF roots + profile -> ResolvedCodeplug -> .neonplug

Profile 0.1 only carries enough radio-specific information for analog channels and zones. Keep this issue to that path and reject unsupported modes rather than inventing values.

Evidence policy

Treat the existing private DM-32 generator and codec as research material, not as a specification.

The old generator currently:

  • Requires a base .neonplug file.
  • Copies analog and digital channel records as templates.
  • Preserves unrelated top-level state from that base.
  • Contains inferred defaults and field meanings learned from exports and hardware behavior.

Its successful outputs demonstrate a working workflow, but do not prove that copied fields are required, that inferred meanings are correct, or that a codeplug can be generated safely from an empty object.

Use this evidence order when making exporter decisions:

  1. Current NeonPlug source and model definitions.
  2. Successful import into a pinned NeonPlug revision/version.
  3. DM-32UV hardware behavior.
  4. Existing private generator behavior and observed exports as hypotheses to test.

Record the source for every NeonPlug-only default. NeonPlug's DM-32 implementation itself contains TODOs and conflicting comments for some fields, so unresolved fields must not be assigned semantics by guesswork.

Verified container contract

Current NeonPlug source directly establishes that:

  • A .neonplug file is a ZIP archive containing codeplug.json.
  • The current format version constant is 1.0.0.
  • Import defaults many absent top-level collections to empty arrays or null.

These facts should still be covered by exporter tests against a pinned NeonPlug revision. Do not assume every property represented by NeonPlug's TypeScript CodeplugData interface must be emitted until import behavior is tested.

Discovery gate: base-free generation

Before implementing the full writer, create the smallest synthetic analog channel/zone document and answer:

  1. Does current NeonPlug import it without a base export?
  2. Can NeonPlug write that imported data to a DM-32UV successfully?
  3. Which channel fields are actually required for a safe analog write?
  4. Which absent fields receive safe defaults, and where are those defaults defined?
  5. Are radioSettings, radioInfo, or other device state required for this workflow?

If base-free generation works, implement explicit source-backed defaults. If it does not, define the minimum base-template contract and explain why each retained field is needed. Do not silently clone arbitrary personal or device-specific state.

Scope

The exporter should emit:

  • Analog channels from ResolvedCodeplug.channels, preserving order.
  • Zones from ResolvedCodeplug.zones, preserving order.
  • Zone membership resolved from stable channel_references to generated channel numbers.
  • RX/TX frequencies, TX prohibition, and analog tones where verified mappings exist.
  • Explicit defaults or a documented minimum base-template contract for required fields absent from ResolvedCodeplug.

Power and bandwidth are not currently represented by ResolvedChannel; their policy must be explicit rather than inherited accidentally from a copied template.

Scan lists, contacts, DMR identities, digital channels, button settings, and profile 0.2+ features are out of scope.

Non-goals

  • Decoding existing .neonplug files into a friendly representation.
  • Byte-identical decode/encode round trips.
  • Preserving unknown or sparse fields from arbitrary exports.
  • A general-purpose NeonPlug editing or migration library.

Reference handling

Display names are not unique. Resolve each zone member by ResolvedChannel.reference, then translate it to the generated channel number. Never resolve membership by display name.

Missing or duplicate stable references must raise an actionable error. Positional reference rules for scan lists, contacts, RX groups, and radio IDs belong in their future feature issues, not this one.

Proposed placement

src/codeplugger/exporters/neonplug/

A minimal public API:

write_neonplug(path: Path, codeplug: ResolvedCodeplug) -> None

Profile and SSRF resolution remain exporter-unaware. Add a CLI output option after the writer contract is covered by tests.

Acceptance criteria

  • A minimal synthetic analog channel/zone fixture imports into a pinned NeonPlug revision without relying on private data.
  • Tests document whether generation is base-free or requires a minimal base template.
  • Channel and zone ordering matches ResolvedCodeplug.
  • Zone references resolve to the intended generated channel numbers.
  • RX/TX frequencies, TX prohibition, and analog tones survive NeonPlug import and are confirmed in the imported model.
  • Power, bandwidth, and every other NeonPlug-only field have a documented source and policy.
  • Unsupported modes and invalid references fail with actionable errors.
  • The archive contains codeplug.json and declares the tested format version.
  • Generated content is deterministic except for any explicitly documented metadata such as an export timestamp.
  • Public tests use synthetic fixtures only.
  • The generated fixture is smoke-tested on DM-32UV hardware before calling the initial milestone complete.

Source material and provenance

The private muehl-ssrf-private/radios/dm32/ implementation may be consulted for experiments and candidate mappings. Port only code whose behavior is needed and independently verified for this narrower exporter.

This repository is licensed under Apache-2.0. Preserve attribution required by any imported source and do not commit real radio exports or personal/device-specific base codeplugs.

Relationship to #3

This issue produces DM-32 output. #3 defines radio constraints. Implement currently enforceable analog channel/zone constraints alongside or immediately after this exporter; defer scan-list constraints until the profile model supports scan lists.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions