业余无线电 FMO 平台的日志与控制台 · 战术 HUD 主题 · FmoLogs 的二次开发版本
仓库地址:https://github.com/wh0am1i/FmoDeck
本项目由 AI 辅助编码完成(Claude Code),大部分实现、重构与样式是 AI 生成的,人类做架构决策 / 需求拍板 / 审阅。
修复
- 修复了 App 无法保存图片
新增
- 网页版支持「添加到主屏幕」以独立窗口(PWA ·
display: standalone)打开,隐藏浏览器地址栏(需 HTTPS · Tauri 壳内不注册 Service Worker)
修复
- 安卓 App 收到 SSTV 图片后无法下载/复制:安卓 WebView 不支持
<a download>与图片剪贴板。下载改为存系统相册(tauri-plugin-android-fs· MediaStore + 图库扫描),复制改为系统分享面板。Web/桌面行为不变
调整
- 默认不再进入全屏值守屏:打开 app 落地日志页(旧行为),值守屏移到独立路由,导航栏新增「值守」tab 按需进入
首页重做为全屏地图值守屏
新增
- 地图满屏铺底(深色夜图),讲话者切换自动定位,我 ↔ 对方连线 + 距离标签
- 右侧信息列:UTC/本地双时钟、☰ 页面菜单、讲话者 Hero、讲话名册
- Hero 显示群组名并可切换群组,内嵌波形与收听控制
- 名册按呼号去重(×次数),点击在地图上聚焦该呼号
- 手机横屏适配,竖屏提示旋转
- 新 QSO 入库实时刷新日志(今日已联 ⭐ / 统计不再滞后)
修复
- 自己讲话被误判为对方:本机呼号改用服务器返回值判定(地图连线 / 本机徽章 / 自言自语静音三处)
- iOS 横屏页面可滚、内容被裁(
100vh→100dvh+ 大字体模式限高屏生效) - 地图可无限缩小到灰板(限制 minZoom + 世界边界)
- 讲话者无网格时地图视角乱跳
- 连线在浅色路网上看不清(白边加粗)
新增
- SSTV 新模式:Scottie S1 / Scottie S2
- 超时残图保留:帧未解完不再直接丢弃,保留已解出的部分行 + 右上角挂"未完整 N/M"徽章
- 手动模式选择:VIS 漏识别时的救场入口,可强制按指定模式从最近 N 秒开始解码
- 信号指示器:等待期三色能量条(1900 LEAD / 1200 SYNC / 1700 IMG),直观判断有无 SSTV 信号
- PNG 元数据:导出图片写入 mode 信息(SSTVMode / SSTVDisplay / Software tEXt chunk)
- 消息方向区分:连接后自动获取本机呼号,消息列表 / 详情按 from/to 区分进出方向
改进
- Hampel filter 替换固定中位数:消除所有 mode 行间"5-行台阶"现象,真实 slant 缓变可平滑跟踪
- 静音超时改时间阈值(5 秒,所有 mode 统一);长行 mode(PD120)不再被网络抖动误判
- FM 解调瞬态丢弃:行起始 5ms warmup 前缀,sync 检测不再被 LPF 启动瞬态污染
- VIS 检测放宽:仅需任一段 leader 通过即可识别,弱信号 / QSB 不再漏帧
- 历史筛选改为动态从模式注册表生成,新增 mode 自动出现
- SSTV 全文案 i18n 中英双语
- 消息已读状态跨 nav 保留:刷新合并时不再覆盖客户端已设置的已读 / to 字段
- 顶部模式列表改成 chip 风格
修复
- 网格坐标点击跳转改为自家项目
https://maidenmap.wh0am1i.com/
已知问题
- 安卓端接收 SSTV 会出现行间错位,排查中
新增
- SSTV 接收:新
/sstvtab,自动识别并解码 Robot 36 / Robot 72 / Martin M1 / Martin M2 / PD 120 五种模式,渐进式绘图,最近 5 帧滚动缩略,完整历史存 IndexedDB - 今日已联 ⭐ 标:Top20 / 老朋友 / 日志表的呼号右侧显示今日是否联过
- Header 版本号动态从
APP_VERSION读,不再硬编码
改进
- 解码对 Opus 压缩的鲁棒性提升(Robot 72 对角剪切、Martin M2 长图后半段偏移等典型问题修复)
新增
- Android release 签名:APK 改走正式 release keystore(原 debug 随机 key), 后续版本可以覆盖升级保留本地数据(IndexedDB 日志 / FMO 地址 / HUD 设置等)。 签名指纹固定 = 所有后续版本通行证
- 应用内升级(仅 Android):启动自动检查 + Settings
[ 更新 ]手动触发 + HUD 风 Dialog(发现新版 → 下载进度 → sha256 校验 → 调起系统包安装器); 24h 去重、cache: no-store防 WebView 缓存、@Keep防 R8 strip - 自建升级服务器契约:纯静态 nginx,manifest 走
<host>/fmodeck/android/latest.json, 不依赖 GitHub API(国内可达性友好);通过VITE_UPDATE_BASE_URL构建时注入 - Android 原生:
REQUEST_INSTALL_PACKAGES权限 +FileProvider+ KotlininstallApkcompanion + Rustinstall_apkJNI 命令
改进
- Passcode 计算器从 Settings 再挪回 APRS 菜单(v0.1.5 反向挪回来,按功能归属)
- WebView 远程调试:release APK 也强制
setWebContentsDebuggingEnabled(true), 可通过chrome://inspect看 Console - cleartext 配置:Tauri 2.10+ 默认
usesCleartextTraffic="false",改为无条件覆盖 为true,保证 FMOws://明文连接在 release 构建下不被拦 - Vite 环境变量注入:CI 新增
VITE_UPDATE_BASE_URLSecret
升级说明 · 从 v0.1.5 Android 版升级时必读
- 本版本起 APK 签名从 debug 切到 release,签名指纹不同 → 直接覆盖安装会被
Android 系统拒绝(
INSTALL_FAILED_UPDATE_INCOMPATIBLE) - 老用户必须先卸载 v0.1.5,再全新安装 v0.1.7(本地 QSO 日志请先导出 ADIF 备份)
- v0.1.7 之后的所有升级可以正常覆盖,不再需要卸载
- 首次点应用内"立即安装"会弹"允许此应用安装未知应用"系统设置页,授权后回到应用 再点一次即可走到系统包安装器
工程
- 跳过 v0.1.6 版本号(升级链路端到端调试用的过渡版本)
- updater feature 346 个单测全绿:version-compare / manifest / download-sha256 / 状态机
- prettier 统一 format,ESLint warnings 清零
新增
- 频谱页面(
/spectrum):五面板组合 —— 柱状频谱、示波器、瀑布图、 实时遥测(RMS / 峰值频率 / 峰值强度)、讲话名册(滚动时粘顶) - SpeakingBar 迷你频谱:收听开启时实时显示
- 日志页「历史」切换:融合进日期过滤组,点击原地把下方表格切换为 讲话历史,再点任一日期切回日志
- Android 打包:新增 GitHub Actions workflow,支持 aarch64 + armv7 双 ABI,debug APK 约 60MB
改进
- 地名反查:主链路换到高德
regeo(需VITE_AMAP_KEY),格式 精简为「市 - 省」;没配 key 时 fallback 到 BigDataCloud - SpeakingBar:呼号和地名字号放大、地名改高亮色,移动端改两行 结构,HOST / 新朋友徽章不再被长地名挤下一行
- 移动端排版:日志 / 历史 / 老朋友表格在窄屏把 GRID 内联到呼号 下;日志头三个操作按钮只留图标;中文地名不再按字符断行
- APRS passcode 计算器从 APRS 页挪到
[ 设置 ] - Android 状态栏不再遮挡内容(主动消费 systemBars insets)
部署 / 构建
- Docker 构建新增
VITE_AMAP_KEY参数;CI workflow 注入AMAP_KEYGitHub secret
其他
- README 标注项目由 AI 辅助编码;git 提交去掉
Co-Authored-By尾行
- APRS 远程控制按构建目标分流:桌面版(Tauri)默认关闭并 tree-shake,
web 版保留;通过
VITE_ENABLE_APRS+.env.tauri控制 - Windows 安装包图标修复:重新生成
icon.ico为 BMP 格式多尺寸条目 (16–256px),解决 NSIS 安装器不认 PNG 压缩 ICO 导致的空图标
新增
- 桌面应用:基于 Tauri 2 打包为原生 macOS / Windows / Linux 应用,
用系统 WebView 渲染,产物 ~10 MB;
pnpm tauri:dev/pnpm tauri:build一键出包 - 跨平台 CI:推
v*tag 时自动在 GitHub Actions 上构建 4 个平台 (macOS arm64 / x86_64、Linux x64、Windows x64),产物自动关联到 Release 草稿 - 桌面版两个隐形福利:地址栏无 "Not secure" 提示;不受 Chrome
Private Network Access 限制,
ws://fmo.local随便连
改进
- 排行榜每行重构为上下两行布局:主行「序号 + 呼号 + 次数」、 第二行「位置 + 最近日期」,移动端也不再挤爆
- 排行榜「次数」列数字 tabular-nums 右对齐,位数不同也对得上
其他
- GitHub Actions 升级到 Node 24 兼容版本(Node 20 即将弃用)
- 外链统一走
openExternal()工具函数,桌面 / 浏览器端都正确跳转
- 音频收听:裸 Web Audio 直连 FMO
/audio,8kHz PCM + HPF/LPF/EQ/压缩; SpeakingBar 右侧开关 + VU + 音量 + 静音;自己讲话时自动过滤回授 - 大字体模式(仅桌面)、中继搜索(StationSwitcher ≥ 6 条时出现)
- 网格 → 地名:Maidenhead 反查 OSM Nominatim,可点击跳转,结果缓存
- 修若干 bug:网格链接误触父行、消息字段名、呼号/SSID 拆分等
- APRS 远程控制(普通 / 待机 / 软重启 + 参数表单 + 20 条历史 + Passcode 计算器)
- 桌面通知、消息回复与批量删除、日期范围筛选、按过滤结果导出 ADIF
- SpeakingBar「✦ 新朋友」徽章、HUD 风删除确认 Dialog
- ADIF 二次导入去重、中继切换竞态修复、全量 i18n
首个可用版本 —— 基于 FmoLogs 的 React + TypeScript 完整重写,战术 HUD 主题。
FmoDeck 是基于 FmoLogs(作者 @dingle1122)的二次开发作品。原项目 完整搭建了与 FMO 设备交互的协议实现、日志同步、APRS 相关能力等核心业务逻辑。 本仓库在其基础上做界面与交互层的重写、增加战术 HUD 主题与若干打磨, 但所有"能用起来"的根基都来自 FmoLogs。特此鸣谢 ✨
- 日志:连 FMO 实时拉 QSO,本地过滤(呼号前缀 / 日期范围)+ 分页 + 按过滤结果导出 ADIF
- ADIF 导入:
.adi文件解析到本地 IndexedDB;与服务器日志合并展示,(呼号, 时间戳)维度自动去重 - 排行榜 / 老朋友:基于已有日志聚合;行内显示对方网格 + 实际地名
- 消息:收件箱 + 撰写 + 回复 + 一键全部已读 / 单条 / 全部删除 + 推送到达即时刷新
- 控制:一键普通/待机模式 + 重启 APRS 服务
- APRS 远程控制:走 APRS-IS 网关跨网络控制设备(普通 / 待机 / 软重启)+ 20 条历史 + Passcode 计算器
- 音频收听:直连 FMO
/audioWebSocket 播放对讲音频;音量可调、 VU 电平条;自己讲话时自动过滤回授 - SpeakingBar:实时显示讲话者 + 位置 + 与对方通联统计;只通联过 一次的呼号高亮"新朋友"徽章
- 网格 → 地名:Maidenhead 网格反查 OpenStreetMap,展示实际地名 可点击跳转地图;结果缓存
- 中继切换:一键 prev/next、列表选择、模糊搜索
- 双语:简体中文 / English(右上角切换)
- HUD 视觉:霓虹辉光强度、扫描线不透明度可调;桌面端大字体模式
- 构建:Vite 7 · TypeScript 5.7+ strict
- UI:React 19 · React Router v7 · Tailwind CSS v4 · shadcn/ui · Radix
- 状态:Zustand 5(含 persist)
- 存储:IndexedDB(本地 QSO 持久层)· sql.js(ADIF 解析)
- i18n:i18next · react-i18next
- 测试:Vitest 3 · jsdom · @testing-library/react
- 质量:ESLint 9(flat config)· Prettier 3 · vite-plugin-checker
- CI:GitHub Actions(
.github/workflows/ci.yml)
前置:Node ≥ 20.19 · pnpm ≥ 9
pnpm install # 安装依赖
pnpm dev # 本地开发(http://localhost:5173)
pnpm build # 生产构建(输出到 dist/)
pnpm preview # 预览构建产物
pnpm test # 运行测试
pnpm test:watch # 监视测试
pnpm typecheck # 类型检查
pnpm lint # ESLint 检查
pnpm lint:fix # ESLint 自动修复
pnpm format # Prettier 格式化
pnpm format:check # Prettier 检查冷蓝 #00D9FF 为主调,琥珀 #FFB000 为警戒色,品红 #FF3E5C 为危险色。
支持 light / dark / system 三档(右上角切换)。装饰强度通过 CSS 变量
--hud-intensity 控制(Settings → [ 界面视觉 ])。
除了浏览器版,FmoDeck 也可打包为原生桌面应用 —— 用系统 WebView 渲染, 产物 ~10 MB。桌面版有两个额外好处:
- 没有地址栏「Not secure」提示
- 不受 Chrome Private Network Access 限制,
ws://fmo.local随便连
前置:Rust toolchain(rustc ≥ 1.77)+ 平台 WebView 依赖(macOS 自带;Linux 需 webkit2gtk)。
pnpm install
pnpm tauri:dev # 起 dev 壳 + Vite HMR
pnpm tauri:build # 打包当前平台产物到 src-tauri/target/release/bundle/.github/workflows/tauri-release.yml —— 推 v* tag 时自动在 4 个平台
(macOS arm64 / x86_64、Linux x64、Windows x64)构建,产物关联到
GitHub Release 草稿。
- macOS:CI 做 ad-hoc 本地签名,首次打开右键 → 打开;若报「已损坏」
跑
xattr -cr /Applications/FmoDeck.app清 quarantine 即可 - Windows:SmartScreen 警告点「仍要运行」
- Linux:无签名负担
为什么只有 HTTP? FMO 设备只开
ws://,没有 TLS 证书。如果前端 走 HTTPS,浏览器的"混合内容"策略会把所有ws://连接拦截,页面就无法 与设备通信。因此当前唯一可行的部署是 HTTP —— 代价是地址栏会显示 "Not secure",且桌面通知 / PWA 安装等依赖 secure context 的 API 用不了。
仓库根目录已备好 Dockerfile(多阶段:Node 构建 → nginx:alpine 托管)+
docker-compose.yml + docker/nginx.conf.template(SPA fallback + gzip)。
git clone https://github.com/wh0am1i/FmoDeck.git
cd FmoDeck
docker compose up -d --build默认监听 127.0.0.1:8080,走宿主机反代对外。直接暴露公网把
docker-compose.yml 的 ports 改成 "8080:80"。可调参数在 Dockerfile
顶部 ARG(BASE_PATH / LISTEN_PORT / NODE_VERSION / NGINX_VERSION)。
MIT —— 与上游 FmoLogs 保持一致。版权声明里同时保留 FmoLogs Contributors 与 FmoDeck Contributors, 以示来源。