summaryrefslogtreecommitdiff
path: root/stacks/crdop/docs/DEVELOPER.md
diff options
context:
space:
mode:
authorinfo@mode42.com <info@mode42.com>2026-08-07 18:25:13 +0000
committerinfo@mode42.com <info@mode42.com>2026-08-07 18:25:13 +0000
commit04d965d67a7264a1c7c211494aebda1953df7603 (patch)
tree0ebd700a6e219f84a26a656f4bee8778bc75da7b /stacks/crdop/docs/DEVELOPER.md
Initial push
Diffstat (limited to 'stacks/crdop/docs/DEVELOPER.md')
-rw-r--r--stacks/crdop/docs/DEVELOPER.md188
1 files changed, 188 insertions, 0 deletions
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 |
git clone -b <branch> https://cgit.mode42.com/<repo>.git
git clone -b <branch> git://cgit.mode42.com/<repo>.git

info@mode42.com