diff options
Diffstat (limited to 'stacks/crdop/docs')
| -rw-r--r-- | stacks/crdop/docs/ACOUSTIC-TEST-PROTOCOL.md | 84 | ||||
| -rw-r--r-- | stacks/crdop/docs/AUDIO-ARCHITECTURE.md | 80 | ||||
| -rw-r--r-- | stacks/crdop/docs/BUILD.md | 99 | ||||
| -rw-r--r-- | stacks/crdop/docs/CHANGELOG.md | 25 | ||||
| -rw-r--r-- | stacks/crdop/docs/CONFIG.md | 50 | ||||
| -rw-r--r-- | stacks/crdop/docs/DEVELOPER.md | 188 | ||||
| -rw-r--r-- | stacks/crdop/docs/EXAMPLES.md | 91 | ||||
| -rw-r--r-- | stacks/crdop/docs/FEC-SPEC.md | 99 | ||||
| -rw-r--r-- | stacks/crdop/docs/G3RUH-DESIGN.md | 72 | ||||
| -rw-r--r-- | stacks/crdop/docs/HARDWARE-INTERFACE.md | 194 | ||||
| -rw-r--r-- | stacks/crdop/docs/HOST-PROTOCOL-SPEC.md | 103 | ||||
| -rw-r--r-- | stacks/crdop/docs/INDEX.md | 77 | ||||
| -rw-r--r-- | stacks/crdop/docs/LICENSE-USAGE.md | 116 | ||||
| -rw-r--r-- | stacks/crdop/docs/MAX25-USAGE.md | 292 | ||||
| -rw-r--r-- | stacks/crdop/docs/PROTOCOL.md | 36 | ||||
| -rw-r--r-- | stacks/crdop/docs/README.md | 31 | ||||
| -rw-r--r-- | stacks/crdop/docs/SOFTMODEM.md | 127 | ||||
| -rw-r--r-- | stacks/crdop/docs/SOUNDCARD-QUALIFICATION.md | 95 |
18 files changed, 1859 insertions, 0 deletions
diff --git a/stacks/crdop/docs/ACOUSTIC-TEST-PROTOCOL.md b/stacks/crdop/docs/ACOUSTIC-TEST-PROTOCOL.md new file mode 100644 index 0000000..9656c2a --- /dev/null +++ b/stacks/crdop/docs/ACOUSTIC-TEST-PROTOCOL.md @@ -0,0 +1,84 @@ +# Acoustic bench test protocol — P0 bench acceptance + +**Purpose:** Close CRDOP P0 bench without RF. All steps runnable offline in CI or on a dev host. + +**Related:** [HOST-PROTOCOL-SPEC.md](HOST-PROTOCOL-SPEC.md) · [DEVELOPER.md](DEVELOPER.md) · [ROADMAP.md](../ROADMAP.md) P0 bench. + +--- + +## Prerequisites + +| Item | Requirement | +|------|-------------| +| Build | `MAX25_BUILD_CRDOP=ON`, `cmake --install` or source tree | +| Python | 3.10+ with repo on `PYTHONPATH` | +| ALSA | Optional for tests 3–4; loopback tests need no hardware | +| PulseAudio | **Off** or bypassed (`no_pulse=yes` in INI) | + +--- + +## Test matrix + +| ID | Name | RF | Pass criteria | +|----|------|-----|---------------| +| **T0** | Bell 202 round-trip | No | `test_bell202_line_code.py` green | +| **T1** | HDLC + CRC | No | `hdlc_codec` round-trip in unit tests | +| **T2** | Software loopback | No | `max25-signal-sniffer.py --loopback` decodes AX.25 UI | +| **T3** | `acoustic_engine` self-test | No | `AcousticEngine.loopback_self_test()` returns True | +| **T4** | `audio-dummyd` + host TCP | No | Connect :8515/:8516, `PROTOCOLMODE KISS` → `OK` | +| **T5** | `max25d` audio-dummy backend | No | `test_audio_dummy_backend.py` green | +| **T6** | `max25d` crdop-tcp backend | No | `test_crdop_backend.py` green | +| **T7** | ALSA loopback cable | No* | Sniffer decodes tone from `aplay`/`arecord` pair | +| **T8** | WAV fixture | No | Sniffer `--wav` decodes recorded Bell 202 capture | + +\*Requires `snd-aloop` or physical loopback interface. + +--- + +## Commands (copy-paste) + +```bash +cd MAX25-Stack +./scripts/build.sh + +# T0–T1 +python3 -m pytest stacks/crdop/test_bell202_line_code.py -q +python3 -m pytest stacks/daemon/test_crdop_backend.py stacks/daemon/test_audio_dummy_backend.py -q + +# T2 +python3 stacks/crdop/tools/max25-signal-sniffer.py --loopback + +# T4 (background) +python3 stacks/crdop/tools/audio-dummyd.py --ctrl-port 8515 --data-port 8516 & +sleep 1 +python3 -c " +import socket +c=socket.create_connection(('127.0.0.1',8515),timeout=2) +c.sendall(b'PROTOCOLMODE KISS\n') +print(c.recv(64)) +" +``` + +--- + +## P0 bench exit gate + +**100 % P0 bench** when: + +- [x] T0, T1, T2, T5, T6 pass in `release-check.sh` +- [ ] T3 documented green in CI log (or explicit skip reason) +- [ ] T4 manual or scripted smoke in release-check +- [ ] Host ports unified: launcher, `audio-dummyd`, `max25d` use [HOST-PROTOCOL-SPEC.md](HOST-PROTOCOL-SPEC.md) defaults + +P0 on-air starts only after P0 bench exit — see [docs/HARDWARE-ACCEPTANCE.md](../../docs/HARDWARE-ACCEPTANCE.md). + +--- + +## Failure triage + +| Symptom | Check | +|---------|-------| +| Wrong tones | Mark 1200 Hz / Space 2200 Hz, sample rate 48000 | +| CRC fail | `ax25_codec.ax25_crc` vs `hdlc_codec` | +| TCP refuse | Port 8515 free; `audio-dummyd` running for host tests | +| ALSA xrun | `period_frames`, cable levels — [SOUNDCARD-QUALIFICATION.md](SOUNDCARD-QUALIFICATION.md) | diff --git a/stacks/crdop/docs/AUDIO-ARCHITECTURE.md b/stacks/crdop/docs/AUDIO-ARCHITECTURE.md new file mode 100644 index 0000000..6320253 --- /dev/null +++ b/stacks/crdop/docs/AUDIO-ARCHITECTURE.md @@ -0,0 +1,80 @@ +# CRDOP audio architecture — kernel ALSA only + +**CRDOP** = stack acronym for **MAX25-SoftModem** (device id `soft-crdop`). + +MAX25-SoftModem (CRDOP) talks to the radio **through the sound hardware directly**. There is **no PulseAudio**, **no PipeWire default route**, and **no desktop sound server** in the path. + +## Stack layers + +``` +┌─────────────┐ M25/1 TCP ┌──────────────┐ host TCP ┌─────────────┐ +│ max25d │ ◄────────────► │ CRDOP modem │ ◄──────────► │ max25- │ +│ (stack) │ │ (DSP/AX.25) │ │ terminal │ +└─────────────┘ └──────┬───────┘ └─────────────┘ + │ + MAX25 sound-proxy + (buffer, timing, duplex) + │ + ▼ + libasound (userspace) + │ + ▼ + Linux kernel ALSA + (/dev/snd/*, drivers) + │ + ▼ + Sound hardware (USB/PCI codec) + │ + ▼ + Radio interface (line / acoustic) +``` + +| Layer | Role | Allowed | +|-------|------|---------| +| **max25d** | Device lifecycle, `CrdopTcpBackend`, no audio I/O | — | +| **CRDOP modem** | AFSK encode/decode, AX.25 framing, duplex policy | — | +| **MAX25 sound-proxy** | **Only** audio shim between modem and ALSA — period size, xrun recovery, hw params, PTT timing hooks | MAX25-owned | +| **ALSA userspace** | `snd_pcm_*` on **`hw:` / `plughw:`** devices | Direct libasound | +| **Kernel ALSA** | Driver, DMA, card registry | Required | +| **PulseAudio / PipeWire** | — | **Forbidden** in production path | + +The sound-proxy is **not** a second network hop — it is the in-process (or co-process) module that owns ALSA opens and keeps modem timing off the desktop audio stack. + +## Why no PulseAudio + +PulseAudio and PipeWire insert mixing, resampling, and variable latency between applications and the kernel. CRDOP must: + +- Reproduce **exact mark/space frequencies** on playback +- Sample capture with **stable phase** for demodulation +- Meet **tighter deadlines** as baud rises toward 19200 + +A userspace mixer breaks those guarantees. Operators must bind **card and device explicitly** (`hw:Card,Device` or tested `plughw:`). + +## Operator setup + +1. Identify hardware: `arecord -l` and `aplay -l` (kernel cards, not `pulse` pseudo-devices). +2. Set INI `[audio] capture` and `playback` to **hardware ALSA names**. +3. Ensure no session steals the device (`pasuspender` / stop PulseAudio on dedicated hosts). +4. Verify loopback before on-air traffic. + +Example INI: + +```ini +[audio] +backend = alsa-kernel +no_pulse = yes +capture = hw:1,0 +playback = hw:1,0 +``` + +Dedicated packet-radio hosts: prefer **single-purpose Linux** without a desktop sound daemon, or `PULSE_SERVER=` / `PIPEWIRE_RUNTIME_DIR=` unset for the `crdop` service unit. + +## Development (Eigenentwicklung) + +Native CRDOP will implement the sound-proxy in-tree (`include/crdop/sound_proxy.h`). Legacy optional vendor builds used `ALSASound.c` with the same rule: **open ALSA devices directly**. + +## See also + +- [SOFTMODEM.md](SOFTMODEM.md) — product scope, baud, duplex +- [CONFIG.md](CONFIG.md) — INI keys +- [BUILD.md](BUILD.md) — `libasound2-dev` dependency diff --git a/stacks/crdop/docs/BUILD.md b/stacks/crdop/docs/BUILD.md new file mode 100644 index 0000000..e72fac0 --- /dev/null +++ b/stacks/crdop/docs/BUILD.md @@ -0,0 +1,99 @@ +# Build + +Standalone CRDOP (**MAX25-SoftModem** — MAX25-SoftModem) — embedded `vendor/ardopcf/`, no submodules. + +## Requirements + +- CMake ≥ 3.16 +- **GCC** or **Clang / LLVM** +- Platform libraries (see table below) +- Optional tests: **cmocka** (Linux, *BSD) + +## Platform matrix (tested) + +| OS | Toolchain | Audio / libs | Status | +|----|-----------|----------------|--------| +| **Linux** | GCC, Clang | `libasound2-dev`, `pkg-config` | tested | +| **FreeBSD** | GCC, Clang | `alsa-lib`, `pkgconf` | tested | +| **OpenBSD / NetBSD** | GCC, Clang | `alsa-lib` if available | experimental | +| **Windows** | MinGW-w64, MSVC | WinMM (built-in) | **untested** | +| **macOS** | Clang (Xcode) | CoreAudio (built-in) | **untested** | + +Other operating systems are not tested. + +### Debian / Ubuntu + +```bash +sudo apt install build-essential cmake libasound2-dev pkg-config +sudo apt install libcmocka-dev # optional unit tests +``` + +### FreeBSD + +```bash +pkg install cmake alsa-lib pkgconf cmocka +``` + +### macOS + +```bash +xcode-select --install +# or: brew install cmake +``` + +### Windows (MinGW-w64 on Linux host) + +```bash +sudo apt install mingw-w64 +CRDOP_TOOLCHAIN=x86_64-w64-mingw32 ./scripts/build-crdop.sh +# → build/crdopc.exe +``` + +Native Windows: Visual Studio 2022+ with CMake, or MSYS2 MinGW. + +## Build + +```bash +./scripts/build-crdop.sh +CC=clang ./scripts/build-crdop.sh +./scripts/test-all.sh # smoke + cmocka (Unix) +``` + +Output: `build/crdopc` (Windows: `build/crdopc.exe`) + +## Install + +```bash +./scripts/install-crdop.sh # → /usr/local +CRDOP_PREFIX=$HOME/.local ./scripts/install-crdop.sh +``` + +Installs: + +| Path | Contents | +|------|----------| +| `bin/crdopc` | Modem binary | +| `bin/crdop` | Profile launcher (`scripts/crdopc`) | +| `share/crdop/` | INI examples, `VERSION` | + +Run: `crdop` or `CRDOP_INI=~/.config/crdop/crdop.ini crdop` + +## Cross-compile (Linux host) + +```bash +CRDOP_TOOLCHAIN=aarch64-linux-gnu ./scripts/build-crdop.sh +CRDOP_TOOLCHAIN=arm-linux-gnueabihf ./scripts/build-crdop.sh +CRDOP_TOOLCHAIN=x86_64-w64-mingw32 ./scripts/build-crdop.sh +``` + +## Troubleshooting + +| Problem | Fix | +|---------|-----| +| `vendor/ardopcf missing` | Full repo checkout | +| No ALSA (Linux/BSD) | Install dev package / `alsa-lib` port | +| macOS build fails | Xcode CLI tools; CMake ≥ 3.16 | +| Tests skipped | Install cmocka dev package | +| Stale build | `rm -rf build && ./scripts/build-crdop.sh` | + +Vendor refresh (maintainers): `./scripts/refresh-vendor-ardopcf.sh` diff --git a/stacks/crdop/docs/CHANGELOG.md b/stacks/crdop/docs/CHANGELOG.md new file mode 100644 index 0000000..2bbfbf6 --- /dev/null +++ b/stacks/crdop/docs/CHANGELOG.md @@ -0,0 +1,25 @@ +# Changelog + +## [CUR999] — dev track (current) + +**CUR999** — internal dev track id for MAX25-SoftModem inside MAX25-Stack v1.0.0. Dev track `CUR999`; ships with MAX25-Stack v1.0.0. + +- Dev track id `CUR999` in `$SRC/stacks/crdop/VERSION` +- Native modem DSP in development; optional legacy vendor build (`-DCRDOP_VENDOR_ARDOPCF=ON`); **ARDOP-plugin** documented separately +- `max25d` `crdop-tcp` backend, INI scaffold, launcher, HyBBX attach examples + +## [0.5.0] — planned (not yet released) + +Target standalone CRDOP v0.5 after v1.0.0 stack ship. + +- Standalone CRDOP modem (`crdopc`) with native DSP +- Embedded optional `vendor/ardopcf` (MIT) for dev-only legacy builds only +- CB / dual / amateur profiles; Linux and *BSD tested + +## [0.1.0-l2-cb] — 2026-07-03 + +Experimental CB profile bootstrap. + +## [0.0.0-bootstrap] + +Repository scaffold. diff --git a/stacks/crdop/docs/CONFIG.md b/stacks/crdop/docs/CONFIG.md new file mode 100644 index 0000000..821fd1c --- /dev/null +++ b/stacks/crdop/docs/CONFIG.md @@ -0,0 +1,50 @@ +# Configuration + +`scripts/crdopc` reads INI → starts native `audio-dummyd` (M25/KISS host). + +## INI search order + +1. `$CRDOP_INI` +2. `~/.config/crdop/crdop.ini` +3. `share/crdop.ini.example` + +Templates: `share/crdop.ini.example` · `share/crdop-dual.ini.example` · `share/crdop-amateur.ini.example` + +## Keys + +### `[profile]` + +| Key | Default | Values | +|-----|---------|--------| +| `radio_profile` | `cb` | `cb` · `dual` · `amateur` | + +### `[modem]` + +| Key | Default | Notes | +|-----|---------|-------| +| `arq_bandwidth` | profile | `500MAX` (cb/dual) · `1000MAX` (amateur) | +| `duplex` | `half` | `half` · `full` (`full` → `EXTRADELAY 0`) | +| `extra_delay_ms` | _(auto)_ | Override delay; empty = profile default | + +### `[host]` · `[mycall]` · `[audio]` + +| Section | Key | Default | Notes | +|---------|-----|---------|-------| +| host | `port` | `8515` | TCP control | +| mycall | `call` | `NOCALL-0` | On-air ID | +| audio | `backend` | `alsa-kernel` | Kernel ALSA only — see [AUDIO-ARCHITECTURE.md](AUDIO-ARCHITECTURE.md) | +| audio | `no_pulse` | `yes` | Reject PulseAudio/PipeWire pseudo devices | +| audio | `capture` | _(required)_ | e.g. `hw:1,0` from `arecord -l` | +| audio | `playback` | _(required)_ | e.g. `hw:1,0` from `aplay -l` | +| audio | `sample_rate` | modem default | Hz | +| audio | `period_frames` | auto | Buffer tuning | + +**No PulseAudio** in the production path. The **MAX25 sound-proxy** opens ALSA directly; `scripts/crdopc` clears Pulse/PipeWire session env when `no_pulse=yes`. + +## Environment + +| Variable | Purpose | +|----------|---------| +| `CRDOP_INI` | Config file path | + +Examples: [EXAMPLES.md](EXAMPLES.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 | diff --git a/stacks/crdop/docs/EXAMPLES.md b/stacks/crdop/docs/EXAMPLES.md new file mode 100644 index 0000000..b6d32c5 --- /dev/null +++ b/stacks/crdop/docs/EXAMPLES.md @@ -0,0 +1,91 @@ +# Examples + +MAX25-Stack integration (plugins, `max25d`, terminal): [MAX25-USAGE.md](MAX25-USAGE.md). + +## Build + +```bash +./scripts/build-crdop.sh +CC=clang ./scripts/build-crdop.sh +CRDOP_BUILD_TESTS=ON ./scripts/test-all.sh # needs libcmocka-dev +``` + +Cross-build (Linux host): see [BUILD.md](BUILD.md). + +## Run — CB (default, native M25 host) + +```bash +./scripts/crdopc +# or explicit example INI: +CRDOP_INI=share/crdop.ini.example ./scripts/crdopc +``` + +This starts `audio-dummyd` with native M25/KISS host on TCP :8515/:8516. + +Copy config for daily use: + +```bash +mkdir -p ~/.config/crdop +cp share/crdop.ini.example ~/.config/crdop/crdop.ini +$EDITOR ~/.config/crdop/crdop.ini +./scripts/crdopc +``` + +## Run — dual (CB ↔ amateur) + +```bash +CRDOP_INI=share/crdop-dual.ini.example ./scripts/crdopc +``` + +## Run — amateur (secondary) + +```bash +CRDOP_INI=share/crdop-amateur.ini.example ./scripts/crdopc +``` + +## Full-duplex CB + +`share/crdop.ini.example` with: + +```ini +[modem] +duplex = full +arq_bandwidth = 500MAX +``` + +## Custom call / ALSA devices + +```ini +[mycall] +call = CB01-0 + +[audio] +capture = plughw:1,0 +playback = plughw:1,0 +``` + +Pass extra args after port (reserved for future native modem flags): + +```bash +./scripts/crdopc 8515 +``` + +## Direct audio-dummyd (no launcher) + +```bash +./build/bin/audio-dummyd --ctrl-port 8515 --data-port 8516 +``` + +## max25d integration + +```ini +[devices] +soft-crdop = crdop:default + +[device.soft-crdop] +host = 127.0.0.1 +port = 8515 +listen = yes +``` + +See [../../docs/PLUGINS-DEVICE-MODEL.md](../../docs/PLUGINS-DEVICE-MODEL.md). diff --git a/stacks/crdop/docs/FEC-SPEC.md b/stacks/crdop/docs/FEC-SPEC.md new file mode 100644 index 0000000..53b1f93 --- /dev/null +++ b/stacks/crdop/docs/FEC-SPEC.md @@ -0,0 +1,99 @@ +# CRDOP FEC and duplex — implementation spec + +**Principle:** *Send more often with shorter frames rather than one long payload.* + +**Related:** [ROADMAP.md](../ROADMAP.md) FEC section · [docs/CRDOP.md](../../../docs/CRDOP.md) + +**Status:** MAX25-Stack **v1.0.0** / dev track `CUR999` — L1 optional FEC **planned**; operator-layer strategy **active now**. + +--- + +## Layer model + +| Layer | Mechanism | v1.0.0 | +|-------|-----------|--------| +| **L3 Application** | Short beacons, staggered intervals | HyBBX / `max25d.ini` | +| **L2 AX.25** | UI = no ARQ; I-frames optional ARQ via host | `ax25_codec.py` | +| **L1 Modem** | CRC-16-CCITT mandatory; optional repeat/FEC blocks | `hdlc_codec.py` — CRC done | +| **PHY** | Bell 202 AFSK (P0) | `afsk_*` | + +--- + +## Half-duplex (CB default) + +| Parameter | Value | INI / host | +|-----------|-------|------------| +| Duplex | half | `[modem] duplex = half` | +| CSMA persist | 255 | KISS `0x02` / CB profile | +| Extra TX delay | 150 ms class | `extra_delay_ms` / `EXTRADELAY` | +| Max UI info | **≤128 bytes** recommended | Application | +| PTT | explicit | No VOX on CB | + +### Retransmission strategy (no L2 ARQ on UI) + +1. Prefer **N small identical beacons** over one large burst. +2. Stagger dual-radio sites (`ax25_auto_interval` + offset). +3. Optional L1: **2–3 repeats** of same short codeword vs one RS block >256 B. + +--- + +## Full-duplex + +| Parameter | Value | +|-----------|-------| +| `duplex` | `full` | +| `EXTRADELAY` | `0` | +| FEC | Stronger codes allowed when bandwidth permits | +| Audio | Full-duplex sound card; echo control required | + +--- + +## Reference protocols (not on-air targets) + +| Protocol | Lesson for CRDOP | +|----------|------------------| +| **FX.25** | RS wrapper around AX.25; bit-stuffing hurts naive FEC | +| **IL2P** | No bit-stuffing; packet-sync scramble — reference for future L2 | +| **VARA DSP** | Multi-stage FEC — PHY reference only | + +--- + +## Planned L1 FEC (post P0 on-air) + +| Requirement | Target | +|-------------|--------| +| Block size | ≤64 B payload + CRC per repeat unit | +| Latency | <300 ms added on 1200 bd half-duplex | +| Compatibility | Plain AX.25 receivers must not break (FX.25-style optional path) | +| Test | AWGN + FM clip simulator before on-air | + +### INI keys (future) + +```ini +[fec] +enabled = no # default off v1.0.0 +repeat_count = 0 # 0 = no repeat; 2–3 for CB marginal +profile = half # half | full +``` + +--- + +## CB 1200 parameters (normative operator) + +| Parameter | Value | +|-----------|-------| +| Baud | 1200 | +| Modulation | Bell 202 (1200/2200 Hz) | +| Frame | AX.25 UI | +| persist | 255 | +| Beacon | 300 s + stagger (site INI) | + +--- + +## Implementation checklist + +- [x] CRC-16 in `hdlc_codec.py` +- [x] Duplex INI keys in `crdop.ini.example` +- [ ] `fec.enabled` INI + launcher +- [ ] Offline BER vs frame-length simulator +- [ ] Document measured CB SNR thresholds (field) diff --git a/stacks/crdop/docs/G3RUH-DESIGN.md b/stacks/crdop/docs/G3RUH-DESIGN.md new file mode 100644 index 0000000..ae1a164 --- /dev/null +++ b/stacks/crdop/docs/G3RUH-DESIGN.md @@ -0,0 +1,72 @@ +# G3RUH FSK — CRDOP P1 design spec + +**Scope:** 9600–**19200** baud direct FSK. **Hard maximum 19200** — nothing above. + +**Prerequisite:** P0 on-air (1200 AFSK) stable. **Not** CB FM mic path. + +See [ROADMAP.md](../ROADMAP.md) P1 milestone (stack scheduling: **DEV-Level 4**). + +--- + +## PHY difference from P0 + +| | 1200 AFSK (P0) | G3RUH FSK (P1) | +|---|----------------|----------------| +| Modulation | Audio tones 1200/2200 Hz | Scrambled baseband FSK | +| RF path | Mic / speaker / line | **Direct** varactor + discriminator | +| CB FM voice channel | ✅ | ❌ | +| VHF backbone | Access only at 1200 | ✅ at 9600–19200 | + +--- + +## Parameters (normative) + +| Baud | Peak deviation | RF bandwidth (~-60 dB) | +|------|----------------|------------------------| +| 4800 | ±1.5 kHz | ~8 kHz | +| **9600** | **±3 kHz** | **~20 kHz** | +| **19200** | **±6 kHz** | **~30 kHz** | + +CRDOP implements **up to 19200** only. + +--- + +## Radio interface requirements + +| Requirement | Detail | +|-------------|--------| +| TX | Direct modulation input (varactor), not acoustic | +| RX | Discriminator / data output, wide IF | +| Filter | Flat amplitude 10 Hz–5 kHz, constant group delay | +| Scrambler | G3RUH compatible — interoperate with standard 9600 peers | + +Standard narrow FM voice audio **fails** eye-diagram tests at 9600+. + +--- + +## Software modules (planned) + +| Module | Path | Status | +|--------|------|--------| +| Scrambler | `stacks/crdop/lib/g3ruh_scrambler.py` | Not started | +| FSK modulator | `stacks/crdop/lib/g3ruh_modulator.py` | Not started | +| FSK demodulator | `stacks/crdop/lib/g3ruh_demodulator.py` | Not started | +| HDLC/framing | Reuse `hdlc_codec.py` | Done | + +Host protocol unchanged — same M25/KISS TCP; baud selected via INI `speed_baud` (future). + +--- + +## P1 exit criteria + +1. Loopback with recorded baseband fixtures at 9600. +2. Over-the-air exchange with reference 9600 FSK peer. +3. 19200 optional stretch after 9600 stable. + +--- + +## Out of scope + +- 38400+ satellite/backbone rates +- Acoustic coupling at 9600+ +- CB 27 MHz deployment diff --git a/stacks/crdop/docs/HARDWARE-INTERFACE.md b/stacks/crdop/docs/HARDWARE-INTERFACE.md new file mode 100644 index 0000000..9c36e21 --- /dev/null +++ b/stacks/crdop/docs/HARDWARE-INTERFACE.md @@ -0,0 +1,194 @@ +# CRDOP hardware interface specification + +Generic specification for builders who manufacture **radio ↔ sound-card interfaces** for CRDOP (MAX25-SoftModem). **No product-specific wiring examples** — apply these rules to your own design. + +**CRDOP** = **MAX25-SoftModem**. Project rule: [docs/CRDOP.md](../../../docs/CRDOP.md). + +--- + +## Scope + +CRDOP connects a host sound card to a transceiver via one of: + +| Path | Typical use | Max practical baud | +|------|-------------|-------------------| +| **Audio AFSK** | Mic IN / speaker OUT / line level | **1200** (P0) | +| **Direct FSK** | Varactor TX + FM discriminator RX | **9600–19200** (P1) | +| **SSB audio** | USB/LSB suppressed carrier | **300** (P2 optional) | +| **Acoustic coupling** | Speaker ↔ microphone (test / field) | **1200** | + +RF path matrix and delivery milestones: [ROADMAP.md](../ROADMAP.md). + +--- + +## Audio levels + +### Line level (preferred) + +| Signal | Typical level | Notes | +|--------|---------------|-------| +| **Playback → radio mic IN** | −20 to −10 dBV (adjust per rig sensitivity) | Use potentiometer or fixed attenuator; verify with sniffer or oscilloscope | +| **Radio speaker/line → capture** | 0.1–1.0 V RMS typical | Attenuate hot speaker outputs before codec input | +| **Full-scale digital** | Avoid clipping on TX and RX | Leave 6–12 dB headroom | + +### Mic level + +Some transceivers expose mic-level inputs only. Attenuate line output to mic sensitivity (often 5–20 mV). Use coupling capacitor if DC bias is present on the mic jack. + +### Isolation + +| Method | Purpose | +|--------|---------| +| **Audio transformer** (1:1 or step-down) | Galvanic isolation; breaks ground loops | +| **Opto-isolator** (digital PTT path) | Isolate PTT from radio ground | +| **Separate ground reference** | Star ground at interface PCB; one tie point to radio chassis if required | + +**Ground loops** cause hum, false transitions, and decode failures. Always isolate PC USB ground from radio chassis when using direct cable coupling. + +--- + +## PTT control + +CRDOP is **half-duplex by default**. The host must key the transmitter before playback and unkey after tail. + +| Method | Interface | Caveats | +|--------|-----------|---------| +| **GPIO** | USB-serial RTS/DTR, parallel port, dedicated GPIO | Cleanest; software-controlled timing | +| **Serial CAT** | Radio command port | Latency depends on radio firmware | +| **VOX** | Audio-derived keying | **Not recommended** for packet — tail timing unpredictable; false keys from noise | +| **Manual** | Operator PTT | Acceptable for bench only | + +### Timing parameters + +Configure in `crdop.ini` / `max25d.ini`: + +| Parameter | Typical half-duplex | Full-duplex | +|-----------|--------------------|-------------| +| Pre-TX delay (`extra_delay_ms`) | 80–200 ms | 0 | +| TXTAIL | Match radio requirement | N/A | +| PTT release | After last space tone + margin | N/A | + +**Rule:** explicit GPIO/serial PTT beats VOX for AX.25 UI bursts. + +--- + +## Acoustic coupling + +Valid for **bench validation** and some field setups: + +``` +[Host speaker] ──air gap──► [Radio mic] +[Radio speaker] ──air gap──► [Host mic] +``` + +| Principle | Detail | +|-----------|--------| +| **Distance** | Minimize path loss; foam gasket reduces room noise | +| **Frequency response** | FM voice channel ≈ 3 kHz — sufficient for 1200/2200 Hz Bell 202 | +| **Level** | Low volume on playback; avoid speaker distortion | +| **Duplex** | Half-duplex only — acoustic feedback if TX and RX paths overlap | +| **Test** | Run `max25-signal-sniffer --loopback` on host before acoustic trial | + +Acoustic coupling does **not** scale to G3RUH direct FSK (P1). + +--- + +## Direct FSK path (P1 — G3RUH) + +When audio AFSK is insufficient (9600–19200 baud): + +| Element | Requirement | +|---------|-------------| +| **TX** | Discriminator input bypassed; varactor or dedicated FSK input on data port | +| **RX** | Flat FM discriminator output (de-emphasis off or compensated) | +| **Bandwidth** | ~20 kHz IF for 9600; wider for 19200 | +| **Scrambler** | G3RUH polynomial — implemented in future `lib/` module | +| **Radio** | Data-port capable; standard FM mic/speaker path **not** sufficient | + +CB FM rigs at 27 MHz are generally **not** direct-FSK capable at 9600+ — P1 targets VHF/UHF amateur backbone paths. + +--- + +## Sound card requirements + +| Requirement | 1200 baud (P0) | 9600–19200 (P1) | +|-------------|----------------|-----------------| +| **Sample rate** | ≥ 48 kHz recommended | ≥ 48 kHz; 96 kHz preferred | +| **Bit depth** | 16-bit mono | 16-bit mono | +| **Duplex** | Full-duplex USB codec or half-duplex with PTT | Full-duplex, low jitter | +| **Clock** | Stable crystal; avoid drift during TX | Jitter < 50 ppm class | +| **Driver** | Kernel ALSA (`hw:` / `plughw:`) | Same | +| **Isolation** | Transformer on radio side | Same + verified loopback | + +### Unsuitable interfaces + +- Onboard laptop mic/speaker without external interface +- Bluetooth audio bridges +- PulseAudio / PipeWire virtual devices +- Consumer "USB sound sticks" without galvanic isolation + +Configure devices in `crdop.ini`: + +```ini +[audio] +backend = alsa-kernel +no_pulse = yes +capture = plughw:1,0 +playback = plughw:1,0 +sample_rate = 48000 +``` + +Verify with: + +```bash +max25-signal-sniffer --loopback +max25-signal-sniffer -D plughw:1,0 -t 2.0 +``` + +--- + +## Compliance and operator responsibility + +| Topic | Policy | +|-------|--------| +| **Transmit licence** | Operator must hold valid authorization for the band and mode | +| **CB (27 MHz EU)** | EN 300 433 / national rules — voice primary; data may be restricted or channel-specific | +| **Amateur bands** | Band plan, power limits, identification | +| **EMC** | Builder responsible for conducted/radiated emissions from interface hardware | +| **CRDOP software** | GPLv3 — no warranty; see [LICENSE-USAGE.md](LICENSE-USAGE.md) | + +CRDOP documentation does **not** constitute legal advice. Check local regulations before on-air operation. + +--- + +## Acceptance checklist (builder) + +| Step | Pass criterion | +|------|----------------| +| Loopback | `max25-signal-sniffer --loopback` decodes test UI | +| Level | Mark/space tones within radio spec; no clipping | +| Isolation | No hum on decode when PC and radio share mains | +| PTT | Key before TX audio; clean tail; no truncation | +| On-air | AX.25 UI exchange with known-good peer at 1200 baud | +| Duplex | Half-duplex CSMA behaviour matches `persist` / `extra_delay` INI | + +--- + +## Related in-repo docs + +| Topic | Document | +|-------|----------| +| Sound-card requirements | [SOUNDCARD-QUALIFICATION.md](SOUNDCARD-QUALIFICATION.md) | +| Bell 202 / 1200 AFSK (P0) | [ROADMAP.md](../ROADMAP.md) · [SOFTMODEM.md](SOFTMODEM.md) | +| G3RUH 9600–19200 (P1) | [G3RUH-DESIGN.md](G3RUH-DESIGN.md) | +| AX.25 / packet context | [docs/PACKET-RADIO.md](../../../docs/PACKET-RADIO.md) | + +--- + +## Related + +| Doc | Topic | +|-----|--------| +| [AUDIO-ARCHITECTURE.md](AUDIO-ARCHITECTURE.md) | Kernel ALSA, sound-proxy | +| [MAX25-USAGE.md](MAX25-USAGE.md) | Stack integration and bench modes | +| [ROADMAP.md](../ROADMAP.md) | P0/P1/P2 phases | diff --git a/stacks/crdop/docs/HOST-PROTOCOL-SPEC.md b/stacks/crdop/docs/HOST-PROTOCOL-SPEC.md new file mode 100644 index 0000000..d45c7e5 --- /dev/null +++ b/stacks/crdop/docs/HOST-PROTOCOL-SPEC.md @@ -0,0 +1,103 @@ +# M25 SoftModem host protocol — implementation spec (frozen for v1.0.0) + +**CRDOP** = **MAX25-SoftModem** · Native host wire for MAX25-SoftModem · **Not** ARDOP FEC/ARQ. + +**Normative code:** `stacks/crdop/lib/m25_host_protocol.py` · **Consumer:** `max25d` `CrdopTcpBackend`, `audio-dummyd`, `crdopc` launcher. + +--- + +## Transport + +| Channel | Default port | Wire | +|---------|--------------|------| +| **Control** | **8515** | Line-oriented ASCII, **`\n`** terminated (native) | +| **Data** | **8516** (= ctrl + 1) | Binary AX.25 UI body **without** HDLC flags or FCS | + +| Mode | Control terminator | Data semantics | +|------|-------------------|----------------| +| **Native M25/KISS** | `\n` | KISS DATA equivalent — 16+ byte UI body | + +### Bench alternate ports + +When `soft-crdop` and `audio-dummyd` run on one host, bench daemon may use **8520/8521** via CLI. Production `crdopc` and INI default **8515/8516**. + +--- + +## Control commands (native) + +All commands are single lines. Reply is one line ending `\n`. + +| Command | Args | Reply | Behaviour | +|---------|------|-------|-----------| +| `INITIALIZE` | — | `OK` | Reset session state | +| `PROTOCOLMODE` | `KISS` | `OK` | Select KISS-semantics data channel | +| `MYCALL` | `CALL-SSID` | `OK` | Store station ID (uppercase) | +| `LISTEN` | `TRUE`/`FALSE` | `OK` | RX enable (default true) | +| `PING` | — | `OK` | Liveness | +| `STATUS` | — | `STATUS ready mycall=…` | State snapshot | +| *(unknown)* | — | `ERR unknown command` | | + +**Not supported on native path:** `PROTOCOLMODE FEC`, `FECMODE`, ARQ bandwidth commands — ARDOP is a separate optional plugin, not part of CRDOP. + +--- + +## Data channel + +### TX (host → modem) + +1. TCP connect to data port. +2. Send **one** AX.25 UI frame body: `dest(7) + src(7) + ctrl(1) + pid(1) + info(0..256)`. +3. Modem responds `OK` or `ERR …` (ASCII line). + +Modem builds on-air: HDLC flags, bit-stuffing, frequency-toggle line code, AFSK PHY — see `acoustic_engine.encode_ax25_ui()`. + +### RX (modem → host) + +Native bench: optional `STATUS` lines on control channel. Production: `max25d` parses demodulated UI and displays `[CRDOP AX25 UI src>dst] text`. + +--- + +## max25d integration + +```ini +[devices] +soft-crdop = crdop:default + +[device.soft-crdop] +host = 127.0.0.1 +port = 8515 +listen = yes +``` + +`CrdopTcpBackend` on `open()`: + +1. TCP connect `host:port` (ctrl), `host:port+1` (data). +2. Send `INITIALIZE`, `PROTOCOLMODE KISS`, `MYCALL …`, `LISTEN TRUE` (each `\n`). + +--- + +## Layer separation + +| Layer | On data TCP | On air | +|-------|-------------|--------| +| AX.25 UI body | ✅ | Inside HDLC | +| HDLC + CRC-16 | ❌ | ✅ | +| Bell 202 AFSK | ❌ | ✅ | + +Same rule as KISS `0x00` DATA frames in `kiss_bridge.py`. + +--- + +## Compliance tests + +| Test | Command | +|------|---------| +| Unit | `pytest stacks/daemon/test_crdop_backend.py` | +| Bench host | `python3 stacks/crdop/tools/audio-dummyd.py --ctrl-port 8515 --data-port 8516` | +| Loopback DSP | `pytest stacks/crdop/test_bell202_line_code.py` | + +--- + +## Version + +Frozen for **MAX25-Stack v1.0.0** (dev track `CUR999`). Changes require bump in this file + `PROTOCOL.md` + `test_crdop_backend.py`. diff --git a/stacks/crdop/docs/INDEX.md b/stacks/crdop/docs/INDEX.md new file mode 100644 index 0000000..91a75b4 --- /dev/null +++ b/stacks/crdop/docs/INDEX.md @@ -0,0 +1,77 @@ +# CRDOP documentation index + +**CRDOP** = stack acronym for **MAX25-SoftModem** (device id `soft-crdop`). Product: **MAX25-SoftModem** (`stacks/crdop/`). Version: MAX25-Stack **v1.0.0**; dev track `CUR999` in `$SRC/stacks/crdop/VERSION`. + +**Project rule (authoritative):** [docs/CRDOP.md](../../../docs/CRDOP.md) +**AI agent map:** [DEVELOPMENT.md](../../../docs/DEVELOPMENT.md) (repo root) + +--- + +## Start here + +| Audience | Doc | One-line purpose | +|----------|-----|------------------| +| Everyone | [SOFTMODEM.md](SOFTMODEM.md) | What CRDOP is — baud scope, duplex, ALSA path | +| MAX25 operator | [MAX25-USAGE.md](MAX25-USAGE.md) | Build, `max25d`, plugins, terminal, HyBBX attach | +| Developer | [DEVELOPER.md](DEVELOPER.md) | Source tree, modules, tests, extending the modem | +| Hardware builder | [HARDWARE-INTERFACE.md](HARDWARE-INTERFACE.md) | Generic radio/audio interface specification | +| Legal / distribution | [LICENSE-USAGE.md](LICENSE-USAGE.md) | GPLv3 — private and commercial use rights | + +## Implementation specs (v1.0.0) + +| Doc | One-line purpose | +|-----|------------------| +| [HOST-PROTOCOL-SPEC.md](HOST-PROTOCOL-SPEC.md) | **Frozen** M25/KISS TCP :8515/:8516 — normative host wire | +| [ACOUSTIC-TEST-PROTOCOL.md](ACOUSTIC-TEST-PROTOCOL.md) | P0 bench acceptance matrix | +| [FEC-SPEC.md](FEC-SPEC.md) | Duplex + FEC strategy — implementable parameters | +| [SOUNDCARD-QUALIFICATION.md](SOUNDCARD-QUALIFICATION.md) | Interface qualification tests (class A/B/C) | +| [G3RUH-DESIGN.md](G3RUH-DESIGN.md) | P1 — 9600–19200 direct FSK (DEV-Level 4) | + +--- + +## Technical reference + +| Doc | One-line purpose | +|-----|------------------| +| [AUDIO-ARCHITECTURE.md](AUDIO-ARCHITECTURE.md) | Kernel ALSA only — sound-proxy, no PulseAudio | +| [PROTOCOL.md](PROTOCOL.md) | M25 host summary — **normative:** [HOST-PROTOCOL-SPEC.md](HOST-PROTOCOL-SPEC.md) | +| [CONFIG.md](CONFIG.md) | `crdop.ini` keys and launcher behaviour | +| [BUILD.md](BUILD.md) | Standalone CMake build and cross-compile | +| [EXAMPLES.md](EXAMPLES.md) | INI profiles, launcher, `max25d` snippets | +| [CHANGELOG.md](CHANGELOG.md) | Release notes | + +--- + +## Planning + +| Doc | One-line purpose | +|-----|------------------| +| [ROADMAP.md](../ROADMAP.md) | P0/P1/P2 modulation, RF paths, FEC, delivery milestones | + +--- + +## Plugin registry + +| Id | Type | Role | +|----|------|------| +| `soft-crdop` | device | Production sound-card modem (`soft-modems` hardware) | +| `audio-dummy` | device | Acoustic bench — loopback / ALSA / host (`acoustic-bench`) | +| `soft-modems` | hardware | CRDOP device family | +| `acoustic-bench` | hardware | Dev/test bench — no RF required | +| `ardop-plugin` | optional | Third-party ARDOP host registry (separate from CRDOP) | + +--- + +## Install tree (MAX25-Stack build) + +| Path | Contents | +|------|----------| +| `bin/crdop` | Launcher (`scripts/crdopc`) | +| `bin/audio-dummyd` | M25 host + acoustic engine | +| `bin/max25-signal-sniffer` | Bell 202 analysis tool | +| `share/crdop/` | INI examples, `lib/*.py`, `VERSION` | +| `share/hybbx/crdop-host.ini.example` | HyBBX Secondary attach | + +--- + +*Compact index — depth in linked docs. Roadmap: [ROADMAP.md](../ROADMAP.md).* diff --git a/stacks/crdop/docs/LICENSE-USAGE.md b/stacks/crdop/docs/LICENSE-USAGE.md new file mode 100644 index 0000000..84ad5ff --- /dev/null +++ b/stacks/crdop/docs/LICENSE-USAGE.md @@ -0,0 +1,116 @@ +# CRDOP license and usage rights + +**CRDOP** (MAX25-SoftModem) is **free software** under the **GNU General Public License version 3** (GPL-3.0-or-later). + +License files: + +| File | Scope | +|------|-------| +| [stacks/crdop/LICENSE](../LICENSE) | CRDOP subproject | +| [LICENSE](../../../LICENSE) | MainAX25-Stack repository root | + +--- + +## Summary (not legal advice) + +GPLv3 grants everyone — **private individuals and commercial entities** — the right to: + +| Right | Meaning | +|-------|---------| +| **Use** | Run CRDOP for any purpose (personal, club, business, government) | +| **Study** | Read and learn from source code | +| **Modify** | Change code to suit your needs | +| **Distribute** | Share originals or modified versions | +| **Commercial use** | Sell services, products, or support built on CRDOP | + +**Copyleft obligation:** When you **distribute** CRDOP (or a combined work based on it), you must: + +1. Provide **corresponding source** (or a written offer valid ≥ 3 years). +2. License derivatives under **GPLv3** (or later, per "or later" clause). +3. Preserve copyright notices and license text. +4. Document changes if you modify files. +5. Pass through patent retaliation and anti-tivoization terms as GPLv3 requires. + +**No warranty:** Software is provided **AS IS** — no implied fitness for a particular purpose. + +--- + +## Private use + +| Scenario | Obligation | +|----------|------------| +| Run CRDOP on your own computer | None beyond GPLv3 acceptance | +| Modify for personal use only | No distribution → no source-offer obligation | +| Internal company use (no binary redistribution) | Same as private — no copyleft trigger until you distribute | + +--- + +## Commercial use + +| Scenario | Obligation | +|----------|------------| +| Sell pre-built PCs with CRDOP installed | Provide source (or written offer) to recipients | +| Ship hardware interface **with** CRDOP binaries | GPL applies to distributed software; hardware schematics you create separately may use another license if they are not a derivative of the GPL code | +| SaaS / hosted service (no binary to users) | AGPL would impose network copyleft — **CRDOP is GPL, not AGPL**; consult counsel for your deployment model | +| OEM integration in a product | Ensure GPL compliance for all distributed GPL components; document how users obtain source | + +**Practical path:** Include `LICENSE`, offer source at your support URL or ship `stacks/crdop/` tree, and state "based on CRDOP (GPLv3)". + +--- + +## Combined works (MAX25-Stack) + +MAX25-Stack bundles CRDOP with daemon, terminal, and plugins. Distribution of the **combined stack** triggers GPLv3 obligations for the GPL-licensed portions. Other components may carry their own licenses — check root `LICENSE` and per-directory notices. + +| Component | Typical license | +|-----------|-----------------| +| `stacks/crdop/` | GPL-3.0-or-later | +| MAX25 daemon / terminal | GPL-3.0-or-later (repository default) | + +--- + +## What you may not do + +| Restriction | Reason | +|-------------|--------| +| Relicense CRDOP under proprietary terms | GPLv3 copyleft | +| Remove copyright / license notices | GPLv3 §4 | +| Imply warranty or liability from authors | GPLv3 §15–16 | +| Distribute without source (when distributing binaries) | GPLv3 §6 | + +Patent suits against GPL users can terminate your license (GPLv3 §8). + +--- + +## ARDOP-plugin (separate) + +**ARDOP-plugin** is an optional MAX25-Stack registry entry — **not** part of the CRDOP GPLv3 tree. + +| Item | Policy | +|------|--------| +| CRDOP coupling | **None** — third-party ARDOP host software | +| Registry | [plugins/external/ardop/README.md](../../../plugins/external/ardop/README.md) | + +CRDOP GPLv3 obligations apply to **CRDOP code**, not to operator ARDOP host software. + +--- + +## Contributor grants + +By contributing to `stacks/crdop/`, you agree that contributions are licensed under **GPL-3.0-or-later**, consistent with the repository [CONTRIBUTING.md](../../../CONTRIBUTING.md). + +--- + +## Full license text + +- Online: <https://www.gnu.org/licenses/gpl-3.0.html> +- In tree: [stacks/crdop/LICENSE](../LICENSE) · [LICENSE](../../../LICENSE) + +--- + +## Related + +| Doc | Topic | +|-----|--------| +| [docs/CRDOP.md](../../../docs/CRDOP.md) | Project rule §5 License | +| [INDEX.md](INDEX.md) | Documentation index | diff --git a/stacks/crdop/docs/MAX25-USAGE.md b/stacks/crdop/docs/MAX25-USAGE.md new file mode 100644 index 0000000..616093c --- /dev/null +++ b/stacks/crdop/docs/MAX25-USAGE.md @@ -0,0 +1,292 @@ +# CRDOP in MAX25-Stack — usage guide + +Complete operator and integrator workflow for **MAX25-SoftModem (CRDOP)** inside MainAX25-Stack. + +**CRDOP** = stack acronym for **MAX25-SoftModem** (device id `soft-crdop`). Project rule: [docs/CRDOP.md](../../../docs/CRDOP.md). + +**Versions:** MAX25-Stack **v1.0.0** · CRDOP dev track **CUR999** (`$SRC/stacks/crdop/VERSION`). + +**Dependency:** CRDOP sources are standalone-capable in principle, but **MAX25-Stack is required** for build, `max25d`, INI, operator tooling, and plugin registry until **CRDOP-v1.0.0** marks a mature standalone release. + +--- + +## Quick start + +```bash +# 1. Build (CRDOP ON by default) +./scripts/build.sh + +# 2. Configure daemon +sudo cp share/max25/max25d.ini.example /etc/max25/max25d.ini +# Edit: enable soft-crdop block (see below) + +# 3. Start stack +./scripts/max25-ctl start --hardware soft-modems --device soft-crdop + +# 4. Terminal session +max25-terminal -U /run/max25/modem.sock +# or TCP: max25-terminal -H 127.0.0.1 -P 7325 +``` + +Inside terminal: `SET DEVICE soft-crdop` → `CONNECT` → `SEND …` + +--- + +## Build and install + +### CMake options + +| Option | Default | Effect | +|--------|---------|--------| +| `MAX25_BUILD_CRDOP` | **ON** | Build/install CRDOP scaffold + tools | +| `MAX25_BUILD_DAEMON` | ON | `max25d` with `CrdopTcpBackend` | +| `MAX25_BUILD_TERMINAL` | ON | `max25-terminal` / `max25-client` | +| `CRDOP_VENDOR_ARDOPCF` | OFF | Dev-only legacy ARDOP — **never** in releases | + +Disable CRDOP only when not needed: + +```bash +cmake -B build -DMAX25_BUILD_CRDOP=OFF +cmake --build build -j$(nproc) +``` + +### Install tree + +After `cmake --install` (or `./scripts/build.sh` + local prefix): + +| Path | Contents | +|------|----------| +| `bin/crdop` | Launcher — starts `audio-dummyd` with INI | +| `bin/audio-dummyd` | M25 host TCP daemon + acoustic engine | +| `bin/max25-signal-sniffer` | Bell 202 analysis | +| `share/crdop/crdop.ini.example` | CB profile template | +| `share/crdop/crdop-dual.ini.example` | CB ↔ amateur preset | +| `share/crdop/crdop-amateur.ini.example` | Amateur secondary preset | +| `share/crdop/lib/*.py` | DSP modules (installed for tooling) | +| `share/crdop/VERSION` | `CUR999` | +| `share/hybbx/crdop-host.ini.example` | HyBBX Secondary attach | +| `share/clients/soft-crdop.yaml` | Terminal device profile | + +--- + +## Plugin model + +| Plugin id | Type | Hardware family | Role | +|-----------|------|-----------------|------| +| `soft-crdop` | device | `soft-modems` | Production sound-card modem | +| `audio-dummy` | device | `acoustic-bench` | Bench — loopback / ALSA / host | +| `soft-modems` | hardware | — | CRDOP device family | +| `acoustic-bench` | hardware | — | Dev/test without RF | + +Registry: `plugins/manifest.yaml`. Discovery: + +```bash +./scripts/discover-plugins.sh --json | jq '.devices[] | select(.id|test("crdop|audio-dummy"))' +``` + +--- + +## max25d configuration + +### Production path — `soft-crdop` + +```ini +[daemon] +hardware = soft-modems +device = soft-crdop + +[devices] +default = soft-crdop +soft-crdop = crdop:default + +[device.soft-crdop] +host = 127.0.0.1 +port = 8515 +listen = yes +``` + +`max25-ctl start` launches `crdop` (→ `audio-dummyd`) when `auto_start = yes` in `[stack]`. + +### Bench path — `audio-dummy` + +No RF; validates DSP loopback or ALSA capture: + +```ini +[daemon] +hardware = acoustic-bench +device = audio-dummy + +[devices] +default = audio-dummy +audio-dummy = audio:loopback +``` + +Modes for `audio-dummy` spec: + +| Spec | Behaviour | +|------|-----------| +| `audio:loopback` | Internal DSP encode/decode | +| `audio:alsa:plughw:N,M` | Sniff live ALSA capture | +| `audio:host` | Attach to running `audio-dummyd` on :8515 | + +Start bench: + +```bash +./scripts/max25-ctl start --hardware acoustic-bench --device audio-dummy +max25-terminal -U /run/max25/modem.sock +``` + +### Sniffer (no daemon) + +```bash +max25-signal-sniffer --loopback +max25-signal-sniffer -D plughw:1,0 -t 3.0 +max25-signal-sniffer --mark # 1200 Hz calibration tone +max25-signal-sniffer --space # 2200 Hz calibration tone +``` + +--- + +## crdop.ini (modem side) + +Copy and edit: + +```bash +mkdir -p ~/.config/crdop +cp share/crdop/crdop.ini.example ~/.config/crdop/crdop.ini +``` + +Key sections — full reference: [CONFIG.md](CONFIG.md). + +```ini +[profile] +radio_profile = cb ; cb | dual | amateur + +[modem] +duplex = half ; half | full +arq_bandwidth = 500MAX + +[mycall] +call = CB01-0 + +[audio] +backend = alsa-kernel +no_pulse = yes +capture = plughw:1,0 +playback = plughw:1,0 + +[host] +port = 8515 +``` + +Launch: + +```bash +CRDOP_INI=~/.config/crdop/crdop.ini ./scripts/crdopc +# or after install: +crdop +``` + +--- + +## max25-terminal / M25/1 + +CRDOP uses the same M25/1 session flow as other `max25d` device backends: + +``` +SET DEVICE soft-crdop +CONNECT +SEND Hello packet world +``` + +| Item | Value | +|------|-------| +| Daemon socket | `/run/max25/modem.sock` (Linux) | +| Daemon TCP | `7325` (configurable in `max25d.ini`) | +| Modem host TCP | `8515` ctrl, `8516` data (CRDOP native) | +| Client profile | `share/clients/soft-crdop.yaml` | + +Protocol reference: [PROTOCOL.md](PROTOCOL.md) · [include/max25/protocol.md](../../../include/max25/protocol.md). + +When `ax25_ui = yes` in `max25d.ini`, terminal `SEND` lines encode as AX.25 UI frames. + +--- + +## HyBBX attach + +HyBBX is **external** — attach after MAX25 stack is up. + +1. Start CRDOP path: + + ```bash + ./scripts/max25-ctl start --hardware soft-modems --device soft-crdop + ``` + +2. Merge `share/hybbx/crdop-host.ini.example` into HyBBX `hybbx.ini` on Secondary: + + ```ini + [networks] + crdop = yes + + [transport.crdop1] + enabled = yes + modem_host = 127.0.0.1 + modem_port = 8515 + mycall = CB-0 + listen = yes + circuit_host = main.example.com + circuit_port = 7323 + ``` + +MAX25 owns modem lifecycle; HyBBX owns sessions. See [docs/HYBBX.md](../../../docs/HYBBX.md). + +--- + +## Operator profiles + +| INI template | Profile | Use | +|--------------|---------|-----| +| `crdop.ini.example` | CB | Primary — K24/K25 class channels | +| `crdop-dual.ini.example` | dual | CB ↔ amateur switching | +| `crdop-amateur.ini.example` | amateur | Amateur secondary | + +```bash +CRDOP_INI=share/crdop-dual.ini.example ./scripts/crdopc +``` + +--- + +## Testing in MAX25-Stack + +```bash +./scripts/test.sh # cmake --build build --target max25_test +cmake --build build --target max25_daemon_smoke +./scripts/release-check.sh # install + policy checks +``` + +CRDOP-specific offline tests: `test_crdop_backend.py`, `test_audio_dummy_backend.py`, `test_bell202_line_code.py`. + +--- + +## Troubleshooting + +| Symptom | Check | +|---------|-------| +| `crdopc not found` | Run `./scripts/build.sh`; launcher uses `audio-dummyd` from install or source tree | +| No decode | `max25-signal-sniffer --loopback`; verify ALSA devices in INI | +| PulseAudio hijack | `no_pulse = yes`; use `hw:` / `plughw:` explicitly | +| Port conflict | Default :8515/:8516 — change `[host] port` + `[device.soft-crdop] port` | +| Terminal no device | `SET DEVICE soft-crdop`; check `[devices]` in `max25d.ini` | + +--- + +## Related + +| Doc | Topic | +|-----|--------| +| [INDEX.md](INDEX.md) | Full doc table | +| [DEVELOPER.md](DEVELOPER.md) | Source, modules, extending | +| [HARDWARE-INTERFACE.md](HARDWARE-INTERFACE.md) | Radio interface spec | +| [docs/PLUGINS-DEVICE-MODEL.md](../../../docs/PLUGINS-DEVICE-MODEL.md) | Unified device workflow | +| [docs/LINUX-HOST-SETUP.md](../../../docs/LINUX-HOST-SETUP.md) | Host prerequisites | +| [ROADMAP.md](../ROADMAP.md) | P0/P1/P2 milestones and modulation priorities | diff --git a/stacks/crdop/docs/PROTOCOL.md b/stacks/crdop/docs/PROTOCOL.md new file mode 100644 index 0000000..2d28bd4 --- /dev/null +++ b/stacks/crdop/docs/PROTOCOL.md @@ -0,0 +1,36 @@ +# Protocol + +Normative wire: **[HOST-PROTOCOL-SPEC.md](HOST-PROTOCOL-SPEC.md)** (frozen v1.0.0). + +CRDOP (**MAX25-SoftModem**) uses the **native MAX25 M25/KISS host protocol** on TCP :8515 (control) / :8516 (data). + +## Stack (default) + +``` +[Radio] ↔ [audio/ALSA] ↔ audio-dummyd / native modem ↔ TCP :8515 ctrl, :8516 data ↔ max25d +``` + +## Over-the-air + +AX.25-compatible AFSK tones (1200 baud primary, up to 19200 baud design range). See [SOFTMODEM.md](SOFTMODEM.md). + +## Host TCP (native M25) + +| Port | Role | +|------|------| +| 8515 | Control (line-oriented ASCII, `\n` terminated) | +| 8516 | Data (AX.25 UI body without HDLC/FCS) | + +Common commands: `INITIALIZE` · `PROTOCOLMODE KISS` · `MYCALL` · `LISTEN` · `STATUS` · `PING` + +Implementation reference: `stacks/crdop/lib/m25_host_protocol.py` + +## CRDOP launch presets + +| Profile | Role | +|---------|------| +| `cb` | Default — CB / 500MAX class | +| `dual` | CB ↔ amateur turnaround | +| `amateur` | Amateur bandwidth class | + +Runtime overrides via host TCP or `crdop.ini` always apply. diff --git a/stacks/crdop/docs/README.md b/stacks/crdop/docs/README.md new file mode 100644 index 0000000..4a763a2 --- /dev/null +++ b/stacks/crdop/docs/README.md @@ -0,0 +1,31 @@ +# CRDOP documentation + +**CRDOP** = stack acronym for **MAX25-SoftModem** (device id `soft-crdop`). + +**MAX25-SoftModem** — in-house sound-card modem subproject (`stacks/crdop/`). + +## Entry point + +**[INDEX.md](INDEX.md)** — complete table of all CRDOP docs with one-line purpose. + +Project rule: [docs/CRDOP.md](../../../docs/CRDOP.md) + +## Policy + +| Rule | | +|------|--| +| Standard | Native MAX25 M25/KISS host on TCP :8515/:8516 | +| Focus | CB-first; `dual` and `amateur` are presets | +| Scope | Modem only — no host application | + +## Dev-only vendor tree + +Legacy **[pflarue/ardop](https://github.com/pflarue/ardop)** (ardopcf, MIT) may exist under `vendor/ardopcf/` for local dev builds (`-DCRDOP_VENDOR_ARDOPCF=ON`). **Never installed in MAX25 releases.** + +Pin: `a7c9228` · record: `vendor/ardopcf.ref` + +## Platforms (tested) + +Linux · FreeBSD — **tested** (GCC, Clang) + +Windows · macOS — build supported, **not yet tested** diff --git a/stacks/crdop/docs/SOFTMODEM.md b/stacks/crdop/docs/SOFTMODEM.md new file mode 100644 index 0000000..fef0a0e --- /dev/null +++ b/stacks/crdop/docs/SOFTMODEM.md @@ -0,0 +1,127 @@ +# MAX25-SoftModem — product definition + +> **Authoritative rule:** [docs/CRDOP.md](../../../docs/CRDOP.md) · **Doc index:** [INDEX.md](INDEX.md) + +## Name and role + +- **Product:** MAX25-SoftModem (stack acronym **CRDOP**; plugin id `soft-crdop`) +- **Stack version:** MAX25-Stack **v1.0.0** · **Dev track:** `CUR999` in `$SRC/stacks/crdop/VERSION` +- **Stack:** MainAX25-Stack — **standard** component (built/installed unless `MAX25_BUILD_CRDOP=OFF`) +- **Nature:** Pure MAX25 in-house development — in **development and test phase**, openly documented + +## Use like a hardware modem + +CRDOP uses the host **sound card IN and OUT** plus a **suitable radio** (or acoustic coupling to existing RF hardware). In `max25d` it registers like any other device backend — operators use the same M25/1 flow (`SET DEVICE`, `CONNECT`, `SEND`). + +``` +┌──────────┐ line or acoustic ┌─────────────┐ TCP ┌─────────┐ +│ Radio │ ◄──────────────────► │ CRDOP/ALSA │ ◄──────► │ max25d │ +└──────────┘ └─────────────┘ └─────────┘ + │ + 1200–19200 baud + half or full duplex +``` + +### Acoustic AX.25 compatibility + +On the **acoustic / RF layer**, CRDOP targets **AX.25 Packet Radio** interoperability at the PHY level: + +- **1200 baud Bell 202 AFSK** (1200/2200 Hz) — standard VHF/UHF and CB packet class +- **Half- and full-duplex** per radio and audio capability +- **Acoustic or line-level** coupling — valid test and field configuration + +CRDOP is an **alternative modem** when the sound card is the RF interface. It does not replace an existing UART/KISS path when that path is already in use. + +Interface specification for builders: [HARDWARE-INTERFACE.md](HARDWARE-INTERFACE.md). MAX25-Stack usage: [MAX25-USAGE.md](MAX25-USAGE.md). + +## Protocol stack + +| Layer | Policy | +|-------|--------| +| On-air / acoustic | AX.25-compatible AFSK tones (1200 baud primary) | +| Host to max25d | MAX25-native TCP control + data (see [PROTOCOL.md](PROTOCOL.md)) | + +We study legacy stacks; we implement our own modem and document it for **reproducible hardware builds**. + +## Sound path — kernel ALSA only + +CRDOP does **not** use PulseAudio, PipeWire-as-default, or any desktop sound server. + +| Layer | Function | +|-------|----------| +| **MAX25 sound-proxy** | Sole shim between modem DSP and ALSA — buffers, timing, duplex, xrun recovery | +| **libasound** | Direct `hw:` / `plughw:` opens | +| **Kernel ALSA** | Driver + `/dev/snd/*` | +| **Hardware** | Sound card → radio interface | + +Configure explicit devices in `crdop.ini` `[audio]` (`capture`, `playback`). See [AUDIO-ARCHITECTURE.md](AUDIO-ARCHITECTURE.md). + +## Baud scope + +| Priority | Range | Status | +|----------|-------|--------| +| **P0** | **1200 baud** Bell 202 AFSK | Primary — CB + VHF on-air | +| **P1** | **9600–19200** G3RUH direct FSK | Planned after 1200 stable (VHF backbone) | +| **P2** | **300** HF AFSK | Optional | +| — | **>19200 baud** | **Not implemented** — do not configure or expect | + +Full modulation matrix, RF paths, band context, delivery milestones: **[ROADMAP.md](../ROADMAP.md)**. + +Higher baud rates demand **proportionally better** audio hardware (sample rate, jitter, SNR, isolation). G3RUH requires a **direct FSK radio path** — not the CB FM mic/speaker route. + +## Duplex + +| Mode | Status | +|------|--------| +| **Half-duplex** | Standard CB / packet-radio (PTT or VOX-style timing) | +| **Full-duplex** | Supported when radio and audio hardware allow simultaneous TX/RX paths | + +Configure in `crdop.ini`: `[modem] duplex = half|full`. + +## Sound card — required (not optional) + +CRDOP must: + +1. **Generate** precise mark/space frequencies on **playback**. +2. **Decode** those tones reliably on **capture**. + +Requirements **scale with baud**: + +| Baud class | Audio expectation | +|------------|-------------------| +| 1200 | Stable full-duplex or half-duplex USB codec or PCI interface; galvanic isolation to radio | +| 9600–19200 | Low-jitter interface, adequate bandwidth, verified loopback before on-air | + +**Unsuitable:** onboard laptop mic/speaker shortcuts, anonymous USB “sound sticks”, Bluetooth bridges, unisolated consumer line inputs. + +Document your ALSA device in INI (`[audio] capture` / `playback`) and verify with loopback or sidetone before traffic. + +## Open hardware + +Documentation targets builders who want to **manufacture radio ↔ sound-card interfaces**: + +- Level matching (radio mic/speaker ↔ line level) +- Galvanic isolation (transformers / opto) +- PTT integration (GPIO, serial — VOX not recommended for packet) +- Acoustic coupler fixtures for bench validation + +Generic interface specification: [HARDWARE-INTERFACE.md](HARDWARE-INTERFACE.md). Delivery milestones: [ROADMAP.md](../ROADMAP.md). + +## Build / release (MAX25-Stack-v1.0.0) + +**CRDOP dev track:** `CUR999` in `$SRC/stacks/crdop/VERSION` (ships with MAX25-Stack v1.0.0). + +| Item | Shipped | +|------|---------| +| CRDOP scaffold (INI, launcher, install) | **Yes** — default CMake | +| `max25d` `crdop-tcp` backend | **Yes** — offline-tested | +| Native `crdopc` DSP binary | **In development** — in-house modem DSP | +| ARDOP / ardopcf vendor tree | **Never** in release tarball | + +## HyBBX + +When CRDOP is active, HyBBX Secondary attaches via `share/hybbx/crdop-host.ini.example`. HyBBX remains an external project. + +## Testing phase + +Current work: loopback audio, acoustic bench validation, field on-air trials, acceptable sound-card criteria. Report results via project issues — RF acceptance remains manual until automated gates exist. Developer tests: [DEVELOPER.md](DEVELOPER.md). diff --git a/stacks/crdop/docs/SOUNDCARD-QUALIFICATION.md b/stacks/crdop/docs/SOUNDCARD-QUALIFICATION.md new file mode 100644 index 0000000..b35d55b --- /dev/null +++ b/stacks/crdop/docs/SOUNDCARD-QUALIFICATION.md @@ -0,0 +1,95 @@ +# Sound card qualification — CRDOP / MAX25-SoftModem + +**Purpose:** Measurable criteria for interface hardware used with CRDOP. No product endorsements — class-based requirements. + +See also [ROADMAP.md](../ROADMAP.md) (P0/P1 audio requirements). + +--- + +## Mandatory architecture + +``` +CRDOP DSP → sound_proxy → libasound (hw:) → codec → radio interface +``` + +| Forbidden in production path | Reason | +|------------------------------|--------| +| PulseAudio default route | Mixing, resampling, latency | +| PipeWire default | Variable buffer | +| Bluetooth A2DP/HFP | Codec + AGC | + +INI: `[audio] backend = alsa-kernel`, `no_pulse = yes`. + +--- + +## Sample rate + +| Baud target | Minimum rate | CRDOP default | +|-------------|--------------|---------------| +| 1200 AFSK | 48000 Hz | **48000** | +| 9600 G3RUH (audio tap) | 48000–96000 | 48000 + tuning | +| 19200 | 96000 Hz | 96000 if hardware supports | + +--- + +## Qualification tests + +Run on candidate interface **before** field deployment. + +| ID | Test | Pass | +|----|------|------| +| **Q1** | Loopback cable, 1200 Hz sine 2 s | THD acceptable by ear + FFT peak at 1200 ±2 Hz | +| **Q2** | Loopback Bell 202 sniffer | `max25-signal-sniffer --loopback` or ALSA loop | +| **Q3** | Mark/space calibration tones | `--mark` / `--space` tools within ±5 Hz | +| **Q4** | 60 s full-duplex record+play | No xrun in `dmesg` / ALSA log | +| **Q5** | Clock drift | 10 min loopback: no cumulative symbol slip at 1200 bd | + +--- + +## Interface classes + +| Class | Isolation | 1200 AFSK | 19200 path | +|-------|-----------|-----------|------------| +| **A** | Galvanic line I/O, fixed gain | Required minimum | Test Q1–Q5 | +| **B** | USB codec, headphone/mic level | Acceptable with level match | Not recommended | +| **C** | Onboard mic/speaker only | Acoustic bench only | Fail | + +CRDOP production path: **Class A or B** with line-level radio interface — see [HARDWARE-INTERFACE.md](HARDWARE-INTERFACE.md). + +--- + +## Level guidelines + +| Path | Typical level | +|------|---------------| +| Line OUT → radio mic in | 100–300 mV RMS (adjust pad) | +| Radio speaker/line → Line IN | Attenuate to avoid clipping | +| DC offset | AC coupling required | + +--- + +## Baud scaling + +Requirements **rise faster than linear** above 1200 bd: + +| Baud | Clock jitter tolerance | Filter flatness | +|------|------------------------|-----------------| +| 1200 | moderate | 300–3000 Hz FM audio path OK | +| 9600 | strict | 10 Hz–5 kHz flat, constant group delay | +| 19200 | professional | Direct FSK tap — not soundcard mic path | + +--- + +## Failure actions + +| Symptom | Action | +|---------|--------| +| xruns | Increase `period_frames`; reduce system load | +| Wrong tones | Verify 48000 Hz; check resampler not inserted | +| Intermittent decode | Replace Class C with A/B; check ground loop | + +--- + +## Community list (planned) + +P0 bench exit: publish operator-submitted **class + Q1–Q2 result** table — no vendor ranking. |
