Roadmap - icom-lan
Description: Python library for controlling Icom transceivers over LAN

Skip to content

# Roadmap

## Current State: v0.2.0 (Python)

- ✅ Phase 1 — Transport (UDP, auth, keep-alive, dual-port)
- ✅ Phase 2 — CI-V Commands (frequency, mode, power, meters, PTT, CW, VFO, split)
- ⬜ Phase 3 — Audio Streaming
- ⬜ Phase 4 — Polish & Publish
- ⬜ Phase 5 — Rust Core
- ⬜ Phase 6 — Spectrum & Waterfall
- ⬜ Phase 7 — Web UI (full wfview/RS-BA1 replacement)

## Phase 3 — Audio Streaming (Python)

Goal: RX/TX audio через Opus codec на порту 50003.

### Tasks

- [ ] Изучить `icomudpaudio.cpp` — формат аудио-пакетов, заголовки, sequence
- [ ] Открытие аудио-потока (OpenClose на audio port, аналогично CI-V)
- [ ] RX: приём Opus-пакетов → декодирование → callback API
- [ ] TX: PCM input → Opus encode → отправка на радио
- [ ] Буферизация и jitter buffer (компенсация неравномерности UDP)
- [ ] Sample rate negotiation (8/16/24/48 kHz — задаётся в conninfo)
- [ ] Тесты с реальным радио

### API Design

    # RX — callback-based
    def on_audio(pcm_data: bytes, sample_rate: int):
        # process audio...

    async with IcomRadio(...) as radio:
        radio.start_audio_rx(callback=on_audio)
        await asyncio.sleep(10)
        radio.stop_audio_rx()

    # TX — push-based
    async with IcomRadio(...) as radio:
        radio.start_audio_tx(sample_rate=48000)
        radio.push_audio(pcm_data)
        radio.stop_audio_tx()

    # Full duplex
    async with IcomRadio(...) as radio:
        radio.start_audio(rx_callback=on_audio, tx_sample_rate=48000)

### Dependencies

- `opuslib` — Python bindings for libopus (optional dependency, `pip install icom-lan[audio]`)

## Phase 4 — Polish & Publish (Python)

Goal: Production-ready библиотека на PyPI.

### Tasks

- [ ] Sync API wrapper (для тех кто не хочет async)
- [ ] Autodiscovery улучшение (multicast, timeout config)
- [ ] Multi-model support (CI-V address presets для IC-705, IC-7300, IC-9700)
- [ ] Token renewal timer (wfview: TOKEN_RENEWAL = 60s, sends `sendToken(0x05)`)
- [ ] Reconnect logic (auto-reconnect при потере связи)
- [ ] Proper integration test suite (`@pytest.mark.integration`)
- [ ] PyPI публикация (`uv publish` / `twine upload`)
- [ ] GitHub Release с changelog
- [ ] MkDocs documentation site (GitHub Pages) — ✅ done
- [ ] Badges: PyPI version, downloads, CI status

### Sync API Design

    from icom_lan.sync import IcomRadio

    # Blocking API (wraps asyncio internally)
    with IcomRadio("192.168.1.100", username="u", password="p") as radio:
        freq = radio.get_frequency()
        radio.set_frequency(14_074_000)

## Phase 5 — Rust Core 🦀

Goal: Переписать ядро на Rust. Python API остаётся, но внутри — нативный код. Плюс standalone CLI и WASM.

### Мотивация

Фича | Почему Rust
--- | ---
Audio latency | Нет GIL, нет GC pauses → предсказуемый real-time audio
Один бинарник | Скачал → запустил. Без Python, pip, venv
Memory safety | Парсинг бинарных UDP-пакетов с гарантией компилятора
Multi-platform FFI | Один codebase → Python, Node.js, C/C++, Swift, WASM
Tokio async | 3 порта + pings + retransmit + audio — эффективнее asyncio
WASM | Управление радио из браузера (!)
Embedded | Raspberry Pi, embedded Linux без Python runtime

