You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何同时运行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

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.04.13 16:42:57