11. Component Model (ART 0515)
11.1 Component Inventory
| Component | Type | File | Responsibility |
|---|---|---|---|
| FastAPIApp | Backend core | server.py |
Route registration, lifespan, auth middleware, static serving, all WebSockets |
| RadioBackendFactory | Backend core | backends/__init__.py |
create_backend(model) lazy factory + known_models(); registered keys ft710, ic7300, ic7300mk2, ic705, ic7610, ic7760, ftdx10, ftdx101d, ftdx101mp, ftx1, ft891 |
| RadioBackend | Backend core | backends/base.py |
RadioBackend ABC + RadioCapabilities dataclass + ScopeProducer protocol; CAT surface, defaulted hooks for bands/modes/filter tables/poll lists/scope init |
| YaesuProfiles | Backend (Yaesu) | backends/yaesu/yaesu_profiles.py |
Per-model Yaesu facts: mode registers and the separate CAT character table (the FTX-1’s H/I are not hex digits), filter slots, bands, attenuator/preamp steps, power format, S-meter curves, verified/unverified_meters and provenance (AD-018) |
| YaesuCatCore | Backend (Yaesu) | backends/yaesu/cat_core.py |
YaesuCatController: transport ported from the verified FT-710 path (framing, AI-frame filtering, priority PTT/TUNE, ENXIO vs transient classification, reconnect) parameterised by profile, plus the FTX-1 VM000; leave-memory step |
| YaesuBackend | Backend (Yaesu) | backends/yaesu/backend.py |
YaesuBackend + four model subclasses: capabilities/tables/poll items from the profile, TX gate and read-only ID; identity check; scope_type="none" (AD-019) |
| YaesuDiagnostic | Tooling | _diag_yaesu.py |
Read-only field self-check for the five Yaesu models: probes the claimed commands, compares ID; with the profile, optional bounded key-up, paste-ready Markdown report |
| CatController | Backend core | backends/ft710/cat_controller.py (root shim: cat_controller.py) |
FT-710 serial CAT protocol: connect, disconnect, send/query/set, priority set path for PTT/TUNE preemption; high-level FT-710 command helpers |
| CivCodec | Backend core | backends/ic7300/civ_codec.py |
Pure CI-V framing/BCD encoding/scope-segment codec for IC-7300/MK2 |
| CivController | Backend core | backends/ic7300/civ_controller.py |
Async CI-V demux: reader thread → frame parser → echo drop / 0x27 scope queue / transceive broadcast / pending-response matching; 3-tier priority; reconnect |
| CivScopeProducer | Backend core | backends/ic7300/civ_scope.py |
ScopeProducer implementation: CI-V 0x27 waveform (475 or 689 bins per profile) → scale amp ceiling (160/200) → 255 → upsample 850 → ScopeHandler |
| RadioState | Backend core | radio_state.py |
Dataclass with dirty-field change tracking; to_dict/to_dirty_dict serialization; from_sync_result deserialization; derived properties (mode_name, s_unit, band_name, filter_hz) |
| PollScheduler | Backend core | poll_scheduler.py |
7-task asyncio polling (IF/VFO/TX-status/TX-meters/settings/slow/watchdog), skip-on-command, cancel-aware preemption for priority radio writes; watchdog re-runs scope init (on_reconnected hook) after reconnect |
| AudioHandler | Backend core | audio_handler.py |
PyAudio device enumeration, RX capture stream, TX playback stream, Opus encode (via RxOpusEncoder), multi-layer audio device auto-detection parameterized by backend (name hints + mono heuristic + full-duplex) |
| OpusCodec | Backend support | opus_rx.py |
RxOpusEncoder (48kHz mono, 64kbps), TxOpusDecoder (48kHz mono); direct ctypes libopus bindings; bitrate via max_data_bytes cap |
| ScopeHandler | Backend core | scope_handler.py |
Spectrum data container; update_from_scope_frame (real) and update_from_radio_state (synthetic); get_spectrum_binary for WS broadcast |
| ScopePipe | Backend core | backends/ft710/scope_pipe.py (root shim: scope_pipe.py) |
Standalone subprocess: FT4222 SPI init + read loop; frame sync; stdout binary frames + stderr STATUS diagnostics (FT-710 only) |
| ScopePipeProducer | Backend core | backends/ft710/scope_producer.py |
ScopeProducer implementation: owns scope_pipe subprocess spawn/read/auto-restart/TX-notify (FT-710 only) |
| ScopeFrame | Backend support | backends/ft710/scope_frame.py (root shim: scope_frame.py) |
Shared frame parsing: parse_pipe_payload, WF_SIZE constant, quality metrics |
| ScopeLibraries | Backend support | backends/ft710/scope_libraries.py (root shim: scope_libraries.py) |
FTDI library discovery and SPI clock configuration |
| CivModelProfile | Backend core | backends/ic7300/civ_profiles.py |
Per-model CI-V facts (address, Transceive item, scope geometry, bands, modes, attenuator steps, meter curves, verified/unverified_meters, provenance) + get_profile()/known_models(); one entry per Icom model, defaults reproduce the IC-7300 constants |
| Recorder | Backend support | recorder.py |
RecordingSession: 16 kHz mono timeline (monotonic timestamps, 50 ms jitter tolerance, silence gap fill) + _StreamingDecimator (96-tap FIR, 48k→16k) + incremental lameenc MP3 (flush() per block) + mrrc-compatible naming and the recordings.json index helpers |
| Config | Backend support | config.py |
Protocol-neutral constants + shared UI mode tables; per-backend tables live in backends/ft710/config_ft710.py and backends/ic7300/config_ic7300.py |
| ATR1000Client | Backend support | atr1000_client.py |
Optional asyncio client for networked ATR1000 tuner (frame protocol, reconnect/refresh, TX-no-SYNC, learning, throttled relay writes) |
| TunerStorage | Backend support | atr1000_tuner.py |
LC-learning persistence (SWR-gated, atomic JSON) |
| COOPCOEPMiddleware | Backend support | server.py |
Sets COOP:same-origin / COEP:credentialless for SharedArrayBuffer support |
| AuthMiddleware | Backend support | server.py |
Cookie + query-param token validation; public path whitelist; redirect to /login |
| ControlWS | Backend core | server.py |
/WSradio JSON message dispatch, state broadcast |
| RXAudioWS | Backend core | server.py |
/WSaudioRX binary fan-out of tagged audio frames |
| TXAudioWS | Backend core | server.py |
/WSaudioTX tagged mic frame ingress → decode → queue |
| SpectrumWS | Backend core | server.py |
/WSspectrum binary fan-out of scope data |
| AudioRXLoop | Backend core | server.py |
_audio_rx_loop() asyncio task: read PyAudio → encode → broadcast, 20ms cadence |
| AudioTXDrainLoop | Backend core | server.py |
_audio_tx_drain_loop() asyncio task: drain PCM queue → PyAudio write, 10ms cadence |
| ScopeReadTask | Backend core | server.py |
_read_scope_pipe() asyncio task: read stdout frames, parse, update scope |
| SpectrumBroadcastLoop | Backend core | server.py |
_broadcast_spectrum_loop() asyncio task: 30fps broadcast |
| StateBroadcastTask | Backend core | server.py |
_broadcast_state() called after commands and poll updates |
| MobileHTML | Frontend core | static/index.html |
UI structure: header, waterfall canvas, S-meter, meters, controls, PTT footer, menu |
| MobileStyles | Frontend core | static/ft710.css |
Dark amber theme, safe-area support, responsive layout |
| MainJS | Frontend core | static/ft710_main.js |
WebSocket connect/reconnect, state management, message dispatch, audio RX/TX setup, spectrum receiver |
| UIJS | Frontend core | static/ft710_ui.js |
All rendering: waterfall, S-meter, meters, button labels, PTT state, menu modals, event wiring |
| PTTManager | Frontend safety | static/modules/ptt_manager.js |
PTT state machine, safety watchdog (500ms verify), beforeunload/pagehide beacons |
| SettingsManager | Frontend support | static/modules/settings_manager.js |
Cookie persistence for auth token reading and all preferences (legacy web-storage values migrated to cookies on first load) |
| ATR1000Module | Frontend support | static/modules/atr1000.js |
Tuner WS client + meter row rendering + ATR TUNE button (inert unless atr1000Enabled) |
| OpusWASM | Frontend audio | static/modules/opus_wasm.js |
Emscripten-compiled libopus WASM binary |
| OpusCodecJS | Frontend audio | static/modules/opus_codec.js |
JavaScript OpusEncoder/OpusDecoder classes wrapping WASM |
| RxWorklet | Frontend audio | static/rx_worklet_processor.js |
AudioWorklet: queue-based playback with time-based jitter buffer (prebuffer 220ms, recovery 90ms, max 800ms) |
| TxCaptureWorklet | Frontend audio | static/tx_capture_worklet.js |
AudioWorklet: mic capture → 48kHz float32 resample → 20ms frames via postMessage (SAB ring code present but currently unwired) |
| TxOpusWorker | Frontend audio | static/tx_opus_worker.js |
Web Worker: Opus encoder (48kHz, 960-sample frames, 64kbps CBR, complexity=5); frames arrive via postMessage (SAB path dormant); posts to main thread for WS send (transferable buffers) |
| NanoVna | Instrument (Nano, AD-030) | nano/vna.py |
The only module that touches the analyzer’s serial port: port discovery (env → by-id → cu.usbmodem* → ttyACM*, never the CAT port), scan_bin binary frame with exact-length validation, prompt sync before every write, device sweep set/restore, trace & marker control, SAVE/RECALL slots, saveconfig, reset dfu |
| NanoAnalysis | Instrument (Nano) | nano/analysis.py |
Pure math with no I/O: Γ → SWR / Z ± jX, resonance plateaus, isolated-1.00 suspect points, band table, CSV and Touchstone s1p export, two-sweep comparison |
| NanoFlash | Instrument (Nano) | nano/flash.py |
Firmware upgrade as a state machine: official release asset by variant (MS/SI/ZK), SHA-256 verification, ROM-DFU entry, dfu-util argv, version read-back as the success criterion, upgrade proof record; every side effect injectable |
| NanoPages | Frontend (Nano) | static/nano.html, static/nano.js |
Field-oriented antenna bench at /Nano: verdict card (band max SWR + trim advice), SWR/Smith/ |
| ServiceWorker | Frontend support | static/sw.js |
Cache static assets; bypass JS/HTML to prevent stale cache |
11.1.1 Cloud Hub 前置能力新增/变更的组件(2026-09-30)
| 组件 | 作用 | 备注 |
|---|---|---|
session_metrics.py |
会话遥测聚合与周期日志(AD-023) | 由 GET /api/session_metrics 读取 |
config.py → FT710Settings.basePath / .url |
路径前缀的唯一来源 | 供服务端与前端共用的 URL 组装 |
static/modules/ptt_manager.js |
增加发射活性闸门(MRRC_REMOTE_SESSION_TX_HEARTBEAT_S,默认 0=关) |
与 MRRC_PTT_MAX_TX_SECONDS 构成两条独立防线(SDD/15 §15.4) |
| 前端资源引用 | 由绝对路径改为相对路径,sw.js 预缓存保持绝对 |
断言规则见 tests/test_path_prefix.py |
| TX 编码 Worker | 修复其逃逸路径前缀的加载方式 | 症状为 frames=0(表现为”按下不发射”) |
Ft8DspWorker | FT8 engine |
Ft8Engine | FT8 engine |
Ft8PlatformAdapters | FT8 platform |
Ft8ModeController | FT8 platform |
Ft8Web | FT8 platform |
ft8/core/* | DSP Worker 子进程(ctypes 绑定 libwsjt_core、Shm 协议、监督重启);入口在 NumPy/wsjt_core 加载前设 OMP_STACKSIZE=10M(同步于 mrrc_modern 的 ft8 分支移植,源 HAM/ft8 server/core/) |Ft8Engine | FT8 engine |
ft8/engine/* | FT8 编排层 verbatim 移植:orchestrator(UTC 时隙)、sequencer(QSO 状态机)、safety(TX 权威链)、tx_driver、dsp_decode/encode、msgparse/qso_log/repository/dxcc/waterfall |Ft8PlatformAdapters | FT8 platform |
ft8/platform/* | 与平台的唯一耦合层:rx_tap(设备率→12k 流式重采样 + UtcRing)、tx_sink(48k 波形→AudioHandler.feed_tx_audio)、cat(CatController→引擎 rig 协议)、settings(FT8 独立设置) |Ft8ModeController | FT8 platform |
ft8/mode.py | 全局 work_mode(ssb/ft8)状态机:SSB 快照/恢复、引擎生命周期、失败回滚、广播 |Ft8Web | FT8 platform |
ft8/web/* | /api/ft8/* REST 与 /WSft8 事件流;认证复用平台 token,listen 只读 |11.2 Backend Component Collaboration (Startup)
FastAPIApp lifespan startup:
1. create_backend(MRRC_RADIO_MODEL) → `RadioBackend` instance + `RadioCapabilities`
2. backend.connect() → open serial port → send ID; → verify radio (FT-710 ID or IC-7300 CI-V ID)
3. If connected: backend.initial_state_sync() → backend-specific queries → RadioState.from_sync_result()
4. backend.init_scope() → FT-710: send scope-init extended CAT commands; IC-7300: enable CI-V 0x27 scope
5. PollScheduler(backend, radio, on_state_changed=_broadcast_state).start()
6. AudioHandler() → init PyAudio → scan devices (per-backend hints/rate) → start_rx() → open capture stream
7. TxOpusDecoder() → init libopus decoder for TX path
8. create_task(_audio_rx_loop()) → 20ms RX capture + encode + broadcast loop
9. create_task(_audio_tx_drain_loop()) → 10ms TX queue drain loop
10. ScopeHandler() → set up on_frame callback
11. create_task(_broadcast_spectrum_loop()) → 30fps spectrum broadcast
12. backend.create_scope_producer() → FT-710: launch scope_pipe subprocess + create_task(_read_scope_pipe()); IC-7300: wire CI-V 0x27 demux to ScopeHandler
11.3 Frontend Component Collaboration (Page Load)
bodyload():
1. connectWebSocket() → /WSradio?token=...
→ onopen: updateConnectionStatus(true), startPing()
→ connectSpectrumSocket() → /WSspectrum?token=...
→ connectAudioRX() → /WSaudioRX?token=... → startAudioRXPlayback()
→ new AudioContext(48000)
→ audioWorklet.addModule('rx_worklet_processor.js')
→ new AudioWorkletNode('rx-player')
→ connectAudioTX() → /WSaudioTX?token=...
→ onmessage: handleMessage(msg) → renderUpdates()
2. requestWakeLock() → navigator.wakeLock.request('screen')
11.4 Frontend Component Collaboration (PTT)
PTT button touchstart/mousedown:
→ PTTManager.pttStart()
→ handlePTTStart()
→ sendCommand('ptt', true)
→ startTXAudio()
→ new Worker('tx_opus_worker.js') (if not cached)
→ navigator.mediaDevices.getUserMedia({audio:{sampleRate:48000}}) // 48kHz
→ AudioContext({sampleRate:48000}) + AudioWorklet 'tx-capture' (ScriptProcessor fallback)
→ float32 20ms frames → Worker → Opus encode (48kHz, 960-sample frames, 64kbps CBR) → wsAudioTX.send()
PTT button touchend/mouseup:
→ PTTManager.pttEnd()
→ handlePTTEnd()
→ sendCommand('ptt', false)
→ stopTXAudio()
→ worker.postMessage({type:'stop'})
→ Keep mic stream cached (avoid re-prompt on next PTT)
→ wsAudioTX.send('s:')
→ PTTManager starts watchdog (500ms verify)