如何同时运行WebSocket服务器与独立while循环?
如何同时运行WebSocket服务器与独立while循环?
嘿,看你在搞基于树莓派Zero 2W的RC飞控项目,要同时跑WebSocket收iPhone的控制信号,还要用while循环管PID和舵机对吧?这俩任务要同时跑,得处理好并发,我给你俩靠谱的实践方案:
我正在做一个基于树莓派Zero 2W的RC飞行控制器项目,用Python编写代码,需要实现两个核心功能:一是接收iPhone发送的控制输入,二是运行独立的while循环来管理PID控制器,进而操控飞机的操纵面。目前的代码基础框架如下:
import smbus from time import sleep import time import math import RPi.GPIO as GPIO from gpiozero import Servo from gpiozero.pins.pigpio import PiGPIOFactory import asyncio import websockets import json
方案一:用asyncio异步任务整合(最适配你的现有依赖)
因为你已经导入了asyncio和websockets,这俩本身就是异步生态的组件,直接把PID循环改成异步函数,就能和WebSocket服务器一起跑在同一个事件循环里,完全不用额外的线程/进程开销。
给你搭个大概的代码结构参考:
# 定义异步的PID控制循环 async def pid_control_loop(): # 先初始化你的硬件和PID参数 factory = PiGPIOFactory() # 示例:初始化舵机(根据你的实际引脚调整) elevator = Servo(17, pin_factory=factory) aileron = Servo(18, pin_factory=factory) pid_setpoint = 0.0 # 比如滚转的设定值 while True: # 这里写你的核心逻辑:读传感器、PID计算、更新舵机 # 比如根据设定值计算舵机输出 elevator.value = pid_setpoint * 0.5 aileron.value = pid_setpoint * 0.3 # 重点!异步睡眠,别用time.sleep(),会卡死整个事件循环 await asyncio.sleep(0.01) # 10ms一次循环,按需调整 # WebSocket的消息处理函数 async def websocket_handler(websocket): global pid_setpoint async for message in websocket: # 解析iPhone发来的JSON控制指令 control_data = json.loads(message) # 更新PID的设定值(也可以用类封装状态,比全局变量更优雅) pid_setpoint = control_data.get("roll", 0.0) # 主函数,启动所有异步任务 async def main(): # 启动WebSocket服务器,绑定所有网卡的8765端口 async with websockets.serve(websocket_handler, "0.0.0.0", 8765): # 把PID循环任务加入事件循环 pid_task = asyncio.create_task(pid_control_loop()) # 等待任务运行(会一直跑,直到手动中断) await pid_task if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: # 退出时记得清理GPIO资源,避免树莓派引脚异常 GPIO.cleanup() print("程序已退出,资源已清理")
这个方案的关键注意点:
- 绝对不能在异步函数里用
time.sleep(),必须用await asyncio.sleep(),否则会阻塞整个事件循环,导致WebSocket收不到消息 - 如果需要共享数据(比如控制指令和PID设定值),用全局变量虽然简单,但更推荐用一个自定义的状态类来封装,代码更整洁
- 退出时一定要做资源清理,比如GPIO的cleanup,避免下次运行出问题
方案二:用多线程分离任务(适合有同步阻塞代码的场景)
如果你的PID控制逻辑里用了很多不支持异步的老库,或者不想改现有同步代码,那用多线程把PID循环单独丢到一个线程里跑,主线程跑WebSocket服务器就行。
给你个参考结构:
import threading # 同步的PID控制循环(不用改现有逻辑,直接用while+time.sleep) def pid_control_thread(): factory = PiGPIOFactory() elevator = Servo(17, pin_factory=factory) pid_setpoint = 0.0 # 用锁保证共享变量的线程安全,避免读写冲突 setpoint_lock = threading.Lock() while True: # 加锁读取设定值,避免和WebSocket线程同时修改 with setpoint_lock: current_setpoint = pid_setpoint # PID计算和舵机控制逻辑 elevator.value = current_setpoint * 0.5 # 这里可以正常用time.sleep,因为是单独线程 time.sleep(0.01) # WebSocket处理函数 async def websocket_handler(websocket): global pid_setpoint, setpoint_lock async for message in websocket: control_data = json.loads(message) # 加锁修改设定值,保证线程安全 with setpoint_lock: pid_setpoint = control_data.get("roll", 0.0) async def main(): # 启动PID线程,设置daemon=True让线程随主程序退出 pid_thread = threading.Thread(target=pid_control_thread, daemon=True) pid_thread.start() # 启动WebSocket服务器,保持运行 async with websockets.serve(websocket_handler, "0.0.0.0", 8765): await asyncio.Future() # 让服务器一直挂着 if __name__ == "__main__": try: asyncio.run(main()) except KeyboardInterrupt: GPIO.cleanup() print("程序已退出")
这个方案的关键注意点:
- 共享变量一定要用
threading.Lock()加锁,不然多线程同时读写会出现奇怪的bug - 用
daemon=True创建线程,这样主程序退出时,线程会自动跟着结束,不用手动终止 - 如果线程里有长时间阻塞的操作,这个方法比异步更省心,不用改代码
备注:内容来源于stack exchange,提问作者Otto Sundell
相关产品推荐
相关产品推荐

