MattCheramie/GopherTrunk

GitHub: MattCheramie/GopherTrunk

这是一个纯Go的数字集群无线电扫描和音频处理工具,用于解码和监控无线电信号。

Stars: 39 | Forks: 4

GopherTrunk logo

GopherTrunk

面向 RTL-SDR 的纯Go数字集群无线电扫描引擎。
P25 · DMR · TETRA · NXDN · Motorola Type II · EDACS · LTR · MPT 1327 · dPMR · D-STAR · YSF。
零CGO依赖,单一静态二进制文件,支持无头守护进程 + Bubbletea TUI控制台 + 浏览器网页控制台。

CI Release License Go version Go Report Card Docs

## 这是什么? GopherTrunk 是一款软件定义无线电扫描器,能够跟踪数字集群无线电语音通话并将其解码为音频。它运行在一系列廉价的 RTL-SDR 加密狗上,构建或运行时无需 C 依赖(无 `librtlsdr` / `libusb`),并作为适用于 Linux、macOS 和 Windows 的单个约 10 MB 静态二进制文件分发。 为什么会有这个项目?请阅读 **[GopherTrunk 的故事](https://gophertrunk.org/story.html)** —— 该项目的个人背景故事,从一个听着模拟扫描器的小孩,到借助现代 AI 工具构建的纯Go数字集群扫描器。 ## 快速开始 ``` # Linux x86_64 — see https://gophertrunk.org/downloads.html for macOS, Windows, ARM64 VERSION=v0.1.7 curl -L -o gophertrunk.tar.gz \ https://github.com/MattCheramie/GopherTrunk/releases/download/${VERSION}/gophertrunk-${VERSION}-linux-amd64.tar.gz tar xzf gophertrunk.tar.gz && cd gophertrunk-${VERSION}-linux-amd64 cp config.example.yaml config.yaml ./gophertrunk version # Plain `./gophertrunk` (no subcommand, on a TTY) drops into the # interactive launcher: pick [1] TUI, [2] Web, or [3] Headless. # Skip the prompt with -tui / -web / -headless. ./gophertrunk -config config.yaml ``` 完整的跨操作系统安装(Windows 安装程序 / macOS Apple Silicon / Linux aarch64):**[gophertrunk.org/downloads.html](https://gophertrunk.org/downloads.html)** · 启动器 / `-tui` / `-web` / `-headless`:[`docs/launcher.md`](docs/launcher.md) · 网页控制台设置 + 快速入门:**[gophertrunk.org/web.html](https://gophertrunk.org/web.html)** · TUI 按键绑定:[`docs/tui.md`](docs/tui.md) · 硬件设置(udev 规则、WinUSB / Zadig):[`docs/hardware.md`](docs/hardware.md) · 生产环境加固(TLS、bearer-token 认证、Docker):[`docs/hardening.md`](docs/hardening.md)。 ## 功能特性 **集群无线电信道控制解码器** —— P25 Phase 1 + Phase 2(完整的 TIA-102 链,带 RS(24,16,9) 外部校验器 + PN44 扰码器)、DMR Tier II + Tier III、NXDN、Motorola Type II / SmartZone、EDACS / GE-Marc、LTR、MPT 1327、dPMR Mode 3、TETRA TMO。所有协议均通过 `internal/scanner/ccdecoder` 在 IQ 流上实时运行,每种协议的 FEC 链默认开启。 **业余无线电数字模式** —— D-STAR(JARL DV 模式 K=5 R=½ Viterbi + PN15 扰码器 + 22×30 交织器)和 Yaesu System Fusion(C4FM + FICH 格状编码)。 **纯Go语音路径** —— 在Go中实现了 IMBE(P25 Phase 1)和 AMBE+2(P25 Phase 2 / DMR / NXDN)声码器,无需 DVSI / mbelib 依赖。每次通话生成 WAV + 原始帧旁路文件;通过直接 ALSA / WASAPI / CoreAudio 进行实时 PCM 播放(运行时无需 `libasound2`)。 **SDR 层** —— 跨 USBDEVFS(Linux)、WinUSB(Windows)、IOKit(macOS)的纯Go RTL-SDR 驱动程序。支持所有主流调谐器驱动(R820T / R820T2 / R828D / E4000 / FC0012 / FC0013 / FC2580)—— 每个都是 osmocom librtlsdr 的 `tuner_*.c` 的线路级移植,具有 librtlsdr 一致性的初始化突发分块、EPIPE 预热 + 打开时复位、平衡的 LNA+Mixer 增益算法,以及与非 R820T 检测相同的 `rtlsdr_open` 探测顺序 + GPIO 4/5/6 操作序列。I²C 桥在每个公共方法(`SetFreq`/`SetGain`/...)时切换一次,而不是每次寄存器写入时切换——在忙碌的 USB 集线器上,这减少了约 10 倍的 USB 控制传输。支持多设备池与角色分配、每设备增益 / PPM / bias-tee。 **DSP** —— 多相信道化器 + CIC + 半带滤波器,Kaiser / RRC / Gaussian FIR 设计器,FM / C4FM / GFSK / FFSK / DQPSK / π/4-DQPSK / π/8-H-DQPSK 解调器,Mueller-Müller + Gardner 时钟恢复,LMS + CMA 盲均衡器,选择式 + 最大比合并分集。 **API + 可观测性** —— gRPC + HTTP/SSE + WebSocket 接口,可选的 TLS + bearer-token 认证(针对修改操作),Prometheus `/metrics` 端点,纯Go SQLite 通话日志,带类型化负载的进程内发布/订阅事件总线。 **操作界面** —— 一流的 Bubbletea TUI 控制台,包含 12 个面板(包括实时导入面板和可内联编辑的设置面板)以及一个配套的**网页控制台**(一个纯浏览器的 React/Tailwind SPA,可以在构建时嵌入守护进程二进制文件,或作为预构建的 `gophertrunk-web/` 目录与二进制文件一起分发——参见 [`web/README.md`](web/README.md) 和 [§网页控制台](#web-console) 章节);守护进程二进制文件自身也充当 UI 启动器(`gophertrunk -tui` / `-web` / `-headless` —— 参见 [`docs/launcher.md`](docs/launcher.md));带有 CTCSS / DCS 静噪功能的传统 FM 扫描器,双音 QC-II 寻呼检测器,运行时信道锁定,手动 VFO 调谐。实时的 `PATCH /api/v1/settings` 将操作员编辑直接写入 `config.yaml`(保留注释)并热重载可以安全加载的部分;`POST /api/v1/import` 接受 PDF / CSV 上传,解析并预览,然后合并到运行中的守护进程。 **系统初始化** —— `gophertrunk import-pdf` 解析 RadioReference.com 的集群系统 PDF 导出文件和结构化的多段 CSV 捆绑包,然后将站点 + 讲话组合并到 `config.yaml`(保留注释)以及每系统的 Trunk-Recorder 格式 CSV 文件中。交互式 Bubbletea TUI 用于修剪站点,在写入前切换扫描 / 锁定 / 优先级;`-no-tui` / `-dry-run` / `-force` 用于 CI 环境。**`-wizard`** 启动一个交互式配置构建器,引导用户完成 `config.yaml` 的每个部分(日志、API、认证、CORS、存储、录音、保留策略、SDR 设备、扫描器、音频),这样首次使用的操作员无需手动编辑 YAML 就能得到一个可运行的配置文件。参见 **[`docs/import.md`](docs/import.md)**。 完整的百科式详解——每种协议的 FEC 链、接收器内部结构、帧布局、API 修改路由、遥测事件——见 **[`docs/architecture.md`](docs/architecture.md)**。 ## 状态与已知差距 一旦 `grant` 事件到达总线,引擎 + 录音器管道就会端到端运行:分配语音设备,合成器将 IQ 拉取为 PCM,录音器写入 WAV 文件(数字语音协议通过 `voice.DefaultVocoderForProtocol` 解码),通话记录写入 SQLite,API + TUI 界面全部亮起。纯Go的 IMBE / AMBE+2 能产生可懂的音频。CC Hunter 监督器和传统 FM 扫描器由 `cmd/gophertrunk` 构建,并通过 `/api/v1/scanner` 和 TUI 控制台面板暴露其状态。**功能表中的每种集群控制调制方式现在都已拥有一个端到端的 IQ → CC 链**——由 `cmd/gophertrunk` 构建的 `ccdecoder` 连接器覆盖所有 10 种集群协议(P25 Phase 1, P25 Phase 2, DMR Tier III, NXDN, dPMR Mode 3, EDACS, Motorola Type II, LTR, MPT 1327, TETRA TMO)以及业余侧的 DMR Tier II 常规模式和 YSF / D-STAR。 剩余的差距: - **每种协议的空中 FEC 层——大部分已发布,一些内层待完成。** 每个协议的 `ControlChannel.Process` 适配器都提供了一个可工作的 IQ → CC 链(完整参考见 [FEC 选择退出](#fec-opt-outs))。每种协议默认启用符合规范的链路;对于预先剥离的捕获文件,操作员可以按系统选择退出。TETRA 发布了完整的 ETSI EN 300 392-2 §8.3.1 链(解扰 + 解交织 + 解打孔 + Viterbi + CRC-16);DMR Tier III + Tier II 都发布了完整的 BPTC(196,96) + RS(12,9) + CSBK CRC;LTR 发布了 CRC-7 FCS + Manchester 软解码;D-STAR 发布了完整的 JARL DV 模式头链(K=5 R=1/2 + PN15 + 22×30 交织器);P25 Phase 1, P25 Phase 2 格状编码,EDACS BCH(40,28,2),MPT 1327 BCH(64,48,2),Motorola BCH(64,16,11) 均作为可选项发布。仍在等待的内层 FEC: - **NXDN 每协议交织器 + 打孔。** `ViterbiSpec` 模式运行完整的 §4.5.1.1 链;`ViterbiOn` 是旧版 MMDVMHost / DSDcc 固件使用的更简单的裸通道。两者都通过连接器连接;与捕获的 MMDVMHost 传输进行交织器/打孔细节匹配校准是接下来的步骤。4-FSK 切片器的峰值偏差参考现在作为每系统的 `nxdn_deviation_hz` 旋钮暴露(默认 1800 Hz,符合公共空中接口规范),这样捕获的来自偏离规范的发射机的信号——症状:切片器输出出现双峰 dibit 分布——无需代码更改即可重新平衡。参见 [`samples/nxdn/README.md`](samples/nxdn/README.md#tuning-deviation-for-non-spec-captures) 了解扫描配方。位于 [`cmd/gophertrunk/integration_cc_nxdn_realair_test.go`](cmd/gophertrunk/integration_cc_nxdn_realair_test.go) 的跳过门控实测环境在贡献者根据记录的格式将 `.cfile` + `.metadata.json` 对放入 `samples/nxdn/` 后,会自动运行 SystemID + SiteID + 3 秒锁定延迟的验收标准;同时通过记录的 `t.Skipf` 降级使 CI 保持绿色。 - **P25 Phase 2 FEC 链(格状编码 + 外部 RS + PN44 扰码器 + 每突发偏移探测,均已发布)。** 完整的 TIA-102 链将 MAC PDU 封装在三层中,每层通过每系统标志启用: - 内层 4 状态半速率格状解码器(`SetTrellisMode(TrellisOn)`)处理线上的 FEC。 - 外层 RS(24, 16, 9) over GF(2^6)(根据 TIA-102.BAAA-A §5.9)(`SetRSMode(RSOn)`)丢弃伴随式非零的 MAC PDU。 - 根据 TIA-102.BBAC-1 §7.2.5 的 PN44 LFSR 扰码器,带有每突发时隙偏移盲探测(`SetScramblerMode(ScramblerProbe)`),遍历图 7-5 中的所有 12 个时隙偏移,并接受通过 RS 校验的那个——无需外部超帧同步。种子派生自每系统(WACN, SystemID, NAC)三元组(`SetScramblerSeed` / `framing.PN44SeedFromIdentity`)。 之前的后续工作——完整的超帧感知每突发偏移跟踪以及 NSB 驱动的运行时种子安装——现在均已发布。`ScramblerProbe` 盲探测遍历所有 12 个时隙偏移;`ControlChannel.Ingest` 路径通过 `pn44SeedFromNSB` 从每个网络状态广播 - 更新 MAC PDU(操作码 0xFB)自动重新计算种子。每系统的静态配置仍然为 NSB 到达前的前几个 PDU 提供初始种子,并作为覆盖选项保持可用。 - ~~**MPT 1327 同步检测 + 容错 CWSC**~~ (现已发布)。根据 MPT 1327 标准,BCH(64, 48, 2) 每码字校验 + 16 位码字同步码(`1100010011010111`)对齐均已发布。Process 适配器现在将 CWSC 与汉明距离阈值(默认 2/16 位,与在嘈杂的实测捕获中的商用 MPT 1327 接收器匹配)进行匹配,而不是精确匹配,当没有 CWSC 窗口在容差范围内时,回退到旧的“第一个可解析码字”对齐方式。操作员重放预先剥离的合成固件时,可以通过 `mpt1327_cwsc_tolerance: 0` 为每个系统选择回精确匹配。之前提到的“跨 5 个码字 CCDB 组的码字间比特交织器”在标准中不存在;MPT 1327 以 1200 bps FFSK 连续传输 64 位码字,无码字间比特置换。没有剩余的 MPT 1327 规范后续工作。 - ~~**YSF FICH 空中交织器 / 打孔验证**~~ (现已发布规范级编解码器)。K=5 半速率格状编码器 + 解码器(`internal/radio/ysf/fich_trellis.go`)在单元测试中往返正常。`EncodeFICHOnAir` / `DecodeFICHOnAir` 现在叠加了完整的空中链——打孔(丢弃信道比特位置 `{0, 1, 102, 103}`)加上列优先 10×10 交织(out[k] = depunctured[(k%10)*10 + (k/10)]) —— 根据 MMDVMHost / DSDcc / Pi-Star 参考实现。100 位空中流中的每一位翻转都被 Viterbi 修复(`TestFICHOnAirRecoversFromSingleBitFlip` 详尽验证了所有 100 个位置)。针对真实 Yaesu 传输的空中捕获验证是剩余的实测阻碍部分——如果捕获的 FICH 在空中解码后 CRC 失败,根据 `samples/ysf/README.md` 中的文档,切换到备用调度只需两行代码更改。 - **TETRA 空中恢复裕度。** 单元测试往返于干净的固件正常;空中恢复裕度(Viterbi 纠错深度 vs. 真实的同信道 + 邻信道干扰)需要现场捕获来表征。 - ~~**DMR Tier II 合成 IQ 固件**~~ (现已发布)。Tier II 管道 + Process 适配器 + 单元测试均在 PR #184 中发布;端到端集成测试(`TestDaemonCCDecodesDMRTier2`)之前被 `t.Skip`,因为合成的语音 LC 头部 IQ 固件的符号分布比 Tier III 结构相同的 CSBK Aloha 固件更严重地挑战了 Mueller-Müller 时钟环路。诊断测试(`cmd/gophertrunk/dmr_tier2_diagnostic_test.go` 中的 `TestDMRTier2VsTier3SymbolDensity` / `TestDMRTier2SlotTypeVsPayloadIsolation`)将不同的统计量定位到 BPTC(196, 96) 编码的 payload 的 class-3 dibit 过高表示(Tier II 21.4% vs Tier III 5.1%)以及匹配的平均跳变幅度(1.27 vs 0.90);RS(12, 9) 种子 `0x96 0x96 0x96` 和 BPTC 奇偶校验行将高汉明重量比特分布在整个信道比特输出中。修复位于 `internal/scanner/ccdecoder/pipelines.go` 的 `newDMRTier2Pipeline`:将每协议管道的 ClockGain 从 0.025(与 Tier III 共享的值)降低到 0.015,可在更困难的符号分布下保持 MM 环路锁定。接收器在第一个突发后约 100 ms 内锁定;更保守的增益在实时捕获中保持在环路的噪声裕度内。 - **数字语音电平校准。** 纯Go的 IMBE / AMBE+2 端到端输出真实音频。`internal/voice/calibrate/` 中的比较工具已就绪;参考数据(捕获的 P25 P1 / DMR 语音交换以及 DSD-FME / OP25 解码)应存放在 `internal/voice/{imbe,ambe2}/testdata/`)是剩余的缺口。工具数学本身现在通过提取的 `calibrate.CompareSamples([]int16, []int16) Result` 辅助函数以及 `TestCompareSamplesSyntheticGainOffset` 进行了无条件的测试覆盖——RMS 比率或互相关数学中的回归无需参考数据先落地即可在 CI 中暴露。Knox / 呼叫提醒 AMBE+2 音调(b₁ ∈ [144, 163])是厂商特定的,在每厂商频率表到位前保持静音;拥有定制表格的操作员可以通过 `ambe2.RegisterPreset` 注册命名捆绑包(预设名称通过 `ambe2.ListPresets()` 暴露用于诊断)。参见 [docs/vocoders.md](docs/vocoders.md) 了解许可立场和资源清单。 - **CTCSS + DCS 亚音频静噪 + 通话结束尾渐落。** 传统 FM 扫描器除了 IQ 功率外,还可以选择基于亚音频音调或数字码进行静噪门控,这样同频率上的相邻系统通信就不会触发虚假驻留。每信道 YAML: ``` conventional: - label: "Sheriff Repeater" frequency_hz: 155895000 tone: mode: ctcss # ctcss | dcs | none ctcss_hz: 100.0 # ctcss 必填 # dcs_code: "023" # dcs 必填(3位八进制) ``` 两个检测器共享一个 `FM 鉴频器 → 单极点 IIR 低通 → 比特/频率箱检测器` 管道。**CTCSS** 在配置的频率上运行 Goertzel 算法,加上在 ±5 Hz 处的两个反向箱 Goertzel;匹配要求目标箱既超过幅度下限,又要以可配置的因子(默认 1.5 倍)主导最大的反向箱。这拒绝了相邻的 EIA 码,其频谱泄漏在 5 Hz Goertzel 分辨率下会出现在目标箱中;38 个码的列表在低端码间距低至约 3 Hz,单箱路径容易在其上误触发。复用了现有的 `internal/voice/toneout` Goertzel 基元(约 200 ms 块)。**DCS** 恢复 134.4 波特的亚音频 NRZ 流,滑动一个 23 位窗口,并与 46 个预计算的旋转(23 个循环移位 × 2 种极性)进行匹配,这些旋转基于配置的 3 位八进制码构建的 Golay(23,12,7) 码字,复用了与 P25 Phase 1 IMBE 共享的 `internal/radio/framing.GolayEncode23_12` 基元。当任一条件(载波或音调/码)变为假时触发保持时间,这样发射机脱开门控的行为就像真正的载波消失一样。当任何信道具有音调门控时,扫描器会自动将每信道最小驻留时间提升到 250 ms,以便比特/Goertzel 窗口有时间触发。合成器还在通话结束时发出 10 ms 线性渐落尾音(`internal/voice/composer`),这样音频宿在主机扬声器上不会听到突兀的静噪关闭点击声。 - **gRPC `AudioService.StreamAudio` 实时音频扇出。** 守护进程现在提供一个 `api.AudioPublisher`,将每通话合成器解码的 PCM 扇出给任意数量的 gRPC 订阅者。`StreamAudio`(在 `proto/audio.proto` 中定义)读取 `device_serials` + `talkgroup_ids` 作为过滤器允许列表;默认的空过滤器转发所有通话。每个订阅者获得一个 64 帧的有界通道——慢速客户端在通道满时丢帧,而不是对合成器施加背压(每个订阅者和发布者级的丢帧计数器通过发布者的 `Stats()` 方法暴露)。发布者通过事件总线跟踪每设备的 `Grant` 上下文,因此每帧都携带讲话组 + 系统元数据。优雅禁用:当守护进程没有运行合成器(无 SDR 池、音频关闭等)时,RPC 返回 `Unavailable`,这样远程客户端会得到一个干净的错误而不是挂起的流。本地尝试:`grpcurl -plaintext -d '{}' 127.0.0.1:50051 gophertrunk.v1.AudioService/StreamAudio`。 - **从 TUI / API 手动 VFO 调谐。** 扫描器面板现在将 `f` 绑定到 bubbles/textinput 覆盖层:输入以 MHz 为单位的频率,回车,传统 FM 扫描器会附加一个运行时“手动”信道并强制驻留在其上。同样的流程可通过 REST 接口 `POST /api/v1/scanner/manual_tune` 使用(以及 `DELETE /api/v1/scanner/manual_tune/{idx}` 撤销),受 `api.auth` 门控(参见 [API 认证](#api-authentication))。要在没有任何静态 `scanner.conventional` 条目的情况下运行手动调谐,请在配置中设置 `scanner.manual_tune_enabled: true`——然后守护进程将基于最后一个语音 SDR 构建传统扫描器,而不管静态信道数量如何。`internal/scanner/conventional` 现在接受空的种子信道列表,并暴露 `AddTemporaryChannel` / `RemoveTemporaryChannel`,因此相同的 VFO 界面可从任何嵌入器调用。 - **将实时音频播放到扬声器 + TUI / API 音频控制台。** 守护进程提供一个 `voice.Player` 宿(`internal/voice/player`),将解码的 PCM 路由到主机的默认音频输出。在 Linux 上,它通过 `github.com/ebitengine/purego` 直接与 `libasound2.so.2` 通信——无需 cgo,构建时无需 `libasound2-dev`,无需 pkg-config;运行时库在所有标准 Linux 镜像上都有。macOS / Windows 使用 `github.com/ebitengine/oto/v3`(CoreAudio + WASAPI,同样通过 purego)。当在配置中设置 `audio.enabled: true` 时,每通话合成器和传统 FM 扫描器会将 PCM 扇出到播放器,同时现有 WAV 录音器继续工作,因此通话会实时通过主机的默认输出设备播放。音量 / 静音 / 录音可以实时切换:TUI 的扫描器面板绑定 `+` / `-` 用于音量(5% 步进),`M` 用于静音,`R` 用于开启/关闭录音;相同的旋钮通过 `GET` / `PATCH /api/v1/audio` 暴露给远程客户端(PATCH 受 `api.auth` 门控,与其他所有写入端点一样;参见 [API 认证](#api-authentication))。录音门控会停止新的 WAV 文件落地,而不会截断正在进行的会话,符合扫描器肌肉记忆。默认禁用;无头服务器保持静音并继续与以前相同地记录 WAV 文件。新 CLI:`gophertrunk audio list` 镜像 `sdr list`。如果 `libasound2.so.2` 不可达(精简容器等),后端会记录一次并回退到空播放器,这样守护进程的其余部分继续运行。 Go 接口、事件负载和每协议管道均已为功能表中的每种协议发布;上面的剩余工作是每协议 FEC 内层细节 + 参考数据获取。 ## 路线图 仍在考虑中的内容。顺序不固定;每个项目都包含在其自己的包中,并可独立落地。 - **DVSI USB-3000 / AMBE-3003 硬件后端(USB 传输)。** `Vocoder` + AMBE-3003 线路协议 + `voice.Vocoder` 接口一致性发布在 [`internal/voice/dvsi/`](internal/voice/dvsi/),通过 `-tags dvsi` 启用;该包的 `init()` 使用 `voice.DefaultRegistry` 注册 `"dvsi"`。CI 通过脚本化的 mock Transport 和软件环回 Transport(`make test-dvsi`)测试线路协议 + Vocoder 管道。mock Transport 现在覆盖了完整的错误面——`Write` / `Read` 失败包装、环回 Close-then-Write、乱序 Read、畸形包拒绝、`Open` 中的 `Transport` 优先于 `LoopbackOnly` 以及 VID/PID 默认值 + 诊断错误路径——因此重构芯片通信层的贡献者可以依赖测试套件捕获回归,而无需硬件。与物理芯片通信的 USB / FTDI 批量端点管道仍然是一个返回 `ErrNoDevice` 的存根——当没有芯片连接时,录音器回退链会优雅激活。实际的 FTDI 硬件集成将在 DVSI USB-3000 可用于往返测试时落地。 - **声码器电平校准(参考数据)。** 管道已发布——`internal/voice/calibrate` 中的比较工具,`internal/voice/{imbe,ambe2}/testdata/` 中的每声码器测试数据 README,[docs/voice-calibration.md](docs/voice-calibration.md) 中的端到端配方,以及 `cmd/voice-calibrate` 中的一次性 CLI 包装器(运行 `go run ./cmd/voice-calibrate -raw call.raw -ref-wav ref.wav -vocoder imbe`)。操作员只需将 DSD-FME / OP25 从同一 `.raw` 解码的参考 WAV 放入 testdata;一旦两个文件都存在,现有的校准测试就会无保护地运行。AMTMF 双音合成(b₁ ∈ [128, 143])已根据 ITU-T Q.23 4×4 矩阵连接;knox / 呼叫提醒对(b₁ ∈ [144, 163])是厂商特定的——拥有每厂商参考的操作员通过 `ambe2.SetKnoxTone` 注册(freqA, freqB)对,匹配的音调帧通过与 DTMF 相同的双音路径合成。定制表格通过 `ambe2.RegisterPreset` 作为命名 `ambe2.KnoxPreset` 捆绑包发布,这样每个厂商子包的 `init()` 可以在一次调用中点亮整个表格;`ambe2.ListPresets()` 报告哪些捆绑包处于活动状态,供操作员诊断。 - **YSF 空中交织器 / 打孔验证(实测捕获)。** 规范级空中编解码器发布在 `internal/radio/ysf/fich_trellis.go` 的 `EncodeFICHOnAir` / `DecodeFICHOnAir` 中,根据 MMDVMHost / DSDcc / Pi-Star 参考实现(打孔位置 `{0, 1, 102, 103}`,列优先 10×10 交织)。单元测试确认每一位翻转都被 Viterbi 纠正。剩余工作是针对真实捕获的 YSF 传输进行校准——如果捕获的 FICH 在空中解码后 CRC 失败,根据 `samples/ysf/README.md` 切换到备用调度。 ### 近期发布 - **针对静默失败案例的 RTL-SDR 诊断(问题 #275)。** 该池现在在打开时对每个设备编程配置的 IQ 采样率——关闭了 librtlsdr 一致性缺口,该缺口曾使芯片的重采样器处于未定义的上电状态,并将垃圾符号静默地馈送到解码器。纯Go驱动程序还在 `runBringup` 期间编程了 2.048 MS/s 的安全默认值,这样任何忘记设置速率的消费者仍然能获得连贯的 IQ。新的 `gophertrunk_sdr_iq_power_dbfs{system}` Prometheus 仪表(窗口 ≈ 1 秒,空闲 ≈ -45 dBFS,健康信号 ≈ -25 dBFS)使“cc-hunt: 无限重试”的静默面变得可观察;cc 解码器还在电平低于 -55 dBFS 时发出节流的调试日志(“检查天线、增益、USB”)。P25 phase 1 + phase 2 控制信道在同步检测无结果时,节流记录“no FSW/sync hits in chunk”,而不是保持沉默。Bias-tee GPIO 现在是 `internal/sdr/rtlsdr/purego/devices.go` 中每(VID, PID)的覆盖项——现有行继承历史 GPIO 0 默认值,但具有不同引脚的板可以无需分叉设备驱动程序即可添加。 - **网页操作控制台达到与 TUI 的功能对等。** 每个 TUI 面板现在在 [`web/`](web/) 中都有一个浏览器对应版本(Vite + React + TypeScript + Tailwind),作为独立的 `gophertrunk-web/` 目录在每次发布存档中与二进制文件一起分发:仪表盘、活动通话(带结束通话修改)、历史记录(带保留策略扫描修改)、系统、讲话组(带优先级 / 锁定 / 扫描 PATCH 控件)、设备、事件、**音调**(实时铃声 + 每设备重置)、**指标**(Chart.js 趋势图,包含策划的 `gophertrunk_*` 计数器 + Prometheus 快照图块)、**扫描器**(每系统的 CC hunter 持有/恢复/重调谐,传统信道驻留/锁定/解锁,手动 VFO 调谐,scan_mode 切换)、设置。所有修改都通过 `selectCanMutate` 选择器进行了与门控制——设置中的写入模式切换与守护进程的 `/api/v1/mutations` 能力标志结合——并在破坏性操作时包裹确认模态框。独立捆绑包、守护进程在 Pi / 笔记本在沙发上的场景在 [`web/README.md`](web/README.md) 中端到端记录。 - **`gophertrunk import-pdf` 子命令。** 通过解析 RadioReference.com PDF 导出文件或结构化的多段 CSV 捆绑包(可在单次调用中通过 `-pdf` 和 `-csv` 混合使用),将一个区域的集群系统定义引导到 `config.yaml` + 每系统 Trunk-Recorder 讲话组 CSV 文件中。默认启动一个 Bubbletea TUI,用于审查解析的站点并在写入前切换每讲话组的扫描 / 锁定 / 优先级;`-no-tui` / `-dry-run` / `-force` 用于脚本和 CI。原子写入(内存模式验证 → 临时文件 → 重命名),这样格式错误的源文件永远不会损坏现有配置。支持 P25 Phase 1 + Phase 2 PDF;CSV 捆绑包涵盖 P25 / DMR / NXDN。完整的操作员参考见 [`docs/import.md`](docs/import.md)。 - **每个实测阻碍后续工作的捕获规范验收标准。** 每个 [`samples//README.md`](samples/) 现在记录了明确的数值阈值,拥有硬件的贡献者可以运行捕获来关闭相应的后续工作:TETRA 需要 5 秒锁定延迟 + ≥ 90% 帧恢复 + 一个新的(尚未连接的)`gophertrunk_tetra_viterbi_corrections` Prometheus 直方图;NXDN 需要 ≥ 80% CRC 验证的 CAC 突发 + SystemID 匹配;MPT 1327 需要 ≥ 95% 真阳性锁定率 + 非递减的容差扫描;DMR Tier II 需要逐字节 FLC 匹配 + 干净的 Terminator-with-LC 处理。DMR Tier II 和 MPT 1327 后续工作已通过算法关闭(PR-A, PR-C);它们的捕获现在是可选的二次验证,而不是阻碍项。[`samples/README.md`](samples/) 顶层表格总结了所有五种协议的状态 + 验收标准。 - **发布就绪的版本元数据 + AMBE+2 专利横幅。** [`internal/version/`](internal/version/) 现在暴露 `Version`、`Commit`、`BuildTime` 和一个 `String()` 格式化器(`"vX.Y.Z (sha=…, built=…)"`);三者都由 Makefile + 发布工作流通过 `-ldflags` 填充。守护进程在启动时记录一行 AMBE+2 专利立场通知,指向 [`docs/vocoders.md`](docs/vocoders.md);在 CI / 测试工具中设置 `GOPHERTRUNK_QUIET_BANNER=1` 可以抑制它。新的 `make release-dry-run VERSION=v0.99.0` 目标会在本地预演发布构建,这样 ldflags + 打包在标签切割前就能显现——参见 [`CONTRIBUTING.md` §"Cutting a release"](CONTRIBUTING.md#cutting-a-release)。 - **CI 门禁:govulncheck + 许可证审计 + 全树集成运行。** `.github/workflows/ci.yml` 增加了一个 `vulncheck` 作业(对直接 + 传递依赖图运行 govulncheck),一个 `licenses` 作业(通过 google/go-licenses 重新生成传递依赖清单并与提交的 `THIRD_PARTY_LICENSES.csv` 进行 diff),以及一个 `integration` 作业,该作业遍历整个模块运行 `make test-integration`(现有的 `build-test` 作业仅对 `cmd/gophertrunk/` 运行 `make integration`;这为将来在其他包中集成标记的测试做好了准备)。新的 `Makefile` 目标:`make vulncheck`、`make licenses`、`make test-integration`。 [`THIRD_PARTY_LICENSES.md`](THIRD_PARTY_LICENSES.md) 提供了一个手动整理的直接依赖表 + mbelib 派生的 AMBE+2 / IMBE 码本表的 ISC 归属。 - **运维文档落地。** [`SECURITY.md`](SECURITY.md) 记录了漏洞披露流程(私有 GitHub 安全公告)、支持的版本、范围内 vs. 范围外的问题,以及维护者的响应时间 SLA。[`CONTRIBUTING.md`](CONTRIBUTING.md) 涵盖了开发环境设置、内部风格规范和 PR 范围规则。[`CHANGELOG.md`](CHANGELOG.md) 用自校准 / 加固以来所有用户可见的更改创建了一个 Keep-a-Changelog。[`docs/gophertrunk.service`](docs/gophertrunk.service) 是一个示例 systemd 单元(DynamicUser + ProtectSystem + USB device-allow),操作员将其安装到 `/etc/systemd/system/`。 - **HTTP + gRPC 的可选 TLS + 扩展健康端点。** `config.yaml` 中的 `api.tls_cert` / `api.tls_key` 将 HTTP REST/SSE/WebSocket 服务器和 gRPC 服务器都切换到 TLS;当只设置其中一个时,守护进程拒绝启动。对于环回 / 可信局域网部署,默认保持纯 TCP。`GET /api/v1/health` 现在除了传统的 `status` + `now` 外,还返回 `pool_attached_count`、`active_calls`、`db_connected`、`metrics_enabled`、`auth_mode` 和 `version`——支持区分“进程启动”和“实际工作”的 k8s / Nomad 两字段就绪探测。参见 [docs/hardening.md](docs/hardening.md#transport-encryption-tls) 了解 TLS 配方和 [docs/hardening.md](docs/hardening.md#health-endpoint-diagnostics) 了解健康模式。 - **API 服务器加固:HTTP 超时 + gRPC 保活 + 排空窗口。** `internal/api/server.go` 现在在现有的 `ReadHeaderTimeout` 之上,为 HTTP 服务器设置了 `ReadTimeout` / `WriteTimeout` / `IdleTimeout`(30 秒 / 30 秒 / 120 秒)。SSE(`/api/v1/events`)通过 `http.ResponseController.SetWriteDeadline(time.Time{})` 按请求选择退出 `WriteTimeout`,这样长时间存活的流不会在通话中途中断;WebSocket 端点在升级时劫持连接,不受影响。`internal/api/grpc.go` 配置了 `keepalive.ServerParameters`(30 秒空闲 ping,10 秒确认超时)+ `EnforcementPolicy`(5 秒最小时间底线,`PermitWithoutStream: true`),这样长时间存活的 `AudioService.StreamAudio` 订阅者可以检测死掉的对等方,而不会对发布者施加背压。关闭 ctx 从 5 秒增加到 30 秒,这样正在进行的 SSE / WebSocket / 音频流订阅者可以在守护进程重启时干净地排空。参见 [docs/hardening.md](docs/hardening.md#timeouts-and-keep-alive) 了解完整的旋钮参考。 - **运行时主题切换(Ctrl+T)+ docs/tui.md 同步。** 暗色和单色调色板自操作员控制台 PR 以来一直存在于 `internal/tui/theme` 中,但从 UI 无法访问;`Ctrl+T`(以及命令面板中的“Toggle theme”条目)现在可以在运行时循环切换它们。一个 `panels.ThemeChangedMsg` 广播让每个 `bubbles/table` 支持的面板重新应用其缓存的 `tableStyles()`,这样切换在下一次渲染时生效,无需重启。`docs/tui.md` 被完全重写,涵盖了自操作员控制台工作以来发布的所有内容:设置面板及其选项卡循环、Ctrl+P 命令面板、鼠标命中测试和滚轮转发、Revealer 驱动的光标预定位、异步历史刷新,以及旧参考中未包含的每个音频 / 扫描器 / 运行时端点。 - **每个表格面板的鼠标覆盖 + 滚轮滚动。** 在 Revealer PR 中添加的 `MouseAware` 接口现在不仅存在于系统、讲话组和设备面板,还存在于活动通话、历史记录、音调和指标面板——左键单击数据行会将光标移动到每个表格支持的面板中的该行。滚轮滚动以相同方式转发(每次滚动上或下移动一行),因为 bubbles/table v1.0.0 本身不处理 `MouseMsg`。`MouseAware` 签名从 `HandleMouseAt(localX, localY int)` 更改为 `HandleMouse(msg tea.MouseMsg, localY int)`,这样面板可以区分行上的点击、滚轮滚动和按钮释放。一个共享的 `handleTableMouse` 辅助函数集中了左键按下 + 滚轮切换,因此每个表格面板都以相同的方式处理输入。 - **TUI Revealer / MouseAware:调色板 + 鼠标收敛于同一行。** 从 `Ctrl+P` 选择系统 / 讲话组 / 设备现在会跳转到目标面板,并在打开详细模态框之前将面板的光标预定位在匹配的行上——后续的按键(`Enter`,修改键)无需额外滚动即可操作所选内容。新的 `panels.Revealer` 接口形式化了该契约;SystemsPanel(按名称)、TalkgroupsPanel(按十进制 ID)、DevicesPanel(按序列号)和 ScannerPanel(按 `sys:` / `conv:`)实现了它。鼠标命中测试通过兄弟的 `panels.MouseAware` 接口扩展到了选项卡条之外:三个表格面板上的数据行左键单击将点击位置转换为行索引(考虑了规范的 panelFrame 镶边偏移)并调用 `SetCursor`。镶边点击被忽略;超出范围的点击会钳位到最后一行。 - **从 Update goroutine 异步刷新历史记录。** 历史面板是唯一一个在 `Update` 内联构建其 bubbles/table 行的表面。转换现在在 `tea.Cmd` 中运行,并通过路由的 `HistoryRefreshedMsg` 提交;`pendingAt` 守卫防止重复分发,过时的结果(在飞行中更新了快照)被静默丢弃。reducer 在不需要持有它的行格式化上保持不被阻塞。 - **直接 ioctl ALSA 后端(移除运行时 libasound2 依赖)。** 在 Linux 上设置 `audio.device: ioctl`(或 `ioctl:hw:C,D` 用于特定卡/设备)会选择一个直接内核后端,它打开 `/dev/snd/pcmC{card}D{device}p` 并通过 `SNDRV_PCM_IOCTL_*` 系统调用驱动播放状态机。运行时无需 `libasound2.so.2`,无需 `purego.Dlopen`,无需 cgo——适用于不提供用户空间库但内核声音子系统可用的无发行版 / Alpine / scratch 容器镜像。默认为卡 0,设备 0;格式固定为 `audio.sample_rate` 下的 S16_LE 单声道,周期大小由 `audio.buffer_ms` 决定。dlopen 路径仍然是 Linux 的默认选项,因为它会与硬件自动协商;ioctl 路径使用固定值,因此仅在底层设备原生支持它们时才有效。 - **AMBE+2 DTMF 双音合成。** 具有 b₁ ∈ [128, 143](AMBE+2 DTMF 范围)的音调帧现在合成正确的叠加正弦波,而不是路由到静音。16 项的 b₁ → (低 Hz,高 Hz) 查找表来源于 ITU-T Q.23 的 4×4 DTMF 矩阵(行 697/770/852/941 × 列 1209/1336/1477/1633);b₁ → 键的映射遵循 mbelib / DSDcc / DSD-FME 共享的 AMBE+2 布局——128 是 "1",143 是 "D"。两个振荡器相位跨越帧边界,这样按住的键不会产生点击声。Knox / 呼叫提醒对(b₁ ∈ [144, 163])是厂商特定的(Motorola Trbo vs. Hytera vs. 通用),在每厂商频率表到位前仍路由到静音——需要它们的操作员可以将行放入 `ambeDualToneTable` 的上范围。 - **`audio.state` SSE 事件用于即时 TUI 收敛。** PATCH `/api/v1/audio` 现在在事件总线上将结果状态发布为 `KindAudioState`,SSE 泵将其转发给每个订阅者。TUI 监听并在接收时重新获取音频快照,因此两个 TUI / 一个 TUI 加一个 `curl` PATCH 在一个 SSE 往返内收敛,而不是等待最多 3 秒的下一次轮询。负载与 HTTP 响应携带的 `AudioStatusDTO` 相同——客户端可以直接使用它,或者仅将事件视为轮询触发器。 - **传统信道运行时锁定。** 在 TUI 的扫描器面板上按 `L`(或 `POST /api/v1/scanner/conventional/{idx}/lockout`)可在扫描期间跳过一个传统 FM 信道,匹配 Uniden / Whistler 锁定按钮的肌肉记忆。跳过的信道在信道列表中显示 `✗` 标记,并从 `pickNextChannel` 轮换中省略;再次按 `L`(或 POST `unlockout`)可恢复它们。如果被锁定的信道当前正在驻留,合成通话会立即结束(`EndReasonLockout`),因此操作员的意图在一个 IQ 块内生效。锁定是运行时的——它们不会跨守护进程重启持久化,因为配置是永久排除信道的正确位置。 - **macOS RTL-SDR 序列号 / 制造商 / 产品字符串。** 纯Go USB 枚举器的 Darwin 后端(`internal/sdr/rtlsdr/usb/usb_darwin.go`)现在通过 `CFStringGetCString` 读取 IORegistry 字符串属性,而不是返回空占位符,因此 macOS 上的 `gophertrunk sdr list` 会打印与 Linux 和 Windows 自 PR-10 以来已发布的相同每设备标识(序列号、制造商、产品)。对于希望在 `config.yaml` 中通过序列号固定特定 SDR 的多加密狗主机很有用。关闭了自 PR-03 以来在文件中标记的 `TODO(macos-strings)`。 - **YSF integration-cc + P25 P1 grant-chain 扩展。** **关闭了最初的规划路线图**——gophertrunk 解码的每种集群协议现在都有一个端到端的“点亮实时集群接收”集成测试,*并且* P25 Phase 1 路径现在通过生产守护进程 + 总线 + 监督器 + API + 指标链断言了完整的 status → IdentifierUpdate → GroupVoiceChannelGrant TSBK 链(之前的版本停在 cc.locked)。 - `cmd/gophertrunk/integration_cc_ysf_test.go` 使用合成的 4800 波特 C4FM IQ 启动守护进程,该 IQ 携带背靠背的 YSF FSW 承载帧(480-dibit 帧布局,偏移 0 处的 FSWPattern,零填充的 FICH + 负载区域),并断言生产 `newYSFPipeline` + 监督器 + API + 指标链恢复了锁定。与 P25 P1 / NXDN / DMR / dPMR 使用相同的 C4FM 调制器 + RRC 脉冲成型;接收器的 `Options.DeviationHz` 切片器校准旋钮现在在 `internal/radio/ysf/receiver` 上发布(根据 Yaesu 的 C4FM 发射链,峰值规范偏差为 1800 Hz)。`make integration-cc-ysf` 独立运行它。 - `cmd/gophertrunk/integration_cc_test.go` 增加了第二个测试 `TestDaemonCCDecodesP25Phase1GrantChain`,它使用 `ccdecoder.SetTestFactory` 安装一个存根管道,在第一个 IQ 块上将合成的 FSW + NID + TSBK dibit 流直接泵入真实的 `phase1.ControlChannel`。测试了 IQ → dibit 之上的所有内容——工厂调度、频段规划、总线发布、引擎、监督器、API、指标处理器——通过生产代码路径,而不依赖于接收器的 Mueller-Müller 时钟环路在单个流式传递中锁定每个后续的 FSW + NID + 98-dibit TSBK 格状窗口(它在*第一次*锁定时可靠地做到,但在授权链所需的多帧 status → identifier → grant 序列中并非如此)。`make integration-cc-grant` 独立运行它。 - `trunking.ProtocolYSF` 进入协议枚举(字符串形式 `"ysf"`);`P # FEC 选择退出 每个具有公开规范 FEC 链的协议,默认**开启**其规范正确的解码器链:连接器在构建每个 `ControlChannel` 时应用完整的空中 FEC 层,而拥有预先剥离捕获文件(DSD-FME `-r` 转储、OP25 固件、MMDVMHost / DSDcc 测试数据)的操作员可通过在 `config.yaml` 中设置 `: off` 来按系统选择退出。空/缺失的键对于每个协议默认映射到新的“开启”默认值。 在 TUI 的**设置**面板中验证哪些协议开启/关闭 — 它列出了每个已配置的系统,并附带一行 FEC 状态摘要(`channel coding: on (color=…, sch/f)`, `viterbi: spec`, `bch: on` 等)。该面板为只读;运行时修改是未来的 PR。要更改模式,请编辑 `config.yaml` 并重启守护进程。 | 协议 | YAML 键 | 开(默认) | 关(选择退出) | | --- | --- | --- | --- | | TETRA | `tetra_colour_code` (uint32, 低 30 位 — 非 BSCH 必需), `tetra_channel` (`"sch/hd"` / `"sch/f"` / `"sch/hu"` / `"bsch"` / `"aach"`, 默认 `sch/hd`), `tetra_channel_coding` (`""` / `"on"` / `"off"`) | 每次突发完整的 ETSI EN 300 392-2 §8.3.1 类型 5 → 类型 1 链(解扰 + 解交织 + 去凿孔 + Viterbi + CRC-16 校验 + 去尾)。`tetra_colour_code` 为 0 仅对 BSCH 有效;非 BSCH 信道需要每个蜂窝的颜色码,否则解扰会产生垃圾(连接器会对此情况发出警告日志)。 | `tetra_channel_coding: off` 回退到旧的 48-dibit 原始 PDU 路径。在实时捕获上 CRC 将失败;仅适用于预先剥离的固件。 | | LTR | `ltr_fcs_mode` (`""` / `"on"` / `"off"`), `ltr_manchester_mode` (`""` / `"on"` / `"soft"` / `"strict"` / `"off"` / `"nrz"`) | `fcs: on` — 根据 sdrtrunk 的 CRCLTR.java 布局进行 CRC-7 FCS 校验。`manchester: soft` — 对每对进行多数判决解码(匹配次声信令 LTR 信号的主流空中编码)。 | `fcs: off` 跳过 CRC 校验(适用于未填充 FCS 尾字段的合成固件)。`manchester: off` / `nrz` 将流视为原始 NRZ(合成 NRZ 固件)。 | | P25 Phase 2 | `p25_phase2_trellis_mode` (`""` / `"on"` / `"off"`),`p25_phase2_rs_mode` (`""` / `"on"` / `"off"`),`p25_phase2_scrambler_mode` (`""` / `"on"` / `"probe"` / `"off"`) | `trellis: on` — 在MAC PDU窗口上应用4状态½速率网格前向纠错(146通道双比特 → 72信息双比特,依据TIA-102.AABF)。`rs: off` — 对外层RS(24, 16, 9)校验默认关闭,依据TIA-102.BAAA-A §5.9;启用后将丢弃具有非零伴随式的MAC PDU。`scrambler: off` — PN44解扰器默认关闭,依据TIA-102.BBAC-1 §7.2.5。 | `trellis: off` — 适用于预剥离信号源的旧版72双比特原始MAC PDU路径。`rs: on` — 对经过网格解码的MAC PDU进行RS(24, 16, 9)伴随式校验。`scrambler: on` — 将经过网格解码的144位MAC PDU与从配置的突发偏移开始的PN44序列进行异或运算。`scrambler: probe` — 遍历图7-5中定义的全部12个时隙偏移,并接受第一个通过RS校验的结果(需要启用`rs: on`;否则退化为偏移0解扰)。 | | NXDN | `nxdn_viterbi_mode` (`""` / `"spec"` / `"on"` / `"off"`) | `spec` — 完整的NXDN-TS-1-A rev 1.3 §4.5.1.1出站CAC处理链(150双比特 → 解交织25×12 → 解收缩50/350 → K=5 Viterbi → 16位CRC校验 → 155信息比特)。 | `on` — 适用于旧版MMDVMHost / DSDcc信号源的中间92双比特K=5 Viterbi路径。`off` — 适用于预剥离信号源的旧版44双比特原始CAC路径。 | | EDACS | `edacs_bch_mode` (`""` / `"on"` / `"off"`) | 在40位线上码字上应用BCH(40, 28, 2)单/双比特纠错;有效的CCW承载28位信息比特(命令+状态+地址+高位LCN比特),其余比特成为BCH奇偶校验位。 | 退回到旧版预剥离的40位CCW;负载结构体的LCN比特0+辅助字段被视为数据而非奇偶校验位。 | | MPT 1327 | `mpt1327_bch_mode` (`""` / `"on"` / `"off"`) | 对64位线上码字进行BCH(63, 38)解码。 | 退回到旧版38位预剥离码字。 | | Motorola Type II | `motorola_bch_mode` (`""` / `"on"` / `"off"`) | 将两个64位BCH(64, 16, 11)码字重新组装成32位OSW,每个码字支持单比特到11比特纠错。 | 退回到适用于预剥离DSD-FME `-r`信号源的旧版32位原始OSW路径。 | 所有字符串值不区分大小写且允许空格; 识别的"开"值包括 `""` (空) / `"on"` / `"true"` / `"1"` (NXDN也接受 `"spec"`; LTR Manchester接受 `"soft"` / `"strict"`);"关"值为 `"off"` / `"false"` / `"0"` (LTR Manchester也接受 `"nrz"`)。无法识别的值将 退回到"开"默认值,并记录一条`warn`级别日志("ccdecoder: 无法识别的 ``;回退到 on"),这样拼写错误不会 静默地破坏解码器。 每个协议的 `ControlChannel` 都暴露了匹配的获取器 (`tetra.ControlChannel.ChannelCoding()` / `ExpectedChannel()` / `ColourCode()`, `ltr.ControlChannel.FCSMode()` / `ManchesterMode()`, `p25phase2.ControlChannel.TrellisMode()`, `nxdn.ControlChannel.ViterbiMode()`, `edacs.ControlChannel.BCHMode()`, `mpt1327.ControlChannel.BCHMode()`),以便测试和可观测性代码 可以内省已配置的状态,而无需访问未导出的字段。TUI设置面板通过 `/api/v1/systems` 端点的每个系统DTO读取这些配置,该DTO携带每个 选择加入字段作为 `omitempty` JSON值。 ## 文档 ### 项目文档 - [`docs/architecture.md`](docs/architecture.md) — 分层概述, 并发模型,驱动程序注册,构建标签 - [`docs/tui.md`](docs/tui.md) — TUI键绑定,面板参考, 故障排除 - [`docs/web.md`](docs/web.md) — 浏览器Web控制台设置及快速 入门:获取资源包,守护进程CORS/认证配置,打开 `index.html`,局域网场景,PWA安装,故障排除。也可在 **[gophertrunk.org/web.html](https://gophertrunk.org/web.html)** 查看 - [`web/README.md`](web/README.md) — Web控制台开发者参考: 发布面板矩阵,架构,开发工作流 (`make web-dev` / `make web-build`) - [`docs/hardware.md`](docs/hardware.md) — udev规则,DVB黑名单, 用于重放的IQ捕获 - [`docs/vocoders.md`](docs/vocoders.md) — IMBE / AMBE+2许可 现状,插件模型,DVSI后端布局,以及 knox / 呼叫提醒扩展钩子 - [`docs/voice-calibration.md`](docs/voice-calibration.md) — 操作员 针对DSD-FME / OP25参考录音,通过`cmd/voice-calibrate`工具调整 IMBE / AMBE+2解码器的配方 - [`docs/import.md`](docs/import.md) — `gophertrunk import-pdf` 操作员参考:RadioReference PDF工作流,结构化CSV 资源包格式(元数据/站点/通话组部分),TUI键绑定, 及CLI标志参考 - [`docs/hardening.md`](docs/hardening.md) — API认证,TLS 设置,健康端点诊断,HTTP / gRPC超时 + 保活参考,优雅关闭,Prometheus目录, Docker / compose USB直通,冒烟测试清单 - [`docs/opt-in-features.md`](docs/opt-in-features.md) — 操作员 参考守护进程携带的每一个默认设置:协议FEC 默认值,接收器时钟恢复,守护进程级特性 (开/关/自动检测的组合),以及永久性的构建时 门控(DVSI专利标签,集成测试) - [`docs/gophertrunk.service`](docs/gophertrunk.service) — 示例 systemd单元(DynamicUser, ProtectSystem, USB device-allow),适用于 在服务器上部署守护进程的Linux操作员 - [`docs/specs/`](docs/specs/) — 空中接口实现所依据的参考PDF文档 (NXDN-TS-1-A, ETSI EN 300 392-2 TETRA,以及一份用于EDACS的反向参考M/A-COM LBI文档,记录了*不应*查找的内容) ### 项目元数据 - [`CHANGELOG.md`](CHANGELOG.md) — 每次发布的用户可见变更, Keep-a-Changelog格式 - [`CONTRIBUTING.md`](CONTRIBUTING.md) — 开发环境设置,PR范围 规则,内部风格约定,发布剪裁流程 - [`SECURITY.md`](SECURITY.md) — 通过GitHub私有安全公告 进行的漏洞披露流程;范围内 vs. 范围外;响应时间SLA - [`THIRD_PARTY_LICENSES.md`](THIRD_PARTY_LICENSES.md) — 直接 Go模块依赖项 + mbelib ISC对AMBE+2 / IMBE码本表的归属说明 ## 许可证 参见 [`LICENSE`](LICENSE)。
标签:DMR协议, dPMR协议, D-STAR协议, EDACS协议, Go语言, LTR协议, Motorola Type II协议, MPT 1327协议, NXDN协议, P25协议, Python工具, RTL-SDR, TETRA协议, TUI界面, Web控制台, WSL, YSF协议, 单二进制文件, 实时扫描, 扫描工具, 数字通信, 数字集群无线电, 无线电扫描, 无线电监控, 日志审计, 程序破解, 纯Go实现, 软件定义无线电, 静态二进制, 音频处理