uasyncio reactor loop?

General discussions and questions abound development of code with MicroPython that is not hardware specific.
Target audience: MicroPython Users.
Post Reply
bitrat
Posts: 41
Joined: Fri Jul 26, 2019 4:13 am

uasyncio reactor loop?

Post by bitrat » Thu Oct 07, 2021 6:21 pm

Hi,

I've made some progress on this, but interested in comments from uasyncio afficianadoes. Am I doing it right? Here's what the program does:
  • Handle interrupts and post events to a shared event queue.
  • Read a socket and post received messages to a shared event queue.
  • Post 'ping' events to a shared event queue.
  • Post adc events to a shared event queue.
  • Consume the shared event queue and write events to the same socket in the main loop.
I'm assuming no issues with resource contention and that everything is in the single thread of control. Is that right?

Code: Select all

from machine import Pin, Timer, ADC
import uasyncio as asyncio
from ucollections import namedtuple
import micropython
import utime

import urandom
import network
import usocket
import uerrno

##============================================


def get_sock(ip, port):

    sock = usocket.socket(usocket.AF_INET, usocket.SOCK_DGRAM)
    sock.setsockopt(usocket.SOL_SOCKET, usocket.SO_REUSEADDR,1)
    sock.settimeout(1)
    sock.bind((ip, port))
    
    return sock


class Board(object):

    def __init__(self):

        self.wlan_ = network.WLAN(network.AP_IF)
        self.wlan_ .active(True) # must be true before using .config
        self.wlan_.config(essid='ZZZ', password='ZZZ')
        
        ifconf = self.wlan_.ifconfig()
        print('ifconfig = {}'.format(ifconf))
        self.ip_ = self.wlan_.ifconfig()[0]
        self.port_ = 20001

    def deactivate(self):
        self.wlan_.active(False)
        
    def host(self):
        return self.ip_

    def port(self):
        return self.port_
        


##============================================

Event = namedtuple("Event", ("events", "flag", "value"))

##============================================


last_value_change_ms = 0

def debounce():
    global last_value_change_ms 
    cur_time = utime.ticks_ms()
    diff = cur_time - last_value_change_ms
    bounce = diff < 500
    last_value_change_ms  = utime.ticks_ms()
    return bounce


def add_button_event(event):
    
    event.events.insert(0, 'B:{}'.format(event.value))
    event.flag.set()
       


def get_button_handler(events, flag):
    
    def handler(pin):

        if debounce():
            return
        
        event = Event(events, flag, pin)
        micropython.schedule(add_button_event, event)
    return handler

# ==============================================================


board = Board()
sock = get_sock(board.host(), board.port())
sock.settimeout(0)

async def get_messages(events, flag):
    global sock
    
    BUFSZ  = 1024
    
    sock.settimeout(0)
    while True:
        try:
            await asyncio.sleep_ms(250) 
            message, address = sock.recvfrom(BUFSZ)

            events.insert(0, message)
            events.insert(0, address)

            flag.set()

            
        except OSError as e:
            if e.args[0] not in [uerrno.EINPROGRESS, uerrno.ETIMEDOUT, uerrno.EAGAIN]:
                raise
    
# ================================================================

count = 0
async def ping(events, flag):
    global count
    
    while True:    

        await asyncio.sleep(10) 

        count += 1

        message = 'PING:{}'.format(count)
        events.insert(0, message)
        flag.set()



# ===============================================================



pot = ADC(Pin(34))
pot.atten(ADC.ATTN_11DB)

pot_prev = 0
pot_curr = 0

def add_adc_event(event):
    global pot
    global pot_prev
    global pot_curr
    
    pot_curr = pot.read()//100
    if abs(pot_curr - pot_prev) > 1:
        pot_prev = pot_curr
        event.events.insert(0, 'POT:{}'.format(pot_curr))
        event.flag.set()
       


def get_adc_handler(events, flag):
    
    def handler(timer):

        event = Event(events, flag, timer.value())
        micropython.schedule(add_adc_event, event)
    return handler




# =================================================================



def add_ping_event(event):
    global count

    event.events.insert(0, 'ZZZZ:{}'.format(count))
    event.flag.set()
       


def get_ping_handler(events, flag):
    
    def handler(timer):

        event = Event(events, flag, timer.value())
        micropython.schedule(add_ping_event, event)
    return handler




# =================================================================









async def reactor():
    global sock
    
    pins = [14, 15, 27]
    
    events = []
    
    flag = asyncio.ThreadSafeFlag()

    for p in pins:
        pin = Pin(p, Pin.IN, Pin.PULL_UP)
        pin.irq(trigger = Pin.IRQ_FALLING, handler = get_button_handler(events, flag))

        await asyncio.sleep(0)
        
        
            
    timer0 = Timer(0)
    timer0.init(mode=Timer.PERIODIC, period=500, callback=get_adc_handler(events, flag)) 

    timer1 = Timer(1)
    timer1.init(mode=Timer.PERIODIC, period=5000, callback=get_ping_handler(events, flag)) 
        
        
        
    asyncio.create_task(get_messages(events, flag))
    asyncio.create_task(ping(events, flag))
        
        
    address = None
    while True:
        
        if len(events) == 0:
            await flag.wait()

        while len(events):
            event = events.pop()
            print(event)
            
            if type(event) == tuple:
                address = event
            elif address is not None:
                sock.sendto(event, address)	

asyncio.run(reactor())   
An unrelated question... I'm fairly sure the interrupt handler for edges on pins used to take a pin id (an int in my case) as the argument, but apparently it now takes the pin object, 'Pin(10)', or whatever... I've searched in vain for a way to get this id from the Pin object. Do I need to parse the string rep??

Post Reply