Skip to content

handsomeZR-netizen/smartshower-controller

Repository files navigation

趣智校园热水洗澡 BLE 控制系统

Release Python Platform BLE Tests Safety

趣智校园热水洗澡 BLE 控制系统是一个面向课程作业和实验演示的 Windows PC 端工具,用于展示校园热水/智能淋浴设备的 BLE/GATT 通信闭环:扫描设备、连接、枚举 GATT、写入授权控制指令、订阅 notify 状态反馈,并把全过程记录为可审计日志。

本项目只用于本人授权设备、课程演示和 BLE 通信模型学习。项目不包含云端破解、账号绕过、动态 token 逆向、暴力枚举指令或批量控制能力。

目录

功能概览

能力 状态 说明
BLE 扫描 已完成 支持扫描附近 BLE 设备,记录 Name / Address / RSSI / Service UUIDs。
GATT 枚举 已完成 支持连接目标设备并导出 Characteristic 清单。
Profile 配置 已完成 服务 UUID、控制特征、notify 特征、start/stop 指令全部配置化。
控制命令 已完成 支持 start/stop,控制前校验 profile、状态机和 allowlist。
Notify 监听 已完成 订阅 notify 并记录原始 hex 数据。
JSONL 日志 已完成 每条日志带 run_id、运行模式、profile 路径、BLE 模式等上下文。
Tkinter UI 已完成 提供扫描、连接、开始、停止、断开、GATT 显示和日志窗口。
Mock 模式 已完成 无蓝牙硬件时可完整跑通离线闭环。
自动停止 已完成 start 后可按 max_demo_seconds 自动 stop。
安全限制 已完成 require_allowlist=true 时必须命中 allowlist 才能写控制指令。

系统架构

用户操作 / CLI
    |
    v
