1. 项目概述与核心思路
最近在捣鼓一个挺有意思的小项目:如何用手机蓝牙,去控制一个连接在电脑GPIO引脚上的LED灯。听起来像是把硬件、软件和无线通信揉在了一起,对吧?其实这个项目的核心,就是搭建一个从手机App到电脑端Python脚本,再到物理硬件的完整控制链路。它非常适合想入门物联网、嵌入式交互或者单纯想搞点酷炫小玩意儿的朋友。无论你是学生、创客,还是对自动化感兴趣的开发者,这个项目都能帮你把“无线控制物理设备”这个抽象概念,变成手边可触摸、可实现的乐趣。
整个项目的逻辑链条非常清晰。我们会在电脑(这里特指运行Windows系统,并安装了Visio的电脑,但Visio在本项目中并非必需,更多是一个运行Python的环境标识)上运行一个Python程序。这个程序有两个核心任务:一是通过蓝牙与手机建立通信,接收来自手机的指令;二是根据接收到的指令,通过电脑的GPIO接口(通常需要一块USB转GPIO的适配板,比如树莓派本身也可以,但这里我们聚焦于x86电脑的扩展方案)去控制LED的亮灭。手机端,我们则使用一个现成的、功能强大的蓝牙串口调试App,例如“Serial Bluetooth Terminal”,来充当遥控器。这样一来,你就能在手机上点击按钮,无线点亮或熄灭桌上的小灯了。
这个项目麻雀虽小,五脏俱全。它涉及了蓝牙通信协议、串口数据解析、GPIO编程、事件驱动编程等多个知识点。通过动手实现它,你不仅能学会如何用Python操作硬件,还能深入理解一个典型的“指令-响应”物联网控制模型是如何搭建的。下面,我就把自己从硬件选型、环境搭建、代码编写到调试排错的全过程,以及踩过的那些坑,毫无保留地分享出来。
2. 硬件选型与连接方案解析
工欲善其事,必先利其器。硬件是整个项目的物理基础,选对器材能避免很多后续的麻烦。首先我们要明确一个关键点:普通的台式机或笔记本电脑主板,通常不会直接暴露通用的GPIO引脚供我们使用。因此,我们需要一块“桥梁”设备,将电脑的USB口转换为可编程控制的GPIO口。
2.1 GPIO适配板的选择
市面上常见的方案有以下几种,我逐一分析其优劣:
USB转GPIO适配器:这是最直接、最通用的方案。这类设备通过USB连接电脑,在系统中虚拟成一个串口(COM口)或特定的设备接口,并提供一组GPIO、I2C、SPI等引脚。品牌如FTDI、CH341芯片方案的模块都很常见。其优点是即插即用,驱动完善,在Python中通常有成熟的库(如
pyftdi、pyserial配合特定协议)来控制。缺点是功能可能比较基础,且需要额外学习对应库的API。Arduino开发板:这是一个非常流行且强大的选择。你可以将Arduino(如Uno、Nano)通过USB线连接到电脑,它会被识别为一个串口。我们的Python程序通过这个串口,向Arduino发送简单的指令(如“ON”、“OFF”),然后由Arduino板载的程序(Sketch)来解析指令并控制其板载的GPIO引脚。这是我最推荐给新手的方案。原因有三:首先,Arduino的GPIO操作极其简单,有现成的
digitalWrite()函数;其次,它隔离了电脑端复杂的硬件操作,让Python程序只需关注通信;最后,Arduino生态庞大,后续想扩展传感器、电机等都十分方便。树莓派:树莓派本身就是一台微型电脑,其GPIO可以直接用Python(如
RPi.GPIO库)控制。但在这个项目中,如果树莓派作为“被控端”,那么它需要同时运行蓝牙服务端和GPIO控制程序,角色更像是服务器。而我们标题中提到的“Visio”环境通常指Windows PC,因此更常见的场景是把树莓派当作一个独立的物联网设备来用,与本次“PC作为主控”的场景略有不同。不过,其GPIO编程思想是相通的。
我的选择与理由:为了最大化降低入门门槛,并确保过程的清晰可控,我选择了Arduino Uno作为GPIO适配板。这样,Python代码只需处理简单的串口字符串通信,而所有硬件层面的时序、电平操作都交给Arduino固件,分工明确,调试方便。
2.2 蓝牙通信的硬件基础
对于电脑端,蓝牙能力是必备的。如今绝大多数笔记本电脑和部分台式机主板都集成了蓝牙模块。你需要确保你的电脑蓝牙功能已开启,并且驱动正常。可以在Windows设置中查看“蓝牙和其他设备”,确认蓝牙开关已打开。
对于手机端,任何一款智能手机都具备蓝牙功能,无需特殊准备。
2.3 LED与电路连接
这是一个基础的电子学实践。我们需要:
- 一个LED灯(颜色任选)。
- 一个220Ω至1kΩ的电阻(用于限流,防止电流过大烧毁LED或Arduino引脚)。
- 若干杜邦线(公对公、公对母)。
连接电路(以Arduino Uno为例):
- 将LED的长脚(阳极,正极)通过一个220Ω电阻,连接到Arduino的某个数字引脚,例如引脚13(该引脚板载了一个LED,方便测试,但我们外接一个更直观)。
- 将LED的短脚(阴极,负极)连接到Arduino的GND(地)引脚。
- 用USB数据线将Arduino连接到电脑。
注意:务必串联限流电阻!直接连接LED到5V电源或GPIO引脚会瞬间导致LED损坏。电阻值可以根据公式
R = (Vcc - Vf) / If估算,其中Vcc是电源电压(5V),Vf是LED正向压降(约1.8-3.3V,视颜色而定),If是期望的工作电流(通常5-20mA)。取220Ω是一个安全通用的值。
3. 软件环境搭建与核心库配置
软件层面需要搭建两端的环境:电脑端的Python开发环境,和Arduino的编程环境(如果采用Arduino方案)。
3.1 电脑端Python环境准备
安装Python:访问Python官网,下载并安装最新版本的Python 3.x(如3.8+)。安装时务必勾选“Add Python to PATH”,这样可以在命令行中直接使用
python和pip命令。安装必要的Python库:我们将用到两个核心库。
- PySerial:用于通过串口与Arduino通信。
- PyBluez或bleak:用于创建蓝牙服务端,与手机通信。在Windows上,
PyBluez的安装有时会遇到编译问题,因此我推荐使用纯Python实现的bleak库,它支持现代的BLE(蓝牙低功耗)协议,兼容性更好。
打开命令行(CMD或PowerShell),执行以下命令安装:
pip install pyserial pip install bleak
3.2 Arduino IDE与固件编写
- 安装Arduino IDE:从Arduino官网下载并安装Arduino IDE。
- 编写并上传固件:打开Arduino IDE,新建一个项目,输入以下代码。这段代码的作用是监听串口,如果收到字符‘1’,则点亮LED;收到字符‘0’,则熄灭LED。
const int ledPin = 13; // 定义LED连接的引脚 void setup() { pinMode(ledPin, OUTPUT); // 将引脚设置为输出模式 digitalWrite(ledPin, LOW); // 初始状态为熄灭 Serial.begin(9600); // 初始化串口通信,波特率9600 } void loop() { if (Serial.available() > 0) { // 检查串口是否有数据 char command = Serial.read(); // 读取一个字符 if (command == '1') { digitalWrite(ledPin, HIGH); // 点亮LED Serial.println("LED ON"); // 回传状态,便于调试 } else if (command == '0') { digitalWrite(ledPin, LOW); // 熄灭LED Serial.println("LED OFF"); } // 可以忽略其他字符 } } - 选择板卡与端口:在“工具”菜单中,选择正确的板卡类型(如Arduino Uno)和对应的COM端口(连接Arduino后会在设备管理器中看到)。
- 点击上传:将这段固件烧录到Arduino Uno中。上传成功后,Arduino就会开始运行这段程序,等待来自串口的指令。
3.3 手机端App准备
在手机应用商店搜索并安装Serial Bluetooth Terminal。这是一款功能强大且免费的蓝牙串口调试工具,支持发送和接收文本、快捷键设置等,完美符合我们的需求。
4. 核心代码实现:构建蓝牙到串口的桥梁
这是整个项目的“大脑”,即运行在电脑上的Python程序。它的核心架构是一个“桥接”服务:作为一个蓝牙服务端等待手机连接,同时作为一个串口客户端连接Arduino。收到手机指令后,转发给Arduino。
4.1 程序整体架构设计
程序采用异步编程模型,这是处理蓝牙这种I/O密集型操作的推荐方式。我们使用asyncio库配合bleak。主要流程如下:
- 初始化:扫描并连接指定的蓝牙设备(手机),同时打开连接Arduino的串口。
- 启动蓝牙服务:广播一个蓝牙服务,并定义用于接收指令的特征值(Characteristic)。
- 事件循环:当手机向这个特征值写入数据(即发送指令)时,触发回调函数。
- 指令转发:在回调函数中,解析手机发来的数据,并通过串口发送对应的字符(‘1’或‘0’)给Arduino。
- 状态反馈(可选):读取Arduino从串口回传的状态信息(如“LED ON”),并通过蓝牙发送回手机,实现双向通信。
4.2 Python代码详解
以下是完整的Python服务端代码,我将其保存为bluetooth_led_gateway.py。代码中包含大量注释,解释了每一步的意图和关键点。
import asyncio import logging from bleak import BleakServer, BleakScanner from bleak.backends.characteristic import BleakGATTCharacteristic import serial import serial.tools.list_ports # 配置日志,方便查看运行状态 logging.basicConfig(level=logging.INFO) logger = logging.getLogger(__name__) # 蓝牙服务配置 # 这里需要生成一些唯一的UUID,可以使用在线生成器,确保不与标准服务冲突 LED_SERVICE_UUID = "0000ffe0-0000-1000-8000-00805f9b34fb" # 自定义服务UUID LED_CHARACTERISTIC_UUID = "0000ffe1-0000-1000-8000-00805f9b34fb" # 自定义特征值UUID # 串口配置(需要根据你的实际端口修改) ARDUINO_PORT = "COM3" # Windows上的端口,如COM3, COM4等 # 在Linux或macOS上可能是 /dev/ttyUSB0 或 /dev/ttyACM0 BAUDRATE = 9600 # 必须与Arduino代码中的Serial.begin()设置一致 class BluetoothLEDController: def __init__(self): self.server = None self.serial_conn = None self.connected_device = None async def initialize_serial(self): """初始化串口连接""" try: # 自动查找Arduino端口的技巧(可选) # ports = list(serial.tools.list_ports.comports()) # for p in ports: # if 'Arduino' in p.description or 'USB Serial' in p.description: # ARDUINO_PORT = p.device # break self.serial_conn = serial.Serial(ARDUINO_PORT, BAUDRATE, timeout=1) logger.info(f"串口连接成功: {ARDUINO_PORT},波特率{BAUDRATE}") # 清空缓冲区 self.serial_conn.reset_input_buffer() except serial.SerialException as e: logger.error(f"无法打开串口 {ARDUINO_PORT}: {e}") logger.info("请检查:1. Arduino是否连接? 2. 端口号是否正确? 3. 其他软件是否占用了此端口?") raise async def serial_write(self, data: str): """向串口写入数据""" if self.serial_conn and self.serial_conn.is_open: self.serial_conn.write(data.encode('ascii')) logger.debug(f"已向串口发送: {data}") else: logger.warning("串口未打开,无法发送数据") async def serial_read_loop(self): """持续读取串口数据(用于接收Arduino状态反馈)""" if not self.serial_conn: return while self.serial_conn and self.serial_conn.is_open: try: # 读取一行 if self.serial_conn.in_waiting: line = self.serial_conn.readline().decode('ascii', errors='ignore').strip() if line: logger.info(f"从Arduino收到: {line}") # 这里可以将line通过蓝牙发送回手机(需要获取手机端的特征值句柄) # await self.notify_client(line) except Exception as e: logger.error(f"读取串口时出错: {e}") break await asyncio.sleep(0.1) # 避免CPU占用过高 async def led_characteristic_callback( self, characteristic: BleakGATTCharacteristic, data: bytearray ): """蓝牙特征值被写入时的回调函数""" client_address = characteristic.service.server.address try: command = data.decode('utf-8').strip().upper() logger.info(f"收到来自 {client_address} 的指令: {command}") # 解析指令 if command == "ON" or command == "1": await self.serial_write('1') response = "LED已点亮" elif command == "OFF" or command == "0": await self.serial_write('0') response = "LED已熄灭" else: response = f"未知指令: {command}" logger.warning(response) # 可选:通过蓝牙向手机发送响应(需要特征值支持NOTIFY或INDICATE) # await characteristic.write_value(response.encode('utf-8')) except Exception as e: logger.error(f"处理指令时出错: {e}") async def run_server(self): """主函数:启动蓝牙服务器并初始化串口""" # 1. 初始化串口 await self.initialize_serial() # 2. 启动串口读取循环(作为后台任务) serial_task = asyncio.create_task(self.serial_read_loop()) # 3. 创建蓝牙服务器 self.server = BleakServer() # 4. 定义并添加服务与特征值 from bleak.backends.service import BleakGATTServiceCollection from bleak.backends.characteristic import BleakGATTCharacteristic from bleak.backends.descriptor import BleakGATTDescriptor # 创建服务 service = self.server.add_service(LED_SERVICE_UUID) # 创建特征值,属性为 WRITE(允许手机写入),也可以添加READ或NOTIFY char_flags = ["write"] characteristic = service.add_characteristic( LED_CHARACTERISTIC_UUID, char_flags, self.led_characteristic_callback # 绑定回调函数 ) # 5. 启动蓝牙广告,让手机能发现我们 device_name = "PC-LED-Controller" await self.server.start_advertising(device_name, service_uuids=[LED_SERVICE_UUID]) logger.info(f"蓝牙服务器已启动,设备名: '{device_name}', 等待手机连接...") logger.info(f"服务UUID: {LED_SERVICE_UUID}") logger.info(f"特征值UUID: {LED_CHARACTERISTIC_UUID}") try: # 保持服务器运行,直到程序被终止 await asyncio.Future() except asyncio.CancelledError: logger.info("服务器正在关闭...") finally: # 6. 清理资源 await self.server.stop() if self.serial_conn and self.serial_conn.is_open: self.serial_conn.close() serial_task.cancel() logger.info("资源已释放,程序退出") if __name__ == "__main__": controller = BluetoothLEDController() try: asyncio.run(controller.run_server()) except KeyboardInterrupt: logger.info("用户中断程序") except Exception as e: logger.error(f"程序运行出错: {e}")4.3 代码关键点解析
- UUID的作用:蓝牙服务和特征值都需要唯一的UUID来标识。我们使用了自定义的UUID(在标准蓝牙UUID范围内随机生成即可),确保手机App能准确找到我们的服务。在手机App连接时,需要输入这些UUID。
- 异步编程:
asyncio和bleak库都是基于异步的。await关键字用于等待耗时的I/O操作(如蓝牙通信、串口读写)完成,而不会阻塞整个程序。这使得服务器可以同时处理多个连接和任务(如并行的串口读取)。 - 回调函数:
led_characteristic_callback是核心。当手机App向特征值写入数据时,这个函数被自动调用,参数data就是手机发来的字节数据。我们在这里进行指令解析和转发。 - 错误处理:代码中包含了基本的
try...except块,用于捕获串口打开失败、数据解码错误等异常,并记录到日志中,这对于调试至关重要。 - 资源管理:在
finally块中,我们确保了蓝牙广告停止、串口关闭、后台任务被取消,这是一种良好的编程习惯,防止程序异常退出后资源未释放。
5. 手机端配置与全链路测试
现在,硬件已连接,固件已烧录,电脑端的桥梁程序也已就绪。接下来是最后一步:配置手机并完成端到端测试。
5.1 手机App连接配置
- 打开手机上的Serial Bluetooth TerminalApp。
- 点击界面上的连接按钮(通常是一个蓝牙图标),开始扫描附近的蓝牙设备。
- 在设备列表中,你应该能看到名为“PC-LED-Controller”的设备(即我们Python程序广播的名称)。点击它进行连接。
- 连接时,App可能会要求配对(输入配对码如0000或1234),按照提示操作即可。
- 连接成功后,App界面通常会显示已连接的设备和服务。关键步骤来了:我们需要让App知道向哪个特征值发送数据。
- 在App的设置或连接详情页面,找到“服务UUID”和“特征值UUID”的配置项。
- 将我们在Python代码中定义的
LED_SERVICE_UUID(0000ffe0-...) 和LED_CHARACTERISTIC_UUID(0000ffe1-...) 分别填入。 - 确保通信模式设置为“通过特征值写入”(Write Characteristic)。
- 配置完成后,App的主界面通常会有一个输入框和一个发送按钮。
5.2 端到端测试流程
启动服务:在电脑上,首先确保Arduino已通过USB连接,并且已上传固件。然后在命令行中运行我们的Python程序:
python bluetooth_led_gateway.py如果一切正常,你会看到类似以下的日志:
INFO:__main__:串口连接成功: COM3,波特率9600 INFO:__main__:蓝牙服务器已启动,设备名: 'PC-LED-Controller', 等待手机连接...手机连接:按照5.1的步骤,在手机App上完成连接。当手机连接成功时,电脑端的日志会显示有设备连接。
发送指令:在手机App的输入框中,输入“ON”或“1”(不区分大小写,代码中做了
upper()转换),然后点击发送。- 预期现象:电脑端日志会显示“收到来自 XX:XX:XX:XX:XX:XX 的指令: ON”和“已向串口发送: 1”。紧接着,你连接在Arduino引脚13上的LED灯应该被点亮。
- 同时,Arduino的串口监视器(可在Arduino IDE中打开)会打印出“LED ON”。
发送关闭指令:在手机App输入框中输入“OFF”或“0”,点击发送。
- 预期现象:LED灯熄灭,电脑日志和Arduino串口监视器显示相应信息。
至此,一个完整的“手机蓝牙控制电脑GPIO引脚LED”系统就成功运行了!你可以通过手机,在房间的任何角落控制这个小灯。
6. 深度优化与功能扩展
基础功能实现后,我们可以从稳定性、用户体验和功能层面进行优化和扩展。
6.1 稳定性优化
- 自动重连机制:蓝牙连接可能因距离或干扰而中断。可以在Python代码中添加重连逻辑。在
BleakServer的回调中监听连接断开事件,然后尝试重新启动广告或主动重连手机(如果知道手机地址)。 - 串口心跳检测:定期(如每30秒)通过串口向Arduino发送一个特定字符(如‘?’),并期待一个固定的回复(如‘OK’)。如果收不到回复,可以尝试重新初始化串口连接。这能检测Arduino是否意外复位或USB接触不良。
- 指令队列与确认:对于关键指令,可以实现一个简单的“发送-确认”机制。手机发送指令后,等待电脑通过蓝牙特征值(设置为NOTIFY属性)返回“ACK”确认,然后再进行下一个操作。这能有效避免指令在无线环境中丢失。
6.2 用户体验提升
- 开发图形化控制界面(GUI):用Python的
Tkinter、PyQt或Kivy库为电脑端的服务程序做一个简单的GUI。界面可以显示蓝牙连接状态、串口状态、历史指令日志,并提供按钮来手动控制LED,方便本地调试。 - 手机App定制化:使用MIT App Inventor、React Native或Flutter等工具,开发一个专属的App界面。可以设计漂亮的开关按钮、滑动调光器(通过发送PWM值,如‘D128’代表50%亮度),甚至添加定时开关功能。
- 支持更多指令:扩展指令集。例如:
BLINK,5,200:让LED以200毫秒间隔闪烁5次。PWM,128:将LED亮度设置为50%(假设Arduino端支持PWM输出,需修改固件)。GET_STATUS:查询当前LED状态。
6.3 功能扩展设想
这个项目是一个完美的起点,可以衍生出许多有趣的应用:
- 多设备控制:在Arduino上连接多个LED、继电器或舵机,通过蓝牙指令控制不同的引脚。指令可以设计为
DEVICE1:ON、DEVICE2:OFF的形式。 - 传感器数据回传:在Arduino上连接温湿度传感器(如DHT11)、光敏电阻等。修改固件,使其定期读取传感器数据并通过串口发送。Python程序收到后,再通过蓝牙NOTIFY特性推送到手机App上显示。这就构成了一个简单的无线环境监测器。
- 与智能家居平台集成:将电脑端的Python程序封装成一个本地服务,然后通过Home Assistant、OpenHAB等开源智能家居平台的插件接口,将其接入。这样,你就可以通过Siri、Google Assistant或统一的智能家居App来控制这个LED,并与其他设备联动。
- 改用Wi-Fi或MQTT:如果你觉得蓝牙距离有限,可以将通信方式替换为Wi-Fi。让Arduino(如ESP8266/ESP32)直接连接家庭Wi-Fi,并作为一个MQTT客户端。手机和电脑都作为MQTT客户端,通过MQTT Broker(服务器)来发布和订阅控制指令。这种架构更适用于真正的物联网项目。
7. 常见问题与深度排错指南
在实际操作中,你几乎一定会遇到一些问题。下面是我在多次实践中总结的“坑位”和解决方案。
7.1 蓝牙连接相关问题
问题1:手机App扫描不到“PC-LED-Controller”设备。
- 可能原因A:蓝牙服务未成功广播。
- 排查:检查电脑蓝牙是否开启。在Windows服务中查看“Bluetooth Support Service”是否正在运行。
- 排查:检查Python程序是否报错。确认
bleak库安装正确,且程序以管理员权限运行(在某些系统上,广播蓝牙服务需要权限)。 - 排查:查看代码中的UUID格式是否正确,确保没有拼写错误。
- 可能原因B:手机与电脑蓝牙不兼容或距离过远。
- 排查:将手机靠近电脑(1米内)再尝试扫描。
- 排查:尝试用手机先配对电脑本身的蓝牙(在系统设置里配对),然后再用App连接。有时App需要系统层先完成配对。
- 可能原因C:防火墙或安全软件拦截。
- 排查:暂时关闭Windows防火墙和第三方安全软件,测试是否有效。
问题2:App连接后立即断开,或无法发送数据。
- 可能原因A:特征值UUID配置错误。
- 这是最常见的原因!务必确保手机App中配置的服务UUID和特征值UUID,与Python代码中定义的完全一致,包括大小写(通常UUID不区分大小写,但最好一致)。建议直接从代码里复制粘贴。
- 可能原因B:特征值属性不匹配。
- 排查:我们的Python代码中,特征值属性只设置了
["write"]。如果手机App尝试去读取(READ)或订阅通知(NOTIFY/INDICATE),而服务端不支持,可能会导致连接不稳定。确保App端配置为“只写”模式。
- 排查:我们的Python代码中,特征值属性只设置了
- 可能原因C:Python程序异常退出。
- 排查:观察运行Python程序的命令行窗口,是否有异常堆栈信息打印。常见的有串口被占用、UUID格式错误、异步任务异常等。
7.2 串口通信相关问题
问题3:Python程序提示“无法打开串口”或“SerialException”。
- 可能原因A:端口号错误。
- 排查:这是最可能的原因。
ARDUINO_PORT变量需要根据你的实际情况修改。在Windows设备管理器的“端口(COM和LPT)”下查看Arduino对应的COM号(如COM3、COM4)。注意,每次拔插Arduino,这个号可能会变。 - 技巧:使用代码中注释掉的自动查找端口功能,可以避免手动修改。
- 排查:这是最可能的原因。
- 可能原因B:端口被占用。
- 排查:关闭Arduino IDE的串口监视器窗口。任何其他打开了该串口的程序(如其他终端软件、之前的Python脚本未正常退出)都会导致占用。
- 排查:重启电脑或Arduino有时能解决幽灵占用问题。
- 可能原因C:波特率不匹配。
- 排查:确保Python代码中的
BAUDRATE(9600)与Arduino固件中Serial.begin(9600)的波特率设置完全一致。
- 排查:确保Python代码中的
问题4:手机发送指令后,LED无反应,但电脑日志显示指令已收到并发送。
- 可能原因A:电路连接错误或接触不良。
- 排查:这是硬件项目永恒的“第一步”。用万用表通断档检查LED、电阻、杜邦线、面包板(如果使用)的连接是否可靠。确认LED正负极没有接反(长脚为正)。
- 排查:确认LED连接的Arduino引脚号(代码中是13)与固件中
const int ledPin = 13;的定义一致。
- 可能原因B:Arduino固件未上传或上传失败。
- 排查:打开Arduino IDE的串口监视器(波特率设为9600),手动输入‘1’和‘0’,看LED是否有反应,以及是否有“LED ON/OFF”回显。如果没有,说明固件没有运行,重新检查上传步骤。
- 可能原因C:发送的字符格式问题。
- 排查:Python代码中发送的是单个字符
‘1’或‘0’。确保发送的不是字符串“1\n”或带其他不可见字符。可以在Python发送代码后加一句print(repr(data))来查看实际发送的内容。
- 排查:Python代码中发送的是单个字符
7.3 程序运行与依赖问题
问题5:运行Python脚本时提示“ModuleNotFoundError: No module named ‘bleak‘”。
- 解决:说明
bleak库没有安装成功。在命令行中重新执行pip install bleak。如果遇到网络问题,可以使用国内镜像源:pip install bleak -i https://pypi.tuna.tsinghua.edu.cn/simple。
问题6:程序运行后CPU占用率很高。
- 可能原因:
serial_read_loop或主事件循环中没有适当的休眠。代码中我们使用了await asyncio.sleep(0.1),这是一个适中的间隔。如果去掉这行,循环会全速运行,导致CPU占用高。确保所有死循环中都包含await asyncio.sleep()。
问题7:想同时控制多个LED或设备,代码结构变得混乱。
- 建议:进行代码重构。将蓝牙服务管理、串口管理、设备状态管理、指令解析器等模块拆分成不同的类。使用字典来映射设备ID和对应的控制函数。这样代码会更清晰,易于维护和扩展。例如,可以设计一个
DeviceManager类,里面注册了{‘led1‘: led1_control_function, ‘relay1‘: relay_control_function},指令解析器根据前缀来调用对应的函数。