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 | 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)