如何使用Python在Windows系统中编程连接蓝牙设备?
Windows下Python实现等效系统UI的蓝牙连接方案
你判断的方向是对的,这类和系统蓝牙行为完全一致的操作必须调用原生Win32蓝牙API实现,BluetoothAuthenticateDevice确实是配对认证环节的核心接口,但只调用这一个接口无法实现完整的连接效果——系统UI点击「连接」的动作实际会串行走完设备枚举、配对认证、服务启用三个流程,缺任意一个环节都达不到和手动操作一致的结果。
前置准备
- 安装依赖库:执行
pip install pywin32,用于调用Win32系统接口 - 运行要求:所有蓝牙相关API调用必须以管理员权限启动Python进程,否则会返回权限不足错误
- 提前获取目标设备的MAC地址,格式为
XX:XX:XX:XX:XX:XX,可在Windows蓝牙设置的设备属性页查看
核心API调用逻辑
系统手动连接蓝牙的内部调用链路如下,写代码时必须完整覆盖:
- 第一步:调用
BluetoothFindFirstRadio获取本地蓝牙适配器的有效句柄,这是所有后续蓝牙API调用的必填参数,直接传空值会直接调用失败 - 第二步:调用
BluetoothAuthenticateDevice触发配对认证,该接口的行为和系统弹出的标准配对流程完全一致:支持无密码自动配对、弹出PIN码输入框、自动匹配固定PIN等所有系统原生支持的认证场景 - 第三步:调用
BluetoothSetServiceState启用目标设备对应的核心服务(比如蓝牙键鼠的HID服务、蓝牙耳机的A2DP音频服务),这一步是真正建立连接的动作,跳过的话设备只会停留在「已配对未连接」状态,和手动操作效果不一致
可直接运行的实现代码
import ctypes from ctypes import wintypes # 加载系统蓝牙相关API库 bthprops = ctypes.WinDLL('bthprops.cpl', use_last_error=True) # 常用蓝牙服务GUID常量 BLUETOOTH_SERVICE_HID = 0x00001124 # 蓝牙键鼠等输入设备 BLUETOOTH_SERVICE_A2DP_SINK = 0x0000110B # 蓝牙耳机音频播放 BLUETOOTH_SERVICE_A2DP_SOURCE = 0x0000110A # 蓝牙音频采集 BLUETOOTH_SERVICE_CONNECT_FLAG = 0x00000001 | 0x00000002 # 认证+连接标志 # 蓝牙API所需结构体定义 class BLUETOOTH_ADDRESS(ctypes.Structure): _fields_ = [('ullLong', ctypes.c_ulonglong)] class BLUETOOTH_FIND_RADIO_PARAMS(ctypes.Structure): _fields_ = [('dwSize', wintypes.DWORD)] class SYSTEMTIME(ctypes.Structure): _fields_ = [ ('wYear', wintypes.WORD), ('wMonth', wintypes.WORD), ('wDayOfWeek', wintypes.WORD), ('wDay', wintypes.WORD), ('wHour', wintypes.WORD), ('wMinute', wintypes.WORD), ('wSecond', wintypes.WORD), ('wMilliseconds', wintypes.WORD) ] class BLUETOOTH_DEVICE_INFO(ctypes.Structure): _fields_ = [ ('dwSize', wintypes.DWORD), ('Address', BLUETOOTH_ADDRESS), ('ulClassofDevice', wintypes.ULONG), ('fConnected', wintypes.BOOL), ('fRemembered', wintypes.BOOL), ('fAuthenticated', wintypes.BOOL), ('stLastSeen', SYSTEMTIME), ('stLastUsed', SYSTEMTIME), ('szName', ctypes.c_wchar * 248) ] def mac_to_btaddr(mac_str: str) -> BLUETOOTH_ADDRESS: """将XX:XX:XX:XX:XX:XX格式的MAC地址转换为API要求的地址结构""" addr_bytes = bytes.fromhex(mac_str.replace(':', ''))[::-1] bt_addr = BLUETOOTH_ADDRESS() bt_addr.ullLong = int.from_bytes(addr_bytes, 'little') return bt_addr def connect_bt_device(target_mac: str, service_guid: int): # 初始化本地蓝牙适配器句柄 radio_params = BLUETOOTH_FIND_RADIO_PARAMS() radio_params.dwSize = ctypes.sizeof(radio_params) h_radio = wintypes.HANDLE() h_radio_find = bthprops.BluetoothFindFirstRadio(ctypes.byref(radio_params), ctypes.byref(h_radio)) if h_radio_find == wintypes.INVALID_HANDLE_VALUE: raise ctypes.WinError(ctypes.get_last_error(), "未检测到可用蓝牙适配器") try: # 初始化目标设备信息结构 dev_info = BLUETOOTH_DEVICE_INFO() dev_info.dwSize = ctypes.sizeof(dev_info) dev_info.Address = mac_to_btaddr(target_mac) # 触发配对认证,和系统UI配对逻辑完全一致 auth_ret = bthprops.BluetoothAuthenticateDevice( None, # 不需要自定义父窗口时传None,系统自动弹出标准配对框 h_radio, ctypes.byref(dev_info), None, 0 ) if auth_ret != 0: err = ctypes.get_last_error() # 错误码1244代表设备已完成配对,不属于致命错误 if err != 1244: raise ctypes.WinError(err, "设备配对失败") # 启用对应服务,完成最终连接 svc_ret = bthprops.BluetoothSetServiceState( h_radio, ctypes.byref(dev_info), ctypes.byref(ctypes.c_ulonglong(service_guid)), BLUETOOTH_SERVICE_CONNECT_FLAG ) if svc_ret != 0: raise ctypes.WinError(ctypes.get_last_error(), "设备服务连接失败") finally: bthprops.BluetoothFindRadioClose(h_radio_find) ctypes.windll.kernel32.CloseHandle(h_radio) if __name__ == "__main__": # 替换为目标设备的MAC地址 TARGET_MAC = "12:34:56:78:9A:BC" # 根据设备类型替换为对应服务GUID connect_bt_device(TARGET_MAC, BLUETOOTH_SERVICE_A2DP_SINK) print("蓝牙设备连接完成")
常见调用失败排查
- 传参错误:很多人调用
BluetoothAuthenticateDevice失败是因为没有先获取本地蓝牙适配器句柄,直接传空值作为第二个参数,会直接返回参数无效错误 - 权限问题:非管理员进程调用蓝牙API会返回错误码5(拒绝访问),必须提权运行
- 服务匹配错误:不同类型的设备需要启用对应的服务GUID,传错GUID会导致连接完成后设备无法正常使用(比如蓝牙耳机只连HID服务就不会出音频输出)
- 静默配对需求:如果已知设备固定PIN码(比如老款设备默认PIN为0000或1234),可以传入
BLUETOOTH_AUTHENTICATION_CALLBACK_PARAMS结构配置PIN码,实现无弹窗静默配对,行为和系统自动连接已知设备完全一致
内容的提问来源于stack exchange,提问作者JustOscarJ
相关产品推荐
相关产品推荐

