-
Notifications
You must be signed in to change notification settings - Fork 2
Using Async
Let's start with an example of using Thimble to put an API interface on the popular DHT22 temperature and humidity sensor. The code is shown below.
from machine import Pin
from dht import DHT22
from thimble import Thimble
import uasyncio
dht22 = DHT22(Pin(14))
temp_c = 0
temp_f = 32
humidity = 0
async def read_dht():
global temp_c
global temp_f
global humidity
while (True):
dht22.measure()
temp_c = dht22.temperature()
temp_f = 1.8 * temp_c + 32
humidity = dht22.humidity()
print(f'Temp: {temp_c}C, {temp_f}F Humidity: {humidity}%')
await uasyncio.sleep(5)
api = Thimble()
@api.route('/temperature/celsius')
def get_temp_c(req):
return round(temp_c)
@api.route('/temperature/fahrenheit')
def get_temp_f(req):
return round(temp_f)
@api.route('/humidity')
def get_humidity(req):
return round(humidity)
loop = uasyncio.get_event_loop()
api.run_async(loop=loop)
uasyncio.create_task(read_dht())
loop.run_forever()
If you've never used asyncio before, it can be quite daunting. You can think of it like this: Imagine it's time to renew your driver's license. You go to the DMV and naturally, there's only one person working that day. There's a sign that says, "Take a number," so you do. Then you sit and wait for your number to be called.
This is how asyncio works. There's a loop defined by the statement loop = uasyncio.get_event_loop()
This is analogous to the one employee working at the DMV that day. The loop can only do one thing at a time, so it will tell you to take a number and have a seat and wait your turn.
When your number is called, you get the full attention of the employee. Maybe you'll get a form to fill out and then go back to the end of the line to wait some more. Then it's off to take an eye test and more waiting. It's tedious, but in the end, everyone gets their driver's license renewed.
Asynchronous functions work the same way. They all compete for the system's attention, but if everyone cooperates, all the functions get their driver's licenses.
If you look at the example code, you'll see four functions defined. Three of them are for routes and they are regular old functions. One of them, read_dht()
is defined as asynchronous using the async
keyword. But it's not the only asynchronous function. There's also api.run_async(loop=loop)
and if the name doesn't tip you off, the fact that the loop
variable is passed should be a strong hint that it's also asynchronous.
With this setup, the Thimble functions that run the api are in the loop along with read_dht
. This let's them appear to run at the same time. This is important, because the read_dht()
function waits five seconds between each update of temperature and humidity readings. But, because of the line await uasyncio.sleep(5)
it lets other functions in the loop run while it waits.
So why aren't the routes defined with the async keyword? It's because they don't do much and therefore they run very quickly.
By constructing the program this way, with the longer running tasks like reading sensor values and responding to HTTP requests running asynchronously, there's a better chance that no one making a call to the API will have to wait very long to get the data they want. Imagine if the read_dht()
function used a standard sleep. There could be up to a five second delay before an API request could be serviced.
You can define some or all of the routes with the async
keyword and Thimble will handle them asynchronously. So you could move the code from read_dht()
directly into the temperature and humidity routes. You won't tie up the loop, but you will make the client wait.
Take a look at the example code again. By making the routes do nothing more than grab a value, round it to the nearest whole number before passing it to the client, we're ensuring a fast delivery. Temperature and humidity readings are still taken every five seconds, but nobody has to wait for the data.