RPi Pico异步Socket通信阻塞WS2812 LED运行问题求助
Raspberry Pi Pico 艺术装置异步通信问题及解决方案
项目背景
- 基于多台Raspberry Pi Pico搭建艺术装置,使用MicroPython开发:
- 一台Pico作为远程控制器,以Web服务器形式提供电位器状态数据
- 另外3台Pico通过Socket连接控制器获取数据,每台配备电机和级联WS2812 LED,实现色彩与运动联动
- 希望了解其他多客户端通信方案(蓝牙实现复杂度高,暂不考虑),或相关技术学习书籍推荐
核心问题
使用asyncio时,s.recv()代码会导致LED彩虹动画卡顿1秒,需要保证LED动画持续流畅运行。尝试过线程方案但无效,设置s.setblocking(False)后卡顿时间反而更长。
原始客户端代码
import machine import network import time from secret import ssid, password import socket from neopixel import Neopixel import asyncio wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(ssid, password) while not wlan.isconnected() and wlan.status() >= 0: print("Waiting to connect:") time.sleep(1) # Should be connected and have an IP address wlan.status() # 3 == success wlan.ifconfig() print(wlan.ifconfig()) async def get_data(): while True: try: if wlan.isconnected: print("requesting data") ai = socket.getaddrinfo("192.168.0.24", 80) # Address of Web Server addr = ai[0][-1] # Create a socket and make a HTTP request s = socket.socket() # Open socket s.connect(addr) s.send(b"GET Data") # Send request ss = str(s.recv(512)) # Attempt to receive data s.close() # Close socket await asyncio.sleep_ms(1000) # wait else: print("no connection") await asyncio.sleep_ms(1000) except: s.close() print('no data, connection closed') await asyncio.sleep_ms(1000) async def rainbow(): # This function runs a walking rainbow across the LED strip NUM_LEDS = 74 LED_PIN = 13 pixels = Neopixel(NUM_LEDS, 0, LED_PIN, "GRB") hue_offset = 1028 brightness = 16 while True: for hue in range(0, 65535, 1000): # Set the hues for led in range(NUM_LEDS): color = pixels.colorHSV(hue +(led * hue_offset), 255, brightness) pixels.set_pixel(led, color) pixels.show() print(f"rgb={color}") await asyncio.sleep_ms(100) async def main(): tasks = [] tasks = [ asyncio.create_task(get_data()), asyncio.create_task(rainbow()), ] await asyncio.gather(*tasks) while True: asyncio.run(main())
原始服务器代码
# Webserver to send RGB data # Tony Goodhew 5 July 2022 import network import socket import time from machine import Pin, ADC from secret import ssid,password import random potA = machine.ADC(26) potB = machine.ADC(27) potC = machine.ADC(28) wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(ssid, password) # Wait for connect or fail max_wait = 10 while max_wait > 0: if wlan.status() < 0 or wlan.status() >= 3: break max_wait -= 1 print('waiting for connection...') time.sleep(1) # Handle connection error if wlan.status() != 3: raise RuntimeError('network connection failed') else: print('connected') status = wlan.ifconfig() print( 'ip = ' + status[0] ) # Open socket addr = socket.getaddrinfo('0.0.0.0', 80)[0][-1] s = socket.socket() s.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1) s.bind(addr) s.listen(1) print('listening on', addr) # Listen for connections while True: try: cl, addr = s.accept() print('client connected from', addr) request = cl.recv(1024) print(request) # Do not unpack request # We reply to any request the same way # Generate 3 values to send back print(f"Potentiometer A is: {potA.read_u16()}") print(f"Potentiometer B is: {potB.read_u16()}") print(f"Potentiometer C is: {potB.read_u16()}") r = int((potA.read_u16())) g = int((potB.read_u16())) b = int((potC.read_u16())) # Join to make a simple string with commas as separators rgb = str(r) + "," + str(g) + ","+str(b) response = rgb # This is what we send in reply cl.send(response) print("Sent:" + rgb) cl.close() except OSError as e: cl.close() print('connection closed')
优化后的代码
感谢jupiterbjy的解答,更新后的代码替换了阻塞式Socket调用为asyncio原生的流API,解决了卡顿问题。同时添加gc.collect()解决ENOMEM崩溃问题,但目前彩虹动画仍会在3-7秒后重置,需进一步排查。
优化后客户端代码
# The Jupiter client import asyncio import time import network from machine import Pin from secret import ssid, password from neopixel import Neopixel import gc NUM_LEDS = 102 LED_PIN = 17 pixels = Neopixel(NUM_LEDS, 0, LED_PIN, "GRB") hue_offset = 1028 brightness = 16 async def connect_wlan(): sta_if = network.WLAN(network.STA_IF) sta_if.active(True) sta_if.connect(ssid, password) while not sta_if.isconnected(): await asyncio.sleep(1) return sta_if def log(*args, **kwargs): print(f"{time.time():.1}", *args, **kwargs) async def get_data(): """Periodic network task""" while True: reader, writer = await asyncio.open_connection("192.168.0.24", 80) log(f"Connected!") try: writer.write("Big Ring".encode()) await writer.drain() log(f"Request Sent!") data = str(await reader.read(1024)) print(f"Received Data: {data}") await asyncio.sleep_ms(200) gc.collect() except: gc.collect() writer.close() await writer.wait_closed() log(f"Disconnected!") await asyncio.sleep(2) # This function runs a walking rainbow across the LED strip async def rainbow(): while True: for hue in range(0, 65535, 1000): # Set the hues for led in range(NUM_LEDS): color = pixels.colorHSV(hue +(led * hue_offset), 255, brightness) pixels.set_pixel(led, color) pixels.show() await asyncio.sleep_ms(100) async def blinker(): """Continuous LED blinker task""" # for Pico it's 25, for Pico W/WH it's "LED" led = Pin("LED", Pin.OUT) while True: led.toggle() log("[LED] LED", "ON" if led.value() else "off") await asyncio.sleep(1) async def main(): sta_if = await connect_wlan() tasks = [] tasks = [ asyncio.create_task(rainbow()), asyncio.create_task(blinker()), asyncio.create_task(get_data()), ] await asyncio.gather(*tasks) while True: asyncio.run(main())
优化后服务器代码
# The Jupiter server import asyncio import time import network import machine from machine import Pin from secret import ssid, password led = Pin("LED", Pin.OUT) potA = machine.ADC(26) potB = machine.ADC(27) potC = machine.ADC(28) async def connect_wlan(): #Connect to WLAN wlan = network.WLAN(network.STA_IF) wlan.active(True) wlan.connect(ssid, password) while wlan.isconnected() == False: print('Waiting for connection...') await asyncio.sleep(1) ip = wlan.ifconfig()[0] print(f'Connected on {ip}') return ip async def callback( reader: asyncio.StreamReader, writer: asyncio.StreamWriter, ): addr = writer.get_extra_info('peername') print(f"Connection from {addr!r}") # read from client client_data = await reader.read(1024) print(f"Client data: {client_data}") #assess potentiometers and assign to r, g, b r = int((potA.read_u16())) g = int((potB.read_u16())) b = int((potC.read_u16())) # Join to make a simple string with commas as separators rgb = str(r) + "," + str(g) + ","+str(b) # This is what we send in reply response = rgb print(f"sending {response}") # send to client writer.write(response.encode()) await writer.drain() # wait for connection to close writer.close() await writer.wait_closed() # print(f"Disconnected {addr!r}") async def main(): ip = await connect_wlan() server = await asyncio.start_server(callback, ip, 80) async with server: while True: led.toggle() await asyncio.sleep(1) asyncio.run(main())
额外需求
寻求适合3台Pico客户端的通信方案推荐(排除蓝牙),或MicroPython网络编程、异步编程相关的学习书籍推荐。
内容的提问来源于stack exchange,提问作者Space Cowboy
相关产品推荐
相关产品推荐

