test: 蓝牙主从测试脚本(bleak连接/bless GATT从机+bluetoothctl广播)
This commit is contained in:
@@ -0,0 +1,135 @@
|
||||
#!/usr/bin/env python3
|
||||
# test_bluetooth.py
|
||||
# 参考: https://wiki.sipeed.com/maixpy/doc/zh/modules/bluetooth.html
|
||||
#
|
||||
# 依赖:
|
||||
# 1. 使能蓝牙: bluetoothctl power on
|
||||
# (可选开机自启: echo "bluetoothctl power on" >> /etc/rc.local)
|
||||
# 2. 安装依赖: pip install bleak
|
||||
|
||||
import asyncio
|
||||
import sys
|
||||
from collections import namedtuple
|
||||
|
||||
try:
|
||||
from bleak import BleakScanner, BleakClient
|
||||
except ImportError:
|
||||
print("ModuleNotFoundError: No module named 'bleak'")
|
||||
print("请先在设备上安装依赖:")
|
||||
print(" pip install bleak")
|
||||
print("并使能蓝牙:")
|
||||
print(" bluetoothctl power on")
|
||||
sys.exit(1)
|
||||
|
||||
MODEL_NBR_UUID = "1A2A"
|
||||
|
||||
BLEDev = namedtuple("BLEDev", ["address", "name"])
|
||||
|
||||
|
||||
async def scan_devices(timeout=5.0):
|
||||
"""扫描周围的蓝牙设备"""
|
||||
print("=" * 60)
|
||||
print("Scanning BLE devices...")
|
||||
print("=" * 60)
|
||||
|
||||
found = {}
|
||||
|
||||
def _cb(device, adv):
|
||||
found[device.address] = adv.local_name or device.name
|
||||
|
||||
try:
|
||||
await BleakScanner.discover(timeout=timeout, detection_callback=_cb)
|
||||
except Exception as e:
|
||||
print(f"Scan failed: {e}")
|
||||
print("Possible causes:")
|
||||
print(" 1. Bluetooth not powered on: bluetoothctl power on")
|
||||
print(" 2. bleak not installed: pip install bleak")
|
||||
print(" 3. BLE hardware/driver not available")
|
||||
return []
|
||||
|
||||
if not found:
|
||||
print("No devices found.")
|
||||
return []
|
||||
|
||||
devices = []
|
||||
for address, name in found.items():
|
||||
devices.append(BLEDev(address, name))
|
||||
print(f" {address} {name}")
|
||||
return devices
|
||||
|
||||
|
||||
async def connect_device(address, timeout=20.0, retries=3):
|
||||
"""连接蓝牙设备并尝试读取特征值"""
|
||||
print("=" * 60)
|
||||
print(f"Connecting to {address} ...")
|
||||
print("=" * 60)
|
||||
|
||||
for attempt in range(1, retries + 1):
|
||||
try:
|
||||
async with BleakClient(address, timeout=timeout) as client:
|
||||
print(f"Connected: {client.is_connected}")
|
||||
|
||||
# 遍历服务和特征值
|
||||
for service in client.services:
|
||||
print(f" Service: {service.uuid}")
|
||||
for char in service.characteristics:
|
||||
print(f" Char: {char.uuid} props={list(char.properties)}")
|
||||
|
||||
# 尝试读取 Model Number 特征
|
||||
try:
|
||||
value = await client.read_gatt_char(MODEL_NBR_UUID)
|
||||
print(f"Model Number: {value.decode(errors='replace')}")
|
||||
except Exception as e:
|
||||
print(f"Read {MODEL_NBR_UUID} failed (device may not have it): {e}")
|
||||
|
||||
return True
|
||||
except Exception as e:
|
||||
print(f"[attempt {attempt}/{retries}] Connect failed: {e}")
|
||||
if attempt < retries:
|
||||
await asyncio.sleep(2)
|
||||
|
||||
print("\nAll attempts failed. 请检查:")
|
||||
print(" 1. forrily 是否已连到手机/其它主机(BLE 外设通常只允许一连接),")
|
||||
print(" 先在手机上断开它")
|
||||
print(" 2. 清除 BlueZ 缓存后重试:")
|
||||
print(f" bluetoothctl remove {address}")
|
||||
print(" systemctl restart bluetooth")
|
||||
print(" bluetoothctl power on")
|
||||
print(" 3. 升级依赖:")
|
||||
print(" pip install -U bleak dbus-fast")
|
||||
return False
|
||||
|
||||
|
||||
async def main():
|
||||
target_name = "forrily"
|
||||
|
||||
devices = await scan_devices(timeout=5.0)
|
||||
if not devices:
|
||||
return
|
||||
|
||||
if len(sys.argv) > 1:
|
||||
address = sys.argv[1]
|
||||
else:
|
||||
# 按名字查找目标设备,不连第一个
|
||||
target = None
|
||||
for d in devices:
|
||||
if d.name and d.name.strip().lower() == target_name.lower():
|
||||
target = d
|
||||
break
|
||||
if target is None:
|
||||
for d in devices:
|
||||
if d.name and target_name.lower() in d.name.strip().lower():
|
||||
target = d
|
||||
break
|
||||
if target is None:
|
||||
print(f"\nDevice named '{target_name}' not found!")
|
||||
print("Scanned devices are listed above.")
|
||||
return
|
||||
address = target.address
|
||||
print(f"\nFound '{target_name}': {address}")
|
||||
|
||||
await connect_device(address)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
asyncio.run(main())
|
||||
Reference in New Issue
Block a user