# py_ble_assistant **Repository Path**: jameschenbo/py_ble_assistant ## Basic Information - **Project Name**: py_ble_assistant - **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-03 - **Last Updated**: 2026-09-28 ## Categories & Tags **Categories**: Uncategorized **Tags**: None ## README # BLE 助手(Python + Tkinter) 一个开箱即用的 Windows 桌面 BLE 调试工具,支持**两种角色**: - **中心设备(Client)**:扫描周边蓝牙低功耗设备、按信号强度排序、点击卡片即可连接, 连接后浏览 GATT 服务树,对特征进行 **读 / 写 / 通知订阅**。 - **外围设备(Server / GATT Server)**:把本机变成一个 BLE 外设并自动广播, 手机等中心设备连接后可向本机写数据、订阅本机通知。 底层中心模式使用 [bleak](https://github.com/hbldh/bleak)(Windows 走 WinRT 后端,无需额外驱动); 外围模式使用 Windows 原生 WinRT GATT Server API(`winrt` 投影)。界面使用 Python 标准库自带的 Tkinter。 --- ## 一、快速开始 ### 1. 初始化环境(只需一次) 双击 **`setup.bat`**,脚本会在当前目录创建 `.venv` 虚拟环境并安装依赖(默认走清华镜像)。 ### 2. 启动 - 双击 **`run.bat`** —— 无控制台窗口,干净启动(推荐) - 双击 **`run_console.bat`** —— 带控制台,出错时能看到完整报错信息 也可以手动执行: ```bat .venv\Scripts\python.exe main.py ``` > 环境要求:Windows 10 1703 及以上 + Python 3.10+(必须带 tkinter,官方 CPython 安装包默认包含)。 > 当前项目的虚拟环境基于本机 Python 3.13.12 + bleak 3.0.2。 > > 外围模式仅支持 Windows(依赖 `winrt` 投影包,见 `requirements.txt` 中带 `sys_platform == "win32"` > 标记的依赖)。非 Windows 平台中心模式不受影响,外围模式会提示不支持。 --- ## 二、模式选择 启动后首先进入**模式选择菜单**,两张彩色卡片: | 卡片 | 说明 | | --- | --- | | 中心设备(蓝) | 扫描并连接别人的 BLE 外设,做 GATT 客户端 | | 外围设备(青) | 本机自动广播,做 GATT 服务端,等手机来连 | 进入任一模式后,可随时点左上角「← 模式选择」回到菜单。 --- ## 三、功能特性 ### 3.1 中心模式 · 扫描页 | 功能 | 说明 | | --- | --- | | 实时扫描 | 点击「开始扫描」持续接收广播包,边收边更新 | | RSSI 排序 | 默认按信号强度从强到弱排序,也可切换为按名称 / 最近发现排序 | | 设备卡片 | 展示设备名称、MAC 地址、RSSI(数值 + 信号格 + 强度等级)、广播次数、上次出现时间 | | 广播信息 | 服务 UUID(自动解析为 `0x180F · Battery` 这类可读名称)、厂商数据、Service Data、发射功率 | | 过滤 | 关键字过滤(名称 / MAC)、仅显示有名称的设备、RSSI 下限阈值 | | 信号丢失 | 超过 15 秒未收到广播的卡片自动置灰,不删除 | ### 3.2 中心模式 · 设备详情页 | 功能 | 说明 | | --- | --- | | 连接状态 | 顶部实时显示 连接中 / 已连接(含 MTU)/ 已断开 / 连接失败 | | 服务树 | 每个 GATT 服务可点击展开或折叠,默认展开第一个服务,支持一键全部展开 / 折叠 | | 特征属性 | 以彩色徽章展示 READ / WRITE / WRITE-NO-RSP / NOTIFY / INDICATE 等属性 | | 读取 | 可读特征一键读取,结果以 HEX + ASCII 形式展示在特征行内,同时写入通信日志 | | 写入 | 可写特征展开输入面板,支持 HEX(`A1 02 FF`、`0xA1,0x02`)与 UTF-8(支持 `\n` `\xNN` 转义),可切换 Write Request / Write Command | | 通知 | 可通知特征提供开关,开启后实时接收数据,显示接收计数与最近一帧内容 | | 通信日志 | 底部统一记录连接、读、写、通知、错误,带时间戳与颜色区分,支持暂停 / 清空 / 自动滚动,高速数据流自动节流 | ### 3.3 外围设备模式 把本机变成一个标准 BLE 外设,手机(或另一个 BLE 调试工具)可直接搜到并连接。 **默认 GATT 结构** ``` Service 0000FFE0-0000-1000-8000-00805F9B34FB ├─ FFE1 WRITE / WRITE WITHOUT RESPONSE 手机写进来,本机接收 └─ FFE2 NOTIFY 本机发通知,手机订阅后接收 ``` **界面布局(串口助手风格,左 : 右 = 1 : 2)** ``` ┌──────────────────┬──────────────────────────────────┐ │ 连接状态 │ 接收区域(通信日志) 5 份 │ │ 广播状态 │ │ │ 已连接客户端 │ │ │ 服务 UUID │ │ │ ▶开始 ■停止广播 ├──────────────────────────────────┤ │ │ 发送区域(HEX/ASCII + 发送)1 份 │ │ GATT 服务 │ │ │ 0xFFE0 │ │ │ ├FFE1 Write │ │ │ └FFE2 Notify │ │ └──────────────────┴──────────────────────────────────┘ ``` **收发日志**:每条记录一行,清楚标明方向 + 特征 + 内容 + 字节数,内容显示严格跟随日志面板的 **HEX / ASCII** 选项切换: ``` RX FFE1 ← bogo (4 字节 · 需回复) RX FFE1 ← nice to meet you (16 字节 · 无回复) TX FFE2 → hello,boge (10 字节 · 1 客户端) TX FFE2 → nice to meet you too. (21 字节 · 1 客户端) ``` | 功能 | 说明 | | --- | --- | | 开始 / 停止广播 | 广播服务 UUID `0xFFE0`,手机搜到即可连接 | | 已连接客户端 | 统计订阅了 FFE2 的客户端数量(订阅即视为已连接) | | 发送通知 | 输入框 + HEX / ASCII 编码选择,回车或点「发送」向 FFE2 发通知;ASCII 支持 `\n` `\r` `\t` `\xNN` 转义 | | 接收写入 | 手机写 FFE1 后实时进接收区日志,可作为 RX 数据导出 | **使用步骤** 1. 选「外围设备」卡片 → 点「▶ 开始广播」 2. 手机打开 BLE 调试 App 扫描,找到本机(名称见下方说明)并连接 3. 手机订阅 `FFE2` → 本机在发送区输入内容点「发送」,手机收到通知 4. 手机向 `FFE1` 写数据 → 本机接收区实时显示 **已知限制** - **BLE 广播名称无法从 Python 修改**。扫描列表里显示的是本机 **Windows 蓝牙适配器名**。 WinRT 的 `GattServiceProviderAdvertisingParameters` 没有 `local_name` 字段, `BluetoothAdapter` 也不提供名称读写,且不允许自定义 GATT Server 创建保留的 GAP 服务 `0x1800`。 如需修改,请到 **Windows 设置 → 蓝牙和其他设备** 里改适配器名。 - 外围模式依赖 Windows + `winrt` 投影包,非 Windows 平台不可用。 - 中心模式与外围模式共用同一块蓝牙射频,同时使用时可能互相干扰,建议按需切换。 --- ## 四、目录结构 ``` py_ble_assistant/ ├── main.py # 启动入口 ├── requirements.txt ├── setup.bat / run.bat / run_console.bat ├── core/ │ ├── models.py # 设备 / 服务 / 特征数据模型 + UUID 名称表 │ ├── worker.py # 中心模式 asyncio 后台线程:扫描、连接、GATT 操作 │ └── peripheral.py # 外围模式 asyncio 后台线程:WinRT GATT Server(广播/写/通知) ├── ui/ │ ├── app.py # 主窗口、视图容器、事件泵 │ ├── theme.py # 配色 / 字体 / RSSI 分级 │ ├── mode_view.py # 启动模式选择菜单(中心 / 外围卡片) │ ├── scan_view.py # 扫描页与设备卡片 │ ├── device_view.py # 详情页:服务树、特征行、日志面板 │ ├── peripheral_view.py # 外围模式页:连接状态 / GATT 服务 / 接收区 / 发送区 │ ├── scroll.py # 带滚轮的滚动容器 │ └── widgets.py # 按钮、徽章、信号格等基础控件 └── tests/ ├── unit_fmt.py # 数据格式解析单元测试(不需要蓝牙) ├── peripheral_smoke.py # 外围模式冒烟:字节往返 / 构建 GATT + 广播 / UI 接线 ├── smoke.py # GUI 冒烟:启动 + 扫描 ├── scan_cli.py # 命令行扫描 6 秒并打印设备列表 └── connect_smoke.py # 连接 → 服务发现 → 读 → 通知 全链路测试 ``` ### 架构说明 Tkinter 是单线程的,而 bleak / winrt 基于 asyncio。两者通过**后台线程 + 线程安全队列**解耦: ``` Tk 主线程(UI 渲染) ←—— queue.Queue ←—— asyncio 线程(bleak / winrt) │ ▲ └──── run_coroutine_threadsafe ───────────┘ ``` - 中心模式与外围模式各有一条独立的 asyncio 线程(`BLEWorker` / `PeripheralWorker`), 共用同一个事件队列,事件类型以 `peripheral_*` 前缀区分 - UI 点击操作 → 向对应 asyncio 线程提交协程,不阻塞界面 - 蓝牙回调(广播、通知、写请求、断连)→ 投递事件到队列 → 主线程每 50ms 统一处理 - 通知数据高频到达时,日志按 120ms 批量刷新,避免界面卡死 - 外围模式的 GATT 服务采用**惰性构建**:进入外围模式点「开始广播」时才创建 `0xFFE0`, 避免未使用时占用服务 UUID #### Tk 布局注意点 grid 的 `weight` 只分配**多余空间**,内容的最小宽 / 高会先把空间吃掉。要让列 / 行严格按比例 (如本项目的左:右 = 1:2、接收:发送 = 5:1),必须加 **`uniform=`**: ```python content.columnconfigure(0, weight=1, uniform="cols") content.columnconfigure(1, weight=2, uniform="cols") right.rowconfigure(0, weight=5, uniform="rows") right.rowconfigure(1, weight=1, uniform="rows", minsize=96) ``` --- ## 五、自测 ```bat .venv\Scripts\python.exe tests\unit_fmt.py :: 解析逻辑单元测试,无需蓝牙 .venv\Scripts\python.exe tests\peripheral_smoke.py :: 外围模式冒烟(需 Windows + 蓝牙) .venv\Scripts\python.exe tests\scan_cli.py :: 命令行扫描 6 秒 .venv\Scripts\python.exe tests\smoke.py :: GUI 启动 + 扫描 7 秒后自动关闭 .venv\Scripts\python.exe tests\connect_smoke.py :: 连接 / 读 / 通知全链路 ``` --- ## 六、常见问题 **1. 扫描不到任何设备** - 确认系统蓝牙已开启(Windows 设置 → 蓝牙和其他设备) - 设备必须处于广播状态且未被其他程序独占连接 **2. 连接失败 / 超时** - 部分设备广播但拒绝连接,或需要先在系统里完成配对 - 默认连接超时 15 秒,信号过弱时尝试靠近设备 - 已配对过的设备可先在 Windows 蓝牙设置中「删除设备」再重试 **3. 特征读取报错** - 检查该特征是否具备 `read` 属性(界面上会显示 READ 徽章) - 部分特征需要配对 / 加密链路才能访问 **4. 外围模式手机搜不到 / 连不上** - 确认本机蓝牙已开启,且已点「▶ 开始广播」(左侧状态显示「广播中」) - 手机扫描的是本机 **Windows 蓝牙适配器名**,不是本工具自定义的名字 - 若刚用中心模式扫过描,先停止扫描再开广播,避免同一射频互相干扰 **5. 外围模式能改 BLE 广播名吗** - 不能。WinRT 未提供设置广播名的接口,扫描名只能改 Windows 蓝牙适配器名 - 详见上文「已知限制」 **6. 提示未安装 tkinter** - 请改用 python.org 的官方 CPython 安装包,并在安装时勾选 “tcl/tk and IDLE” **7. 界面在高分屏上发虚** - 入口已调用 `SetProcessDpiAwareness`,若仍模糊可在 Windows 中关闭对该程序的 DPI 缩放替代