13. Feasibility Assessment (ART 0530)
13.1 Feasibility Summary
| Dimension | Assessment | Explanation |
|---|---|---|
| CAT/CI-V control feasibility | High | FT-710 CAT is field-tested; IC-7300/MK2 CI-V frame construction/parsing is conformance-tested against Icom manuals, with physical-radio ACK/timing acceptance pending |
| Spectrum feasibility | High | FT-710 FT4222 is field-tested; IC-7300/MK2 CI-V 0x27 activation, parsing, and queue behavior are software-tested, with physical waveform cadence pending |
| RX audio feasibility | High | PyAudio capture → Opus encode → WS broadcast → browser playback (per-backend sample rate) |
| TX audio feasibility | High | Browser mic → Opus encode → WS → decode → PyAudio → radio (per-backend sample rate) |
| Mobile feasibility | Medium-High | Responsive UI adapts to backend capabilities; iOS requires HTTPS for mic (reverse proxy) |
| Operational feasibility | High | Single-process server, backend selection via env var, start/stop scripts, PID file management |
| Product completeness | High | All core features (control, audio, spectrum, meters, memories) implemented for both supported backends |
| FT8 decode-latency feasibility | High | Default fast path (ft8_lib via DecodePath.FAST) measures ~0.15 s on the W103D; the adaptive cascade re-runs the deep WSJT-X path on the same slot only when a criterion fires, and a budget guard refuses the escalation rather than miss the 4.0 s decision cutoff. Field (W103D, 2026-10-13): decode completion median −0.11 s after slot end for fast-path slots and +1.23 s for escalated slots, versus +4.01 s for always-deep (docs/ft8-w103d-decode-perf-analysis.md §9.7). The escalation rate is served by GET /api/ft8/engine/stats and shown on the FT8 page status bar (Phase A) |
13.2 Risks
| ID | Risk | Probability | Impact | Mitigation |
|---|---|---|---|---|
| R1 | Serial port not found or wrong port | Medium | High | Log available ports; env var configuration; clear error messages |
| R2 | Real scope not available | Medium | Medium | Automatic S-meter fallback; scope_pipe/CI-V 0x27 exits gracefully |
| R3 | PyAudio device not matching selected radio | Low-Medium | Medium | Per-backend name-based auto-detection; device list logging; fallback to system default |
| R4 | TX release command lost | Low | Critical | TX-status poll (500ms) + browser watchdog, dead-man switch, unload beacon |
| R5 | Opus library not available | Low | Medium | Graceful PCM fallback on server and browser |
| R6 | Audio device contention | Low | Medium | PyAudio opens/closes streams on demand; only one TX stream at a time |
| R7 | scope_pipe subprocess crash | Low-Medium | Low | Server continues; falls back to S-meter spectrum; pipe exit handled in finally block |
| R8 | Stale frontend assets | Low | Medium | Service worker bypasses JS/HTML; version query strings |
| R12 | 五台 Yaesu 机型(FTDX10/FTDX101D/MP/FTX-1F/FT-891)的 CAT 表、S 表曲线与音频采样率来自离线证据(Hamlib 4.7.2 + 手册),没有真机验证;频谱通路在 Yaesu CAT 参考中不存在 | Medium | Medium | V2.46:profile 驱动 + 逐表 provenance + unverified_meters 标注;TX 走 MRRC_ALLOW_UNVERIFIED_TX 门禁(默认拒绝发射);ID; 只读校验;_diag_yaesu.py 供现场一键回传(含音频枚举与表头对照),闭合前不声称已验证 |
| R13 | Diagnostics-bundle redaction is best-effort, and the receiver’s create/PUT endpoints are unauthenticated by design (the uploading machine holds no server secret) | Medium | Open | Allow-list config keys (never deny-list), a secret-value pass over every collected text with the hit count published in manifest.json, plus negative tests asserting that private keys, passwords, recordings, memory channels and ATR learning values never enter a bundle. Receiver: rate limit (5 creates/min/IP), unguessable ids, Basic-auth list/download, manual deletion |
| R11 | 电台 USB 串口桥在物理层反复重枚举(macOS ENXIO/ENOENT),导致 CAT 周期性中断(现场单日 171 次);软件无法阻止,只能快速恢复 |
Medium | Low | V2.43:重连不再跑 24 条全量同步(原先占串口锁约 62 秒),改由轮询分层在数秒内补齐;退避提示 + 日志静音 + ENXIO 可操作提示;硬件侧建议见运维文档(直连 USB、避免无源 HUB、检查线缆/供电) |
| R10 | Recording disk growth is unbounded by operator choice — recordings are never deleted automatically; a long-running unattended server can fill the disk (the Pi image boots from an SD card) | Medium | Low-Medium | Deliberate per the V2.42 design review: the panel shows total usage, the per-session cap MRRC_RECORDINGS_MAX_SESSION_MIN (default 240 min) stops a forgotten recording without deleting anything, and MRRC_RECORDINGS_DIR can point at a bigger volume |
| R9 | Unverified model profiles (IC-705/IC-7610/IC-7760) ship without hardware evidence — the 689-bin amplitude ceiling, the inherited+rescaled meter curves and the 48 kHz USB-audio properties are assumptions, and an unverified radio could be keyed with a frame the profile got wrong | Medium-High | Medium | verified=False + unverified_meters capability list; PTT/TUNE refused at the backend boundary until MRRC_ALLOW_UNVERIFIED_TX=1 (release never gated); _diag_civ.py report round-trip turns each assumption into a measurement; SDD V2.41 |
| R14 | FT8 cascade: if the escalation-rate criterion is wrong the net gain shrinks — a busy band that escalates on most slots spends the fast path’s 0.15 s on top of the deep path, i.e. back to always-deep latency plus a little. The default deep_lower_bound_s (2.3 s) is a measured-plus-margin estimate that can drift with SD-card/load |
Medium | Medium | The escalation rate is observed directly via OrchestratorCounters.escalations (never inferred from the latency histogram); thresholds (min_decodes/min_peak_snr) and deep_lower_bound_s are configurable and persisted (CascadePolicy), so the operator can tighten without a rebuild; the budget guard makes a wrong threshold cost a skipped escalation, never a late (display-only) decode |
13.3 Assumptions
| ID | Assumption | Confidence | Validation |
|---|---|---|---|
| A1 | Selected radio connected via USB with correct serial parameters (FT-710 Enhanced COM Port at 38400 baud; IC-7300 CI-V at explicit 115200 8N1 with USB port unlinked from [REMOTE]) | High | Backend ID response; physical-radio acceptance checklist |
| A2 | Selected radio USB audio device recognized by OS | High | PyAudio device enumeration |
| A3 | libopus available on server (Homebrew opus package) |
Medium-High | ctypes find_library(“opus”) |
| A4 | FTDI libraries in lib/ match OS architecture (FT-710 backend only) |
Medium | scope_pipe startup log |
| A5 | Browser supports WebSocket, Web Audio, Canvas | High | Modern browsers |
| A6 | libft4222.dylib from wfview app bundle for correct version (FT-710 backend only) | Medium | scope_pipe SPI read success |
| A8 | lameenc ships prebuilt wheels for every target platform (macOS arm64/x86_64, Windows x64, Linux aarch64), so the recorder needs no ffmpeg and no compiler on the build machines |
High | Verified on macOS locally; must be re-verified inside the DMG and the Windows installer by recording a short sample (packaging step in the V2.42 plan) |
| A7 | The IC-705/IC-7610/IC-7760 share the IC-7300 CI-V command surface (frequency/mode/preamp/AGC/NB/NR/compressor/filter-width/squelch/RF-power/PTT/tune, meter sub-codes) — evidenced by identical wfview rig command tables, not by a radio | Medium | _diag_civ.py read-only probe step; 19 00 identity log |
| A9 | The five Yaesu models share the FT-710’s ASCII-CAT command surface (frequency/mode/filter/PTT/gains/meters/memory), differing mainly in mode codes, filter slots, bands, power class and meter curves — evidenced by Hamlib 4.7.2’s shared newcat.c core plus per-model table files (ftdx10.c, ftdx101.c, ftdx101mp.c, ftx1/, ft891.c) and the FTX-1 CAT reference (90/90 commands mapped) |
Verified offline; no unit tested, so every derived table carries provenance and the transmit gate stays on (AD-019) | |
| A10 | The FT-891’s three facts that no offline source can settle come from a field support report in the legacy product (hub/mrrc/dist/support_answers/20260917-073700-14ef, operator bg7zhs, 2026-09-17): serial parameters 38400 8N1 (data_bits=8, stop_bits=1, parity=None; pinned by hub/mrrc/tests/test_rigctld_supervisor.py:82) — where Hamlib ft891.c:143 carries serial_stop_bits = 2 self-marked Assumed since manual makes no mention, so the assumption is not followed; no USB sound card (the report’s audio devices were the motherboard’s Realtek High Definition pair, not a Yaesu codec — hence audio_name_hints=(), 48 kHz for an external interface, and a dialog label that says so); and an S reading 1–1.5 S above the radio’s own display (recorded in provenance, curve deliberately not adjusted from another product’s observation) |
One field report, no unit here; the report’s own connect failure was diagnosed as rigctld not autostarting (fixed in legacy V6.1.14), not a serial-framing fault, so 8N1 has no counter-evidence. Closes via _diag_yaesu.py --model ft891 |
13.4 Current Issues
| ID | Issue | Priority | Status | Resolution Path |
|---|---|---|---|---|
| I1 | iOS Safari requires HTTPS for getUserMedia (mic access) | Medium | Open | Use TLS reverse proxy (nginx) or connect via HTTPS |
| I2 | PyAudio device index not configurable via env var | Low | Resolved (V2.6) | Implemented as MRRC_AUDIO_RX_DEVICE / MRRC_AUDIO_TX_DEVICE (index or name substring); Windows package pre-locks USB Audio |
| I3 | No per-band TX power control (FT-710 uses hardware power setting) | Low | N/A | FT-710 has hardware RF POWER knob; CAT PC; command sets power globally |
| I4 | No ATR-1000 / external tuner support | Low | Future | Could add via second serial port |
| I5 | No digital mode support (CW decoder, FT8, RTTY) | Low | Future | Specialized DSP/packet decode needed |
| I6 | No multi-client control arbitration — concurrent browsers can issue conflicting PTT/frequency commands (last-writer-wins) | Medium | PTT half resolved 2026-09-25 (field log: two tabs produced 75 TX sessions in 5 min, 16 with zero mic frames — each tab’s watchdog re-sent ptt:false on stale local state and unkeyed the tab that was transmitting. Fix: _ptt_key_ws control-plane owner — only the keying socket’s release is honored while it holds the key, the ignored client gets the authoritative tx_status + ptt_keyed_by_other push so its watchdog stands down, and a keyer disconnect forces RX even with other clients connected; SDD §15 Layer 4b, PTTControlArbitrationTests). TX-uplink half resolved 2026-10-05 (field log: a page reload left two /WSaudioTX sockets under one 30-day cookie token, and the key-up claim resolved that token by taking the first match scanned out of a set — it landed on the half-open predecessor, so the live page’s every mic frame was dropped as non-owner: frames=0 non_owner_drops=236, peak=0%, a keyed silent carrier for 58 s until the stale socket was reaped. Fix: the claim now takes the last-connected socket of the session (_register_tx_socket connect order), and a same-session socket that is actually streaming takes the uplink over on its first frame (_same_session_uplink_takeover); a different session still cannot steal by connecting or by streaming; SDD §15 Layer 4, TXUplinkOwnershipTests). Frequency/mode conflicts remain last-writer-wins |
Define arbitration rules for the remaining set-commands (e.g., single-controller lock or role-based gating) |
| I7 | mem_channels.json POST has no schema validation or backup |
Low | Open | Server-side payload validation; keep .bak copy before overwrite |
| I8 | serve_static path traversal — STATIC_DIR / path (server.py) builds the response path from the request URL without resolve() + containment check, so an authenticated non-browser client can read arbitrary server-readable files (e.g. GET /../server.py, cert keys). Browsers normalize .., so exposure is raw-HTTP clients. |
High | Resolved 2026-08-26 (_resolve_static_path resolves the join and rejects unless contained inside STATIC_DIR; traversal and absolute request paths now 404 instead of falling through to the SPA fallback; regression tests in tests/test_server_security.py) |
is_relative_to(STATIC_DIR) before FileResponse; add a regression test |
| I9 | Login password compared with != (non-constant-time, timing side channel) and a weak default password only logs a warning — a fresh install that skips the warning is effectively open. |
Medium | Partially resolved 2026-08-26 (hmac.compare_digest via _password_matches; _warn_if_default_password() fires a loud startup WARNING when the well-known default is active; login-time <12-char warning kept). Still open: first-login forced password change, shorter cookie TTL. |
hmac.compare_digest |
| I10 | Web client audio/spectrum subchannels (/WSaudioRX, /WSaudioTX, /WSspectrum) have no independent reconnect — only /WSradio auto-reconnects, so a transient drop yields “controls alive but audio dead” until the control channel also cycles. |
Medium | Resolved 2026-08-26 (subchannelReconnect gives each subchannel its own exponential backoff 1s→30s with on-open reset, CONNECTING-state guards against socket stacking, auth-expiry 4001 stays with the control flow, and _webClientOff suppresses self-heal while the power button has the client OFF; contract test in tests/test_server_ws_protocol.py) |
|
| I11 | iOS app PTT release race (P0 in docs/IOS_APP_ANALYSIS.md): DragGesture.onEnded sends ptt:false only when the server echo already shows tx_status > 0, and there is no client watchdog/scenePhase guard — fast taps over WAN can leave the radio keyed up. Approved PTTManager fix design not yet implemented (0/N tasks). |
High | Partially resolved 2026-08-26 (release race fixed: PTT gesture now tracks local pttHeld like TUNE’s tuneHeld, releases unconditionally on gesture end with optimistic local txStatus update in RadioViewModel.setPTT; requires device verification). Still open: 500 ms×3 watchdog, scenePhase force-release. Server side gained an opt-in stuck-keyup layer: MRRC_PTT_MAX_TX_SECONDS watchdog forces RX after continuous TX beyond the limit, covering zombie-but-connected sockets that neither client watchdogs nor the dead-man switch see. |
|
| I12 | Stuck-keyup gap for connected-but-hung clients: every existing TX-release layer assumes either a working client or a disconnect. A zombie socket mid-TX keys the radio forever. | High | Resolved 2026-08-26 (server-side _max_tx_watchdog, opt-in via MRRC_PTT_MAX_TX_SECONDS, default off so operator QSO patterns are never interrupted; fire-and-forget unkey + zeroed TX meters + error toast, no verify loop per ch15) |
Opt-in max-continuous-TX force-RX implemented as a new outermost safety layer |
13.5 Dependencies
| ID | Dependency | Type | Status |
|---|---|---|---|
| D1 | Python 3.12+ | Runtime | Required |
| D2 | FastAPI + Uvicorn | Runtime | Required (pip) |
| D3 | pyserial | Runtime | Required (pip) |
| D4 | PyAudio | Runtime audio | Required for audio (pip + portaudio) |
| D5 | NumPy | Runtime/DSP | Required (pip) |
| D6 | libopus | Optional codec | Optional (brew install opus / apt install libopus0) |
| D7 | libft4222 + libftd2xx | Optional scope | Required for real FT4222 spectrum |
| D8 | Supported radio + USB cable | Hardware | Required |
| D9 | Browser WebSocket/Web Audio/Canvas | Client | Required |
13.6 Feasibility Conclusion
MRRC Modern is fully feasible for remote operation of supported radios. Core software paths — backend-specific control, bidirectional Opus audio, spectrum waterfall, meter telemetry, memories, authentication, and PTT safety — are implemented. FT-710 has field-test history; the IC-7300/MK2 CI-V byte formats and asynchronous behavior are conformance-tested without hardware, so physical USB enumeration, command ACK timing, scope cadence, RF/tuner/power behavior, and RX/TX audio quality remain acceptance items rather than verified claims. iOS still requires HTTPS for microphone access. FT-710 FT4222 scope requires its libraries and degrades to S-meter fallback; IC-7300/MK2 scope requires the CI-V USB port unlinked from [REMOTE] at an explicitly selected 115200 baud.