From 04d965d67a7264a1c7c211494aebda1953df7603 Mon Sep 17 00:00:00 2001 From: "info@mode42.com" Date: Fri, 7 Aug 2026 18:25:13 +0000 Subject: Initial push --- stacks/crdop/docs/DEVELOPER.md | 188 +++++++++++++++++++++++++++++++++++++++++ 1 file changed, 188 insertions(+) create mode 100644 stacks/crdop/docs/DEVELOPER.md (limited to 'stacks/crdop/docs/DEVELOPER.md') diff --git a/stacks/crdop/docs/DEVELOPER.md b/stacks/crdop/docs/DEVELOPER.md new file mode 100644 index 0000000..af43252 --- /dev/null +++ b/stacks/crdop/docs/DEVELOPER.md @@ -0,0 +1,188 @@ +# CRDOP developer guide + +**CRDOP** = **MAX25-SoftModem**. In-house GPLv3 sound-card modem (`stacks/crdop/`). Project rule: [docs/CRDOP.md](../../../docs/CRDOP.md). + +--- + +## Source tree + +``` +stacks/crdop/ +├── lib/ # Native DSP (Python — leads validation) +│ ├── bell202_line_code.py +│ ├── afsk_modulator.py +│ ├── afsk_demodulator.py +│ ├── hdlc_codec.py +│ ├── sound_proxy.py +│ ├── m25_host_protocol.py +│ ├── acoustic_engine.py +│ └── test_bell202_line_code.py +├── include/crdop/ # C API scaffold (future native binary) +│ ├── sound_proxy.h +│ └── version.h +├── tools/ +│ ├── audio-dummyd.py # M25 host TCP daemon +│ └── max25-signal-sniffer.py +├── scripts/ +│ ├── crdopc # Launcher → audio-dummyd by default +│ ├── build-crdop.sh +│ └── install-crdop.sh +├── share/ # crdop.ini.example (+ dual, amateur) +├── cmake/ # Platform, vendor (dev-only), tests +├── docs/ # This tree +├── ROADMAP.md +└── CMakeLists.txt +``` + +**Daemon integration:** `stacks/daemon/device_backends.py` → `CrdopTcpBackend` (`soft-crdop`), `AudioDummyBackend` (`audio-dummy`). + +**AX.25 codec:** `stacks/daemon/ax25_codec.py` — shared with on-air framing in `acoustic_engine.py`. + +--- + +## Module map + +| Module | Layer | Responsibility | +|--------|-------|----------------| +| `bell202_line_code.py` | Line code | Bell 202 frequency-toggle: bit 0 → tone change; mark 1200 Hz / space 2200 Hz | +| `afsk_modulator.py` | PHY TX | Continuous-phase AFSK PCM @ configurable sample rate (default 48 kHz) | +| `afsk_demodulator.py` | PHY RX | Per-symbol Goertzel demod (1200 baud class) | +| `hdlc_codec.py` | Framing | HDLC flags `0x7E`, bit-stuffing, CRC-16-CCITT | +| `sound_proxy.py` | Audio I/O | ALSA capture/playback via `arecord`/`aplay`; buffer config | +| `m25_host_protocol.py` | Host | TCP ctrl (:8515) + data (:8516); M25-family + KISS-semantics | +| `acoustic_engine.py` | Bench | Full path: AX.25 UI → HDLC → AFSK → demod → parse; loopback self-test | + +**Host vs on-air:** `m25_host_protocol.py` carries AX.25 UI on the data port **without** HDLC wrapping. `acoustic_engine.encode_ax25_ui()` builds full on-air HDLC + AFSK for RF/audio output. + +**Not yet in `lib/`:** G3RUH scrambler/FSK (P1), 300 baud HF AFSK (P2), optional short-block FEC. + +--- + +## Running tests + +### MAX25-Stack (recommended) + +```bash +./scripts/build.sh +cmake --build build --target max25_test # all offline tests +cmake --build build --target max25_daemon_smoke # daemon + CRDOP subset +``` + +CRDOP-related tests in `max25_daemon_smoke`: + +| Test file | What it checks | +|-----------|----------------| +| `stacks/crdop/lib/test_bell202_line_code.py` | Bell 202 encode/decode round-trip | +| `stacks/daemon/test_crdop_backend.py` | `CrdopTcpBackend` attach, TX (native M25/KISS) | +| `stacks/daemon/test_audio_dummy_backend.py` | `audio-dummy` loopback backend | +| `stacks/daemon/test_multi_device.py` | Multi-device registry incl. `soft-crdop` | + +### Direct (no CMake) + +```bash +python3 stacks/crdop/lib/test_bell202_line_code.py +python3 stacks/daemon/test_crdop_backend.py +python3 stacks/daemon/test_audio_dummy_backend.py +``` + +### Sniffer loopback (no audio hardware) + +```bash +python3 stacks/crdop/tools/max25-signal-sniffer.py --loopback +# or after install: +max25-signal-sniffer --loopback +``` + +Exit 0 when loopback decode produces AX.25 UI lines. + +### audio-dummyd bench + +```bash +python3 stacks/crdop/tools/audio-dummyd.py --ctrl-port 8515 --data-port 8516 +# Terminal probe (separate shell): +max25-terminal -H 127.0.0.1 -P 8515 +``` + +### Standalone CRDOP vendor tests (dev-only) + +When built with `-DCRDOP_VENDOR_ARDOPCF=ON` and cmocka installed: + +```bash +./scripts/build-crdop.sh +CRDOP_BUILD_TESTS=ON ./scripts/test-all.sh +``` + +Vendor tree is **never** installed in MAX25 releases. + +--- + +## Extending the modem + +### Add a modulation mode + +1. Add PHY module under `lib/` (e.g. `g3ruh_fsk.py`). +2. Wire into `acoustic_engine.py` — select by `baud` / `modulation` parameter. +3. Extend `crdop.ini` `[modem]` keys in `share/crdop.ini.example` and [CONFIG.md](CONFIG.md). +4. Add unit tests beside the module (`test_*.py`). +5. Update [ROADMAP.md](../ROADMAP.md) implementation status table. + +### Extend host protocol + +1. Edit `m25_host_protocol.py` — keep ctrl/data port split. +2. Mirror changes in `stacks/daemon/device_backends.py` (`CrdopTcpBackend`). +3. Document in [PROTOCOL.md](PROTOCOL.md). +4. Add cases to `test_crdop_backend.py`. + +### C port path + +`include/crdop/sound_proxy.h` is the C ABI target. Python `lib/` leads until acoustic bench gates close; then port validated algorithms to native `crdopc` binary. + +--- + +## Build modes + +| Mode | CMake flag | Output | +|------|------------|--------| +| **MAX25-Stack (default)** | `MAX25_BUILD_CRDOP=ON` | `bin/crdop`, `bin/audio-dummyd`, `bin/max25-signal-sniffer`, `share/crdop/` | +| **MAX25 without CRDOP** | `-DMAX25_BUILD_CRDOP=OFF` | No CRDOP install artifacts | +| **Standalone CRDOP** | `stacks/crdop/scripts/build-crdop.sh` | Same scaffold; optional vendor `crdopc` with `-DCRDOP_VENDOR_ARDOPCF=ON` | +| **Dev vendor ARDOP** | `-DCRDOP_VENDOR_ARDOPCF=ON` | Legacy ardopcf binary — local only, never released | + +### Install paths (MAX25 `cmake --install`) + +| Artifact | Destination | +|----------|-------------| +| `crdop` (launcher) | `${CMAKE_INSTALL_PREFIX}/bin/` | +| `audio-dummyd`, `max25-signal-sniffer` | `${CMAKE_INSTALL_PREFIX}/bin/` | +| `crdop.ini.example`, `lib/*.py`, `VERSION` | `${CMAKE_INSTALL_PREFIX}/share/crdop/` | + +--- + +## Dependencies + +| Component | Requirement | +|-----------|-------------| +| Runtime | Python 3, ALSA utils (`arecord`/`aplay`) on Linux | +| Build | CMake ≥ 3.16 | +| Audio | `libasound` — kernel ALSA direct opens only | +| Tests | None beyond Python 3 (cmocka optional for vendor C tests) | + +--- + +## Conventions + +- **English** for all shipped docs and user-facing strings. +- **No PulseAudio / PipeWire** in production audio path. +- **ARDOP** is a separate optional plugin — not part of CRDOP — see [plugins/external/ardop/README.md](../../../plugins/external/ardop/README.md). +- Baud ceiling: **19200** (P1 G3RUH direct FSK). Above that: out of scope. + +--- + +## Related + +| Doc | Topic | +|-----|--------| +| [MAX25-USAGE.md](MAX25-USAGE.md) | Operator workflow in MAX25-Stack | +| [HARDWARE-INTERFACE.md](HARDWARE-INTERFACE.md) | Radio interface spec for builders | +| [AUDIO-ARCHITECTURE.md](AUDIO-ARCHITECTURE.md) | Sound-proxy design | +| [ROADMAP.md](../ROADMAP.md) | P0/P1/P2 milestones and module status | -- cgit v1.3.1