feat: Ailunce HD2 codeplug read/write with delta upload#34
Conversation
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
left a comment
There was a problem hiding this comment.
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 radio — old_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/--programstill appear under "Programming" help. Gate or document. hd2_serial_configurereimplements termios setup already inYModemDevice::SetInterfaceAttribs(used by the firmware path). Custom-baud justifies some of it, but the standard-rate case could reuse tested code.-p/--programon 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 inserial_read_exact/serial_drain.hd2_check_manifest_region_freerefuses 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.
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.