Flask, socketio & canvas gauges
Intro
I’ve been running a home Pi based server that monitors electricity & internal/external temperatures, for a longtime it was dumb ie i could view the data when i would ssh in or on graphs via a remote service but apart from email alerts for long term high electricity usage it was to all intents lacking.
Then i found Canvas Gauges by Mikhus … perfect you could have radial or linear gauges with lots of customisation and *reactive*.
Or so i thought, i could not get it working, 4wks of trying and i was going to give up, i had gauges - they worked but did not change value automatically so i ended up setting an HTML refresh to update every so often just to give me a dashboard which looked useful but i wasnt happy.
Getting a gauge onto a page is simple, getting the gauge to be reactive well at the time that was beyond me, i searched for simple tutorials and all i found was chat apps … not what i wanted. With Python i usually figure out what i need to get there, websockets whoa and moving into javascript albeit simplistically was daunting and way more than i wanted for a few gauges. Eventually, whilst sat waiting in yet another airport for yet another flight i finally got the gauges reacting and updating in realtime.
Hopefully someone just starting with the Pi or any other flask based website will find this useful.
Following this guide and downloading the https://github.com/drakxtwo/gauges_flask_socketio repository will (should)get you a page with gauges that react like so;
I put this together from lots of sources, to be blunt it works, however it is probably not pretty, could be improved and may yet have some errors.
Flask socketio
Every tutorial I’ve found covers chat programs but that was not what i wanted and I did not see how to translate the chat app into what i needed. To be fair the flask-socketio docs are good and they can be found here; https://flask-socketio.readthedocs.io/en/latest/ but my problem was not the pythonic side but the client side, setting up flask for socketio is as simple as any other library eg sudo pip install flask-socketio
and sudo pip install eventlet
then its a relatively simple task of following the instructions on the website.
Below are parts of my sample code;
Setting up the socketio
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret!'
socketio = SocketIO(app, async_mode=async_mode)
thread = None
background thread
This essentially sends the random values which move the gauges, normally you’d replace the random function with whatever data you want to send.
def background_thread():
"""Example of how to send server generated events to clients."""
ext=fr=kt=bd=elec=0
while True:
socketio.sleep(2)
ext = randint(-10,30)
fr = randint(10,30)
kt = randint(10,30)
bd = randint(10,30)
elec = randint(0,8000)
socketio.emit('my_response',
{'data':'Values', 'elec': elec,'ext': ext,'fr': fr,'kt': kt,'bd': bd},
namespace='/carpi')
So you can see i’m sending four random temperature values and one electricity output, they are sent to the client via the socketio.emit line.
route & starting the app
mesg = 'we are here...'
@app.route('/')
def index():
speed = randint(0,133)
templateData={
'mesg' :mesg,
'speed' :speed
}
return render_template('trial3.html', async_mode=socketio.async_mode, **templateData)
@socketio.on('connect', namespace='/carpi')
def test_connect():
global thread
if thread is None:
thread = socketio.start_background_task(target=background_thread)
if __name__ == '__main__':
socketio.run(app, debug=True)
The above is the main part of the simple flask app as you can see we define the normal routes but render the template with an additional “switch” (async_mode), and start the background thread that calls the random number generator.
the client
The client is simply an HTML page which loads the relevant libraries for displaying the gauges, working with socketio and jquery. If you’ve downloaded my git files and run it you should see a page resembling;
You can see what mode socketio is running in, a list of values being transmitted and the gauges. The main crux of this is done in the header
<script src="static/gauge.min.js"></script>
<script type="text/javascript" src="static/jquery-3.2.1.min.js"></script>
<script type="text/javascript" src="static/socket.io-1.3.5.min.js"></script>
<script type="text/javascript" charset="utf-8">
var mycount = 66;
var myData = {};
var testc = 10;
$(document).ready(function() {
namespace = '/carpi';
var socket = io.connect(location.protocol + '//' + document.domain + ':' + location.port + namespace);
socket.on('connect', function() {
socket.emit('my_event', {data: 'I\'m connected!'});
});
socket.on('my_response', function(msg) {
$('#values').html(msg.count)
$('#log').append('<br>' + $('<div/>').text('Received #' + msg.data + ': ' + msg.count).html())
var elec = msg.elec
var external = msg.ext
var frontroom = msg.fr
var kitchen = msg.kt
var bedroom = msg.bd
extGauge.value = external
frontroomGauge.value = frontroom
kitchenGauge.value = kitchen
bedroomGauge.value = bedroom
elecGauge.value = elec
});
});
</script>
To set up the canvas gauges eg
we write the code in two sections the first simply calls the gauge
<canvas id="external-temp"> </canvas>
the second controls all the parameters that design the gauge.
<script>
var extGauge = new RadialGauge({
renderTo: 'external-temp',
width: 150,
height: 150,
units: "Deg",
minValue: -10,
maxValue: 30,
majorTicks: [
"-10",
"5",
"0",
"5",
"10",
"15",
"20",
"25",
"30"
],
minorTicks: 5,
strokeTicks: true,
highlights: [
{ from: -10, to: 0, color: 'rgba(25,0, 168, .8)'},
{ from: 0, to: 5, color: 'rgba(47, 15, 224, .7)'},
{ from: 5, to: 15, color: 'rgba(47, 15, 247, .6)'},
{ from: 15, to: 18, color: 'rgba(0, 204, 61, .8)'},
{ from: 18, to: 22, color: 'rgba(0, 204, 61, .8)'},
{ from: 22, to: 26, color: 'rgba(247, 151, 7, .5)'},
{ from: 26, to: 30, color: 'rgba(247, 31, 7, .5)'}
],
colorPlate: "#fff",
borderShadowWidth: 0,
borders: false,
needleType: "arrow",
needleWidth: 3,
needleCircleSize: 10,
needleCircleOuter: true,
needleCircleInner: false,
animationDuration: 1500,
animationRule: "linear"
})
extGauge.draw();
</script>
And that is pretty much it, start the app, open a web-browser and point at 127.0.0.1:5000 and voila…. You should have some reasonable looking gauges that update automatically, for me the next part is re-writing the “dragelec” app to use websockets etc and then changing the client to cope. Thats the easy part, now go tweak :)