2. 扫描并发现蓝牙设备
任务说明
本案例实现一个基于 BlueZ 的 BLE 扫描器,通过 D-Bus 接口调用 BlueZ 的扫描功能,发现周围的 BLE 广播设备,并输出每个设备的 MAC 地址与 RSSI 值。代码使用 Python 3 与 dbus-next 库,不依赖额外蓝牙硬件抽象,直接使用系统 BlueZ 协议栈。
实现方案
扫描器通过系统 D-Bus 与 BlueZ 通信。核心流程为:获取 BlueZ 的适配器对象(hci0),注册一个 D-Bus 对象接收设备发现信号,然后启动扫描。当收到设备信号时,从信号参数中提取设备路径,再通过该路径的属性接口读取 Address 和 RSSI 属性。扫描持续 10 秒后自动停止,并打印发现的设备列表。
完整代码
#!/usr/bin/env python3
"""BLE 扫描器:发现周边设备并打印 MAC 地址与 RSSI。"""
import asyncio
from dbus_next.aio import MessageBus
from dbus_next import BusType, Variant
BLUEZ_SERVICE = 'org.bluez'
ADAPTER_PATH = '/org/bluez/hci0'
DEVICE_IFACE = 'org.bluez.Device1'
ADAPTER_IFACE = 'org.bluez.Adapter1'
PROPS_IFACE = 'org.freedesktop.DBus.Properties'
OM_IFACE = 'org.freedesktop.DBus.ObjectManager'
class BLEScanner:
def __init__(self):
self.bus = None
self.devices = {} # path -> (mac, rssi)
async def start(self):
self.bus = await MessageBus(bus_type=BusType.SYSTEM).connect()
# 监听新设备出现的信号
self.bus.add_message_handler(self._on_signal)
# 启动扫描
adapter = self.bus.get_proxy_object(BLUEZ_SERVICE, ADAPTER_PATH)
props = adapter.get_interface(PROPS_IFACE)
await props.call_set(ADAPTER_IFACE, 'DiscoveryFilter', Variant('s', 'LE'))
adapter_iface = adapter.get_interface(ADAPTER_IFACE)
await adapter_iface.call_start_discovery()
print('[*] 扫描中,持续 10 秒...')
await asyncio.sleep(10)
await adapter_iface.call_stop_discovery()
self._print_results()
def _on_signal(self, msg):
if msg.interface == OM_IFACE and msg.member == 'InterfacesAdded':
path = msg.body[0]
ifaces = msg.body[1]
if DEVICE_IFACE in ifaces:
self._read_device(path)
def _read_device(self, path):
try:
dev = self.bus.get_proxy_object(BLUEZ_SERVICE, path)
props = dev.get_interface(PROPS_IFACE)
mac = props.get(DEVICE_IFACE, 'Address').value
rssi_v = props.get(DEVICE_IFACE, 'RSSI')
rssi = rssi_v.value if rssi_v else None
self.devices[path] = (mac, rssi)
except Exception:
pass # 设备可能已消失,忽略
def _print_results(self):
print(f'\n[+] 共发现 {len(self.devices)} 个 BLE 设备:')
for mac, rssi in self.devices.values():
rssi_str = f'{rssi} dBm' if rssi is not None else 'N/A'
print(f' MAC: {mac} RSSI: {rssi_str}')
async def main():
scanner = BLEScanner()
await scanner.start()
if __name__ == '__main__':
asyncio.run(main())
依赖与运行
需要 Linux 系统且 BlueZ 版本不低于 5.43,并确保蓝牙适配器已启用(bluetoothctl power on)。安装 Python 依赖:
pip install dbus-next
运行扫描器:
sudo python3 ble_scanner.py
验证方法
程序启动后输出“扫描中,持续 10 秒...”,等待 10 秒后自动停止并打印设备列表。判定标准:
- 若周围存在开启广播的 BLE 设备(如手机、蓝牙耳机、信标),输出中应包含其 MAC 地址,RSSI 为负值(如 -40 到 -90 之间),数值越大表示信号越强。
- 可用手机安装 nRF Connect 应用并开启广播,对比扫描器输出的 MAC 是否与手机显示的 MAC 一致。
- 若输出为空,请确认适配器已开启且周围确实有 BLE 广播设备。
预期输出示例(实际值取决于环境):
[*] 扫描中,持续 10 秒...
[+] 共发现 2 个 BLE 设备:
MAC: C4:63:1B:5A:2F:11 RSSI: -58 dBm
MAC: F0:08:D1:7E:3C:9A RSSI: -71 dBm