An asyncio web-server (Exercise)
WebServer with asyncio
##################################################################################
import network # mqtt needs network connection #
from utils import Config # to read a configurattion file #
from utils import wifi_connect # a routine to connect to a wifi #
import time # standard package for timing #
import json # we publish our data in json format #
import uasyncio # to write the web server #
from random import uniform # in this example we displan random numbers#
##################################################################################
##################################################################################
# The following function simply creates a html webpage and puts it into a
# String variable. The webpage contains a small table for sensor values.
# In a later example we will insert the values read from the sensor. In this
# example we will just fill these values with random numbers. In a later example
# we re-use this code and fill the measured sensor values. This is why this
# functions takes sensor values as parameters. A very convenient way to put the
# values into the html string is offered in python by using so called "f-strings".
# It is a simple way to have placeholders with formatting instructions in the
# string. (A lot of info is on the web in case you do not know this)
# We should also include a simple css style-sheet so we learn how to serve files
# with out webserver. Ask if you do not know what stylesheets are.
def webpage( temp, pressure, humidity ):
##################################################################################
html = f"""<!DOCTYPE html>
<html lan='en'>
<head>
<title>A sensor simulation</title>
<link rel="stylesheet" href="mystyle.css">
</head>
<body>
<h1>Sensor values</h1>
<p></p>
<table class="sensor">
<thead>
</thead>
<tbody>
??? Put here the html string to create the table with the sesor values ???
</tbody>
</table>
</body>
</html>
"""
return html
##################################################################################
##################################################################################
# We define a co-routine to send a file. The following parameters are given to the
# function:
# fn : the filename of the file to send
# write : The stream to which the contents of the file needs to be written
# This object comes from the asyncio library
# ctype : The mime-type of the webpage (needs to be specified in the http header)
#
# This routine is a coroutine since it uses the writer object from the asyncio
# webserver to send out data to the webclient. The writer has some coroutines which
# yield until all the data is really written to the network and it yields a second
# time when the connection to the client is closed (this takes usually some time)
##################################################################################
async def sendfile( fn, writer, ctype ):
# read the file into the variable content
content = ???
# send the HTML header to the HTTP client
writer.write( ('HTTP/1.0 200 OK\r\nContent-type: ' + ctype + '\r\n\r\n').encode() )
# send the contents to the HTTP client (use again the writer.write command)
???
# Now wait until everything is written : use the apropriate coroutine of the writer
await writer.???
# Finally close the stream and wait until it is closed. again use the routines of
# the writer. Note that the function to wait for the closing to finish is a
# co-routine. So you have to await for it!
##################################################################################
# This co-routine is called when a HTTP request comes in. The arguments are
# asyncio streams which can be used for reading the request and sending back the
# reply (e.g. the requested web page)
##################################################################################
async def handle_request(reader, writer):
# read the header from the reader.readline() is a coroutine and as long
# as there is not request coming the EventLoop schedules other tasks.
# The first line of the header containe the request we are interested in.
request_line = await reader.readline()
# skip all other header lines (until the empty line)
stop = False
while not stop:
li = await reader.readline()
if li != b"\r\n":
pass
else:
stop=True
# Now take the second word of the request, which should contain the filename
# we want to send (in our example either a style sheet file (.css) or a html
# page)
request = str(request_line, 'utf-8').split()[1]
# for debugging:
print("request: %s" % request )
# Check for the extension of the file to send back. If it is the css file then
# send it back. If it is the html file then send back the webpage created with
# the function above. For now we take random numbers instead of real sensor
# values
# Note that this is a super simplified example: We do not even check the name
# of the files here.
if request[-3:] == "css":
await sendfile( request[1:], writer, "text/css" )
elif request[-4:] == "html":
#Now for this example generate some reasonable random numbers for the
# three sensor values (temperature pressure and humidity) and then
# generate the webpage with these values inserted. (use the routine
# above)
???
# Finally send the webpage to the client. Do not forget to first write the header.
# (See the sendfile routine)
???
# Now wait until all data has been sent, close the connection and wait for it
# to be closed
???
else:
# If we cannot serve the request we send back an eror message
# The concept is exactly the same as sending out sensor web page. Just the
# contents is the Unknonw request string and nothing else. But header
# and sending/closing connection is the same code as above.
???
writer.write(("<html><body><h2>Unknown request : %s</h2></body></html>" % request).encode())
???
##################################################################################
# The main co-routine which is setting up the network connection and then starts
# the webserver in three lines. Look up the commands in the asyncio documentation
# for micropython.
##################################################################################
async def main():
# connect to the network (the same as in the previous exercise)
ssid = config.get( "ssid" )
passwd = config.get( "password" )
wifi_connect( user, password )
# Now start the web server with uasyncio. It should listen on all network interfaces
# and on port 8888. You need two commands for this. To tell the server to listen on
# all interfaces you use "0.0.0.0" for the host IP. In our case this is not so important
# since we only have one Network interface in the controller, however using this
# method is convenient since we do not need to care which IP address we have received
# when starting up the network interface (you could also insert the IP address which
# you got from the DHCP request when you do "wifi_connect").
???
???
# In a more complex program here other things can be done. But at least from
# time to time you have to yield so that also the webserver gives some cpu-
# time.
while True:
await uasyncio.sleep(10)
####################### Here the main programme starts ###########################
# read the configuration file. We want "config" to be in the global scope
# therefore we create it here and not in the "main()" function.
config = Config( "config_WebServer.json" )
# Start the event loop and the main coroutine.
uasyncio.run(main())