src/smartshower/main.py       tools/*.py
CLI 入口                      扫描、GATT、notify 辅助脚本
    |
    v
src/smartshower/app_controller.py
控制状态机:scan -> connect -> discover -> start -> notify -> stop -> disconnect
    |
    +--> src/smartshower/protocol/profile.py
    |    读取 YAML profile,校验必填字段和 allowlist
    |
    +--> src/smartshower/protocol/parser.py
    |    notify 原始 bytes -> 可读 hex 事件
    |
    +--> src/smartshower/ble/manager.py
    |    BLE provider 选择:real / mock / mock_fallback
    |        |
    |        +--> src/smartshower/ble/real.py
    |        |    基于 bleak 操作真实 BLE 设备
    |        |
    |        +--> src/smartshower/ble/mock.py
    |             本地模拟设备,用于离线验收和 CI
    |
    +--> src/smartshower/utils/logger.py
         JSONL 审计日志,安全序列化复杂对象

UI 路径使用 src/smartshower/ui/app.py。UI 保持一个长期后台 asyncio loop,避免真实 BleakClient 在多个事件循环之间切换。

环境要求

  • Windows 10/11
  • Python 3.11+
  • 蓝牙适配器可用且已开启
  • 真实设备验收时需要本人授权的趣智校园热水/智能淋浴设备

核心依赖:

  • bleak:BLE 扫描、连接、GATT 读写、notify
  • PyYAML:profile 配置读取
  • pytest:自动化测试,安装开发依赖时使用

安装

推荐使用可编辑安装:

cd D:\desktop\quzhi\smartshower-controller
python -m venv .venv
.\.venv\Scripts\Activate
python -m pip install -U pip
pip install -e .[dev]

也可以只安装运行依赖:

pip install -r requirements.txt

安装完成后可以使用两个入口:

python main.py --help
smartshower --help

离线 mock 验收

无蓝牙环境或课堂预演时,先使用 mock 模式确认程序闭环稳定。

$env:SMARTSHOWER_SIMULATE_BLE = "1"
pytest -q
python tools\scan_ble.py --seconds 0.1 --out artifacts\logs
python tools\gatt_dump.py --address "AA:BB:CC:DD:EE:01" --out artifacts\logs
python main.py demo --address "AA:BB:CC:DD:EE:01" --profile profiles\shower_profile.mock.yaml --demo-seconds 1

预期结果:

  • pytest -q 输出 36 passed
  • artifacts\logs\scan_*.json 里出现 Shower-DEV-Mock
  • artifacts\logs\gatt_AA-BB-CC-DD-EE-01.json 里出现 control 和 notify 特征。
  • logs\session.jsonl 包含完整事件链:
connect_success -> discover_chars -> gatt_validation_ok -> command_start -> notify -> command_stop -> disconnect

真实设备验收流程

真实硬件验收前,先准备本地 profile:

Copy-Item profiles\shower_profile.example.yaml profiles\shower_profile.local.yaml
notepad profiles\shower_profile.local.yaml

然后按以下顺序执行。

1. 扫描设备

python tools\scan_ble.py --seconds 10 --out logs

记录候选设备的 addressnamerssi。如果设备名不明显,优先根据 RSSI、出现时间和宿舍设备状态变化判断。

2. 枚举 GATT

python tools\gatt_dump.py --address "AA:BB:CC:DD:EE:FF" --timeout 20 --out logs

在输出 JSON 中寻找:

  • control 特征:properties 包含 writewrite-without-response
  • notify 特征:properties 包含 notify
  • 对应 service UUID

3. 填写 profile

把确认后的 UUID 和抓包得到的 start/stop 十六进制指令写入 profiles\shower_profile.local.yaml

4. 运行 UI

python main.py ui --profile profiles\shower_profile.local.yaml

UI 中按顺序执行:

扫描 -> 选择目标设备 -> 连接 -> 查看 GATT -> 开始 -> 接收 notify -> 停止/自动停止 -> 断开

5. 运行 CLI 演示

python main.py demo --address "AA:BB:CC:DD:EE:FF" --profile profiles\shower_profile.local.yaml --demo-seconds 15

配置说明

profile 类型

文件 用途 是否应提交
profiles/shower_profile.example.yaml 真实设备空模板
profiles/shower_profile.mock.yaml 离线 mock 演示配置
profiles/shower_profile.local.yaml 本机真实设备配置 否,已被 .gitignore 忽略

关键字段

device:
  name_keywords:
    - "Shower"
  address_allowlist:
    - "AA:BB:CC:DD:EE:FF"
  min_rssi: -85

safety:
  require_allowlist: true
  confirm_before_start: true
  max_demo_seconds: 20
  auto_stop: true

gatt:
  service_uuid: ""
  control_char_uuid: ""
  notify_char_uuid: ""

commands:
  start:
    hex: ""
    write_response: true
  stop:
    hex: ""
    write_response: true

字段说明:

  • device.name_keywords:扫描结果过滤关键字。
  • device.address_allowlist:允许写入控制指令的设备地址。
  • device.min_rssi:候选设备最低信号强度。
  • safety.require_allowlist:为 true 时,allowlist 不能为空且必须命中。
  • safety.confirm_before_start:UI 中 start 前是否弹确认框。
  • safety.max_demo_seconds:自动停止倒计时。
  • gatt.service_uuid:目标服务 UUID。
  • gatt.control_char_uuid:写控制命令的特征 UUID。
  • gatt.notify_char_uuid:订阅状态反馈的特征 UUID。
  • commands.start.hex / commands.stop.hex:授权会话中确认过的十六进制指令。
  • commands.*.write_response:是否使用 write with response。

命令参考

主入口

python main.py scan --seconds 10
python main.py gatt --address "AA:BB:CC:DD:EE:FF" --timeout 20
python main.py demo --address "AA:BB:CC:DD:EE:FF" --profile profiles\shower_profile.local.yaml --demo-seconds 15
python main.py ui --profile profiles\shower_profile.local.yaml

安装后的脚本入口

smartshower scan --seconds 10
smartshower gatt --address "AA:BB:CC:DD:EE:FF"
smartshower demo --address "AA:BB:CC:DD:EE:FF" --profile profiles\shower_profile.local.yaml
smartshower ui --profile profiles\shower_profile.local.yaml

工具脚本

python tools\scan_ble.py --seconds 10 --out logs
python tools\gatt_dump.py --address "AA:BB:CC:DD:EE:FF" --timeout 20 --out logs
python tools\notify_probe.py --address "AA:BB:CC:DD:EE:FF" --profile profiles\shower_profile.local.yaml --seconds 20
python tools\android_hci.py status
python tools\android_hci.py bugreport --out logs\android_bugreports --name quzhi_ble_session
python tools\android_hci.py extract logs\android_bugreports\bugreport.zip --out logs\android_bugreports\hci
python tools\android_hci.py analyze logs\android_bugreports\hci\BT_HCI.cfa.curf --out logs\android_bugreports\gatt_writes.csv
python tools\android_hci.py compare --session open_close=logs\android_bugreports\gatt_writes.ascii_messages.txt --out logs\android_bugreports\session_comparison.md
python tools\replay_log.py logs\session.jsonl

Android 官方 App 会话采集

当需要从本人授权的官方 App 会话中核对 BLE/GATT 流程时,使用 Android 真机和 USB ADB:

  1. 手机开启“开发者选项”“USB 调试”和 Bluetooth HCI snoop log。
  2. 电脑端确认 python tools\android_hci.py status 显示设备为 device
  3. 断开本项目 UI,避免电脑占用 BLE。
  4. 手机使用官方 App 正常连接设备并执行授权操作。
  5. 运行 python tools\android_hci.py bugreport --out logs\android_bugreports --name quzhi_ble_session
  6. 使用 extract 提取 btsnoop/CFA 日志,再使用 analyze 导出 ATT write CSV 和 ASCII 消息摘要。
  7. 多次采集后使用 compare 对比各会话,例如 baselineopen_holdclose

android_hci.py analyze 会优先使用内置 btsnoop 解析器;如果遇到不支持的格式且本机安装了 Wireshark tshark,会回退到 tshark 过滤。默认关注 ATT handles 0x0013/0x0014/0x0015/0x0018

如果日志显示控制 payload 包含动态 token、签名、授权时间或计费上下文,不应把它当成固定 start/stop 指令复用。

日志与证据

日志格式为 JSONL,一行一条事件。典型字段:

字段 含义
time 本地时间,毫秒精度
run_id 单次运行 ID
event 事件名
mode cli_demouinotify_probe
profile_path 本次使用的 profile
ble_mode realmockmock_fallback
address 设备地址
char_uuid notify/control 特征
raw_hex notify 原始十六进制

主要日志文件:

  • logs/session.jsonl:CLI demo 闭环日志
  • logs/ui_session.jsonl:UI 操作日志
  • logs/notify_probe.jsonl:notify 探测日志
  • logs/scan_*.json:扫描结果
  • logs/gatt_*.json:GATT 枚举结果

logs/ 默认被 .gitignore 忽略,避免误提交真实设备地址和控制指令。

测试

运行全部测试:

$env:SMARTSHOWER_SIMULATE_BLE = "1"
pytest -q

当前 v1.0.1 覆盖 36 个测试,主要验证:

  • profile 读取、默认值和 allowlist 地址规范化
  • hex 编码/解码
  • notify 解析
  • controller 状态机
  • allowlist 空列表拒绝策略
  • GATT service/property 校验
  • notify sender 安全序列化
  • JSONL logger 上下文和复杂对象序列化
  • UI 按钮状态规则
  • mock BLE manager 选择
  • scan/gatt 工具脚本输出文件
  • Android HCI bugreport 提取、ATT write 解析和会话对比

安全边界

本项目强制遵守以下边界:

  1. 只针对本人授权设备。
  2. 不破解云端账号、签名、token 或加密算法。
  3. 不暴力枚举控制指令。
  4. 不支持批量控制。
  5. require_allowlist=true 时,非 allowlist 设备禁止写入控制命令。
  6. profile 不完整时,UI 和 controller 都会阻止 start/stop。
  7. 真实设备配置和日志默认不提交。

如果发现设备协议依赖动态 token、签名或强加密,本项目应降级为:

扫描 + GATT 枚举 + notify 监听 + 日志分析 + 安全降级演示

目录结构

smartshower-controller/
├── README.md
├── pyproject.toml
├── requirements.txt
├── main.py
├── profiles/
│   ├── shower_profile.example.yaml
│   ├── shower_profile.mock.yaml
│   └── shower_profile.local.yaml
├── src/
│   └── smartshower/
│       ├── main.py
│       ├── config.py
│       ├── app_controller.py
│       ├── ble/
│       ├── protocol/
│       ├── ui/
│       └── utils/
├── tools/
│   ├── scan_ble.py
│   ├── gatt_dump.py
│   ├── notify_probe.py
│   ├── android_hci.py
│   └── replay_log.py
└── tests/
    ├── test_app_controller.py
    ├── test_ble_manager.py
    ├── test_config.py
    ├── test_hex_utils.py
    ├── test_logger.py
    ├── test_parser.py
    ├── test_profile.py
    ├── test_tools_cli.py
    └── test_ui_state.py

故障排查

扫描结果为空

  • 确认 Windows 蓝牙已开启。
  • 确认设备处于广播或可连接状态。
  • 缩短 PC 和设备距离。
  • 关闭其他正在占用蓝牙连接的 App。
  • 先运行 mock 验收确认程序本身无问题。

连接失败

  • 确认 address 来自最新扫描结果。
  • 尝试重新扫描后再连接。
  • 确认设备没有被手机 App 或其他客户端占用。
  • 增加 --timeout 20 或更高。

GATT 未匹配

  • 检查 service_uuidcontrol_char_uuidnotify_char_uuid 是否复制完整。
  • control 特征必须包含 writewrite-without-response
  • notify 特征必须包含 notify
  • UUID 比较不区分大小写,但不能缺字符。

start 被阻止

  • 检查 profile 是否完整。
  • 检查当前状态是否已经 GATT_READY
  • require_allowlist=true 时,确认 address_allowlist 包含当前设备地址。
  • 检查 start/stop hex 是否为空。

notify 没有数据

  • 确认 notify 特征正确。
  • 确认设备需要先 start 才会上报状态。
  • 使用 tools\notify_probe.py 单独验证 notify。

v1.0.1 发布说明

v1.0.1 是课程验收版的补充发布,重点加强真实授权会话的证据采集、协议核对和远程仓库可发现性:

  • 新增 tools/android_hci.py,支持 ADB 设备状态检查、bugreport 采集、HCI/btsnoop 候选日志提取、ATT write CSV 导出和 ASCII 消息摘要。
  • 新增 BLE 协议帧解码与多会话对比,便于判断 start/stop 流程是否包含动态 token、签名或会话字段。
  • 扫描候选设备时支持按 allowlist 地址优先匹配,即使设备广播名不可识别也能在授权地址范围内筛选。
  • README 补充 Android 官方 App 会话采集流程、HCI 分析命令和安全降级说明。
  • 自动化测试扩展到 36 个用例。

v1.0.0 发布说明

v1.0.0 是课程验收版首个稳定发布:

  • 完成 BLE scan/connect/GATT/write/notify/disconnect 闭环。
  • 完成 mock 离线演示路径。
  • 完成 Tkinter UI。
  • 完成 allowlist 安全策略。
  • 完成 JSONL 审计日志。
  • 完成 26 个自动化测试。
  • 完成 Python 包入口 smartshower

真机控制仍依赖用户从本人授权会话中确认 UUID 和 start/stop 指令,并正确填写 profiles/shower_profile.local.yaml

About

趣智校园热水洗澡 BLE/GATT 控制系统:Python + Tkinter UI,支持 mock 离线演示、allowlist 安全控制、Android HCI 日志分析和 JSONL 审计日志。

Topics

Resources

Stars

Watchers

Forks

Packages

 
 
 

Contributors

Languages