### Архитектура

    ┌──────────────────────────────────────────────────┐
    │              icom-lan-core (Rust)                 │
    │                                                   │
    │  ┌───────────┐  ┌──────────┐  ┌───────────────┐  │
    │  │ Transport │  │ Protocol │  │ Audio (Opus)  │  │
    │  │  (tokio)  │  │ (CI-V)   │  │ (decode/enc)  │  │
    │  └─────┬─────┘  └────┬─────┘  └──────┬────────┘  │
    │        └──────────────┴───────────────┘           │
    │                       │                           │
    │  ┌────────────────────┴───────────────────────┐   │
    │  │           Public Rust API                  │   │
    │  │  IcomRadio::connect / get_freq / set_mode  │   │
    │  └────────────────────────────────────────────┘   │
    └───────┬──────────┬──────────┬──────────┬──────────┘
            │          │          │          │
       ┌────┴───┐ ┌───┴────┐ ┌───┴───┐ ┌───┴────┐
       │ PyO3   │ │  CLI   │ │ WASM  │ │ C FFI  │
       │(Python)│ │(clap)  │ │(wasm- │ │(cbind- │
       │        │ │        │ │ pack) │ │ gen)   │
       └────────┘ └────────┘ └───────┘ └────────┘

### Crates (зависимости Rust)

    [dependencies]
    tokio = { version = "1", features = ["full"] }       # async runtime
    bytes = "1"                                            # efficient byte buffers
    opus = "0.3"                                           # Opus codec
    tracing = "0.1"                                        # structured logging
    thiserror = "2"                                        # error types

    # Bindings
    pyo3 = { version = "0.24", features = ["extension-module"], optional = true }
    wasm-bindgen = { version = "0.2", optional = true }
    clap = { version = "4", features = ["derive"], optional = true }

    [features]
    python = ["pyo3"]
    wasm = ["wasm-bindgen"]
    cli = ["clap"]
    ffi = []

### Поэтапный план

#### Phase 5.1 — Rust Core (transport + protocol)

- [ ] Cargo workspace setup (`icom-lan-core`, `icom-lan-cli`, `icom-lan-py`)
- [ ] UDP transport на tokio (connect, ping, retransmit, sequence tracking)
- [ ] Discovery handshake (Are You There → I Am Here → Are You Ready)
- [ ] Authentication (credential encoding, login, token, conninfo)
- [ ] CI-V command encoding/decoding (BCD, frame builder/parser)
- [ ] IcomRadio struct с async API
- [ ] Unit tests (tokio::test)
- [ ] Integration test с IC-7610

#### Phase 5.2 — CLI (standalone binary)

- [ ] `clap` CLI с теми же командами что Python CLI
- [ ] `icom-lan status / freq / mode / meter / ptt / cw / discover`
- [ ] Cross-compile: macOS (arm64, x86_64), Linux (arm64, x86_64), Windows
- [ ] GitHub Releases с бинарниками
- [ ] Homebrew formula: `brew install morozsm/tap/icom-lan`

#### Phase 5.3 — Python bindings (PyO3)

- [ ] PyO3 + maturin setup
- [ ] `IcomRadio` class с async support (pyo3-asyncio)
- [ ] Drop-in замена Python-версии: тот же API, тот же `from icom_lan import IcomRadio`
- [ ] PyPI publish как native wheel (`pip install icom-lan` → Rust binary внутри)
- [ ] Бенчмарки: Python-pure vs Rust+PyO3

#### Phase 5.4 — Audio (Rust-native Opus)

- [ ] Opus decode/encode через `opus` crate (или `audiopus`)
- [ ] Audio port (50003) handling в transport
- [ ] Jitter buffer
- [ ] Callback API для RX, push API для TX
- [ ] Full-duplex audio
- [ ] Latency benchmark vs Python+opuslib

#### Phase 5.5 — WASM (browser control)

- [ ] `wasm-bindgen` + `wasm-pack` build
- [ ] WebSocket → UDP proxy (WASM не может UDP напрямую)
- [ ] Minimal web UI: частота, режим, S-meter, waterfall (?)
- [ ] npm package: `@icom-lan/web`

#### Phase 5.6 — C FFI

- [ ] `cbindgen` для генерации C header
- [ ] Shared library (.so / .dylib / .dll)
- [ ] Пример интеграции с GNU Radio
- [ ] Пример интеграции с SDR++

### Оценка трудозатрат

Этап | Сложность | Время (ориентир)
--- | --- | ---
5.1 Core | Средняя | 2–3 недели
5.2 CLI | Лёгкая | 2–3 дня
5.3 PyO3 | Средняя | 1 неделя
5.4 Audio | Высокая | 2–3 недели
5.5 WASM | Высокая | 2 недели
5.6 C FFI | Средняя | 3–5 дней

