这些小活动你都参加了吗?快来围观一下吧!>>
电子产品世界 » 论坛首页 » DIY与开源设计 » 电子DIY » 【蓝牙信道探测开发板深度测评与创意应用】5.实时可视化:把信道抖动做成一块仪表盘

共1条 1/1 1 跳转至页

【蓝牙信道探测开发板深度测评与创意应用】5.实时可视化:把信道抖动做成一块仪表盘

助工
2026-11-03 01:53:44     打赏

前面把数据采出来了、把事件检出来了,这一篇解决"怎么让人一眼看懂"。


1. 为什么要单独做一块仪表盘

第 4 篇的原型已经能用串口打印 CSV、用 afplay 播放提示音。但演示和测评文章都需要一个"看得见"的东西:

  • 一条正在跳动的 var(phase) 曲线

  • 一个足够大的「有人 / 无人」状态

  • 事件发生的红色区间

  • 检出次数、采样率这些可量化指标

最初想用 matplotlib 的实时窗口,但有两个现实问题:

  1. 这台 Mac 的 Python 没有 tkinter,matplotlib 的桌面后端不可靠

  2. 桌面窗口不方便截图、录屏和远程查看

所以改成 Web 仪表盘:后端用 Flask 提供数据,前端用浏览器 Canvas 画图。不需要装 GUI 工具包,能直接全屏录像,手机连同一个局域网也能看。

2. 整体结构

串口 CSV  ──►  读取线程  ──►  环形缓冲 + 状态  ──►  Flask /api/data  ──►  浏览器轮询(200 ms)
 (LM20)        (pyserial)       (deque 400)         (JSON)                ├── Canvas 曲线
                                                                          ├── 大号状态
                                                                          └── 语音提示

【图片1:仪表盘数据通路】05_1.png

分工很明确:

  • 读取线程只做三件事:读串口、解析 CSV、更新缓冲和计数

  • Flask 只暴露一个 JSON 接口和几个静态路由

  • 前端 只负责轮询、画图和放声音

3. 后端:tools/cs_dashboard.py

3.1 读取线程

with serial.Serial(port, baud, timeout=1) as ser:
    while True:
        line = ser.readline().decode("utf-8", errors="ignore").strip()
        if not line.startswith("CSV,"):
            continue
        parts = line.split(",")
        if len(parts) != 10:
            continue
        try:
            nums = [float(x) for x in parts[1:10]]
        except ValueError:
            continue   # 固件日志与 CSV 交错时,跳过被污染的行

解析后把样本放进固定长度 deque(maxlen=400),同时更新运动状态和检出次数。断连时线程会每 2 秒重试,不会让整个服务退出。

3.2 JSON 接口

@app.get("/api/data")
def api_data():
    with _lock:
        samples = [{k: _finite(v) for k, v in s.items()} for s in _samples]
        return jsonify({
            "samples": samples,
            "motion": _state["motion"],
            "events": _state["events"],
            "rate": _state["rate"],
            "count": _state["count"],
            "connected": _state["connected"],
            "threshold": THRESHOLD,
        })

关键坑:NaN 不是合法 JSON。

CS 在信号质量差时会输出 NaN。Python 的 json 模块会把它原样写成 NaN,curl 看着没问题,但浏览器的 response.json() 会直接抛异常,页面就显示"仪表盘服务未响应"。修复只需要在序列化前把非有限值转成 null:

def _finite(value):
    if isinstance(value, float) and not math.isfinite(value):
        return None
    return value

前端也要相应地把 isFinite 改成 Number.isFinite,否则 null 会被当成 0 画进曲线。

3.3 其他路由

路由用途
/返回 dashboard.html
/api/data实时样本与状态
/audio/alert.mp3edge-tts 生成的语音
/favicon.ico返回 204,避免无意义的 404

4. 前端:tools/dashboard.html

4.1 轮询与状态

setInterval(poll, 200);

