Skip to content

feat: Ailunce HD2 codeplug read/write with delta upload#34

Open
fenfir wants to merge 1 commit into
v0l:mainfrom
fenfir:feat/ailunce-hd2-codeplug-delta
Open

feat: Ailunce HD2 codeplug read/write with delta upload#34
fenfir wants to merge 1 commit into
v0l:mainfrom
fenfir:feat/ailunce-hd2-codeplug-delta

Conversation

@fenfir

@fenfir fenfir commented Jun 15, 2026

Copy link
Copy Markdown

Implement WriteCodeplug/ReadCodeplug for the Ailunce HD2 over the CPS serial protocol (ported from the validated dmrconfig backend): GetVer/ SLC7000 handshake, 0x0F/0x31 read+write frames, the region table, and the END commit. Add a --baud option (default 57600 for OpenRTX firmware, 119200 for vendor CPS) and wire up the --program and --read-codeplug commands, which were previously stubs.

Add --delta dirty-chunk upload: a CRC32-per-chunk manifest is stored in a reserved block at the tail of the channels region (verified free against the channel presence bitmap), so only chunks that differ from the radio are rewritten. The manifest is read back as a targeted 4 KB block rather than the full image, so an incremental write completes in seconds.

Implement WriteCodeplug/ReadCodeplug for the Ailunce HD2 over the CPS
serial protocol (ported from the validated dmrconfig backend): GetVer/
SLC7000 handshake, 0x0F/0x31 read+write frames, the region table, and the
END commit. Add a --baud option (default 57600 for OpenRTX firmware,
119200 for vendor CPS) and wire up the --program and --read-codeplug
commands, which were previously stubs.

Add --delta dirty-chunk upload: a CRC32-per-chunk manifest is stored in a
reserved block at the tail of the channels region (verified free against
the channel presence bitmap), so only chunks that differ from the radio
are rewritten. The manifest is read back as a targeted 4 KB block rather
than the full image, so an incremental write completes in seconds.

@v0l v0l left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Review summary

This adds Ailunce HD2 codeplug read/write (CPS upload/download) ported from dmrconfig's hd2.c, plus a "delta write" optimization backed by an on-radio CRC32 manifest, and new CLI flags (--read-codeplug, --delta, --baud). The protocol port is careful and internally consistent (verified read vs. write address arithmetic — 4096-byte write blocks advance the radio address by 4 per 1024-byte read unit, and manifest block read/write addresses match). A few issues should be addressed before merge.

High priority

1. Likely build break on glibc Linux (custom-baud path)src/ailunce_radio.cpp, hd2_serial_configure:

#elif defined(__linux__) && defined(TCGETS2)
    struct termios2 t2;

TCGETS2 is generally defined transitively via <sys/ioctl.h>, but struct termios2 is not declared by glibc's <termios.h> (it's in <asm/termbits.h>, which isn't included and conflicts with <termios.h>). This branch is compiled unconditionally and will likely fail to build on a standard Linux toolchain. Needs verification on Linux CI.

2. Manifest is written into the user codeplug/channel region — the delta feature reserves 4 KB at file offset 0x96000, inside the channels region, for an "HD2M" manifest. Every WriteCodeplug (even full, non-delta writes) mutates the image via hd2_build_manifest before sending, so the bytes on the radio no longer equal the input .bin, and a later ReadCodeplug dump contains tool metadata instead of real flash. It also sacrifices top channel slots (~2976–2999). The hd2_check_manifest_region_free guard mitigates accidental clobbering (good), but storing tool metadata in the device's user-data region is a smell — consider a host-side cache file keyed by device/serial instead.

3. Delta write can silently desync from the radioold_crcs comes only from the manifest the tool last wrote. If the radio drifts via any other path (vendor CPS, OpenRTX, or front-panel edits), the manifest is stale-but-valid (magic present) and delta will skip chunks that actually differ, leaving the radio not matching the file with no warning. The missing/invalid-manifest fallback is handled, but the "valid manifest, radio changed underneath" case is the dangerous one. At minimum surface a loud warning; ideally treat --delta as best-effort with documented caveats.

Medium / minor

  • Windows is unsupported at runtime (throws not implemented on Windows) but --read-codeplug/--program still appear under "Programming" help. Gate or document.
  • hd2_serial_configure reimplements termios setup already in YModemDevice::SetInterfaceAttribs (used by the firmware path). Custom-baud justifies some of it, but the standard-rate case could reuse tested code.
  • -p/--program on non-Ailunce radios now throws (does not support) where it was previously a no-op — arguably more correct, but a behavior change.
  • Ad-hoc std::cerr << "."/"#" progress output is inconsistent with the rest of the tool.
  • No write-back/verify pass after upload (a corrupted delta write won't be detected).
  • Missing trailing newline at end of src/ailunce_radio.cpp.
  • Licensing OK: dmrconfig is GPL, radio_tool is GPLv3, and the port is credited in comments.

Done well

  • Well-commented port traceable to dmrconfig source.
  • Consistent block/byte unit address math across read and write paths.
  • select()-based timeouts with bounded retries in serial_read_exact/serial_drain.
  • hd2_check_manifest_region_free refuses rather than clobbers a populated channel.
  • Explicit LE (de)serialization avoids endianness bugs.

Recommendation

Requesting changes. Blocking: (1) confirm/fix the Linux termios2 build, and (3) address the delta-desync hazard (at least document it; consider the host-side manifest from (2)). Consider landing read + full write first and treating manifest-based --delta as a follow-up.

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.

2 participants