### Структура репозитория (Rust)

    icom-lan/
    ├── crates/
    │   ├── icom-lan-core/        # Ядро: transport, protocol, commands, audio
    │   │   ├── src/
    │   │   │   ├── lib.rs
    │   │   │   ├── transport.rs
    │   │   │   ├── protocol.rs
    │   │   │   ├── commands.rs
    │   │   │   ├── auth.rs
    │   │   │   ├── audio.rs
    │   │   │   └── error.rs
    │   │   └── Cargo.toml
    │   ├── icom-lan-cli/         # CLI binary
    │   │   ├── src/main.rs
    │   │   └── Cargo.toml
    │   ├── icom-lan-py/          # Python bindings (PyO3)
    │   │   ├── src/lib.rs
    │   │   ├── pyproject.toml
    │   │   └── Cargo.toml
    │   └── icom-lan-wasm/        # WASM bindings
    │       ├── src/lib.rs
    │       └── Cargo.toml
    ├── Cargo.toml                # workspace
    ├── Cargo.lock
    ├── docs/                     # общая документация (текущая)
    ├── tests/                    # integration tests
    └── README.md

### Open Questions

- Mono-repo или separate repo? Вариант: `icom-lan` (Python, текущий) + `icom-lan-rs` (Rust). Или mono-repo с Python в `python/` subdir.
- Minimum Rust version (MSRV)? Предлагаю 1.75+ (async fn in traits стабилизирован).
- Async runtime: tokio (стандарт де-факто) или async-std? → tokio.
- Opus crate: `opus` (C bindings) или `opus-rs` или pure-Rust `symphonia`? → C bindings (`opus`) для совместимости с libopus.

## Phase 6 — Spectrum & Waterfall

Goal: Парсинг waterfall/spectrum данных, которые радио уже отправляет (cmd `0x27`). Сейчас мы их выбрасываем в `_send_civ_raw` — пора начать использовать.

### Контекст

IC-7610 непрерывно шлёт spectrum-данные через CI-V порт (50002) — это те самые пакеты с `cmd=0x27`, которые мы фильтруем. Они содержат: - Scope данные (амплитуды FFT-бинов) - Центральная частота, span - Метаданные отображения (ref level, edge frequencies) - Два scope — Main и Sub (у IC-7610 два независимых приёмника)

### Ключевые ссылки в wfview

- `rigcommander.cpp` → `parseSpectrum()` — парсинг spectrum-пакетов
- CI-V cmd `0x27`, sub `0x00` — scope data
- CI-V cmd `0x27`, sub `0x10`/`0x11` — scope on/off
- Формат: заголовок (division, center freq, span) + массив байтов (амплитуды 0-200)

### Tasks

- [ ] Изучить формат spectrum-пакетов в `rigcommander.cpp` → `parseSpectrum()`
- [ ] Reverse-engineer `0x27` sub-commands (scope enable/disable, mode, speed)
- [ ] Парсер spectrum data → структура `SpectrumFrame(center_freq, span, data: list[int])`
- [ ] Callback API: `radio.on_spectrum(callback)` — continuous stream
- [ ] Scope control: `radio.set_scope(enabled=True, mode="center", span=100_000)`
- [ ] Dual scope support (Main + Sub для IC-7610)
- [ ] Ring buffer для последних N фреймов (для waterfall отображения)
- [ ] Бенчмарк: сколько spectrum-пакетов в секунду шлёт IC-7610

### API Design

    from icom_lan import IcomRadio, SpectrumFrame

    def on_spectrum(frame: SpectrumFrame):
        print(f"Center: {frame.center_freq/1e6:.3f} MHz, "
              f"Span: {frame.span/1e3:.0f} kHz, "
              f"Bins: {len(frame.data)}, "
              f"Peak: {max(frame.data)}")

    async with IcomRadio(...) as radio:
        # Enable spectrum stream
        await radio.set_scope(enabled=True)
        radio.on_spectrum(on_spectrum)

        # ... do work, spectrum data flows via callback ...

        await radio.set_scope(enabled=False)

### Data Structure

    @dataclass
    class SpectrumFrame:
        scope: int              # 0 = Main, 1 = Sub
        center_freq: int        # Hz
        span: int               # Hz
        edge_low: int           # Hz (left edge)
        edge_high: int          # Hz (right edge)
        data: list[int]         # amplitude values (0-200 per bin)
        division: int           # number of divisions
        out_of_range: bool      # scope data out of range flag

## Phase 7 — Web UI 🌐

Goal: Полноценный web-интерфейс для управления радио. Замена RS-BA1 / wfview — open source, в браузере.

