# longyanbci **Repository Path**: RayShen520/longyanbci ## Basic Information - **Project Name**: longyanbci - **Description**: No description available - **Primary Language**: Unknown - **License**: Not specified - **Default Branch**: main - **Homepage**: None - **GVP Project**: No ## Statistics - **Stars**: 0 - **Forks**: 0 - **Created**: 2026-09-30 - **Last Updated**: 2026-10-01 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # longyanbci 前额双极脑电头环 —— 取数链路的逆向与接入代码。 完整的技术记录见 **[脑电头环_协议与接入说明.md](脑电头环_协议与接入说明.md)**, 这里只放最必要的上手信息。 --- ## 这个头环有两条互斥的取数链路 头环同一时刻**只能连一个主机**,两条路不能同时用: | | A. USB dongle 串口 | B. BLE 直连头环 | |---|---|---| | **拿到什么** | 头环算好的**指标值** | **原始脑电波形** | | 数据率 | ~1.6 Hz / 指标 | 250 Hz 采样 | | 典型用途 | 上课场景 (`专注力采集.exe`) | 自己做算法研究 | | **前提** | dongle 插在 USB 上 | **必须拔掉 dongle** | > ⚠️ **最容易踩的坑**:dongle 插着的时候头环不广播,BLE 根本扫不到设备。 > 想走 B 链路,先把 dongle 从 USB 口拔掉。 --- ## 硬件 - **头环**:两片电极,戴在前额(约 Fp1–Fp2),输出**单通道**差分脑电 - **dongle**:`USB\VID_1915&PID_521A`(Nordic),虚拟串口,115200 8N1 - 头环的 BLE 名字可按人改(不是唯一标识),**认设备要认 MAC 地址** --- ## 快速开始 Python 3.12,**venv 建在 `daima/` 下**: ```powershell # 1) 建环境并装依赖 cd daima python -m venv .venv .\.venv\Scripts\python.exe -m pip install -r ..\requirements.txt # 2) 先扫一下,确认能看见头环(记得先拔 dongle) .\.venv\Scripts\python.exe scan.py # 3) 载着的时候跑这个,边调头环位置边看信号质量 .\.venv\Scripts\python.exe eeg_monitor.py --addr D9:D4:E4:29:1F:BB # 3b) 想要真正的波形图窗口(不是终端字符画)就用这个 .\.venv\Scripts\python.exe eeg_plot.py --addr D9:D4:E4:29:1F:BB # 两个人同时看:--addr-a ... --addr-b ... # 头环还没戴上、只想先看看窗口长什么样:加 --demo # 4) 质量确认好了,正式采数据 .\.venv\Scripts\python.exe eeg.py --addr D9:D4:E4:29:1F:BB --seconds 60 --csv eeg.csv # 两个人各戴一个?两个头环可以同时连,共用时间基准 .\.venv\Scripts\python.exe eeg_dual.py ^ --addr-a D9:D4:E4:29:1F:BB --addr-b C6:80:02:21:E5:9F ^ --csv-a 零玖.csv --csv-b 壹拾.csv --seconds 60 # 5) 想在浏览器里看 —— 波形 + 频谱 + 频段占比 + 接触质量五项 .\.venv\Scripts\python.exe api.py # 然后开 http://127.0.0.1:8000/ ``` ### 检查脚本跑法 ```powershell # 不需要头环的(在 daima/ 下跑) .\.venv\Scripts\python.exe test_session.py .\.venv\Scripts\python.exe test_api.py node ..\web\check_render.mjs # 这条要 node # 需要真头环的(**跑之前先把 api.py 停掉**,蓝牙只有一个射频) .\.venv\Scripts\python.exe check_web.py --addr C6:80:02:21:E5:9F --seconds 15 ``` **第 3 步别跳过。** 干电极接触不好采到的整批数据都是废的,而 `eeg_monitor.py` 就是让你在采集前把这件事确认掉 —— 它会实时画波形,并每秒算一次接触质量。 绿色"良好"了再往下走。 跑起来会**每秒打印一行统计**:采样点数、实际采样率、丢包数、幅值范围, 方便你一边戴一边判断信号好不好。 > 脚本之间不互相 import,都是独立可执行的,所以从哪个目录调用都行 > (`--csv` 之类的相对路径会落在当前目录)。 ### 关于 `eeg_plot.py`(波形图窗口) 和 `eeg_monitor.py` 的区别:后者是终端里的字符画,好在**不用装任何东西**、 随开随用;前者是真正的 matplotlib 波形图,能看到细节,但要多装 matplotlib。 快捷键:`f` 切换显示模式,`空格` 暂停,`q` 退出。 **默认显示的是滤波后的信号**(1 Hz 高通 + 50 Hz 陷波),三种模式循环切换: | 模式 | 说明 | |---|---| | 原始 | 蓝牙收到的原样,含基线漂移 | | 高通 1Hz | 去掉漂移,看到脑电节律 | | 高通 + 陷波 | 再去掉工频干扰(**默认**) | > 为什么默认要滤波:干电极的基线漂移能到几百 µV,比脑电本身(±50 µV) > 大一个量级。不滤波的话曲线就是一条缓慢漂移的线,看不出任何脑电节律。 > 但**存盘的数据始终是原始的**,这个滤波只影响显示,不影响数据。 两个头环可以同时显示(`--addr-a` / `--addr-b`)。注意此时**两路各自计时**, 画面上的"现在"不是同一个时刻 —— 做实时观察没问题,要**逐样本对齐**的双人 记录请用 `eeg_dual.py`(它两路共用一个时间基准)。 某一路上线失败(没开机、被 dongle 占着)不会影响另一路,失败的会在终端 报出原因,其余照常采集。 刷新率实测约 **17~18 fps**(默认请求 25 fps,受限于 matplotlib 每帧整幅重绘)。 看脑电足够流畅,不用调。 > 想确认窗口是不是真的在动,跑 `python check_window.py --demo`: > 它会开一个真窗口跑 10 秒,然后报出实际触发了多少帧。 > **为什么要专门有这个**:窗口能打开、数据也在进来、但曲线是死的 —— 这种 > 故障无头测试查不出来,而它确实发生过。 --- ## 代码文件 | 文件 | 作用 | |---|---| | `daima/eeg_plot.py` | **实时波形图窗口**(matplotlib,可同时显示两个头环) | | `daima/test_eeg_plot.py` | `eeg_plot.py` 的无头自检(不需要头环,也不需要 GUI) | | `daima/check_window.py` | 开真窗口验证曲线确实在刷新(无头测试覆盖不到的那一项) | | `daima/check_reconnect.py` | 验证断线重连(需要真头环,而且要你动手把它关掉) | | `daima/eeg_monitor.py` | **实时波形 + 接触质量监视**(终端字符画版,采集前必跑) | | `daima/eeg_dual.py` | **双头环同步采集** —— 双人实验用 | | `daima/eeg.py` | **BLE 原始脑电接收 + 解包** —— 正式采集用这个 | | `daima/dsp.py` | **滤波(高通 / 陷波)+ 接触质量指标** —— 纯 math/numpy | | `daima/session.py` | **Web 版的会话核心**(扫描 / 连接 / 重连 / 缓冲 / 滤波 / 频谱)—— 纯 asyncio,不含 HTTP | | `daima/test_session.py` | `session.py` 的无头自检(不需要头环,也不需要浏览器) | | `daima/api.py` | **Web 后端**:REST + WebSocket —— 只做校验/转发,**不含 BLE** | | `daima/test_api.py` | `api.py` 的无头自检(不需要头环,也不需要浏览器) | | `daima/check_web.py` | 验证整条链路(**需要真头环**,且要先停掉 `api.py`) | | `web/index.html` | **浏览器界面**(原生 JS + canvas,无框架、无构建步骤) | | `web/check_render.mjs` | 绘图逻辑的无头自检(用 node 跑,不需要浏览器;**它还会去读 `session.py`**,把前端那份频段表和服务端钉死) | | `daima/Web接口契约.md` | 前后端之间的约定 —— 改接口先改它 | | `daima/dongle.py` | dongle 串口协议解码(读指标值) | | `daima/scan.py` | 扫描并认出头环(按厂商 ID / 设备名,**不靠广播的服务 UUID**) | | `daima/enumerate.py` | 枚举指定设备的完整 GATT 树 | | `daima/hunt.py` | 兜底:逐设备连接查服务 | | `daima/probe_com.py` | 串口探测(找 dongle) | | `daima/capture.py` | 串口原始字节流存盘 | | `daima/probe5aa.py` | 探测协议文档未记载的特征(默认只读) | ### 样本数据 - `daima/raw.bin` —— dongle 串口实测 7202 字节,可用于离线复现帧格式 - `daima/zero_jr.csv` —— 零玖头环一段真实脑电(2493 点) --- ## 数据格式 波形包(特征 `197A2AA1`,notify,20 字节): ``` Byte0 高4位 DR(采样率) 低4位 G(增益) Byte1 高4位 PN(包序号 0~15) Byte2~19 9 个 int16 采样点 —— 大端 ``` 换算: ``` 真实波形值(µV) = raw × 2.4 / (2^NB × G) × 1e6 ``` 实测参数 `2790` → 250 Hz / 增益 74.85 / 16 bit → 分辨率 **0.4893 µV/LSB**。 CSV 两列 `t_sec,uv`,UTF-8 带表头: - `t_sec` —— **相对秒**,0 点 = 录到的第一个样本。三个脚本 (`eeg.py` / `eeg_monitor.py` / `eeg_dual.py`)现在都是这套基准; 双头环时两路共用同一个 T0,可以逐点对齐。 - `uv` —— 微伏,**未做任何滤波**。 > 绝对时刻(什么时候录的)不在文件里,只有 `eeg.py` 收尾时会打印一次。 > 需要和别的设备对时间就先记下它。 时间轴不是按蓝牙包的到达时刻记的 —— 那是传输抖动,不是采样时刻。程序会重建 一条**均匀网格**,速率由整场记录的到达跨度反推(标称 250 Hz 实测约 244 Hz)。 开头 4 秒的到达节律受头环倒缓冲影响,算不准,所以这段先不落盘、等速率收敛了 再按正确网格补写,免得把偏差冻进整个文件。 --- ## 用之前必须知道的三件事 1. **信号未滤波。** delta 频段占约 59%,基线漂移很重(干电极的正常现象)。 做任何分析前**必须高通滤波(~1 Hz)**,建议再加 50 Hz 工频陷波。 2. **单通道前额,伪迹很重。** 眼电(眨眼)和额肌电(皱眉、咬牙、面部紧张) 都会强烈污染信号,且额肌电落在 20 Hz 以上 —— **做注意力研究时必须先排除 肌电污染**,不能默认测到的都是脑电。 3. **实测采样率 ~245 Hz**(标称 250 Hz,偏差 2.4%)。包序号连续、无丢包, 说明偏差来自头环自身时钟,不是主机漏读。做精确时序分析需要先标定。 --- ## 已知未解问题 见 [脑电头环_协议与接入说明.md](脑电头环_协议与接入说明.md) 第 7 节。摘要: - `197A5AA2` 里的 ASCII 文本命令协议(`*TH0603001010101010`)语法未知 —— 这大概率是切换采样率/增益的入口,**尚未验证** - `197A5AA0`(`4207000000`)、`197A1821`/`197A2AA6`(1 Hz 心跳)用途未明