我正在制作一件艺术品。它由多个树莓派 picos 组成。用 micropython 编码。
有一个基于 RPi pico 的远程控制,其作用类似于网络服务器,提供电位计的状态。
这件艺术品上面还有 3 个 pico,可以打开插座并从遥控器获取锅的状态。每个 pico 都配有电机和级联 WS2812 LED,以结合颜色和运动。
可能还有其他方法来解决与 3 个客户沟通的一般任务。蓝牙有点太复杂了……我乐于接受想法。或者如果有人可以推荐一本书来帮助我学习这些东西。
问题是,在使用 asyncio 时,s.recv 代码会阻止 LED 移动一秒钟,而我希望 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())
这是另一个 picoW 上的发送代码
# 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')
刚刚测试过,确实可以与
asyncio.open_connection
& asyncio.start_server
配合使用。
以下是基于
asyncio
文档页面提供的示例。参考它可以更好地理解这两个异步函数(或协程)的用法。
服务器(桌面、Python 3.12 Win 64x):
"""
Handles concurrent requests asynchronously.
INTENTIONALLY delays response by 5 seconds to simulate receive delay you suffered at `socket.recv()`.
"""
import asyncio
from datetime import datetime
async def callback(
reader: asyncio.StreamReader,
writer: asyncio.StreamWriter,
):
addr = writer.get_extra_info('peername')
print(f"Conenction from {addr!r}")
# read from client
some_request_data = await reader.read(1024)
# intentionally delay response for 5 seconds
await asyncio.sleep(5)
# send to client
writer.write("SomeData".encode())
await writer.drain()
# wait for connection to close
writer.close()
await writer.wait_closed()
print(f"Disconnected {addr!r}")
async def main():
server = await asyncio.start_server(
callback, "192.168.0.56", "9001"
)
async with server:
await server.serve_forever()
asyncio.run(main())
客户端(RPi Pico WH):
"""
Keeps blinking every 2 seconds, while 3 tasks connects to server concurrently & asynchronously, to mimic multiple Pico-s connecting the server.
"""
import asyncio
import time
import network
from machine import Pin
from secret import SSID, PSWD
async def connect_wlan():
sta_if = network.WLAN(network.STA_IF)
sta_if.active(True)
sta_if.connect(SSID, PSWD)
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(task_id: int):
"""Periodic network task"""
while True:
reader, writer = await asyncio.open_connection("192.168.0.56", 9001)
log(f"[Net {task_id}] Connected!")
writer.write("some request data".encode())
try:
await writer.drain()
log(f"[Net {task_id}] Request Sent!")
data = await reader.read(1024)
log(f"[Net {task_id}] Received Data!")
finally:
writer.close()
await writer.wait_closed()
log(f"[Net] Disconnected!")
await asyncio.sleep(2)
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()
blinker_task = asyncio.create_task(blinker())
network_tasks = [asyncio.create_task(get_data(idx)) for idx in range(3)]
try:
await asyncio.gather(blinker_task, *network_tasks)
finally:
sta_if.active(False)
asyncio.run(main())
从下面的 gif 和输出 - 请注意客户端的 Net 0, 1, 2 任务如何同时维护 3 个连接(至少在高级概念中),并且由服务器持有,延迟每个响应 5 秒。
同时,LED任务会毫无问题地闪烁。
客户端输出:
MPY: soft reboot
1714092617 [LED] LED off
1714092617 [Net 0] Connected!
1714092617 [Net 1] Connected!
1714092617 [Net 2] Connected!
1714092617 [Net 0] Request Sent!
1714092617 [Net 1] Request Sent!
1714092617 [Net 2] Request Sent!
1714092618 [LED] LED ON
1714092619 [LED] LED off
1714092620 [LED] LED ON
1714092621 [LED] LED off
1714092622 [LED] LED ON
1714092623 [LED] LED off
1714092624 [LED] LED ON
1714092625 [LED] LED off
1714092625 [Net 1] Received Data!
1714092625 [Net] Disconnected!
1714092626 [LED] LED ON
1714092626 [Net 2] Received Data!
1714092626 [Net] Disconnected!
1714092627 [LED] LED off
1714092627 [Net 1] Connected!
1714092627 [Net 1] Request Sent!
1714092628 [Net 0] Received Data!
1714092628 [Net] Disconnected!
1714092628 [LED] LED ON
1714092628 [Net 2] Connected!
1714092628 [Net 2] Request Sent!
1714092629 [LED] LED off
1714092630 [Net 0] Connected!
1714092630 [Net 0] Request Sent!
1714092630 [LED] LED ON
1714092631 [LED] LED off
1714092632 [LED] LED ON
1714092632 [Net 1] Received Data!
1714092632 [Net] Disconnected!
1714092633 [LED] LED off
1714092633 [Net 2] Received Data!
1714092633 [Net] Disconnected!
1714092634 [LED] LED ON
1714092634 [Net 1] Connected!
1714092634 [Net 1] Request Sent!
1714092635 [Net 0] Received Data!
1714092635 [Net] Disconnected!
1714092635 [LED] LED off
1714092635 [Net 2] Connected!
1714092635 [Net 2] Request Sent!
1714092636 [LED] LED ON
1714092637 [Net 0] Connected!
1714092637 [Net 0] Request Sent!
1714092637 [LED] LED off
1714092638 [LED] LED ON
1714092639 [LED] LED off
...
从这一点来看,假设您知道异步、同步、可等待是什么。
如果没有,请参考这个 - 我知道这可能听起来很刺耳,但相信我,从长远来看,学习这将是有益的!
通常我们甚至可以使用
socket.send()
将 socket.recv()
asyncio.to_thread()
等同步操作移动到线程以使其异步,但 Micropython 的 asyncio
模块的 Subset中缺少该接口。
但不用担心,
socket
只是 CPython(我们看到的通常的 python)和 Micropython 中的低级网络接口。还有更高级别的替代方案,例如 asyncio.streams
- Python 也正式推荐。
要找出你的代码的问题,基本上你写的是这样的:
async def some_func():
time.sleep(1)
本来应该是这样的:
async def some_func():
await asyncio.sleep(1)
注意到很大的区别了吗?一个有
await
,一个没有。
这就是在 python 中做某事所必需的
Asynchronous
。
没有
await
和 Awaitable
,它只是一个同步代码,其中运行函数的线程(无论是主线程还是非主线程)等待,直到该行执行完成。
def main():
sync_work_1()
...
在这里,
main()
会一直握住线,直到里面的所有东西都完成。
然而,对于使用
Awaitable
关键字的await
来说,更像是最终执行并返回结果的PROMISE。 (有趣的事实:出于这个原因,js 的等效项字面意思是 promise
。
import asyncio
...
async def main():
await async_work_1()
...
在这里,
main()
得到承诺,async_work_1()
将最终运行,在那之前所有main()
都无法做任何事情,因为结果还没有到来,所以main()
也会暂停运行。
相反,名为
EventLoop
的无限循环会调度 async_work_1()
的执行。由于 main()
现在无法进一步进行,因此 EventLoop
暂停 main()
并安排在 async_work_1()
完成后运行。在那之前,它会拉出下一个计划的任务来执行并运行/恢复它。
这足以(但不是 100% 准确)来理解
Awaitable
和其他之间的根本区别。
要了解这种结构如何在一切都决定停止运行的情况下工作 - 请参阅这个答案关于 IO 和 GIL。
所以 Tl;DR 你总是需要使用
Awaitable
和 await
关键字或 asyncio.create_task()
来实现并发(但不是并行)。
这意味着,如果您希望任何 IO 操作异步完成,您几乎每次都必须通过您正在使用的异步模块提供的接口来完成。 (这个案例
asyncio
)
这就是
asyncio.open_connection
和 asyncio.start_server
。