Socket 阻塞其他功能 RPi pico

问题描述 投票:0回答:1

我正在制作一件艺术品。它由多个树莓派 picos 组成。用 micropython 编码。

  1. 有一个基于 RPi pico 的远程控制,其作用类似于网络服务器,提供电位计的状态。

  2. 这件艺术品上面还有 3 个 pico,可以打开插座并从遥控器获取锅的状态。每个 pico 都配有电机和级联 WS2812 LED,以结合颜色和运动。

  3. 可能还有其他方法来解决与 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')
sockets python-asyncio micropython raspberry-pi-pico
1个回答
0
投票

刚刚测试过,确实可以与

asyncio.open_connection
&
asyncio.start_server
配合使用。


TL;博士

以下是基于

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

© www.soinside.com 2019 - 2024. All rights reserved.