【物联网】bleak (scan)扫描在干什么? BLE 广播(Advertising)
好的,这里把 **bleak 的扫描(scan)**讲清楚,并结合你正在用的桥接脚本给出最实用的写法。
1) bleak 扫描在干什么?
-
BLE 广播(Advertising):外设(你的 Arduino)周期性广播一个小数据包,里面可能包含:
local_name
(本地名称,BLE 广播名)service_uuids
(该设备声明的服务 UUID)manufacturer_data
/service_data
tx_power
、rssi
等
-
Central(你的 RPi) 不需要连上就能“听”到这些广播。
-
bleak 的 Scanner 就是把广播 “抓包+解析”,返回一个或一批
BLEDevice
(设备基本信息)以及对应的AdvertisementData
(广播详细字段)。
你在代码里写的
device.name
本质上就是广播里的 local name(所以我们强调BLE.setLocalName(...)
)。
2) bleak 扫描的几种方式
A. 一步到位:发现所有设备再自己筛选
from bleak import BleakScannerdevices = await BleakScanner.discover(timeout=5.0) # 扫5秒
for d in devices:print(d.address, d.name) # d.name 通常是 local_name
- 简单直观,后续用
if d.name == "your_name":
取目标。
B. 边扫边筛:find_device_by_filter
from bleak import BleakScanner# 兼容新老 bleak:有的版本 filter 只收 device,有的收 (device, adv)
def match(dev, adv=None):name_ok = (dev.name and "ardunio_ble" in dev.name)if adv: # 新版可以读 adv.local_name / adv.service_uuids / adv.rssiname_ok = name_ok or (adv.local_name == "ardunio_ble")return name_okdevice = await BleakScanner.find_device_by_filter(match, timeout=10.0)
- 只要匹配到就返回目标设备,效率高、代码简洁。
C. 订阅回调:做更细的过滤/调试
from bleak import BleakScannerdef callback(device, adv_data):print(device.address, adv_data.local_name, adv_data.rssi, adv_data.service_uuids)scanner = BleakScanner(callback, service_uuids=["<SERVICE_UUID 可选>"])
await scanner.start()
await asyncio.sleep(5.0)
await scanner.stop()
devices = await scanner.get_discovered_devices()
- 可以实时看到 RSSI、manufacturer data 等,便于调试或做更严格过滤。
平台差异:
- Linux(树莓派):用 BlueZ;确保
bluetoothd
在跑、蓝牙没被 rfkill。- macOS:用 CoreBluetooth;
service_uuids
过滤更有效。- Windows:用 WinRT;名字/UUID 过滤也可用。
bleak 整体是 异步 的,要在asyncio
事件循环里跑。
3) 扫描结果里都有什么?
BLEDevice
:address
(MAC 或随机地址)、name
(通常是 local_name)、rssi
(部分平台)AdvertisementData
(新版可用):local_name
、service_uuids
、manufacturer_data
、service_data
、tx_power
、rssi
…
常见困惑:名字有时“变或看不到”。这是因为:
- 外设没把名字放进广播(或被截断:广播总长 31 字节)。
- 中央设备缓存了 GATT 的 Device Name。解决:保持
setLocalName
和setDeviceName
一致,必要时清除配对、重启蓝牙。
4) 在你的桥接脚本里,扫描为什么这样写?
你脚本里用的是:
device = await BleakScanner.find_device_by_filter(lambda d: d.name and DEVICE_NAME in d.name, timeout=SCAN_TIMEOUT
)
- 优点:只要看到名字匹配就返回;避免误连其它设备。
- 为什么按“名字”而不是“MAC 地址”过滤?
Arduino 的地址是稳定的,但课堂/演示更容易靠名字区分设备(也省去记录 MAC 的步骤)。 - 改进(兼容新版 bleak 的
(device, adv)
过滤,同时加服务 UUID/信号强度兜底):
DEVICE_NAME = "ardunio_ble"
SERVICE_UUID = "f75cfb20-4bce-4d2a-a9a7-3d9a93e0e2f5"def match(dev, adv=None):name_ok = (dev.name and DEVICE_NAME in dev.name)if adv:name_ok = name_ok or (adv.local_name == DEVICE_NAME)# 进一步确认:服务 UUID 命中 和/或 信号强度不太弱uuid_ok = (SERVICE_UUID in (adv.service_uuids or []))rssi_ok = (adv.rssi is None) or (adv.rssi > -90)return (name_ok or uuid_ok) and rssi_okreturn name_okdevice = await BleakScanner.find_device_by_filter(match, timeout=10.0)
5) 扫描 → 连接 → 订阅 Notify 的完整最小流程
from bleak import BleakScanner, BleakClientDEVICE_NAME = "ardunio_ble"
SENSOR_CHAR_UUID = "f75cfb21-4bce-4d2a-a9a7-3d9a93e0e2f5"def on_notify(_handle, data: bytes):print("notify:", data.decode("utf-8", errors="ignore"))async def run():dev = await BleakScanner.find_device_by_filter(lambda d, a=None: (a and a.local_name == DEVICE_NAME) or (d.name == DEVICE_NAME),timeout=10.0)if not dev:print("device not found")returnasync with BleakClient(dev) as client:await client.start_notify(SENSOR_CHAR_UUID, on_notify)print("connected & subscribed. Press Ctrl+C to exit.")while True:await asyncio.sleep(1)asyncio.run(run())
6) 实操建议(避免“扫描不到/连不上”)
- 保证广播名稳定:
BLE.setLocalName
和BLE.setDeviceName
用同一个短名字(例如cc_nano33_01
)。 - 广播不停:Arduino 程序里不要早停广播;Nano 33 IoT 默认 OK。
- 延长扫描时间:广告间隔较长时把
timeout
设为 8–10 秒。 - 靠近一点:RSSI 太低(<-90dBm)容易掉包/连不上。
- Linux 蓝牙服务:
sudo systemctl status bluetooth
要在跑;rfkill list
确保没被软/硬阻断。 - 版本匹配:bleak ≥ 0.22 的
find_device_by_filter
传(device, advertisement_data)
;如果老版本只传device
,用我上面的兼容写法。
7) 回到你的项目:扫描在整条链路中的角色
- 扫描:在 RPi 上用 bleak 找到目标 Arduino(按 LocalName 过滤)。
- 连接 & 订阅:连上后
start_notify(SENSOR_CHAR_UUID, handler)
,接收 Arduino 推的 JSON。 - 上行:把 JSON 发到 MQTT
iot/sensors/data
。 - 下行:订阅
iot/commands/arduino
,收命令后write_gatt_char(COMMAND_CHAR_UUID, ...)
写回 Arduino。
所以“扫描”是整条 BLE→MQTT 桥的入口。稳定、准确地找对设备,才能保证后续 Notify/Write 都能正常工作。