1. 环境准备与PyBluez库安装想用Python玩转蓝牙第一步就是把环境搭好。我刚开始接触的时候也卡在安装这一步特别是不同操作系统下的坑踩过好几次。今天我就把最稳的安装方法分享给你保证你能顺利上车。PyBluez是Python里最主流的蓝牙开发库它把底层那些复杂的C语言接口封装成了我们熟悉的Python函数用起来就像操作网络Socket一样简单。不过它的安装过程可不像pip install requests那么一帆风顺得看你的“主场”是Windows、Linux还是macOS。在Windows上安装算是相对省心的。首先你得确保系统里已经安装了蓝牙适配器并且驱动正常。然后直接打开命令提示符CMD或者PowerShell用管理员权限运行下面这条命令就行pip install pybluez如果安装过程报错提示缺少Windows SDK或者C构建工具别慌。这通常是因为PyBluez在安装时需要编译一些本地组件。去微软官网下载并安装“Visual Studio Build Tools”记得勾选“C桌面开发” workload。装好之后再重试pip install一般就能成功了。我实测下来Windows 10和11的系统兼容性都还不错。如果你用的是Linux特别是像Ubuntu、树莓派的Raspbian这类基于Debian的系统安装前得先装一些系统依赖。打开终端依次执行下面这些命令sudo apt-get update sudo apt-get install python3-dev libbluetooth-dev pip3 install pybluez这里python3-dev提供了Python的头文件libbluetooth-dev则是Linux系统蓝牙开发的核心库没有它们PyBluez是编译不过去的。很多新手朋友会忽略这一步直接pip install结果就是一堆编译错误。至于macOS用户我得先泼点冷水PyBluez对macOS的官方支持很弱而且年久失修。社区里有一些非官方的移植版本但稳定性没法保证。如果你主要在macOS下开发我强烈建议你换条路要么用虚拟机跑个Linux要么考虑使用pyobjc框架配合系统自带的IOBluetooth库但那又是另一个故事了复杂度会高不少。所以为了最好的学习和开发体验我推荐在Windows或Linux环境下进行。安装成功后怎么验证呢打开你的Python解释器输入下面这行代码import bluetooth print(bluetooth.__version__)如果没有报ModuleNotFoundError而是打印出版本号比如0.23那么恭喜你PyBluez已经成功入驻你的开发环境了。接下来我们就可以正式开始探索蓝牙的世界了。2. 蓝牙设备发现与扫描实战环境搞定咱们就动手干第一件有意思的事扫描你身边的蓝牙设备。这个过程就像用手机打开蓝牙搜索周围设备一样只不过这次是用我们自己的代码来当“眼睛”。PyBluez提供了一个非常直接的函数bluetooth.discover_devices()。调用它你的程序就会主动发起一次蓝牙设备发现请求。但这里有个关键细节扫描是需要时间的。默认情况下这个函数会持续扫描大约10秒然后返回一个列表里面是扫描期间所有响应请求的设备的MAC地址。光有地址可不够我们通常更关心设备的名字比如“小明的小米耳机”、“客厅的智能音箱”。这时候就需要配合bluetooth.lookup_name()函数。我写一个完整的扫描函数给你看看import bluetooth import time def scan_nearby_devices(duration10): 扫描附近蓝牙设备 :param duration: 扫描持续时间单位秒 :return: 包含设备地址和名称的列表 print(f开始扫描大约需要{duration}秒...) start_time time.time() # discover_devices 会返回 (地址, 名称) 的元组列表 nearby_devices bluetooth.discover_devices(durationduration, lookup_namesTrue, flush_cacheTrue) elapsed_time time.time() - start_time print(f扫描完成耗时 {elapsed_time:.2f} 秒) print( * 40) if not nearby_devices: print(哎呀附近没有发现任何蓝牙设备。) print(请检查) print(1. 电脑的蓝牙适配器是否已开启) print(2. 周围是否有蓝牙设备处于可发现模式) print(3. 在系统设置里确认蓝牙权限已授予你的Python环境。) return [] print(f共发现 {len(nearby_devices)} 个设备) for i, (addr, name) in enumerate(nearby_devices, 1): # 有些设备可能没有名称显示为None device_name name if name else 未知设备 print(f{i}. 设备名: {device_name}) print(f 地址: {addr}) print(f 信号强度: 暂不支持直接获取 (PyBluez限制)) print(- * 30) return nearby_devices if __name__ __main__: # 执行一个8秒的快速扫描 devices scan_nearby_devices(8)把这段代码保存成scan.py并运行你就能在控制台看到结果了。这里我用了lookup_namesTrue参数让discover_devices一次性把地址和名称都查回来比分开调用效率高。flush_cacheTrue的意思是清除系统之前的蓝牙缓存确保我们拿到的是最新的扫描结果。实际跑起来你可能会遇到几个典型问题扫描时间太长或太短duration参数可以调整。在设备密集的环境比如办公室8-10秒比较合适如果只是找一两个已知设备5秒也够了。时间设得太长程序会“卡住”等待太短可能漏掉响应慢的设备。找不到设备这是最常见的坑。首先确保你的蓝牙耳机、音箱、手机等设备处于“可被发现”模式。很多设备为了省电默认是不可被发现的需要在设置里手动打开。其次检查系统防火墙或安全软件是否阻止了Python的蓝牙访问。设备名显示为None有些设备特别是某些低功耗或老旧设备不会广播自己的名称这是正常现象。我们只要有它的MAC地址一样可以进行后续连接。掌握了扫描你就有了蓝牙世界的“设备清单”。但这只是第一步就像你知道了朋友的电话号码接下来还得搞清楚他擅长什么提供什么服务才能开始有效沟通。这就是我们下一节要做的服务查询。3. 深入蓝牙服务与服务发现找到设备地址后下一步就是“敲门问问它能提供什么服务”。蓝牙设备无论是耳机、键盘还是传感器都不是一个黑盒子它会对外公布一个“服务列表”告诉外界“我能做音乐播放A2DP服务、我能做文件传输OBEX服务、我能用串口通信RFCOMM服务”。在蓝牙技术里每个服务都有一个全球唯一的标识符叫做UUID。比如标准的串口服务Serial Port Profile, SPP的UUID是00001101-0000-1000-8000-00805F9B34FB。PyBluez提供了bluetooth.find_service()函数来帮我们查询指定设备的所有服务。我们来写一个函数它接收设备的MAC地址然后把这个设备的所有家底服务都翻出来看看import bluetooth def explore_device_services(target_address): 探索指定蓝牙设备提供的服务 :param target_address: 目标设备的蓝牙MAC地址格式如 XX:XX:XX:XX:XX:XX if not target_address: print(错误未提供设备地址) return print(f正在查询设备 {target_address} 的服务...) try: services bluetooth.find_service(addresstarget_address) except Exception as e: print(f查询服务时出错: {e}) return if not services: print(f设备 {target_address} 没有找到任何公开的服务。) print(可能的原因) print( - 设备不支持服务发现协议SDP。) print( - 设备需要先配对才能查看服务。) print( - 设备当前没有激活任何服务。) return print(f发现 {len(services)} 个服务) print( * 60) for idx, svc in enumerate(services, 1): print(f服务 #{idx}:) # 服务名称可能为空 name svc.get(name, 未命名服务) print(f 名称: {name}) # 服务的主机即提供服务的设备地址 host svc.get(host, 未知) print(f 主机: {host}) # 描述信息 description svc.get(description, 无描述) print(f 描述: {description}) # 协议这是关键RFCOMM 或 L2CAP protocol svc.get(protocol, 未知) print(f 协议: {protocol}) # 端口/信道连接时需要用到 port svc.get(port, 未知) print(f 端口/信道: {port}) # 服务类UUID列表 service_classes svc.get(service-classes, []) print(f 服务类UUID: {service_classes}) # 配置文件Profile profiles svc.get(profiles, []) print(f 配置文件: {profiles}) # 服务ID service_id svc.get(service-id, 未知) print(f 服务ID: {service_id}) print(- * 40) # 使用示例假设我们扫描到一个设备的地址是 11:22:33:AA:BB:CC if __name__ __main__: # 这里替换成你实际扫描到的设备地址 addr_from_scan 41:42:C5:BF:2C:32 # 示例地址 explore_device_services(addr_from_scan)运行这个代码你会看到类似这样的输出。对于大多数支持串口通信的蓝牙模块比如HC-05、JDY-31你一定能找到一个协议为RFCOMM的服务它的端口port通常是一个1到30之间的数字。这个端口号就是我们后续建立通信连接的“门牌号”千万要记好注意find_service()函数在某些平台或设备上可能返回空列表即使设备明明有服务。这通常是因为设备需要先完成配对Pairing或绑定Bonding过程。在Linux上你可以先用命令行工具bluetoothctl手动配对设备然后再用Python代码查询。PyBluez本身没有提供配对的API这是一个常见的痛点。不过对于很多蓝牙串口模块当你尝试用RFCOMM Socket去连接时系统会自动弹出配对请求确认后即可。4. 建立RFCOMM连接客户端与服务器服务也查明白了端口号也拿到了重头戏来了——建立连接收发数据。蓝牙RFCOMM协议模拟了传统的串口通信在PyBluez里它的编程模型和Python标准的网络Socket编程几乎一模一样。如果你写过TCP客户端/服务器程序那这一部分就是小菜一碟。我们先来打造一个蓝牙服务器服务端。它的角色就像一台等待电话接入的总机import bluetooth import time def run_bluetooth_server(port1): 启动一个蓝牙RFCOMM服务器等待客户端连接 :param port: 监听的RFCOMM端口号通常1-30 # 1. 创建RFCOMM类型的Socket server_sock bluetooth.BluetoothSocket(bluetooth.RFCOMM) server_sock.settimeout(60) # 设置socket超时时间避免无限等待 try: # 2. 绑定到所有本地适配器并指定端口 server_sock.bind((, port)) # 3. 开始监听允许1个等待连接 server_sock.listen(1) print(f蓝牙服务器已启动正在端口 {port} 上等待连接...) print(请确保设备蓝牙已开启并可被发现。) # 4. 阻塞等待直到有客户端连接 client_sock, client_address server_sock.accept() print(f接受来自 {client_address} 的连接) # 5. 连接建立开始通信 # 设置客户端socket的阻塞模式和非阻塞模式 # client_sock.setblocking(True) # 默认就是阻塞的 client_sock.settimeout(30.0) # 设置接收超时 try: while True: # 接收数据 data client_sock.recv(1024) # 每次最多接收1024字节 if not data: print(客户端关闭了连接。) break received_text data.decode(utf-8, errorsignore).strip() print(f收到消息: {received_text}) # 简单回复 if received_text.lower() bye: reply Goodbye from server! client_sock.send(reply.encode(utf-8)) print(发送回复准备关闭连接。) break else: reply fEcho: {received_text} client_sock.send(reply.encode(utf-8)) print(f已回复: {reply}) except bluetooth.btcommon.BluetoothError as e: print(f通信过程中发生错误: {e}) finally: # 6. 关闭连接 client_sock.close() print(客户端Socket已关闭。) except Exception as e: print(f服务器运行出错: {e}) finally: server_sock.close() print(服务器Socket已关闭。) if __name__ __main__: # 通常RFCOMM端口1是常见的SPP服务端口 run_bluetooth_server(port1)服务器代码的核心步骤就是创建Socket - 绑定端口 - 监听 - 接受连接 - 循环收发数据 - 关闭。server_sock.bind((, port))中的空字符串表示绑定到本机所有可用的蓝牙适配器。接下来我们写蓝牙客户端。客户端的任务就是找到服务器并主动“打电话”过去import bluetooth import time def run_bluetooth_client(server_address, port1): 作为客户端连接到指定的蓝牙服务器 :param server_address: 服务器蓝牙MAC地址 :param port: 服务器监听的RFCOMM端口号 if not server_address: print(错误需要提供服务器地址) return # 1. 创建RFCOMM Socket sock bluetooth.BluetoothSocket(bluetooth.RFCOMM) sock.settimeout(10) # 设置连接超时 print(f尝试连接到设备 {server_address} 的端口 {port} ...) try: # 2. 发起连接 sock.connect((server_address, port)) print(连接成功) # 3. 通信循环 for i in range(5): message fHello Server! Message #{i1} print(f发送: {message}) sock.send(message.encode(utf-8)) # 等待并接收服务器的回复 try: reply sock.recv(1024) if reply: print(f收到回复: {reply.decode(utf-8)}) else: print(服务器无回复连接可能已断开。) break except bluetooth.btcommon.BluetoothError as e: print(f接收回复时出错: {e}) break time.sleep(1) # 等待1秒再发下一条 # 发送结束信号 sock.send(bbye) final_reply sock.recv(1024) if final_reply: print(f最终回复: {final_reply.decode(utf-8)}) except bluetooth.btcommon.BluetoothError as e: print(f连接失败: {e}) print(请检查) print(f1. 地址 {server_address} 是否正确) print(f2. 端口 {port} 是否正确) print(3. 目标设备是否已开启蓝牙并运行了服务器程序) print(4. 两台设备是否已完成配对) finally: sock.close() print(连接已关闭。) # 使用示例 if __name__ __main__: # 这里替换成你服务器设备的真实MAC地址 target_addr 41:42:C5:BF:2C:32 # 示例地址请修改 run_bluetooth_client(target_addr, port1)把这两段代码分别运行在两台都有蓝牙和Python环境的电脑上比如一台Windows笔记本和一台树莓派先运行服务器端代码再运行客户端代码你就能看到它们互相打招呼了这就是你亲手实现的第一个点对点蓝牙通信应用。这里有几个我踩过坑的细节要提醒你端口号RFCOMM端口号范围是1-30。很多蓝牙串口模块的SPP服务默认就在端口1。如果你在服务查询时看到了别的端口号比如3、5连接时就要用那个号。地址格式MAC地址里的字母不区分大小写但冒号:不能少。连接超时sock.settimeout(10)非常有用。没有它如果服务器没开客户端会卡在connect那里很久。设置超时能让程序在失败时快速响应。编码与解码蓝牙传输的是二进制数据bytes。我们发送字符串前要用.encode(utf-8)转成字节接收后要用.decode(utf-8)转回字符串。记住要统一编码否则会乱码。5. 实战构建一个简单的蓝牙聊天程序光会收发固定消息不过瘾对吧我们来搞个更实用的一个简单的双向蓝牙聊天程序。这个程序既可以作为服务器等待别人连接也可以作为客户端主动连接别人连接成功后双方就能像在命令行里聊天一样互相发送消息。这个程序的关键在于处理并发我们需要一边监听用户从键盘输入的消息并发送一边随时准备接收对方发来的消息并显示。Python的threading模块可以帮我们轻松搞定。下面是一个整合了客户端和服务器模式的完整示例import bluetooth import threading import sys import time class SimpleBluetoothChat: def __init__(self, mode, target_addressNone, port1): 初始化聊天程序 :param mode: server 或 client :param target_address: 客户端模式时服务器的地址 :param port: RFCOMM端口 self.mode mode self.target_address target_address self.port port self.sock None self.running False def start_server(self): 以服务器模式启动 server_sock bluetooth.BluetoothSocket(bluetooth.RFCOMM) try: server_sock.bind((, self.port)) server_sock.listen(1) print(f[服务器] 已在端口 {self.port} 上启动等待连接...) self.sock, client_info server_sock.accept() print(f[服务器] 已连接到 {client_info}) self._start_chat() except Exception as e: print(f[服务器] 启动失败: {e}) finally: server_sock.close() def start_client(self): 以客户端模式启动 if not self.target_address: print([客户端] 错误未指定服务器地址) return self.sock bluetooth.BluetoothSocket(bluetooth.RFCOMM) try: print(f[客户端] 正在连接 {self.target_address}:{self.port} ...) self.sock.connect((self.target_address, self.port)) print([客户端] 连接成功) self._start_chat() except Exception as e: print(f[客户端] 连接失败: {e}) finally: if self.sock: self.sock.close() def _start_chat(self): 开始聊天会话 self.running True print(\n *40) print(连接已建立现在可以开始聊天了。) print(输入消息并按回车发送。) print(输入 quit 并按回车来结束聊天。) print(*40 \n) # 启动一个线程专门负责接收消息 recv_thread threading.Thread(targetself._receive_messages, daemonTrue) recv_thread.start() # 主线程负责发送消息 try: while self.running: # 获取用户输入 message input(我: ).strip() if not message: continue if message.lower() quit: print(正在关闭连接...) self.running False # 可以发送一个退出通知给对方 try: self.sock.send(b[对方已退出]) except: pass break # 发送消息 try: self.sock.send(message.encode(utf-8)) except bluetooth.btcommon.BluetoothError: print(发送失败连接可能已断开。) self.running False break except KeyboardInterrupt: print(\n检测到中断正在退出...) self.running False finally: if self.sock: self.sock.close() print(聊天结束。) def _receive_messages(self): 在后台线程中持续接收消息 self.sock.settimeout(1.0) # 设置超时以便能检查self.running标志 while self.running: try: data self.sock.recv(1024) if data: message data.decode(utf-8, errorsignore).rstrip() # 使用回车和清行技巧让接收的消息不干扰输入行 sys.stdout.write(f\r对方: {message}\n我: ) sys.stdout.flush() # 如果收到空数据说明连接已关闭 elif data b: print(\n连接已被对方关闭。) self.running False break except bluetooth.btcommon.BluetoothError as e: # 超时错误是预期的继续循环 if timed out in str(e): continue else: print(f\n接收错误: {e}) self.running False break except Exception as e: print(f\n接收线程异常: {e}) self.running False break def main(): print( 简易蓝牙聊天程序 ) mode input(请选择模式 (1: 服务器, 2: 客户端): ).strip() if mode 1: port int(input(输入要监听的端口号 (默认1): ) or 1) chat SimpleBluetoothChat(server, portport) chat.start_server() elif mode 2: addr input(输入服务器的蓝牙MAC地址 (例如 11:22:33:AA:BB:CC): ).strip() port int(input(输入服务器端口号 (默认1): ) or 1) chat SimpleBluetoothChat(client, target_addressaddr, portport) chat.start_client() else: print(无效的选择。) if __name__ __main__: main()这个程序虽然简单但已经具备了实时聊天的核心功能。运行它在一台电脑上选择“服务器”模式在另一台电脑上选择“客户端”模式并输入服务器的MAC地址你们就能愉快地聊天了。这里面的几个技术点值得细说多线程_receive_messages函数在一个独立的线程中运行它死循环等待接收数据。这样主线程就能专注于处理用户输入两者互不干扰。这是实现实时双向通信的经典模式。输入输出处理为了不让接收到的消息把正在输入的文字冲乱代码里用了sys.stdout.write(\r...)这个小技巧。\r是回车符能把光标移回行首然后我们重新打印“对方: xxx”和提示符“我: ”看起来就整洁多了。连接管理程序通过self.running这个标志位来控制线程的退出。当用户输入quit或连接出错时将标志位设为False接收线程检测到后就会安全退出。错误处理网络通信包括蓝牙充满了不确定性。代码里用try...except包裹了所有关键的Socket操作connect,send,recv确保程序在遇到连接断开、超时等问题时不会崩溃而是给出友好的提示并优雅退出。你可以把这个程序当作一个起点在此基础上增加更多功能比如图形界面用Tkinter或PyQt、消息历史记录、文件传输或者连接多个客户端。掌握了这个基础框架你就已经能解决很多实际的蓝牙通信需求了比如用树莓派通过蓝牙接收传感器数据或者用电脑控制一个蓝牙机器人。