async function poll() {
  const res = await fetch('/api/data', { cache: 'no-store' });
  const data = await res.json();
  ...
}

200 ms 的轮询对应 5 Hz 刷新,足够跟上 10 Hz 的 CS 数据,又不会让浏览器太忙。

状态只有三种:

  • 灰色「等待链路…」

  • 绿色「无人 / 链路稳定」

  • 红色「有人 / 检测到运动」

4.2 两张 Canvas

  • 上图:IFFT / Phase Slope / RTT 三条距离曲线,y 轴自动缩放

  • 下图:var(phase) 填充曲线、0.10 阈值虚线、运动区间红色背景

所有绘制都用原生 Canvas 2D,没有引入 Chart.js 之类的依赖,断网也能跑。

4.3 浏览器放声音

浏览器的自动播放策略要求先有一次用户交互。所以界面右下角有一个「启用声音」按钮:

soundBtn.addEventListener('click', () => {
  audio.play().then(() => {
    audio.pause(); audio.currentTime = 0;   // 解锁后立刻暂停
    soundOn = true;
  });
});

之后每次 motion 由 0 变 1,就播放一次语音,并带冷却时间。

5. 界面效果

【图片2:Web 实时仪表盘】05_2.png

截图时的状态:

指标数值
状态有人 / 检测到运动
采样率9.9 样本/秒
累计样本203
检出次数5
距离曲线IFFT / Phase / RTT 实时更新
抖动曲线两个明显峰值,红色背景对应运动区间

截图用的就是第 4 篇那套 LM20 + XIAO 的 2.3 m 链路。人坐在两板之间,起身、走开、再回来,状态和曲线都会跟着变。

6. 踩坑记录

现象原因修复
页面显示"仪表盘服务未响应"固件输出 NaN,浏览器 JSON 解析失败服务端转 null,前端用 Number.isFinite
曲线出现 0 值尖刺全局 isFinite(null) 返回 true改用 Number.isFinite(null)
点了没声音浏览器自动播放限制「启用声音」按钮先解锁
串口打不开cs_alert.py / capture_serial.py 同时占用同一时间只跑一个串口程序
控制台 404浏览器请求 favicon增加 /favicon.ico 返回 204

7. 使用方法

# 依赖(已在 .venv 中)
.venv/bin/pip install flask pyserial

# 启动,可选保存 CSV
.venv/bin/python tools/cs_dashboard.py --save logs/dashboard.csv

# 浏览器打开
open http://127.0.0.1:8765

接口也可以单独调用:

curl -s http://127.0.0.1:8765/api/data | \
  python3 -c "import sys,json; d=json.load(sys.stdin); print(d['count'], d['rate'], d['motion'])"

8. 可以继续做的

  • 用 SSE / WebSocket 替代轮询,进一步降延迟

  • 加历史回放:把 dashboard.csv 拖进页面重放

  • 手机适配:改一下 Canvas 高度,就能在手机上全屏看

  • 录像:直接 OBS 捕获浏览器窗口,适合做演示视频

9. 小结

这一篇把第 4 篇的 CSV 变成了可看、可听、可录的仪表盘:

  • 后端一个读取线程 + 一个 JSON 接口

  • 前端两张 Canvas + 一个状态灯 + 一个声音按钮

  • 最难的不是画图,而是处理 NaN 和浏览器自动播放这类"看起来无关"的细节


附:本系列回顾

篇主题关键收获
1开箱与环境远程编译 + 本地烧录链路
2BLE 基础广播、连接、NUS
3CS 入门双板测距与误差分析
4环境心跳用链路抖动感知环境
5实时可视化Flask + Canvas 仪表盘


代码附件:9991_nrf54lm20_cs_source.zip




关键词: 蓝牙     信道     探测     Nordic    

共1条 1/1 1 跳转至页

回复

匿名不能发帖!请先 [ 登陆 注册 ]