ESP32 MicroPython中uasyncio实现auto_pump与服务器并发运行问题求助
问题:ESP32 MicroPython中uasyncio并发运行auto_pump与服务器的问题
我在ESP32上使用Python 3.9.12和uasyncio V3开发MicroPython IoT后端应用,希望设备启动后auto_pump()能在后台持续运行,同时服务器等待客户端请求。
现有代码片段
import usocket as socket import uasyncio as asyncio d = DHT11(machine.Pin(23)) pump = machine.Pin(22, machine.Pin.OUT) def get_sensor_data(): #return data from sensors async def handle_client(client_socket): request = client_socket.recv(1024).decode('utf-8') print('Request:', request) #manage different GET requests client_socket.send(response) client_socket.close() async def start_server(): #start server while True: client_socket, client_addr = server_socket.accept() print('Client connected from', client_addr) await handle_client(client_socket) async def auto_pump(): while True: pump_state = get_pump_state() if pump_state == 1: pause_duration = get_pause_duration() print(f"Pompa spenta per {pause_duration} minuti") change_pump_state() await asyncio.sleep(get_pause_duration() * 60) else: pump_duration = get_pump_duration() print(f"Pompa accesa per {pump_duration} minuti") change_pump_state() await asyncio.sleep(get_pump_duration() * 60)
尝试的三种主函数实现及问题
第一种实现
async def main(): pump_task = asyncio.create_task(auto_pump()) server_task = asyncio.create_task(start_server()) await asyncio.gather(server_task, pump_task) asyncio.run(main())
问题:auto_pump()仅首次运行,直到服务器收到客户端连接时才会切换泵状态。
第二种实现
async def run_auto_pump(): while True: await auto_pump() asyncio.run(run_auto_pump()) asyncio.run(start_server())
问题:auto_pump()依旧无法持续运行,且asyncio.run()会阻塞到第一个任务结束,第二个任务永远不会启动。
第三种实现
if __name__ == "__main__": loop = asyncio.get_event_loop() loop.create_task(auto_pump()) loop.create_task(start_server()) loop.run_forever()
问题:仍未解决,auto_pump()的执行被服务器的阻塞操作打断。
问题根源
核心问题在于你使用了同步的usocket.accept()调用,这是一个阻塞式操作,会占用uasyncio的事件循环线程,导致事件循环无法切换到auto_pump()任务,直到有客户端连接触发accept()返回,才会继续执行auto_pump()中的后续代码。同理,client_socket.recv()也是同步阻塞操作,会进一步阻塞事件循环。
修复方案
使用uasyncio提供的异步TCP服务器API替代同步socket操作,确保所有IO操作都是异步的,让事件循环可以在等待客户端连接的同时,持续运行auto_pump()任务。
修正后的完整代码
import uasyncio as asyncio from machine import Pin from dht import DHT11 d = DHT11(Pin(23)) pump = Pin(22, Pin.OUT) # 假设你已实现以下辅助函数 def get_sensor_data(): # 返回传感器数据 return {"temperature": d.temperature(), "humidity": d.humidity()} def get_pump_state(): # 返回当前泵状态(1为关闭,0为开启) return pump.value() def get_pause_duration(): # 返回泵暂停运行的时间(分钟) return 5 def get_pump_duration(): # 返回泵持续运行的时间(分钟) return 1 def change_pump_state(): # 切换泵的开关状态 pump.value(not pump.value()) async def handle_client(reader, writer): # 使用uasyncio的StreamReader/StreamWriter进行异步IO request = await reader.read(1024) request_str = request.decode('utf-8') print('Request:', request_str) # 示例响应,可根据请求路径处理不同逻辑 response = b'HTTP/1.1 200 OK\r\nContent-Type: text/plain\r\n\r\nESP32 IoT Server' writer.write(response) await writer.drain() await writer.wait_closed() async def start_server(): # 创建异步TCP服务器,监听所有网卡的80端口 server = await asyncio.start_server(handle_client, '0.0.0.0', 80) async with server: await server.serve_forever() async def auto_pump(): while True: pump_state = get_pump_state() if pump_state == 1: pause_duration = get_pause_duration() print(f"Pompa spenta per {pause_duration} minuti") change_pump_state() await asyncio.sleep(pause_duration * 60) else: pump_duration = get_pump_duration() print(f"Pompa accesa per {pump_duration} minuti") change_pump_state() await asyncio.sleep(pump_duration * 60) async def main(): # 创建两个异步任务并并发执行 pump_task = asyncio.create_task(auto_pump()) server_task = asyncio.create_task(start_server()) await asyncio.gather(server_task, pump_task) asyncio.run(main())
关键修改点
- 替换同步socket为异步服务器API:使用
asyncio.start_server()创建异步服务器,该API不会阻塞事件循环,事件循环可以在等待客户端连接的同时调度auto_pump()任务。 - 异步IO处理客户端请求:改用
StreamReader和StreamWriter替代同步socket对象,await reader.read()和await writer.drain()都是异步操作,不会阻塞事件循环。 - 移除阻塞式调用:彻底删除同步的
usocket.accept()和client_socket.recv(),确保所有IO操作都通过uasyncio的异步接口完成。
内容的提问来源于stack exchange,提问作者Francesco Chiodo
相关产品推荐
相关产品推荐

