1

Im trying to get a sensor value from Flask to auto update in a javascript gauge. The sensor input is named "a1" in Flask python. I got it to work in a regular html code like this:

<div id="main">
  <h4>Sensor 1: <span class="bold white">{{a1}}</span> bar </h4>

and to auto update it every second with this:

$(document).ready(function(){
setInterval(function(){
      $("#main").load(window.location.href + " #main" );
}, 1000);
});

and the value even shows up in the js gauge:

<div id="PT1" class="gauge-container pt">
    <span class="label">Pressure Transmitter 1</span>
    <span class="label2">0 - 400 bar</span>
  </div>

JS:

var PT1 = Gauge(
document.getElementById("PT1"), {
  max: 400,
  dialStartAngle: 135,
  dialEndAngle: 45,
  label: function(value) {
    return Math.round(value * 100) / 100;
  }
}
);

(function loop() {
var value1 = {{a1}}

PT1.setValueAnimated(value1, 1);
setTimeout(loop, 1000);
})();

My problem is that the gauge value dont auto update, it only shows the correct value when I refresh the page, and stays unchanged until I refresh again. (while the html code keeps updating every second)

Is this possible to solve?
Thanks

Vincent

Vincent
  • 23
  • 5

3 Answers3

1

Here's my final working example code.

Python:

from flask import Flask,render_template, jsonify
from random import random

app = Flask(__name__)

@app.route('/read_sensor')
def read_sensor():
    data = {'a1': (random()*150)}
    return jsonify(data)

@app.route('/')
def index():

    return render_template("index.html")

if __name__ == "__main__":
    app.run(host='0.0.0.0', port=80, debug=True)

HTML

<body>
    <div id="PT1" class="gauge-container">
        <span class="label">DEFAULT</span>
    </div>
</div>
  <script type='text/javascript'>

    var PT1 = new Gauge(
    document.getElementById("PT1"), {
      max: 400,
      dialStartAngle: 135,
      dialEndAngle: 45,
      label: function(value) {
        return Math.round(value * 100) / 100;
      }
    }
    );

    function callme(){
    //This promise will resolve when the network call succeeds
    //Feel free to make a REST fetch using promises and assign it to networkPromise
    var networkPromise = fetch('/read_sensor')
      .then(response => response.json())
      .then(data => {
        console.log(data);
        PT1.setValueAnimated(data['a1'], 1);
      });;


    //This promise will resolve when 2 seconds have passed
    var timeOutPromise = new Promise(function(resolve, reject) {
      // 2 Second delay
      setTimeout(resolve, 2000, 'Timeout Done');
    });

    Promise.all(
    [networkPromise, timeOutPromise]).then(function(values) {
      console.log("Atleast 2 secs + TTL (Network/server)");
      //Repeat
      callme();
    });
    }
    callme();
  </script>
</body>

Thanks!

Vincent
  • 23
  • 5
0

You need to write some Javascript to query the endpoint periodically. I used the approach in this answer which makes a request every two seconds, without making a second call when a request is under way.

You'll need to build a Flask route which returns only JSON with the sensor data. I mocked this with random numbers:

import random

@app.route('/read_sensor')
def read_sensor():
    # return the actual sensor data here:
    return {'a1': random.choice(range(1,400))}

Then use the following JS which makes a request every 2000ms. I had to tweak your example slightly, changing the div to a canvas to get guage.js to work properly:

<html>

<body>
   <div class="chart">
    <canvas id="PT1"></canvas>
  </div>

<script src="https://cdnjs.cloudflare.com/ajax/libs/gauge.js/1.3.7/gauge.min.js" integrity="sha512-J0d1VfdfTSDoDPEsahCtf2nC+groXdWkuQFyJjS+s3CpKj63X9Hf3pMEJtjIJt/ODh0QwTRx2/OioL+9fMoqSA==" crossorigin="anonymous"></script>
  <script type='text/javascript'>

    //
    // Guage init code
    //

    var PT1 = new Gauge(
    document.getElementById("PT1"), {
      max: 400,
      dialStartAngle: 135,
      dialEndAngle: 45,
      label: function(value) {
        return Math.round(value * 100) / 100;
      }
    }
    );

    //
    // End guage init code
    //

    function callme(){
    //This promise will resolve when the network call succeeds
    //Feel free to make a REST fetch using promises and assign it to networkPromise
    var networkPromise = fetch('/read_sensor')
      .then(response => response.json())
      .then(data => {
        console.log(data);

        //
        // Set guage code
        //

        PT1.set(data['a1']);

        //
        // End set guage code
        //

      });;


    //This promise will resolve when 2 seconds have passed
    var timeOutPromise = new Promise(function(resolve, reject) {
      // 2 Second delay
      setTimeout(resolve, 2000, 'Timeout Done');
    });

    Promise.all(
    [networkPromise, timeOutPromise]).then(function(values) {
      console.log("Atleast 2 secs + TTL (Network/server)");
      //Repeat
      callme();
    });
    }
    callme();
  </script>
</body>
</html>

Here's what this renders like:

guage gif

v25
  • 7,096
  • 2
  • 20
  • 36
0

Thanks for your reply and example. I just cant get your example to work with my gauge... Can you please show an example of the rest of the flask route? Like the part with "return / render html"

Here is a simplified copy of my old Flask: (that works, but not updating)

@app.route('/')
def index():
a1 = sensors()

return render_template("index.html", a1=a1)

def sensors():
a1 = "%0.2f" % (287.8 + random())
return a1

if __name__ == "__main__":
app.run(host='0.0.0.0', port=80, debug=True)

Isnt there an easy way to just get the "a1" to be updated inside the JS for the gauge?

(function loop() {
 var value1 = {{a1}}

PT1.setValueAnimated(value1, 1);
setTimeout(loop, 1000);
})();

If I replace {{a1}} with "Math.random() * 100," then its updating every second.

Full example of the gauges here: https://codepen.io/ernestoulloa/pen/abmGYyY (I am using gauge1)

Vincent
  • 23
  • 5
  • I hadn't realised you were using `svg-guage`, which is a different library. However the update code remains the same. I've edited my code with some comments, you'd just need to swtich the 'guage init code' with the code which works for your library, as in the codepen (as well as the HTML div to make the guage on the page). Then switch the 'guage set code' with something like: `guage1.setValueAnimated(data['a1'], 1);` – v25 Mar 04 '21 at 01:20
  • As for the render_template line, there's no need to pass arguments- something like `render_template('index.html')` will do, as the fetch call makes a separate request to the `read_sensor` endpoint in my example. Therefor you don't actually have to pass the `a1` value to the template itself, because the Fetch request gets that data in JSON. – v25 Mar 04 '21 at 01:21
  • Hello again :) Thanks for your reply. I tried again with your code, but got the error: "typeerror 'dict' object is not callable" And I found out that I had to covert the return line to jsonify. And then it finally worked! I will post the final code below. – Vincent Mar 04 '21 at 18:47
  • That's good. Having to convert this return line to `jsonify` could indicate an older version of Flask, as later versions *should* automatically jsonify returned dictionaries (not lists or other datastructures though). Either way, glad you go this working :) – v25 Mar 05 '21 at 09:07