ESP32 In MicroPython: Asyncio
Written by Harry Fairhead & Mike James   
Monday, 09 September 2024
Article Index
ESP32 In MicroPython: Asyncio
Awaiting Sleep
Sequential and Concurrent
Shared Variables and Locks
Using uasyncio

Asyncio is the prefered way to implement I/O servers and clients in Python. This extract is from Programming the ESP32 in MicroPython and shows you how asyncio works.

Programming the ESP32in MicroPython

By Harry Fairhead & Mike James

esppython360

Buy from Amazon.

Contents

       Preface

  1. The ESP32 – Before We Begin
  2. Getting Started
  3. Getting Started With The GPIO 
  4. Simple Output
  5. Some Electronics
  6. Simple Input
  7. Advanced Input – Interrupts
  8. Pulse Width Modulation
       Extract:
    PWM And The Duty Cycle
  9. Controlling Motors And Servos
  10. Getting Started With The SPI Bus
  11. Using Analog Sensors
       Extract:
    Analog Input
  12. Using The I2C Bus
       Extract
    : I2C, HTU21D And Slow Reading 
  13. One-Wire Protocols
  14. The Serial Port
  15. Using WiFi
     Extract:
    WiFi 
  16. Sockets
     Extract:
    Client Sockets
     Extract:
    SSL Client Sockets
  17. Asyncio And Servers
     Extract:
    Asyncio ***NEW!
  18. Direct To The Hardware
     Extract:
    Using Hardware Registers 

<ASIN:187196282X>

Asyncio And Servers

MicroPython has a basic implementation of the Python asyncio module. The asyncio module is very useful in providing asynchronous programming in Python and you might think that it would be exceptionally useful in low-level hardware applications as an alternative to polling or event driven approaches. In fact it isn’t quite as useful as you might expect as there are few existing asynchronous subsystems. In full Python we have a complete filing system and network connections and this means that asynchronous programming can improve efficiency by putting the processor to work while waiting for I/O operations to complete. In general the only I/O operations that are lengthy enough to involve waiting for sufficient time to get other tasks completed are WiFi related and this makes WiFi an ideal use case for the asyncio module.

In this chapter we first look at the basic idea of asynchronous programming as implemented by uasyncio, the MicroPython version of asyncio, and then at how to use it to implement a server asynchronously. If you want to know more about asynchronous programming in general and asyncio in particular see Programmer’s Python: Async, ISBN:9781871962765.

Coroutines and Tasks

Asyncio is all about sharing a single thread of execution between different tasks. A key ability of a task is that it can be suspended and resumed and this is what a coroutine is all about. In a single-threaded environment the thread has to save its current state, start or resume work on another function and restore the state when it returns to the previous function. A function that can be suspended and restarted in this way is generally called a “coroutine”.

A modern MicroPython coroutine is created using the async keyword:

async def myCo():
    print("Hello Coroutine World")
    return 42

When you call myCo it doesn’t execute its code, instead it returns a coroutine object which can execute the code. The only problem is that a coroutine isn’t a runnable. You have to use features provided by uasyncio to run a coroutine. The simplest is the uasyncio.run method which creates and manages the task loop without you having to know anything about it:

import uasyncio
async def myCo():
    print("Hello Coroutine World")
    return 42
myCoObject=myCo()
result= uasyncio.run(myCoObject)
print(result)

This runs the coroutine object and displays:

Hello Coroutine World
42

Instead of passing the coroutine object, the uasyncio.run call is usually written as a single action:

result= uasyncio.run(myCo())

It is also important to realize that uasyncio.run runs myCo at once and the thread doesn’t return until myCo is completed. While running myCo a task loop is started and if the thread is freed it starts running any tasks queued before returning to myCo. In this sense the call to uasyncio.run is where the asynchronous part of your program starts and you can think of it as starting the asynchronous main program.

Await

As it stands our coroutine might as well be a standard function as it doesn’t suspend and resume its operation. To suspend a coroutine you have to use the await keyword to pause the coroutine while an awaitable completes.

An await suspends the awaiting program and this means it can only be used within a coroutine, i.e. the only code in Python that can be suspended and resumed. Once you have a coroutine running you can use await within it and within any coroutines it awaits.

What this means is that you have to use uasyncio.run to get a first coroutine running, but after this you can use await to run other coroutines as Tasks.

Most asyncio programs are organized so that there is a single uasyncio.run instruction at the top level of the program and this starts a coroutine, often called main, which then runs the rest of the asynchronous program by awaiting other coroutines. That is, a typical uasyncio program is:

async def main():
	call other coroutines using await
uasyncio.run(main())

The call to uasyncio.run sets up the event loop as well as starting main running. You can call ordinary, i.e. non-coroutine, functions from within coroutines, but these cannot use await. Only a coroutine can use await, for example:

import uasyncio
async def test1(msg):
    print(msg)
async def main():
    await test1("Hello Coroutine World")
uasyncio.run(main())

Notice that even though main now awaits the test1 coroutine there is no new behavior. The program would work in exactly the same way with functions replacing coroutines. The reason is that none of our coroutines actually release the main thread, they simply keep running.

There are two distinct things that can occur when you await another coroutine. Some coroutines hold onto the main thread and continue to execute instructions until they finish – they are essentially synchronous coroutines. Some release the main thread while they wait for some operation to complete and only these are truly asynchronous coroutines. At the moment we only know about synchronous coroutines.



Last Updated ( Tuesday, 10 September 2024 )