【物联网】bleak (scan)扫描在干什么? BLE 广播(Advertising)

发布于:2025-08-31 ⋅ 阅读:(17) ⋅ 点赞:(0)

好的,这里把 **bleak 的扫描(scan)**讲清楚,并结合你正在用的桥接脚本给出最实用的写法。


1) bleak 扫描在干什么?

  • BLE 广播(Advertising):外设(你的 Arduino)周期性广播一个小数据包,里面可能包含:

    • local_name(本地名称,BLE 广播名)
    • service_uuids(该设备声明的服务 UUID)
    • manufacturer_data / service_data
    • tx_powerrssi
  • Central(你的 RPi) 不需要连上就能“听”到这些广播。

  • bleak 的 Scanner 就是把广播 “抓包+解析”,返回一个或一批 BLEDevice(设备基本信息)以及对应的 AdvertisementData(广播详细字段)。

你在代码里写的 device.name 本质上就是广播里的 local name(所以我们强调 BLE.setLocalName(...))。


2) bleak 扫描的几种方式

A. 一步到位:发现所有设备再自己筛选

from bleak import BleakScanner

devices = 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.rssi
        name_ok = name_ok or (adv.local_name == "ardunio_ble")
    return name_ok

device = await BleakScanner.find_device_by_filter(match, timeout=10.0)
  • 只要匹配到就返回目标设备,效率高、代码简洁。

C. 订阅回调:做更细的过滤/调试

from bleak import BleakScanner

def 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) 扫描结果里都有什么?

  • BLEDeviceaddress(MAC 或随机地址)、name(通常是 local_name)、rssi(部分平台)
  • AdvertisementData(新版可用):local_nameservice_uuidsmanufacturer_dataservice_datatx_powerrssi

常见困惑:名字有时“变或看不到”。这是因为:

  • 外设没把名字放进广播(或被截断:广播总长 31 字节)。
  • 中央设备缓存了 GATT 的 Device Name。解决:保持 setLocalNamesetDeviceName 一致,必要时清除配对、重启蓝牙。

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_ok
    return name_ok

device = await BleakScanner.find_device_by_filter(match, timeout=10.0)

5) 扫描 → 连接 → 订阅 Notify 的完整最小流程

from bleak import BleakScanner, BleakClient

DEVICE_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")
        return

    async 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.setLocalNameBLE.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) 回到你的项目:扫描在整条链路中的角色

  1. 扫描:在 RPi 上用 bleak 找到目标 Arduino(按 LocalName 过滤)。
  2. 连接 & 订阅:连上后 start_notify(SENSOR_CHAR_UUID, handler),接收 Arduino 推的 JSON。
  3. 上行:把 JSON 发到 MQTT iot/sensors/data
  4. 下行:订阅 iot/commands/arduino,收命令后 write_gatt_char(COMMAND_CHAR_UUID, ...) 写回 Arduino。

所以“扫描”是整条 BLE→MQTT 桥的入口。稳定、准确地找对设备,才能保证后续 Notify/Write 都能正常工作。


网站公告

今日签到

点亮在社区的每一天
去签到