MqttSensor with WebServer and auto-update (Solution)
WebMqttSensor_Ajax.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 = """<!DOCTYPE html>
<html>
<header>
<link rel="stylesheet" href="mystyle.css">
</header>
<body onload="setInterval( poll, 1000)">
<script>
function poll() {
var xmlhttp = new XMLHttpRequest();
xmlhttp.onreadystatechange = function() {
if (xmlhttp.readyState == XMLHttpRequest.DONE) {
if (xmlhttp.status == 200) {
data = JSON.parse( xmlhttp.responseText );
document.getElementById("temp").innerHTML = data.temperature;
document.getElementById("hum").innerHTML = data.humidity;
document.getElementById("press").innerHTML = data.pressure;
}
else if (xmlhttp.status == 400) {
alert('There was an error 400');
}
else {
alert('something else other than 200 was returned');
}
}
};
xmlhttp.open("GET", "/json", true);
xmlhttp.send();
return 1;
}
</script>
<h1>
Sensor values
</h1>
<p>
</p>
<table class="sensor">
<thead>
</thead>
<tbody>
<tr><th>Temperature</th><th>:</th><td id="temp">{temp:.2f}</td><td class="left">C</td></tr>
<tr><th>Humidity</th><th>:</th><td id="hum">{hum:.2f}</td><td class="left">%</td></tr>
<tr><th>Pressure</th><th>:</th><td id="press">{p0:.2f}</td><td class="left">mb</td></tr>
<div id="debug">
</div>
</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()
elif request[-4:] == "json":
doc = { 'temperature' : "%.1f" % temp,
'pressure' : "%6.1f" % p0,
'humidity' : "%.1f" % hum }
jsonstr = json.dumps(doc)
writer.write( 'HTTP/1.0 200 OK\r\nContent-type: application/json\r\n\r\n'.encode())
writer.write(jsonstr.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,4 )
message = {
"temperature" : temp,
"pressure" : p0,
"humidity" : hum,
"row" : row,
"col" : col }
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())