| SDD V2.18 |
2026-08-16 |
Kimi |
FT710Android native client implemented + web UI compaction (compact canvases, page scroll, RF Gain into menu Settings). New peer subproject FT710Android/ (Kotlin 2.0.21 + Jetpack Compose Material3, minSdk 26, AGP 8.7.3 / Gradle 8.9 / JDK 17, OkHttp 4.12, kotlinx-serialization, DataStore, NDK-built libopus) connects to the same FastAPI server over the 4+1 WS channels (/WSradio JSON control, /WSaudioRX//WSaudioTX tagged audio, /WSspectrum 1701B frames, optional /WSatr1000) and is functionally aligned with the web frontend: frequency/mode/band/filter/gain/DSP/ATU control, RX Opus decode → AudioTrack with jitter buffer, TX AudioRecord → Opus CBR 64 kbps, waterfall+FFT spectrum, S-meter + 5 meters, 6-slot memory channels (server format, label key), DataStore settings + Keystore credentials, self-signed-TLS tolerant client. PTT safety is first-class from day one: pure-Kotlin PTTManager state machine (press only in Idle/Releasing + control channel connected), gesture-finally release, 500 ms watchdog ×3 with onStuckTX, lifecycle onStop forceRelease() — the iOS P0 race classes from docs/IOS_APP_ANALYSIS.md are designed out, not patched. Auth: POST /api/auth/login → token + ft710_auth cookie; 401/429 stops reconnect (login screen). Pure logic (protocol, PTT, spectrum frames, memory channels, RadioState mirror) is JVM-testable: 33 JVM tests green, ./gradlew test assembleDebug lintDebug clean; instrumented Opus round-trip and RF acceptance pending the operator’s real Android device. Design spec + 19-task plan (docs/superpowers/specs|plans/2026-08-16-ft710-android-app*.md) marked implemented. Web UI: waterfall/FFT canvases compacted (mobile 67→45 px / 33→22 px, desktop 120→80 / 60→40 px), page scrolls instead of a fixed viewport (body height:100%/overflow:hidden removed; .app-container height → min-height:100dvh), RF Gain slider moved from the main controls row into the menu Settings (menu section renamed “Scope Display” → “Settings”). Cache bust css v20→v23 / ui v22→v24 (main v23 unchanged), sw.js CACHE ft710-v23→ft710-v24 + ASSETS list synced; pinned cache-bust tests updated in the same commit. Suite 439 tests. Docs: §3.3 scope note, SDD README Quick Facts, website SDD pages (en/zh), CHANGELOG v1.8.1. |
| SDD V2.17 |
2026-08-15 |
Codex |
v1.8.0 Windows Stable publication. Published the commit 4ce4d26 Windows x64 installer after 439/439 Windows tests, three successful PyInstaller targets, and Inno Setup compilation; required bundled-file inspection (FTDI DLLs, opus.dll, static, mem_channels.json) and SHA-256 checks passed. Artifact: 36,888,086 bytes, SHA-256 36a48a5f3f325d112937751bddcdebc581039d0484a40c95c1b00fd4bcc170ea. Adds the RF Gain slider (RG 0-255 → 0-100%), stops false ‘radio disconnected’ alarms from transient serial timeouts (serial_connected now flips off only via the CAT watchdog), and hardens switch/stop scripts (case-insensitive pgrep + serial-holder fallback, AD-008). Stable classification is a publication decision; the Win11 KVM had no authoritative physical FT-710 RF speech/noise path, so over-the-air monitoring remains an operator acceptance check rather than claimed build evidence. Website and Windows operator/packager docs synchronized. |
| SDD V2.16 |
2026-07-31 |
Codex |
v1.7.8 Windows Stable publication. Published the commit 8629f0c Windows x64 installer after 439/439 Windows tests, three successful PyInstaller targets, Inno Setup compilation, required-file inspection, and silent install/server-listen/auth-boundary/uninstall smoke verification. Artifact: 36,820,041 bytes, SHA-256 c1e474b58f9948206990efbc9f8bdb5b183d03e4f462828b56d2d3f8c0b493bb. The release retains AD-011’s fixed 48 kHz codec → 44.1 kHz FT-710 device bridge on Windows. Stable classification is a publication decision; the Win11 KVM had no authoritative physical FT-710 RF speech/noise path, so over-the-air monitoring remains an operator acceptance check rather than claimed build evidence. Website and Windows operator/packager docs synchronized. |
| SDD V2.15 |
2026-07-31 |
Codex |
Windows TX end-to-end audit and diagnosability hardening. Runtime simulations reconfirmed the 48 kHz browser/Opus contract, exact 960→882/20 ms device conversion, 64 kbps CBR packet flow, and fixed 44.1 kHz Windows PortAudio stream/reinitialization behavior. Fixed a frontend reconnect defect where _intentionalClose was declared const but reassigned, which could abort socket cleanup and leave a stale TX owner. Same-session replacement /WSaudioTX sockets now explicitly supersede the old owner; different sessions cannot steal on connect. The 400 ms server queue now counts oldest-frame drops and emits queue_drops in every TX-session release log, exposing the Windows slow-output/crackle signature directly. Four regressions added; suite 439 tests. Software-chain verification is complete; final RF speech/noise acceptance still requires the physical Win11 + FT-710 path. Docs: §9.4, §15, README, AGENTS, tests/README, CHANGELOG. |
| SDD V2.14 |
2026-07-28 |
Codex |
Windows TX restored to the fixed 44.1 kHz FT-710 device domain; V2.9 exception withdrawn. Deep review showed that the same-name WASAPI endpoint’s defaultSampleRate=48000 is the Windows shared-mode audio-engine mix rate, not proof that the FT-710 USB hardware accepts a 48 kHz device stream. The V2.9 KVM pacing experiment is retained as incident evidence but was not an authoritative RF/audio-quality test and must not override AD-011. start_tx() now keeps the selected output device at 44100 Hz with 882 frames per 20 ms on every platform; feed_tx_audio() always converts each decoded 48 kHz/960-sample frame to 44.1 kHz/882 samples (1764 bytes), including after PortAudio reinitialization and device re-enumeration. Removed _wasapi_tx_variant and 48 kHz pass-through. Preserved the Windows TX→RX capture reopen and v1.7.7 PortAudio recovery. Regression coverage locks the selected-device rate, byte budgets, exact SRC frame size, stale-rate immunity, and reinit behavior; suite remains 435 tests. Installer metadata moves to private v1.7.8 test build; actual RF speech/noise acceptance and public release remain pending the operator’s FT-710 test. Docs: AD-011, §9.4, AGENTS, README, Windows installer guide, tests/README, CHANGELOG. |
| ——— |
—— |
——– |
——— |
| SDD V2.13 |
2026-07-28 |
Kimi |
Header power switch WITHDRAWN before release — FT-710 CAT power control is too fragile for a remote UI. V2.11 added the button, V2.12 hardened it; day-2 guarded live testing delivered the verdict: with the radio warm, CAT healthy, and all guards active, a single PS0→PS1 cycle failed — 3 verified PS1 attempts over 36 s never woke the radio (PS1 attempt 1/2/3: radio not answering in the server log), and it again needed physical intervention. Combined with the V2.12 MCU-wedge incident, the feature cannot meet the reliability bar for remote operation, so per the user’s standing instruction the UI button is removed (header vfo-indicator restored). Kept: (1) the power WS command for the maintenance scripts (_power_cycle*.py), now with the V2.12 guards (15 s boot window, PS1 retry≤3× + FA read-back verify, PS0 double-send, TX-active refusal) so the script path cannot wedge the MCU either; (2) Tier-3 PS; poll for truthful power_on state; (3) the PortAudio re-init fix — that one is independently load-bearing (every power cycle re-enumerates the sound card). Also learned: the 36 s synchronous verify loop inside the WS handler starves that connection’s keepalive (server closed the client with 1011 ping-timeout) — acceptable on the maintenance path, would have needed a background task for UI use. test_power_switch.py stays as the guard’s regression coverage; suite 435 tests. Docs: §10.4 power row, CAT knowledge base PS warning, CHANGELOG v1.7.7 rewritten. |
| ——— |
—— |
——– |
——— |
| SDD V2.12 |
2026-07-27 |
Kimi |
Power-command hardening + PortAudio re-enumeration survival (live power-cycle test fallout). Reliability testing of the V2.11 header power switch on the real radio produced two field failures. (1) Boot-interrupted PS0 wedges the CAT MCU: a rapid PS0→PS1→PS0 sequence (PS0 landing seconds after PS1, mid-boot) left the radio with serial/audio/scope USB all enumerated but CAT permanently deaf — direct serial probes (FA;/ID;/PS;, both CP2105 channels, all DTR/RTS combos) answered nothing; only a physical power cycle recovers. A timing probe showed the healthy envelope: PS1 20 s after PS0 boots the radio in ~4 s. Guards added to the power set handler: 15 s boot window after PS1 during which PS0 is rejected with a UI error; PS1 retried ≤3× and verified by a real FA; query before power_on flips (a lost fire-and-forget PS1 previously left the radio silently off); PS0 sent twice; power-off refused while transmitting. (2) Audio dies after any radio power cycle: the FT-710’s whole USB hub (sound card included) re-enumerates on power-off/on, invalidating the CoreAudio device IDs cached inside PortAudio at Pa_Initialize time — every TX/RX stream open then failed -9999 paUnanticipatedHostError until a server restart (the “TX audio device unavailable” field report). audio_handler._reinit_pyaudio() (terminate + re-init) is now invoked once on TX/RX open failure, with device index re-resolved by name before retrying; index-locked FT710_AUDIO_RX/TX_DEVICE=<n> configs remain fragile across re-enumeration — name locking recommended. Diagnosis note: py-spy on the wedged server proved the poll loops were alive and querying (all timing out) — the “stall” was radio-side, not a scheduler deadlock. 12 new tests (test_power_switch.py 9 + PortAudioReinitTests 3); suite 435 tests. Docs: §10.4 power row, tests/README counts + module list, CHANGELOG v1.7.7. |
| ——— |
—— |
——– |
——— |
| SDD V2.11 |
2026-07-27 |
Kimi |
Radio power switch in the web UI header (CAT PS), replacing the VFO indicator. The power set-command path already existed end-to-end (cat.set_power → PS0;/PS1;, power_on in RadioState + initial sync, iOS app consuming it) but the web UI had no control. Frontend: the header vfo-indicator span is replaced by a btn-radio-power ⏻ button (amber = on, red = off) — VFO-A/B identity remains visible as the active button in the VFO control row; OFF is guarded by a confirm dialog, ON is instant (the radio’s soft-off keeps the CAT interface alive, so PS1; wakes it — same path as _power_cycle.py). Backend: the power set handler now calls skip_next_poll("power_on") before the CAT write (V1.7 stale-read guard), and PS joins the Tier-3 settings poll (14 queries) so front-panel power changes sync back — no response while off, skipped by the if resp guard. Poll behavior while powered off is unchanged: 5 failed IF cycles → serial_connected=False, first answered poll flips it back. Cache-bust css v19 / main v22 / ui v21 + sw.js ft710-v22; two pinned-version tests updated; suite 423 tests. Docs: §10.4 power row, §9.6 Tier-3 list, README polling table, tests/README counts, CHANGELOG v1.7.7. |
| ——— |
—— |
——– |
——— |
| SDD V2.10 |
2026-07-26 |
Kimi |
HTTPS by default on Windows — self-signed bootstrap (minimal approach). Root cause of “HTTPS can’t be enabled on a fresh install”: windows/launcher.py hardcoded --no-ssl in build_command(), and server.py only enables SSL when cert+key already exist — a fresh install has neither (the Win11 KVM rig only got HTTPS via a bypass ps1 that ran ft710-server.exe directly with FT710_SSL_CERT/KEY). Per user direction this is the minimal fix, not a local-CA/trust-chain design: new ssl_bootstrap.py generates a throwaway self-signed cert on first run (ECDSA P-256, 10-year, SANs = localhost / hostname / hostname.local / 127.0.0.1 / ::1 / detected LAN IPs, idempotent reuse, graceful None when cryptography missing); launcher ssl_material() honours explicit FT710_SSL_CERT/FT710_SSL_KEY first, FT710_SSL=off restores HTTP; build_command(ssl_pair) passes --ssl-cert/--ssl-key instead of the hardcoded --no-ssl; local_url(secure=) picks the scheme; wait_for_server probes with an unverified context (self-signed isn’t trusted by any store). Why HTTPS matters off-localhost: plain HTTP on a LAN address is not a secure context — browsers disable AudioWorklet and getUserMedia (the exact failure seen during remote phone testing). cryptography>=41 uncommented in requirements.txt (PyInstaller bundles it via the launcher spec’s repo-root pathex). server.py unchanged (existing --ssl-cert/--ssl-key + file-exists logic). 12 new tests (test_ssl_bootstrap.py + launcher SSL tests); suite 421 tests. Docs: AGENTS.md module table, tests/README counts, CHANGELOG v1.7.6, WINDOWS_INSTALLER_GUIDE HTTPS note, win_pack.md §2.4 sidecar scripts marked superseded. |
| ——— |
—— |
——– |
——— |
| SDD V2.9 |
2026-07-26 |
Kimi |
Windows TX crackle fix — WASAPI 48 kHz output (Win11 KVM field report: transmitted audio “咔啦咔啦” noise instead of voice). Measured on the rig: the C-Media codec’s MME 44.1 kHz playback path paces ~1.4× slow (50×20 ms stream.write() block 1.36–1.42 s instead of 1.00 s at 44.1/48/32/16/8 kHz; only 22050 Hz paces 1.02), so the TX drain loop falls behind, the 400 ms queue cap drops 24–34 % of voice frames (TX session written/fed 330/432, 195/296 — macOS production shows 96–97 %), chopping transmitted audio into crackle. The codec’s WASAPI entry at its native 48 kHz mix rate paces correctly (0.96). Fix: start_tx() prefers the same-name WASAPI entry on Windows (_wasapi_tx_variant) and opens the stream at that entry’s native rate; feed_tx_audio passes 48 kHz PCM straight through at 48 kHz (no 48→44.1 resample) and pre-buffer/cap/graceful-drain budgets are computed from the actual stream rate (getattr fallbacks keep module constants for 44.1 kHz). macOS unchanged (CoreAudio device domain stays 44.1 kHz); RX capture stays MME 44.1 kHz on Windows (paces fine). v1.7.5 hotfix: the v1.7.4 branch referenced sys.platform without the module import (NameError on every PTT) — new end-to-end StartTxWindowsTests cover start_tx on both platforms. AD-011 amended: device-domain rate is host-API-dependent, not universally 44.1 kHz — this overrides the audio-pyaudio-rate warn rule with runtime evidence. 7 new tests (WindowsWasapiTxTests + StartTxWindowsTests); suite 409 tests. Docs: AD-011, AGENTS.md module table, tests/README counts, CHANGELOG v1.7.4/v1.7.5. |
| ——— |
—— |
——– |
——— |
| SDD V2.8 |
2026-07-26 |
Kimi |
Windows RX audio dies after first PTT (full-duplex wedge) + scope resync that actually works (Win11 KVM field report: audio perfect after server restart, gone after one PTT). RX root cause: on Windows (MME/DirectSound), opening the TX playback stream on the FT-710’s C-Media USB codec silently wedges the RX capture stream — it stays open and error-free but delivers silence; macOS CoreAudio is unaffected (hardware capture verified healthy, max 43% FS, with the server stopped). Fix: AudioHandler.restart_rx() reopens the capture stream on every TX→RX transition, hooked in _broadcast_state on tx_status dirty (covers PTT/TUNE/physical PTT/dead-man), Windows-only no-op elsewhere (macOS must not pay the reopen cost per PTT). Scope root cause: the byte-by-byte sync_stream resync (wfview port) could never succeed on the FT4222 — every 1-byte SingleRead is its own SPI transaction (CS toggles per call), making a contiguous multi-byte sync pattern unobservable; it only consumed the stream and churned recovery into fatal:too_many_reinits after every PTT (V2.7’s TX-pause resume path used it). Replaced with resync_device() (close → 1 s idle-bus settle → reopen — the pattern that empirically realigns, same as a pipe restart), used by both the TX→RX resume and the bad-frame path; MAX_SYNC_ATTEMPTS/sync_attempts_this_device removed. AD-008 amended (Windows full-duplex wedge + restart_rx). 4 new tests (RestartRxTests); suite 402 tests. Docs: AD-008, AGENTS.md module table, tests/README counts, CHANGELOG v1.7.3. |
| ——— |
—— |
——– |
——— |
| SDD V2.7 |
2026-07-26 |
Kimi |
TX-safe spectrum: scope_pipe TX pause + Windows process-tree kill (field log from the Win11 KVM test rig: after every PTT the waterfall showed fallback/garbage for 30–45 s). Root cause: the FT-710 garbles its scope stream during TX (normal radio behavior), but scope_pipe was TX-agnostic — it kept reading, tripped sync_lost → spi_stalled device re-init → more sync failures → fatal:too_many_reinits, then a server-side pipe restart before real FFT frames returned. Second defect surfaced by the same log: on Windows, killing the PyInstaller onefile bootloader (proc.terminate()) orphaned the real worker, which kept the FT4222 open, so the next pipe failed FT_OpenEx with FT_DEVICE_NOT_FOUND for ~10–15 s (matches the long-standing code comment). Fixes: (1) the pipe protocol gains a stdin control channel — the server pushes TX:1/TX:0 on every tx_status dirty broadcast (_notify_scope_pipe_tx, covers PTT/TUNE from any source, re-synced with force=True on pipe start); while TX is active the pipe pauses SPI reads and freezes all sync/stall/reinit counters (tx_pause STATUS), and on TX→RX runs one clean sync_stream re-sync (tx_resume:resync); (2) Windows pipe teardown uses taskkill /PID <pid> /T /F (_terminate_process_tree_sync, POSIX keeps SIGTERM), and the pipe treats stdin EOF (parent died) as a shutdown signal — a second orphan guard alongside the stdout-heartbeat EPIPE path. Frontend: waterfall/FFT canvases show “TX 发射中 — 频谱暂停” while tx_status != 0 instead of stale rows. AD-005 amended (stdin control channel + tree kill), §9.5.1 diagram updated, UC-004 flow unchanged from the client’s perspective. 13 new tests (test_scope_pipe_tx.py: control-line parsing, TX-notify transitions/force/dead-pipe guard, taskkill vs SIGTERM); suite 398 tests. Docs: AD-005, §9.5.1, AGENTS.md module table, tests/README counts, CHANGELOG v1.7.2. |
| ——— |
—— |
——– |
——— |
| SDD V2.6 |
2026-07-26 |
Kimi |
Windows RX/TX audio device lock + setup guide. Field report: Windows install with working CAT/FT4222 spectrum had broken audio — the FT-710’s built-in USB sound card enumerates on Windows under a generic name (no “FT-710”/“YAESU” in it): USB Audio CODEC on some driver/OS builds, USB Audio Device on others (confirmed on the reporter’s machine), possibly localized like 麦克风 (USB Audio Device) or prefixed 2- USB Audio CODEC. AD-008 name matching missed it and the mono/full-duplex heuristics grabbed a laptop mic (RX) or PC speakers (TX). audio_handler.py adds a generic USB-audio name tier (USB_AUDIO_NAME_HINTS, case-insensitive substring) after the FT-710-specific names and before the channel heuristics, for both _find_rx_device and _find_tx_device; multiple matches (per-host-API duplicates open the same hardware) use the first and log a warning pointing at the env-var lock. windows/default.env now pre-locks FT710_AUDIO_RX_DEVICE/FT710_AUDIO_TX_DEVICE=USB Audio — the common substring of both enumeration forms (name is reboot-stable; index is not). docs/WINDOWS_INSTALLER_GUIDE.md gains an “Audio (RX/TX) Setup” section: device identification from the startup PortAudio device list, env-var locking, Windows sound-panel guidance (never make the radio’s USB audio the default playback device — system sounds would modulate the transmitter; optional 16-bit/44.1kHz formats), and the radio-side modulation routing verified against the FT-710 Operation Manual — RADIO SETTING → MODE SSB (and AM/FM/PSK-DATA) → MOD SOURCE = USB (default AUTO selects by transmission method), RPTT SELECT stays OFF since PTT is CAT-keyed. Troubleshooting table gains 4 audio rows. SDD: AD-008 decision/rationale, NFR-065, §12.2 config table, I2 marked Resolved (implemented under the FT710_AUDIO_RX/TX_DEVICE names). 8 new tests (USBCodecDeviceSelectionTests); suite 385 tests. Docs: tests/README counts, AGENTS.md module table, README env examples. |
| ——— |
—— |
——– |
——— |
| SDD V2.5 |
2026-07-26 |
Codex |
Windows installed-app diagnostics and runtime discovery fixes after field log review. Root cause visible in the Windows console: default FT710_SERIAL_PORT=COM3 did not exist (FileNotFoundError), so CAT was offline and scope init could not run; CAT connect failure now logs the currently visible serial ports to satisfy R1 mitigation. Launcher local URL selection now maps IPv6 wildcard FT710_WEB_HOST=:: to http://localhost:<port> instead of probing 127.0.0.1, avoiding a false 15s startup warning when Uvicorn listens on [::]. opus_rx.py now searches packaged Windows opus.dll locations (opus.dll, _internal\opus.dll, vendor\opus\windows\bin\x64\opus.dll), and the Windows build script warns/copies vendor\opus\windows when present. Docs: Windows installer troubleshooting for COM3/CP210x, libopus, and localhost; README test count. Tests: 4 new regression tests; suite 377 tests. |
| ——— |
—— |
——– |
——— |
| SDD V2.4 |
2026-07-25 |
Kimi |
Optional ATR1000 networked antenna-tuner linkage, ported from the sibling mrrc project. Default disabled: FT710_ATR1000_HOST (empty = off) + FT710_ATR1000_PORT (default 60001); when off there is no client task, no network traffic, linkage hooks short-circuit, /WSatr1000 closes with code 4000, and the frontend module stays inert — zero impact on existing installs. New backend modules: atr1000_client.py (asyncio-native WS client to the tuner hardware: binary frames [0xFF,CMD,LEN,DATA], 5s reconnect, 55-min proactive refresh, TX-no-SYNC policy with 15s watchdog, tuning-clear heuristics, 5s-throttled relay writes, LearningBuffer 4-sample stability-window learning, notify_freq/notify_tx sync hooks, on_change callback) and atr1000_tuner.py (TunerStorage LC-learning JSON store ported verbatim: learn gate SWR 1.0–1.8, 1kHz keys ±5kHz nearest, atomic writes; store file atr1000_tuner.json). 3 linkage behaviors: (1) freq change → auto-apply learned LC relays (hooked in _broadcast_state on vfo_a_freq/vfo_b_freq/active_vfo dirty — dirty holds source fields only); (2) TX on/off → notify_tx (device push mode, learning window); (3) server-side tune assist _atr_tune_assist(): TX2 carrier → skip if SWR≤1.6 → snapshot relays → full tune (mode=2) → keep+learn if SWR improved ≥0.02 else rollback relays → carrier always dropped in finally (module constants ATR_TUNE_*: 0.3s settle, 5s min, 45s deadline, 0.8s compare settle, 2.5s meter wait). New 5th WS endpoint /WSatr1000 (?token= auth, JSON both ways): server→client atrState (connected/power/swr/sw/ind/cap/ind_uh/cap_pf/tuning/tx/freq/last_update), atrTuneResult (start/skipped/success/rollback/error), pong; client→server atrTune, ping. fullState gains atr1000Enabled. Frontend: new static/modules/atr1000.js (WS client, exponential-backoff reconnect, meter row, ATR TUNE button) activated only via window.ATR1000.init(msg.atr1000Enabled===true); new hidden-by-default #atr-row in the meters section (power bar, SWR bar, LC L=x C=y relay text, tuning indicator, TUNE button with ··· progress + toast). Radio-internal TUNE (TX2+AC003) untouched. Architecture simplification vs mrrc: its 4-layer chain (browser→Tornado→unix socket→proxy→device) collapsed to a single-process direct async client; its browser-side tune assist moved server-side (avoids mrrc’s documented PTT-delay/main-thread-blocking incident); ATR data deliberately kept out of RadioState (separate channel, same precedent as spectrum); audio path untouched. Cache busting: sw.js ft710-v21, css v18, main v21, ui v20, settings_manager v13, atr1000.js v1. 98 new tests across 3 files (test_atr1000_tuner.py 36, test_atr1000_client.py 50, test_atr1000_server.py 12); suite 372 tests across 21 modules. Docs: AGENTS.md module table + env vars, Ch9 §9.8, Ch10 service row, Ch11 component rows, Ch15 tune-assist note, tests/README counts, README WS/env tables. Follow-up (2026-07-26): lifespan primes the linkage with the current freq/TX at boot (startup state sync predates the client); frozen-app store path — FT710_ATR1000_STORE env overrides the TunerStorage file (Windows launcher points it at the user data dir so learned data stays writable and survives reinstalls), PyInstaller spec hiddenimports both ATR modules; startup log line for the enabled feature. Suite 373 tests. |
| ——— |
—— |
——– |
——— |
| SDD V2.3 |
2026-07-25 |
Kimi |
All frontend settings unified to cookie storage. Previously persistence was split: ft710_user_settings/ft710_afVol/scope display options (ft710_scopeFloor/Ceil/Theme) in localStorage, memory channels inconsistently in sessionStorage (ft710_main.js) and localStorage (ft710_ui.js). settings_manager.js now provides generic setCookie/getCookie helpers (1-year max-age, path=/, SameSite=Lax, URI-encoded) used by every call site; a one-time migration moves the six legacy localStorage keys plus the sessionStorage memory-channel copy into cookies on first load, then removes them. Side benefit: memory channels now survive browser restarts (sessionStorage did not). Mic gain now persists too: it was the one slider saved nowhere (radio-side CAT only, re-polled on every connect) — the slider’s change handler stores ft710_micGain in a cookie and _applySavedMicGain() pushes it back to the radio on every fullState when it differs from the polled value. New device-side mic gain (🎙 Vol): a software GainNode on the browser mic capture graph (all three TX capture paths: AudioWorklet, ScriptProcessor fallback, PCM fallback), slider 0–200 → linear 0–2× (100 = unity), persisted as ft710_micVol — browser-local like 🔊 Vol and independent of the radio’s CAT mic_gain. index.html cache-bust query strings bumped (settings_manager v13, main/ui v20) and sw.js cache ft710-v18→v20. 3 new tests (CookieSettingsPersistenceTests), cache-bust assertions v18→v19; suite 274 tests. Docs: AGENTS.md module table, Ch11 SettingsManager row, tests/README counts. |
| ——— |
—— |
——– |
——— |
| SDD V2.2 |
2026-07-25 |
Kimi |
Windows installer deep-audit fixes (packaging chain reviewed end-to-end; PyInstaller 6.21.0 onedir layout reproduced locally for evidence). P0 — packaged web UI 404: PyInstaller 6 onedir places datas under _internal/, but STATIC_DIR resolved next to the exe, so the installed app served only the inline fallback login page and “Static files not found” (the V1.6.0 “web login OK” verification passed because the fallback page works). server.py gains _resource_dir() (sys._MEIPASS-aware, same pattern scope_libraries.get_resource_roots() already used) for STATIC_DIR; launcher’s starter-channel seeding also checks _internal/mem_channels.json. P1 — scope_pipe orphan: on Windows, proc.terminate() kills only the onefile bootloader; the real child inherited the stdout pipe, blocked forever on write, and held the FT4222 device. scope_pipe.py now emits the len=0 stdout heartbeat its docstring always promised (1 s) — a dead parent closes the pipe, the next write raises EPIPE, and the pipe exits through its normal cleanup. P1 — silent build failures: build.ps1 wrapped all native commands (python, pyinstaller, iscc) in Invoke-Checked ($LASTEXITCODE — $ErrorActionPreference never covered native commands), and no longer ships windows/__pycache__. P2 — launcher self-spawn chain: frozen launcher with a missing ft710-server.exe (e.g. antivirus quarantine) used to “fall back” to [sys.executable, server.py] = spawning itself recursively; it now reports the missing exe and exits 1. P2 — version drift: installer AppVersion 1.6.0 → 1.6.3 (app CHANGELOG v1.6.3); new packaging/windows/requirements-build.txt pins pyinstaller==6.21.0; installer guide updated. Stale cache-bust test assertions corrected (v17→v18). 9 new tests (ResourceDirTests, launcher seed/self-spawn tests, ScopePipeHeartbeatTests, build-script abort assertion); suite 271 tests. Docs: tests/README counts, AGENTS.md scope_pipe row, README test count. |
| ——— |
—— |
——– |
——— |
| SDD V2.1 |
2026-07-22 |
Kimi |
“PTT keys but no voice / no RF power” soft failure fixed (long-uptime classic). Root cause: /WSaudioTX uplink ownership was assigned only at connect time — with multiple clients (browser tab + iOS app, or a zombie socket surviving a network flap), an idle first-connected client kept ownership while the PTT-ing client’s mic frames were silently dropped (ws is not _tx_owner_ws → continue); and once the owner disconnected, every remaining client stayed muted forever (no promotion). Log evidence: repeated Audio TX client connected (2-3 total, owner=False) clusters with frantic TX audio started retries, RM5=0/1 during keyed sessions. Fixes in server.py: (1) _promote_tx_owner() hands the uplink to a remaining client on owner disconnect; (2) _claim_tx_owner_for_token() — on PTT key-up the keying client (matched by auth token, now tracked per socket) claims ownership; (3) non-owner drops are counted and logged rate-limited. TX-path observability: per-session counters (frames received / decoded / decode-fail / non-owner drops in server; written / write_err / PCM peak via AudioHandler.tx_stats()) logged at every PTT release as TX session: … — the next silent failure will identify its own broken link from the log alone; first write error per session is now WARNING-level (was debug). Also reviewed: iOS TUNE button now runs the real tune sequence (TX2+AC003, press-and-hold), iOS RX AVAudioPlayerNode ScheduleBuffer channel-mismatch crash fixed (live output format + mono upmix), iOS header S-value dedup, _poll_if TypeError + serial_connected recovery (V2.0, same day). 5 new tests (TXUplinkOwnershipTests); suite 262 tests. Docs: Ch15 Layer 4 ownership note, tests/README counts. |
| ——— |
—— |
——– |
——— |
| SDD V2.0 |
2026-07-21 |
Kimi |
Spurious “电台未连接” (radio disconnected) fixed. Live log (2026-07-21 22:48): every _poll_if cycle raised TypeError: unsupported operand type(s) for -: 'int' and 'NoneType' in the freq-change logging guard (abs(freq - _last_logged_freq) with _last_logged_freq=None, introduced 2026-07-17). The exception fired before state.update(), so every poll counted as a failure; after 5 consecutive failures serial_connected=False was broadcast and the waterfall showed “电台未连接 — 检查 USB / 电源” while the radio was in fact fully operational (user commands/band changes all OK). Two fixes in poll_scheduler.py: (1) None-guard the first-poll logging condition; (2) recovery path — a successful poll now flips serial_connected back to True (previously only the watchdog’s full reconnect did, so even a genuine transient timeout streak stuck the flag until reconnect). 1 new regression test (IFPollRecoveryTests); suite 257 tests. Docs: tests/README counts. |
| SDD V1.9 |
2026-07-21 |
Kimi |
Spectrum freeze after USB reconnect fixed. Live log (2026-07-21 22:18): the serial device briefly disappeared (USB re-enumeration), CAT auto-reconnected, but the spectrum never recovered — scope_pipe logged spi_stalled:reinit_1/5 and then silence. Root cause: the connection watchdog’s reconnect path re-synced radio state but never re-ran the scope-init CAT sequence (EX040101/EX040200); a USB re-enumeration resets the FT-710’s scope output, so the radio simply stops sending FFT frames and the FT4222-side SPI re-init cannot help. The waterfall then sits frozen on stale data while serial_connected=true, so the V1.8 “radio disconnected” hint never shows. Fix: PollScheduler gains an on_reconnected async hook invoked by _connection_watchdog after a successful reconnect + state re-sync; server.py wires it to _init_scope_cat. Hook failures are logged and non-fatal. 2 new regression tests (WatchdogReconnectTests); suite 252 tests. Docs: §9.6 Tier-5 line, Ch11 PollScheduler row, tests/README counts. |
| SDD V1.8 |
2026-07-21 |
Kimi |
Web frontend safety & UX overhaul (post-audit fixes). PTT wiring repaired: the PTT/TUNE buttons previously called handlePTTStart/End directly, bypassing PTTManager — pttActive never became true, so the browser watchdog, pagehide force-RX and unload beacon were dead code. Buttons (and Space-bar PTT) now route through PTTManager.pttStart/pttEnd/tuneStart/tuneEnd; the broken sendBeacon('/WSradio') (a WebSocket-only endpoint) was removed — tab-close force-RX is covered server-side by the last-client-disconnect dead-man switch. TUNE is now press-and-hold (was latch-on-click with no confirmation — an accidental-tap TX path). Keyboard guards: keydown handlers ignore e.repeat (held Space/arrow machine-gunned PTT/CAT) and ignore events from inputs (arrow keys in the frequency editor also tuned the radio). Silent render crash fixed: renderScopeSettings() called renderFreqScale(width) without the required range argument, throwing every update cycle (swallowed by the WS message catch, skipping VFO/PTT renders). Server errors surfaced: error WS messages now show a toast banner instead of console-only. AF slider semantics unified: 🔊 Vol is browser-side playback volume persisted in localStorage (ft710_afVol), decoupled from the CAT af_gain poll that used to fight the user’s setting; stale 2.5× comment corrected (actual 10× boost). Waterfall/FFT click-to-QSY: click maps x→frequency within the current span (8 px drag threshold guards scroll gestures). Desktop layout: ≥768 px gets a 720 px container, 120 px waterfall, 60 px FFT plot and larger controls (was a 480 px phone column everywhere); waterfall re-inits debounced on resize. Professionalism: S-meter label is now relative dB (S9=0) instead of misleading dBm; filter width tables are server-authoritative (fullState.filterTables from config.py) with the legacy hardcoded copies as fallback; lame.js (~500 KB) lazy-loads on first REC click instead of every page load. Asset cache bust to v17 (sw.js, index.html). Tests: lame eager-include test inverted to lazy-load assertion, cache-bust versions updated; 247 tests, failure count unchanged from baseline (1F+2E pre-existing). |
| SDD V1.7 |
2026-07-19 |
Kimi |
SSB filter switching race fixed (“works sometimes, ignores other clicks”). Root cause: _poll_settings checked skip_next_poll only BEFORE each Tier-3 query; a SH0; response already in flight when the user’s filter command arrived carried the pre-command width and was applied unconditionally at the end of the 13-query cycle (~0.5 s later), snapping the web UI back to the stale filter until the post-skip poll corrected it ~4 s on. Whether a click landed inside that in-flight window decided “works / doesn’t work” — the radio itself applied every SH00<NN>; correctly (live log: all sets ok=True, read-backs match). Fixes: (1) settings poll re-checks _should_skip(field) + _polling_paused() AFTER each query await and discards stale reads — same guard pattern _poll_if/_poll_vfo already had, now covering all 13 Tier-3 fields (filter, gains, preamp, att, NR, NB, AN, tuner, scope, antenna, AGC, meter display); (2) the filter/filter_width WS handler waits 150 ms after SH00<NN>;, reads SH0; back, and broadcasts the radio’s ACTUAL index (logged as Filter read-back: index=N (requested M)) so a silently-rejected index would self-correct in ~0.2 s instead of ~4 s — live verification showed N==M for all SSB indexes 9/13/17/20/23, confirming the width table. 3 new regression tests (test_settings_poll_discards_stale_reads_after_query, test_filter_set_reads_back_actual_width, plus the pre-existing skip-before-set ordering assertion); suite now 223 tests, all passing. Docs: §9.6, AD-009, §10.4 filter row, README polling note, AGENTS.md module table, tests/README counts. |
| SDD V1.6 |
2026-07-18 |
Kimi |
TX-chain documentation synchronization after end-to-end TX analysis, plus runtime fixes from the “SSB filter no effect” investigation. TX Opus params corrected: AD-004/Ch9.4/Ch11 said 28kbps CBR complexity=3; actual opus_codec.js config is 64kbps CBR complexity=5 (fidelity-priority voice). 44.1kHz device reality: AD-011 rewritten — the FT-710 USB audio interface is 44.1kHz native; audio_resample.py bridges the 48kHz codec domain ↔︎ 44.1kHz device domain (960↔︎882 frame-aligned). Ch9.3/9.4 chain diagrams, Ch4.4 flows, NFR-060, Ch10 TXAudioService, Ch11 component rows and AGENTS.md module table updated; stale docstrings/comments fixed in opus_rx.py (16kHz remnants), tx_opus_worker.js (SAB marked dormant, packet size), tx_capture_worklet.js (header). tx-gain-staging.svg documented as sunmrrc-lineage (its preamp/EQ/tanh gain staging does not exist in this codebase) — retained for reference only. RX zero-audio watchdog: AudioHandler.note_rx_chunk() tracks bit-exact-zero RX runs; ≥20s silent with squelch open → WARNING + new rx_audio_silent state field → status-bar “无声” indicator; “Audio loop” log now includes chunk peak. (Root cause of the filter report: the FT-710 USB audio was streaming digital zeros — wedged codec — so no filter change was ever audible; the watchdog makes this class of failure visible.) WS auth-failure redirect: the server rejects bad-token WS upgrades with HTTP 403 before accept, so browsers see 1006, never 4001 — /WSradio onclose now verifies via /api/auth/check and redirects to /login instead of retrying a stale token forever. TX-owner dead-carrier fix: /WSaudioTX owner disconnect during TX now forces TX0; (previously only stopped audio); Ch15 Layer 4 updated. |
| SDD V1.5 |
2026-07-18 |
Kimi |
Documentation synchronization after deep SDD review. PTT safety chapters aligned with runtime: the triple TX0 verify loop was removed in V1.2 but Ch1/Ch2/Ch3/Ch4/Ch6/Ch8/Ch13/Ch15/README still described it — all updated to the actual 7-layer model (fire-and-forget TX0; + 500ms TX-status poll + browser watchdog). Ch15 rewritten with layer renumbering and a removal note. Polling naming: “5-tier” corrected to 7-task polling in Ch1/Ch3/Ch4/Ch7 and AGENTS.md. TX audio: Ch4.4 TX flow corrected to 48kHz (was 16kHz, pre-AD-011); tx-gain-staging.svg Opus label likewise. Scope clarified (Ch3.3): FT710Mobile/ iOS app and website/ noted as in-repo but outside this SDD’s scope. Tuner AC discrepancy flagged: gap-analysis §1.1 now notes this codebase’s deliberate AC000/AC001/AC003 mapping contradicts the Hamlib AC010/AC011 claim — pending radio re-verification; cross-reference added in Ch10.4. Frame size fix: Ch1 spectrum v1 frame 850B→851B. New open issues (Ch13.4): I6 multi-client control arbitration, I7 mem_channels.json validation/backup. README Quick Facts bumped to V1.5. |
| SDD V1.4 |
2026-07-12 |
Claude |
FFT spectrum line plot: new 33px canvas above waterfall renders real-time amplitude-vs-frequency polyline from shared wf1 data. Cyan (#06b6d4) line with 1px width + subtle glow, EMA smoothing (Float32Array buffer, α=0.30, 2× boost), horizontal grid at 25/50/75% amplitude, vertical grid aligned with frequency scale ticks. Shared Floor/Ceil controls with waterfall. Waterfall refinements: VFO red line thinned 1.0→0.2px, frequency label removed (now scale-only). install.sh: added --dev flag for dev dependency installation, --no-audio and --no-scope flags. Documentation: Chinese introduction article with setup guide for non-technical users, project screenshot added to README and website, FFT feature cards on website index and SDD page, comprehensive FAQ. |
| SDD V1.3 |
2026-07-08 |
Codex |
Runtime/documentation synchronization for CAT control, polling, and website docs. Priority CAT preemption: introduced send_priority_set_command() with _cancel_polls cooperative abort so latency-sensitive TX/TUNE commands preempt queued poll reads/waits. TUNE sequence fix: tune=true now performs TX2 then AC003 (start tuning); tune=false sends AC000 then exits TX. FT-710 CAT corrections: tuner mapping corrected to AC000/AC001/AC003; compressor control kept at PR00/PR01 (Yaesu PDF errata noted). State model expansion: added meter_display (MS), amc_level (AO), rf_gain (RG0), and RI-derived telemetry (hi_swr, recording_status, rx_tx_status, tuner_tuning, scan_status, squelch_open). Polling updates: scheduler now runs 7 tasks (IF, VFO, TX status, TX meters, settings, slow telemetry, connection watchdog), with RM3 included in TX meter polling and poll-side awareness of _cancel_polls. Calibration update: power calibration dead-zone entry removed so low-power modulation reports non-zero values earlier. UI memory recall: selecting a memory channel now applies both frequency and stored mode. Website docs: added dedicated SDD HTML pages (website/sdd.html, website/zh/sdd.html) and linked from landing pages. |
| SDD V1.2 |
2026-07-08 |
Claude |
Major bug fixes and performance optimization. Freq drift fix: DN; is FT-710 active-VFO step-DOWN (~20 Hz), not DNR — removed from settings poll. Active-VFO tracking: added VS/FB polling (0.5 s), freq web command targets active VFO. TX meter fix: FT-710 RM response is 9 chars RM+id+6 digits with raw 0-255 in first 3 digits (was resp[4:]), added piecewise-linear calibration (Power/SWR/Vd/Id from FT-710.rig). PTT latency: 5→3 queries in IF poll, inter-query pause checks, poll timeout 0.25 s, release verify loop removed, TX_DRAIN 150→50 ms. TX audio: reverted background start_tx (race with queue-clearing caused SSB zero-power). RX audio: removed blocking stderr diagnostic prints (30 fps flush), parallelized WS audio sends via asyncio.gather. CAT robustness: AI1 disabled (interfered with FA/MD0/SM0), AI0 sent before ID query. restart.sh: fixed .env source. Updated gap-analysis doc (DN correction). |
| SDD V1.1 |
2026-07-06 |
Claude |
Bug fix: TX audio chain unified to 48 kHz (was 16 kHz mic → 48 kHz playback = crackling). Enhanced _find_rx_device() with multi-layer detection (config env var, name match, mono-channel heuristic, full-duplex TX). Enhanced _find_tx_device() with same heuristics. opus_rx.py: TX_RATE 16k→48k, added TX_FRAME_SAMPLES constant. tx_opus_worker.js: FRAME_SIZE 320→960, OpusEncoder 16k→48k. ft710_main.js: getUserMedia + AudioContext sampleRate 16k→48k (both main + fallback). TX Opus encoder optimized for voice: CBR 28kbps, VBR/FEC/DTX/HPF disabled. Added SSL CLI args. Event-based power button (mobile-first connect). Added AD-011 (Unified 48kHz TX audio pipeline). AudioContext gain explicitly set to 1.0. iOS unlock oscillator during user gesture. |
| SDD V1.0 |
2026-07-06 |
Claude |
Initial SDD baseline for MRRC FT-710 codebase. All 15 chapters written from scratch based on current repository state: server.py, cat_controller.py, audio_handler.py, opus_rx.py, radio_state.py, poll_scheduler.py, scope_handler.py, scope_pipe.py, config.py, static/ (HTML, CSS, JS, modules, worklets, worker). Documents: Full CAT control, FT4222 scope + S-meter fallback, Opus/PCM dual-codec bidirectional audio, AudioWorklet RX playback, TX mic capture, 5-tier polling, dirty-field state broadcasting, session auth, memory channels, PTT safety architecture. |