### Зачем

- RS-BA1 — платный ($100+), Windows only, закрытый
- wfview — GPLv3, Qt desktop app, тяжёлый
- icom-lan Web UI — MIT, в браузере, zero install, работает с телефона

### Архитектура

    ┌──────────────┐     WebSocket      ┌──────────────────┐
    │   Browser    │◄──────────────────►│   icom-lan       │
    │              │                    │   server          │
    │  ┌────────┐  │                    │  ┌────────────┐   │     UDP
    │  │ React/ │  │                    │  │ WebSocket  │   │◄──────────►  Radio
    │  │ Canvas │  │                    │  │ ↔ UDP      │   │   50001-3
    │  │ WebGL  │  │                    │  │ proxy      │   │
    │  └────────┘  │                    │  └────────────┘   │
    └──────────────┘                    └──────────────────┘

Или с WASM (Phase 5.5):

    ┌────────────────────────────────┐
    │          Browser               │
    │  ┌──────────┐  ┌────────────┐  │     WebSocket→UDP
    │  │ WASM     │  │ UI         │  │◄────────────────►  Radio
    │  │ icom-lan │  │ (React/    │  │     (proxy)
    │  │ core     │  │  Canvas)   │  │
    │  └──────────┘  └────────────┘  │
    └────────────────────────────────┘

### Features

#### MVP (Phase 7.1)

- [ ] Frequency display + tuning (click/drag/scroll/keyboard)
- [ ] Mode selector (USB/LSB/CW/AM/FM)
- [ ] S-meter bar (real-time)
- [ ] PTT button (with safety confirm)
- [ ] Power/SWR/ALC meters
- [ ] VFO A/B switch, split indicator
- [ ] Mobile-responsive layout

#### Waterfall (Phase 7.2)

- [ ] Real-time spectrum display (Canvas 2D или WebGL)
- [ ] Waterfall (scrolling history)
- [ ] Click-to-tune на waterfall
- [ ] Zoom/pan по частоте
- [ ] Color schemes (classic, viridis, plasma)
- [ ] Dual waterfall для IC-7610 (Main + Sub)
- [ ] FPS control (10/20/30 fps)

#### Audio (Phase 7.3)

- [ ] RX audio через Web Audio API (Opus → PCM → speakers)
- [ ] TX audio через getUserMedia() (mic → Opus → radio)
- [ ] VOX mode (auto PTT по аудио)
- [ ] Audio level meters (RX/TX)
- [ ] Noise reduction (Web Audio filters)

#### Advanced (Phase 7.4)

- [ ] Band stack (быстрое переключение диапазонов с запоминанием)
- [ ] Memory channels (список сохранённых частот)
- [ ] Logbook integration (ADIF export)
- [ ] DX cluster overlay на waterfall
- [ ] Multi-user (несколько браузеров к одному серверу)
- [ ] PWA (installable, offline capable)
- [ ] Dark/light theme

### Tech Stack (предварительно)

Компонент | Технология
--- | ---
Frontend | React + TypeScript (или Svelte)
Waterfall | Canvas 2D / WebGL (gpu-accelerated)
Audio | Web Audio API + Opus.js (или WASM decoder)
Transport | WebSocket (browser ↔ server)
Server | Rust (axum) или Python (FastAPI + websockets)
State | Zustand / signals (reactive)
Build | Vite

### Вдохновение

- OpenWebRX — web SDR с waterfall (отличный UX)
- WebSDR — pioneer web-based radio
- KiwiSDR — browser-based SDR receiver
- wfview — desktop reference (feature set)
- RS-BA1 — Icom's official (UX anti-pattern 😅)

### Оценка

Этап | Сложность | Время
--- | --- | ---
7.1 MVP (controls) | Средняя | 1–2 недели
7.2 Waterfall | Высокая | 2–3 недели
7.3 Audio | Высокая | 2–3 недели
7.4 Advanced | Очень высокая | ongoing

## The Grand Vision 🔭

    Phase 1-2:  Python CLI + API          ✅ Done
    Phase 3:    Audio streaming            ← next
    Phase 4:    PyPI, polish
    Phase 5:    Rust core + bindings
    Phase 6:    Spectrum/waterfall data
    Phase 7:    Web UI — the full package
                ─────────────────────────
                Open-source RS-BA1 killer
                In your browser
                From your phone
                MIT licensed
                73 de KN4KYD

Created: 2026-02-25 Last updated: 2026-02-25