MqttSensor with WebServer (Solution)

WebMqttSensor.py
##################################################################################
import network                        # mqtt needs network connection            #
import framebuf                       # we also connect a display                #
from umqtt.simple import MQTTClient   # MQTT library                             #
from SH1107_OLED import OLED          # our driver for the OLED display          #
from BME280  import BME280            # our library for the BME280 sensor (T,P)
from utils   import Config            # to read a configurattion file            #
from machine import Pin, I2C          # API for the I2C interfaces               #
import time                           # standard package for timing              #
import json                           # we publish our data in json format       #
import uasyncio
from random import uniform, randrange
##################################################################################




##################################################################################
def wifi_connect(oled):
##################################################################################
    fb = oled.getFramebuffer()
    fb.fill(0)

    # get a "station interface" (opposed to access point interface) from the
    # netwrok library. This object has the magic methods to connect to the
    # wireless network and then to the LAN on the IP level.
    sta_if = network.WLAN( network.STA_IF )
    # If it is already active de-activate it first so that we always start
    # from the same base state.
    if  sta_if.active():
        sta_if.active(False)

    # Now try to connect to the WIFI

    sta_if.active( True )
    fb.text( "Connecting...", 0, 10 )
    oled.copyFramebuf()

    sta_if.connect( config.get("ssid"), config.get("password") )

    # Poll to know when the connection succeeds

    connected = sta_if.isconnected()

    # create some dotted lines on the display to
    # indicate the process which takes time.

    x = 0
    col = 1
    while not connected:
        fb.pixel( x, 0, col)
        fb.pixel( x, 1, col)
        fb.pixel( x+1, 0, col)
        fb.pixel( x+1, 1, col)
        oled.copyFramebuf()
        x += 4
        if x > 122:
            x=0
            col = (col+1)%2
        connected = sta_if.isconnected()
        time.sleep(0.1) # this is 100ms

    # If we arrive here we should be connected

    fb.text( "Success !", 0, 25 )
    oled.copyFramebuf()
    mac = sta_if.config('mac')
    # Show the IP address we got from the DHCP server

    ifparm = sta_if.ifconfig()

    iptxt = "IP:%s" % ifparm[0]
    fb.text( iptxt, 0,40 )
    oled.copyFramebuf()
    print ("IP : %s" % ifparm[0] )
    print ("SUB: %s" % ifparm[1] )
    print ("GAT: %s" % ifparm[2] )
    print ("DNS: %s" % ifparm[3] )
    # short break to read the display before we move on

    time.sleep(2)
##################################################################################



##################################################################################
def mqtt_connect(client_id, mqtt_server, oled):
##################################################################################

    # Now set up the connection to the MQTT Broker
    print("connecting to %s" % mqtt_server)
    fb = oled.getFramebuffer()
    client = MQTTClient(client_id, mqtt_server, keepalive=3600)
    if oled:
        fb.fill(0)
        fb.text( "mqtt connect...", 0,10 )
        oled.copyFramebuf()
    try:
        client.connect()
    except Exception as e:
        print(e)
        if oled:
            fb.text("...failed...", 0,20 )
            fb.text("check broker!", 0,30 )
            fb.text("cont. wo MQTT!", 0,45 )
            oled.copyFramebuf()
            time.sleep(5)
            fb.fill(0)
            return False
        else:
            return False
    if oled:
        fb.text("mqtt broker :", 0,30 )
        fb.text(mqtt_server, 0,40 )
        oled.copyFramebuf()
        time.sleep( 4 )

    return client
##################################################################################


##################################################################################
def webpage( request, temp, p0, hum ):
    html = f"""<!DOCTYPE html>
<html>
    <header>
    <link rel="stylesheet" href="mystyle.css">
    </header>
    <body>
    <h1>
    Sensor values
    </h1>
    <p>
    </p>
    <table class="sensor">
    <thead>
    </thead>
    <tbody>
    <tr><th>Temperature</th><th>:</th><td>{temp:.2f}</td><td class="left">C</td></tr>
    <tr><th>Humidity</th><th>:</th><td>{hum:.2f}</td><td class="left">%</td></tr>    
    <tr><th>Pressure</th><th>:</th><td>{p0:.2f}</td><td class="left">mb</td></tr>
    </body>
    </html>
"""
    return html
##################################################################################


##################################################################################
async def sendfile( fn, writer, ctype ):
    content = open( fn, 'r' ).read()
    writer.write( ('HTTP/1.0 200 OK\r\nContent-type: ' + ctype + '\r\n\r\n').encode() )
    writer.write(content.encode())
    await writer.drain()
    writer.close()
    await writer.wait_closed()
##################################################################################

##################################################################################
async def handle_request(reader, writer):
    # read the header from the reader
    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
    request = str(request_line, 'utf-8').split()[1]

    print("request: %s" % request )

    if request[-3:] == "css":
        await sendfile( request[1:], writer, "text/css" )
    elif request[-4:] == "html":
        response = webpage( request,temp,p0,hum )
        writer.write( 'HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n'.encode())
        writer.write(response.encode())
        await writer.drain()
        writer.close()
        await writer.wait_closed()
    else:
        writer.write( 'HTTP/1.0 200 OK\r\nContent-type: text/html\r\n\r\n'.encode())
        writer.write(("<html><body><h2>Unknown request : %s</h2></body></html>" % request).encode())
        await writer.drain()
        writer.close()
        await writer.wait_closed()
##################################################################################


##################################################################################
async def main():

    global temp, p0, hum

    # Initialise and configure the first I2C port of the ESP32
    # We put both sensors and the display on the same I2C bus.
    # The frequency is the I2C default frequency. You can try
    # and go higher. At some point things will stop working...
    i2c = I2C(0, sda=Pin(33), scl=Pin(32), freq=400000)

    # Instantiate the classes for the 2 sensors we have on board

    bme = BME280(i2c)

    # Setup our super I2C OLED display

    oled = OLED( i2c, 0x3c )
    oled.init()
    oled.setLandscape()
    fb = oled.getFramebuffer()

    # Setup the network connection via the built in WIFI

    wifi_connect( oled )

    # Connect to MQTT broker

    mqtt = mqtt_connect( config.get("client_id"), config.get("mqtt_server"), oled )


    # Now we start out endless loop.
    # We take measurements with the sensors and update the display
    # However the publishing to the MQTT Broker we do less often.
    # It is not super interesting to monitor these values which only
    # vary very slowly, with high frequency. (For practical purposes
    # the publishing frequency in this example is stil nuch too high.
    # But like this you see "something moving" in the MQTT network...)
    #
    # The absolute altitude can only be roughly estimated. For a precise
    # absolute altitude we need to calibrate the formulas, however this
    # requires the knowledge of the absolute altitude. But we can very
    # precisely measure altitude difference. Therefore we take the mean
    # of the first 10 altitude measurements as the reference hight and
    # from then on we display the difference to this height. Remember not
    # to move the Sensor during these 10 first measurements.

    alt_ref = 0
    i_measure = 0
    # Now start the web server
    server = uasyncio.start_server( handle_request, "0.0.0.0", 8888 )
    uasyncio.create_task( server )

    ltime = float(time.time_ns()) / 1000000000.0
    while True:
       # Get the latest greatest sensor values:
       # Both sensors deliver temperature values. We read both of them.

       [temp, press, hum] = bme.doMeasure()
       alt = bme.getAltitude()

       # convert pressure to see level
       # For this we use the altitude given as a constant to the programme.
       # This normalised altitude is used to compare pressure values
       # measured at different places with different (but known!) altitudes. 
       alt0 = config.get("alt0")
       p0 = press*((1-0.0065*alt0/(temp+0.0065*alt0+273.15))**-5.257)


       fb.fill(0)

       # Now check if we need to publish to MQTT by inspecting the time
       # since the last update.

       if mqtt:  # This if statement is useful if we want to run wo MQTT:
                 # We then set the mqtt variable to False in the code above.
           tnow = float(time.time_ns()) / 1000000000.0
           # make a progress bar indicating how much time is left to the
           # next publication
           tdiff = tnow - ltime
           bar_len = int(100.0 * float(tdiff) / float(config.get("mqtt_publish_iv"))+0.5)
           fb.hline(0,1,bar_len,1)
           fb.vline(100,0,3,1)
           fb.vline(0,0,3,1)
           if tdiff >= config.get("mqtt_publish_iv"):
               ltime = tnow
               try:
                   #row = randrange( 0,6 )
                   #col = randrange( 0,3 )
                   row = 0
                   col = 1
                   message = {
                       "temperature" : temp,
                       "pressure"    : p0,
                       "humidity"    : hum,
                       "row"         : row,
                       "col"         : col,
                       "name"        : "Christoph"}
                   mqtt.publish( config.get("mqtt_topic") + "/data", json.dumps( message ) )

                   # Always when we publish values we draw a small box on the
                   # bottom right of the display. 
                   fb.pixel( 122, 62, 1 )
                   fb.pixel( 122, 63, 1 )
                   fb.pixel( 123, 62, 1 )
                   fb.pixel( 123, 63, 1 )
               except Exception as e:
                   # something went wrong: may be the broker went down
                   print( str(e) )
                   fb.fill(0)
                   fb.text("Publish failed !", 0, 10 )
                   fb.text("Continue wo MQTT", 0, 20 )
                   fb.text("Will re-try...", 0, 40 )
                   oled.copyFramebuf()
                   mqtt = False
                   time.sleep(5)
                   fb.fill(0)
       else:
           # A small '-' in the top right shows the user that MQTT publishing is off
           fb.pixel( 120, 1, 1 )
           fb.pixel( 121, 1, 1 )
           fb.pixel( 122, 1, 1 )
           fb.pixel( 123, 1, 1 )

           # Here we try to connect to the mqtt server in case we are not connected already.
           mqtt = mqtt_connect( config.get("client_id"), config.get("mqtt_server"), oled  )

       # Display the values on the OLED 


       temp_str  = " T: %7.2fC"   % temp
       press_str = "P0: %7.2fmb"  % p0
       hum_str   = "rH: %7.2f%%"  % hum

       fb.text( temp_str,  0, 10 );
       fb.text( hum_str,   0, 25 );
       fb.text( press_str, 0, 40 );

       if i_measure < 10:
           alt_ref += alt
           i_measure += 1
       elif i_measure == 10:
           alt_ref = alt_ref / 10.0
           i_measure += 1
       else:
           alt_str   = "dA: %7.2fm"   % (alt - alt_ref)
           fb.text( alt_str,   0, 55 );

       oled.copyFramebuf()

       # Next update in a second.

       await uasyncio.sleep(1)
##################################################################################

####################### Here the main programme starts ###########################

temp = 0
p0 = 0
hum = 0
# 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_mqttSensor.json" )

uasyncio.run(main())