major cleanup, added comments
This commit is contained in:
parent
743aa2b383
commit
d30d6096d1
|
@ -7,6 +7,7 @@ Some components are under a different license.
|
||||||
|
|
||||||
Files for a flask webapp to visualise data from database.
|
Files for a flask webapp to visualise data from database.
|
||||||
|
|
||||||
### scripts
|
### inenvmon_collector.py
|
||||||
|
|
||||||
Helper scripts.
|
A helper script to listen to mqtt topic and write relevant data into the database.
|
||||||
|
Also takes care of timestamping and reporting source status to web app.
|
||||||
|
|
|
@ -0,0 +1,80 @@
|
||||||
|
#!/usr/bin/pyhton -u
|
||||||
|
|
||||||
|
import sqlite3 as sql3db
|
||||||
|
import paho.mqtt.client as mqtt
|
||||||
|
import time
|
||||||
|
import datetime
|
||||||
|
import requests
|
||||||
|
|
||||||
|
MQTT_Broker = "127.0.0.1" # usually on the same machine, if not, adjust accordingly
|
||||||
|
MQTT_Port = 9883 # mqtt port for internal use, can be whatever, determined by the broker config
|
||||||
|
Keep_Alive_Interval = 60 # keep alive interval for broker connection
|
||||||
|
MQTT_Topic = "topic" # mqtt topic, set to the same value as the monitoring device
|
||||||
|
MQTT_Auth = {'username':"", 'password':""} # mqtt authentication, determined by the broker config
|
||||||
|
DB = "./inenvmon_data.db" # path to database file
|
||||||
|
DB_TABLE = "envdata0" # name of the database table (mqtt topic is fitting here)
|
||||||
|
API_SECRET = "secret" # set to same value as in the api
|
||||||
|
|
||||||
|
def write_db(sql): # function for writing to the database
|
||||||
|
dbconn = sql3db.connect(DB)
|
||||||
|
cursor = dbconn.cursor()
|
||||||
|
|
||||||
|
try:
|
||||||
|
cursor.execute(sql)
|
||||||
|
except sql3db.Error as e:
|
||||||
|
print(e)
|
||||||
|
return False
|
||||||
|
|
||||||
|
dbconn.commit()
|
||||||
|
dbconn.close()
|
||||||
|
return True
|
||||||
|
|
||||||
|
def send_heartbeat(): # function for heartbeat reporting, adjust according to server setup
|
||||||
|
return requests.post("http://localhost:80/api/heartbeat", data={'message':time.time(),'key': API_SECRET})
|
||||||
|
|
||||||
|
# on connection callback, used to subscirbe to the set up topic after connecting to the broker
|
||||||
|
def on_connect(client, userdata, flags, rc):
|
||||||
|
print("connected: " + str(rc))
|
||||||
|
mqttc.subscribe(MQTT_Topic, 0)
|
||||||
|
print("subscribed!")
|
||||||
|
|
||||||
|
# on message callback, executes every time a message is published to the topic
|
||||||
|
def on_message(mosq, obj, msg):
|
||||||
|
payload = msg.payload # save the message payload
|
||||||
|
try:
|
||||||
|
dec_msg = payload.decode("unicode_escape").split(",") # decode the payload and split at commas
|
||||||
|
# out of range value check, replaces erroneous values with no value
|
||||||
|
# included as a precaution, the sensors could malfunction and the communication between the devices is not always perfect
|
||||||
|
# the ranges are set to what can be reasonably expected in an interior environment, can be tweaked if desired
|
||||||
|
if float(dec_msg[0]) > 45 or float(dec_msg[0] < 0): # temperature
|
||||||
|
dec_msg[0] = "NULL"
|
||||||
|
if float(dec_msg[1]) < 0 or float(dec_msg[1] > 100): # relative humidity
|
||||||
|
dec_msg[1] = "NULL"
|
||||||
|
if int(dec_msg[2]) < 900 or int(dec_msg[2]) > 1100: # pressure
|
||||||
|
dec_msg[2] = "NULL"
|
||||||
|
if dec_msg[3] == "-1": # co2, value of -1 indicates a measurement error
|
||||||
|
dec_msg[3] = "NULL"
|
||||||
|
|
||||||
|
except Exception as ex:
|
||||||
|
print("Excp: %s" % ex) # print exceptions
|
||||||
|
|
||||||
|
f = '%Y-%m-%d %H:%M:%S' # timestamp format string
|
||||||
|
# create table
|
||||||
|
sql0 = "CREATE TABLE IF NOT EXISTS %s \
|
||||||
|
(\"timestamp\" datetime DEFAULT NULL, \
|
||||||
|
\"temp\" decimal(5 , 2) DEFAULT NULL, \
|
||||||
|
\"hum\" decimal(5 , 2) DEFAULT NULL, \
|
||||||
|
\"pres\" int(4) DEFAULT NULL, \
|
||||||
|
\"co2\" int(5) DEFAULT NULL)" % DB_TABLE
|
||||||
|
write_db(sql0)
|
||||||
|
# formulate sql string with values
|
||||||
|
sql = "INSERT INTO %s (timestamp, temp, hum, pres, co2) VALUES (\"%s\", %s, %s, %s, %s)" % (DB_TABLE, datetime.datetime.now().strftime(f), procdm[0], procdm[1], procdm[2], procdm[3])
|
||||||
|
write_db(sql) # execute the sql string
|
||||||
|
send_heartbeat() # send heartbeat
|
||||||
|
|
||||||
|
mqttc = mqtt.Client() # instantiate the client class
|
||||||
|
mqttc.on_message = on_message
|
||||||
|
mqttc.on_connect = on_connect # set up callbacks
|
||||||
|
mqttc.username_pw_set(MQTT_Auth['username'], MQTT_Auth['password']) # set mqtt credentials
|
||||||
|
mqttc.connect(MQTT_Broker, int(MQTT_Port), int(Keep_Alive_Interval)) # connect to the broker using provided settings
|
||||||
|
mqttc.loop_forever() # keep the connection running as long as the script is running
|
|
@ -1,6 +0,0 @@
|
||||||
# scripts
|
|
||||||
|
|
||||||
### inenvmon_collector.py
|
|
||||||
|
|
||||||
A helper script to listen to mqtt topic and write relevant data into the database.
|
|
||||||
Also takes care of timestamping and reporting source status to web app.
|
|
|
@ -1,70 +0,0 @@
|
||||||
#!/usr/bin/pyhton -u
|
|
||||||
|
|
||||||
import mysql.connector as mariadb
|
|
||||||
import paho.mqtt.client as mqtt
|
|
||||||
import time
|
|
||||||
import datetime
|
|
||||||
import requests
|
|
||||||
#import ssl
|
|
||||||
|
|
||||||
MQTT_Broker = "127.0.0.1"
|
|
||||||
MQTT_Port =
|
|
||||||
Keep_Alive_Interval = 60
|
|
||||||
MQTT_Topic = ""
|
|
||||||
MQTT_Auth = {'username':"", 'password':""}
|
|
||||||
DB = ""
|
|
||||||
DB_USER = ""
|
|
||||||
DB_PASSWORD = ""
|
|
||||||
API_SECRET = ""
|
|
||||||
|
|
||||||
|
|
||||||
def write_db(sql):
|
|
||||||
dbconn = mariadb.connect(user=DB_USER, password=DB_PASSWORD, database=DB)
|
|
||||||
cursor = dbconn.cursor()
|
|
||||||
|
|
||||||
try:
|
|
||||||
cursor.execute(sql)
|
|
||||||
except mariadb.Error as e:
|
|
||||||
print(e)
|
|
||||||
return False
|
|
||||||
|
|
||||||
dbconn.commit()
|
|
||||||
dbconn.close()
|
|
||||||
return True
|
|
||||||
|
|
||||||
def send_heartbeat():
|
|
||||||
return requests.post("http://localhost:80/api/heartbeat", data={'message':time.time(),'key': API_SECRET})
|
|
||||||
|
|
||||||
def on_connect(client, userdata, flags, rc):
|
|
||||||
print("connected: "+str(rc))
|
|
||||||
mqttc.subscribe(MQTT_Topic, 0)
|
|
||||||
print("subscribed!")
|
|
||||||
|
|
||||||
def on_message(mosq, obj, msg):
|
|
||||||
try:
|
|
||||||
procdm = msg.payload.decode("unicode_escape").split(",")
|
|
||||||
if procdm[3] == -1:
|
|
||||||
procdm[3] = None;
|
|
||||||
except Exception as ex:
|
|
||||||
print("Fug: %s" % ex)
|
|
||||||
|
|
||||||
f = '%Y-%m-%d %H:%M:%S'
|
|
||||||
|
|
||||||
sql = "INSERT INTO envdata (timestamp, temp, hum, pres, co2) VALUES (%s, %s, %s, %s, %s)" % (str("\"%s\"") % datetime.datetime.now().strftime(f), str("\"%s\"") % procdm[0], str("\"%s\"") % procdm[1], str("\"%s\"") % procdm[2], str("\"%s\"") % procdm[3])
|
|
||||||
write_db(sql)
|
|
||||||
send_heartbeat()
|
|
||||||
|
|
||||||
def on_subscribed(mosq, obj, mid, granted_qos):
|
|
||||||
pass
|
|
||||||
|
|
||||||
mqttc = mqtt.Client()
|
|
||||||
|
|
||||||
mqttc.on_message = on_message
|
|
||||||
mqttc.on_connect = on_connect
|
|
||||||
mqttc.on_subscribed = on_subscribed
|
|
||||||
|
|
||||||
#mqttc.tls_set(ca_certs="/etc/ssl/cert.pem", tls_version=ssl.PROTOCOL_TLSv1_2)
|
|
||||||
mqttc.username_pw_set(MQTT_Auth['username'], MQTT_Auth['password'])
|
|
||||||
mqttc.connect(MQTT_Broker, int(MQTT_Port), int(Keep_Alive_Interval))
|
|
||||||
|
|
||||||
mqttc.loop_forever()
|
|
|
@ -1,5 +1,5 @@
|
||||||
from flask import Flask, render_template, make_response, jsonify, request
|
from flask import Flask, render_template, make_response, jsonify, request
|
||||||
import mysql.connector as mariadb
|
import sqlite3 as sql3db
|
||||||
import datetime
|
import datetime
|
||||||
import time
|
import time
|
||||||
import io
|
import io
|
||||||
|
@ -7,27 +7,26 @@ import json
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
from collections import OrderedDict
|
from collections import OrderedDict
|
||||||
|
|
||||||
DB = ""
|
DB = "../inenvmon_data.db" # path to the database file as set up in inenvmon_collector.py
|
||||||
DB_USER = ""
|
DB_TABLE = "envdata0" # name of database table, as set up in inenvmon_collector.py
|
||||||
DB_PASSWORD = ""
|
API_SECRET = "secret" # api secret for the heartbeat method, set the same in inenvmon_collector.py
|
||||||
API_SECRET = ""
|
|
||||||
|
|
||||||
class Source():
|
class Source():
|
||||||
def __init__(self):
|
def __init__(self):
|
||||||
self.status = "unknown"
|
self.status = "unknown" #used to persistently store status
|
||||||
self.last_msg = 0.0
|
self.last_msg = 0.0
|
||||||
|
|
||||||
source = Source()
|
source = Source()
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
|
|
||||||
def query_db(sql):
|
def query_db(sql): #function for executing database queries
|
||||||
dbconn = mariadb.connect(user=DB_USER, password=DB_PASSWORD, database=DB)
|
dbconn = sql3db.connect(DB)
|
||||||
cursor = dbconn.cursor()
|
cursor = dbconn.cursor()
|
||||||
|
|
||||||
try:
|
try:
|
||||||
cursor.execute(sql)
|
cursor.execute(sql)
|
||||||
except mariadb.Error as e:
|
except sql3db.Error as e:
|
||||||
print(e)
|
print(e) # output errors
|
||||||
|
|
||||||
data = cursor.fetchall()
|
data = cursor.fetchall()
|
||||||
dbconn.close()
|
dbconn.close()
|
||||||
|
@ -36,63 +35,67 @@ def query_db(sql):
|
||||||
|
|
||||||
@app.route('/')
|
@app.route('/')
|
||||||
def index():
|
def index():
|
||||||
return render_template('index.html')
|
# no actual template rendering is going on, this is used for convenience since the api and web app are on the same domain
|
||||||
|
# the html is only a skeleton, all functionality is in the js
|
||||||
|
return render_template('index.html')
|
||||||
|
|
||||||
@app.route('/api/getData', methods = ['GET','POST'])
|
@app.route('/api/getdata', methods = ['GET','POST'])
|
||||||
def process_data():
|
def process_data():
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
if request.args.get('samples') is not None:
|
if request.args.get('samples') is not None:
|
||||||
try:
|
try:
|
||||||
samples = int(request.args.get('samples'))
|
samples = int(request.args.get('samples'))
|
||||||
if samples > 10800:
|
if samples > 10800: # limit the maximum amount of data points, working with values above this threshold was observed unstable
|
||||||
samples = 120
|
samples = 120
|
||||||
except Exception as excp:
|
except Exception as excp:
|
||||||
print(excp)
|
print(excp) # output errors
|
||||||
samples = 120
|
samples = 120
|
||||||
else:
|
else:
|
||||||
samples = 120
|
samples = 120 # silently default to 120 in case of any problems
|
||||||
|
# same as above but for POST requests this time
|
||||||
elif request.method == 'POST':
|
elif request.method == 'POST':
|
||||||
try:
|
try:
|
||||||
samples = int(request.form.get('samples'))
|
samples = int(request.form.get('samples'))
|
||||||
except Exception as excp:
|
except Exception as excp:
|
||||||
print(excp)
|
print(excp) # output errors
|
||||||
samples = 120
|
samples = 120
|
||||||
|
|
||||||
if samples > 10800:
|
if samples > 10800:
|
||||||
samples = 120
|
samples = 120
|
||||||
|
|
||||||
sql = "SELECT * FROM envdata ORDER BY timestamp DESC LIMIT %s" % samples
|
sql = "SELECT * FROM %s ORDER BY timestamp DESC LIMIT %s" % (DB_TABLE, samples) # sql selection string
|
||||||
dbdata = query_db(sql)
|
dbdata = query_db(sql) # store data returned from the db
|
||||||
|
|
||||||
f = '%Y-%m-%d %H:%M:%S'
|
f = '%Y-%m-%d %H:%M:%S' # string format
|
||||||
|
|
||||||
|
# initialize arrays
|
||||||
labels = []
|
labels = []
|
||||||
temps = []
|
temps = []
|
||||||
hums = []
|
hums = []
|
||||||
press = []
|
press = []
|
||||||
concs = []
|
concs = []
|
||||||
|
# split data returned from db into arrays
|
||||||
for i in range(len(dbdata)):
|
for i in range(len(dbdata)):
|
||||||
labels.append(dbdata[i][0])
|
labels.append(datetime.datetime.strptime(dbdata[i][0], f))
|
||||||
temps.append(dbdata[i][1])
|
temps.append(dbdata[i][1])
|
||||||
hums.append(dbdata[i][2])
|
hums.append(dbdata[i][2])
|
||||||
press.append(dbdata[i][3])
|
press.append(dbdata[i][3])
|
||||||
concs.append(dbdata[i][4])
|
concs.append(dbdata[i][4])
|
||||||
|
|
||||||
|
# pandas dataset processing
|
||||||
|
# detects gaps in the dataset timestamps and fills them with no values
|
||||||
|
# gaps of one datapoint are bridged with an average value
|
||||||
df = pd.DataFrame({'dt': labels, 'temp': temps, 'hum': hums, 'pres': press, 'conc': concs})
|
df = pd.DataFrame({'dt': labels, 'temp': temps, 'hum': hums, 'pres': press, 'conc': concs})
|
||||||
r = pd.date_range(start=df['dt'].min(), end=df['dt'].max(), freq='T')
|
r = pd.date_range(start=df['dt'].min(), end=df['dt'].max(), freq='T')
|
||||||
# df = df.drop_duplicates('dt').set_index('dt').reindex(r, method='pad', tolerance='90s').reset_index()
|
|
||||||
df = df.set_index('dt').reindex(r, method='nearest', tolerance='90s').reset_index()
|
df = df.set_index('dt').reindex(r, method='nearest', tolerance='90s').reset_index()
|
||||||
|
# pandas returns weird stuff, converted here to a numpy array
|
||||||
|
# numpy because that's what available without additional work
|
||||||
darry = df.to_numpy()
|
darry = df.to_numpy()
|
||||||
start = len(darry)
|
start = len(darry) # save the length for further slicing
|
||||||
print("darry %s" % str(len(darry)))
|
# array slicing and ordering
|
||||||
print("samples %s" % str(samples))
|
# because js graphing function expects the dataset with inverted timebase
|
||||||
print(start)
|
# different procedures for different scenarios
|
||||||
print(start-samples)
|
# in case of missing values
|
||||||
print(samples-start)
|
|
||||||
|
|
||||||
if len(darry) != samples:
|
if len(darry) != samples:
|
||||||
if samples > len(darry):
|
if samples > len(darry):
|
||||||
labels=darry[samples-start:start:1,0]
|
labels=darry[samples-start:start:1,0]
|
||||||
|
@ -106,12 +109,14 @@ def process_data():
|
||||||
hums=darry[start-samples:start:1,2]
|
hums=darry[start-samples:start:1,2]
|
||||||
press=darry[start-samples:start:1,3]
|
press=darry[start-samples:start:1,3]
|
||||||
concs=darry[start-samples:start:1,4]
|
concs=darry[start-samples:start:1,4]
|
||||||
|
# in case of a single value (when updating the plot)
|
||||||
elif len(darry) == 1:
|
elif len(darry) == 1:
|
||||||
labels=darry[0,0]
|
labels=darry[0,0]
|
||||||
temps=darry[0,1]
|
temps=darry[0,1]
|
||||||
hums=darry[0,2]
|
hums=darry[0,2]
|
||||||
press=darry[0,3]
|
press=darry[0,3]
|
||||||
concs=darry[0,4]
|
concs=darry[0,4]
|
||||||
|
# in case of uninterrupted dataset
|
||||||
else:
|
else:
|
||||||
labels=darry[0:start:1,0]
|
labels=darry[0:start:1,0]
|
||||||
temps=darry[0:start:1,1]
|
temps=darry[0:start:1,1]
|
||||||
|
@ -119,16 +124,18 @@ def process_data():
|
||||||
press=darry[0:start:1,3]
|
press=darry[0:start:1,3]
|
||||||
concs=darry[0:start:1,4]
|
concs=darry[0:start:1,4]
|
||||||
|
|
||||||
|
# order of the values is important therefore ordered dict
|
||||||
tempdict = OrderedDict()
|
tempdict = OrderedDict()
|
||||||
humdict = OrderedDict()
|
humdict = OrderedDict()
|
||||||
presdict = OrderedDict()
|
presdict = OrderedDict()
|
||||||
co2dict = OrderedDict()
|
co2dict = OrderedDict()
|
||||||
|
# fill the dict straight away with the single value
|
||||||
if len(darry) == 1:
|
if len(darry) == 1:
|
||||||
tempdict[labels.strftime(f)] = str(temps)
|
tempdict[labels.strftime(f)] = str(temps)
|
||||||
humdict[labels.strftime(f)] = str(hums)
|
humdict[labels.strftime(f)] = str(hums)
|
||||||
presdict[labels.strftime(f)] = str(press)
|
presdict[labels.strftime(f)] = str(press)
|
||||||
co2dict[labels.strftime(f)] = str(concs)
|
co2dict[labels.strftime(f)] = str(concs)
|
||||||
|
# iterate over the arrays and fill the dicts
|
||||||
else:
|
else:
|
||||||
for i in range(len(labels)):
|
for i in range(len(labels)):
|
||||||
tempdict[labels[i].strftime(f)] = str(temps[i])
|
tempdict[labels[i].strftime(f)] = str(temps[i])
|
||||||
|
@ -136,18 +143,20 @@ def process_data():
|
||||||
presdict[labels[i].strftime(f)] = str(press[i])
|
presdict[labels[i].strftime(f)] = str(press[i])
|
||||||
co2dict[labels[i].strftime(f)] = str(concs[i])
|
co2dict[labels[i].strftime(f)] = str(concs[i])
|
||||||
|
|
||||||
|
# check status
|
||||||
if (time.time() - source.last_msg) > 180:
|
if (time.time() - source.last_msg) > 180:
|
||||||
status = "dead"
|
status = "dead"
|
||||||
else:
|
else:
|
||||||
status = "alive"
|
status = "alive"
|
||||||
|
# final response dict
|
||||||
respdict = OrderedDict()
|
respdict = OrderedDict()
|
||||||
|
# ready data and corresponding key for json
|
||||||
datas = ["status", "temp", "hum", "pres", "co2"]
|
datas = ["status", "temp", "hum", "pres", "co2"]
|
||||||
dicts = [status, tempdict, humdict, presdict, co2dict]
|
dicts = [status, tempdict, humdict, presdict, co2dict]
|
||||||
|
# iterate over the data and fill the response dict
|
||||||
for i in range(len(datas)):
|
for i in range(len(datas)):
|
||||||
respdict[datas[i]] = dicts[i]
|
respdict[datas[i]] = dicts[i]
|
||||||
|
# formulate json response from the prepared dict
|
||||||
response = app.response_class(
|
response = app.response_class(
|
||||||
response=json.dumps(respdict),
|
response=json.dumps(respdict),
|
||||||
status=200,
|
status=200,
|
||||||
|
@ -156,31 +165,33 @@ def process_data():
|
||||||
return response
|
return response
|
||||||
|
|
||||||
@app.route('/api/heartbeat', methods = ['GET','POST'])
|
@app.route('/api/heartbeat', methods = ['GET','POST'])
|
||||||
def heartbeat():
|
def heartbeat(): # device status reporting
|
||||||
global source
|
global source
|
||||||
if request.method == 'POST':
|
if request.method == 'POST':
|
||||||
key = request.form.get('key')
|
key = request.form.get('key') # check the secret
|
||||||
if key == API_SECRET:
|
if key == API_SECRET:
|
||||||
if request.form.get('message') is not None:
|
if request.form.get('message') is not None:
|
||||||
source.last_msg = float(request.form.get('message'))
|
source.last_msg = float(request.form.get('message')) # save the status if timestamp present
|
||||||
else:
|
else:
|
||||||
source.last_msg = "nodata"
|
source.last_msg = "nodata"
|
||||||
source.status = "unknown"
|
source.status = "unknown" #unknown status if no timestamp
|
||||||
|
#respond with 200 ok
|
||||||
post_response = app.response_class(
|
post_response = app.response_class(
|
||||||
status=200)
|
status=200)
|
||||||
return post_response
|
return post_response
|
||||||
else:
|
else:
|
||||||
|
# respond with 401 unauthorized if secret is incorrect
|
||||||
err_response = app.response_class(
|
err_response = app.response_class(
|
||||||
status=401)
|
status=401)
|
||||||
return err_response
|
return err_response
|
||||||
|
|
||||||
if request.method == 'GET':
|
if request.method == 'GET':
|
||||||
|
# if get request calculate time delta
|
||||||
if (time.time() - source.last_msg) > 180:
|
if (time.time() - source.last_msg) > 180:
|
||||||
source.status = "dead"
|
source.status = "dead"
|
||||||
else:
|
else:
|
||||||
source.status = "alive"
|
source.status = "alive"
|
||||||
|
# formulate response json
|
||||||
resp_data = {'status': source.status, 'last_msg': source.last_msg}
|
resp_data = {'status': source.status, 'last_msg': source.last_msg}
|
||||||
get_response=app.response_class(
|
get_response=app.response_class(
|
||||||
response=json.dumps(resp_data),
|
response=json.dumps(resp_data),
|
||||||
|
|
File diff suppressed because one or more lines are too long
Before Width: | Height: | Size: 15 KiB After Width: | Height: | Size: 15 KiB |
File diff suppressed because one or more lines are too long
0
webapp/static/jquery.min.js → webapp/static/js/jquery.min.js
vendored
Normal file → Executable file
0
webapp/static/jquery.min.js → webapp/static/js/jquery.min.js
vendored
Normal file → Executable file
0
webapp/static/justgage.min.js → webapp/static/js/justgage.min.js
vendored
Normal file → Executable file
0
webapp/static/justgage.min.js → webapp/static/js/justgage.min.js
vendored
Normal file → Executable file
|
@ -1,3 +1,4 @@
|
||||||
|
//declare variables for gauges and charts, defining options as per the plugin documentation
|
||||||
var temp_gauge = new JustGage({
|
var temp_gauge = new JustGage({
|
||||||
id: 'temp_gauge',
|
id: 'temp_gauge',
|
||||||
value: 0,
|
value: 0,
|
||||||
|
@ -73,80 +74,6 @@ var co2_gauge = new JustGage({
|
||||||
levelColors: ['#33ff00', '#ffff00', '#ff8800', '#ff0008']
|
levelColors: ['#33ff00', '#ffff00', '#ff8800', '#ff0008']
|
||||||
});
|
});
|
||||||
|
|
||||||
function updateGauge(dbdata) {
|
|
||||||
for(var key in dbdata.temp){};
|
|
||||||
temp_gauge.refresh(dbdata.temp[key]);
|
|
||||||
hum_gauge.refresh(dbdata.hum[key]);
|
|
||||||
pres_gauge.refresh(dbdata.pres[key]);
|
|
||||||
co2_gauge.refresh(dbdata.co2[key]);
|
|
||||||
};
|
|
||||||
|
|
||||||
var reqform = $('#sample_select_form');
|
|
||||||
reqform.submit(function (f) {
|
|
||||||
f.preventDefault();
|
|
||||||
clearChartData();
|
|
||||||
getServerDataAndUpdate($('input[name=samples]').val());
|
|
||||||
});
|
|
||||||
|
|
||||||
setInterval(function fx() {
|
|
||||||
var statusData = $.ajax ({
|
|
||||||
type: 'GET',
|
|
||||||
url: '/api/heartbeat',
|
|
||||||
data: {},
|
|
||||||
dataType: 'json',
|
|
||||||
success: console.log("requested heartbeat data"),
|
|
||||||
}).done(function (retdata) {
|
|
||||||
if ( typeof fx.statusWas === 'undefined' ) {
|
|
||||||
fx.statusWas = "dead";
|
|
||||||
};
|
|
||||||
|
|
||||||
if (retdata.status == "alive" && fx.statusWas == "alive") {
|
|
||||||
getServerDataAndUpdate(1);
|
|
||||||
trimChartData();
|
|
||||||
fx.statusWas = "alive";
|
|
||||||
}
|
|
||||||
else if (retdata.status == "alive" && fx.statusWas == "dead") {
|
|
||||||
$('#footer_text').text("Live update enabled. (Data might be delayed up to a minute)");
|
|
||||||
clearChartData();
|
|
||||||
getServerDataAndUpdate($('input[name=samples]').val());
|
|
||||||
fx.statusWas = "alive";
|
|
||||||
}
|
|
||||||
else if (retdata.status == "dead" && fx.statusWas == "alive") {
|
|
||||||
$('#footer_text').text("Source not online, live update disabled. Showing latest available data.");
|
|
||||||
fx.statusWas = "dead";
|
|
||||||
}
|
|
||||||
else if (retdata.status == "dead" && fx.statusWas == "dead") {
|
|
||||||
fx.statusWas == "dead";
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
location.reload(forceGet);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
}, 90000);
|
|
||||||
|
|
||||||
$('#refresh').click(function() {
|
|
||||||
clearChartData();
|
|
||||||
getServerDataAndUpdate($('input[name=samples]').val());
|
|
||||||
});
|
|
||||||
|
|
||||||
$(document).ready(function() {
|
|
||||||
var statusData = $.ajax ({
|
|
||||||
type: 'GET',
|
|
||||||
url: '/api/heartbeat',
|
|
||||||
data: {},
|
|
||||||
dataType: 'json',
|
|
||||||
success: console.log("requested heartbeat data"),
|
|
||||||
}).done(function (retdata) {
|
|
||||||
if (retdata.status == "alive") {
|
|
||||||
$('#footer_text').text("Live update enabled. (Data might be delayed up to a minute)");
|
|
||||||
}
|
|
||||||
else if (retdata.status == "dead") {
|
|
||||||
$('#footer_text').text("Source not online, live update disabled. Showing latest available data.");
|
|
||||||
}
|
|
||||||
});
|
|
||||||
getServerDataAndUpdate($('input[name=samples]').val());
|
|
||||||
});
|
|
||||||
|
|
||||||
Chart.defaults.global.responsive = false;
|
Chart.defaults.global.responsive = false;
|
||||||
var nan=NaN;
|
var nan=NaN;
|
||||||
|
|
||||||
|
@ -370,31 +297,40 @@ var multiChartSettings = {
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
//instantiate chart objects with defined options
|
||||||
var ctx = document.getElementById('tempchart').getContext("2d");
|
var ctx = document.getElementById('tempchart').getContext("2d");
|
||||||
var tempChart = new Chart(ctx,tempChartSettings);
|
var tempChart = new Chart(ctx,tempChartSettings);
|
||||||
|
|
||||||
var ctx2 = document.getElementById('humchart').getContext("2d");
|
var ctx2 = document.getElementById('humchart').getContext("2d");
|
||||||
var humChart = new Chart(ctx2,humChartSettings);
|
var humChart = new Chart(ctx2,humChartSettings);
|
||||||
|
|
||||||
var ctx3 = document.getElementById('co2chart').getContext("2d");
|
var ctx3 = document.getElementById('co2chart').getContext("2d");
|
||||||
var co2Chart = new Chart(ctx3,co2ChartSettings);
|
var co2Chart = new Chart(ctx3,co2ChartSettings);
|
||||||
|
|
||||||
var ctx4 = document.getElementById('multichart').getContext("2d");
|
var ctx4 = document.getElementById('multichart').getContext("2d");
|
||||||
var multiChart = new Chart(ctx4,multiChartSettings);
|
var multiChart = new Chart(ctx4,multiChartSettings);
|
||||||
|
//function to clear chart datasets without redrawing
|
||||||
function getServerDataAndUpdate(samples) {
|
function clearChartData() {
|
||||||
var jsonData = $.ajax({
|
tempChart.data.datasets[0].data = [];
|
||||||
type: 'POST',
|
tempChart.data.labels = [];
|
||||||
url: '/api/getData',
|
humChart.data.datasets[0].data = [];
|
||||||
data: {'samples' : samples },
|
humChart.data.labels = [];
|
||||||
dataType: 'json',
|
co2Chart.data.datasets[0].data = [];
|
||||||
success: console.log("requested db data"),
|
co2Chart.data.labels = [];
|
||||||
}).done(function (dbdata) {
|
multiChart.data.datasets[0].data = [];
|
||||||
updateChart(dbdata);
|
multiChart.data.datasets[1].data = [];
|
||||||
updateGauge(dbdata);
|
multiChart.data.labels = [];
|
||||||
});
|
|
||||||
};
|
};
|
||||||
|
//function to remove last value of chart dataset, used to create a "scrolling" effect as new values come in
|
||||||
|
function trimChartData() {
|
||||||
|
tempChart.data.datasets[0].data.shift();
|
||||||
|
tempChart.data.labels.shift();
|
||||||
|
humChart.data.datasets[0].data.shift();
|
||||||
|
humChart.data.labels.shift();
|
||||||
|
co2Chart.data.datasets[0].data.shift();
|
||||||
|
co2Chart.data.labels.shift();
|
||||||
|
multiChart.data.datasets[0].data.shift();
|
||||||
|
multiChart.data.datasets[1].data.shift();
|
||||||
|
multiChart.data.labels.shift();
|
||||||
|
}
|
||||||
|
//function to update the chart datasets with data from the database
|
||||||
function updateChart(dbdata) {
|
function updateChart(dbdata) {
|
||||||
$.each(dbdata['temp'], function(key, value) {
|
$.each(dbdata['temp'], function(key, value) {
|
||||||
tempChart.data.labels.push(key);
|
tempChart.data.labels.push(key);
|
||||||
|
@ -411,33 +347,103 @@ function updateChart(dbdata) {
|
||||||
$.each(dbdata['co2'], function(key, value){
|
$.each(dbdata['co2'], function(key, value){
|
||||||
co2Chart.data.datasets[0].data.push(value);
|
co2Chart.data.datasets[0].data.push(value);
|
||||||
});
|
});
|
||||||
|
|
||||||
tempChart.update();
|
tempChart.update();
|
||||||
humChart.update();
|
humChart.update();
|
||||||
co2Chart.update();
|
co2Chart.update();
|
||||||
multiChart.update();
|
multiChart.update();
|
||||||
};
|
};
|
||||||
|
//function to update the gauges
|
||||||
function clearChartData() {
|
function updateGauge(dbdata) {
|
||||||
tempChart.data.datasets[0].data = [];
|
for(var key in dbdata.temp){};
|
||||||
tempChart.data.labels = [];
|
temp_gauge.refresh(dbdata.temp[key]);
|
||||||
humChart.data.datasets[0].data = [];
|
hum_gauge.refresh(dbdata.hum[key]);
|
||||||
humChart.data.labels = [];
|
pres_gauge.refresh(dbdata.pres[key]);
|
||||||
co2Chart.data.datasets[0].data = [];
|
co2_gauge.refresh(dbdata.co2[key]);
|
||||||
co2Chart.data.labels = [];
|
$('#timestamp').text(key);
|
||||||
multiChart.data.datasets[0].data = [];
|
|
||||||
multiChart.data.datasets[1].data = [];
|
|
||||||
multiChart.data.labels = [];
|
|
||||||
};
|
};
|
||||||
|
//ajax data fetching function
|
||||||
function trimChartData() {
|
function getServerData(samples) {
|
||||||
tempChart.data.datasets[0].data.shift();
|
return $.ajax({
|
||||||
tempChart.data.labels.shift();
|
type: 'POST',
|
||||||
humChart.data.datasets[0].data.shift();
|
url: '/api/getdata',
|
||||||
humChart.data.labels.shift();
|
data: {'samples' : samples },
|
||||||
co2Chart.data.datasets[0].data.shift();
|
dataType: 'json',
|
||||||
co2Chart.data.labels.shift();
|
success: console.log("requested db data"),
|
||||||
multiChart.data.datasets[0].data.shift();
|
});
|
||||||
multiChart.data.datasets[1].data.shift();
|
};
|
||||||
multiChart.data.labels.shift();
|
//the main function, updates the values based on the device status, run on a timer
|
||||||
}
|
function getStatusAndDecide() {
|
||||||
|
var statusData = $.ajax ({
|
||||||
|
type: 'GET',
|
||||||
|
url: '/api/heartbeat',
|
||||||
|
data: {},
|
||||||
|
dataType: 'json',
|
||||||
|
success: console.log("requested heartbeat data"),
|
||||||
|
}).done(function (retdata) {
|
||||||
|
if ( typeof getStatusAndDecide.statusWas === 'undefined' ) {
|
||||||
|
getStatusAndDecide.statusWas = "dead";
|
||||||
|
};
|
||||||
|
if (retdata.status == "alive" && getStatusAndDecide.statusWas == "alive") {
|
||||||
|
$.when(getServerData(1)).done(function(retdata) {
|
||||||
|
updateChart(retdata);
|
||||||
|
updateGauge(retdata);
|
||||||
|
});
|
||||||
|
trimChartData();
|
||||||
|
getStatusAndDecide.statusWas = "alive";
|
||||||
|
}
|
||||||
|
else if (retdata.status == "alive" && getStatusAndDecide.statusWas == "dead") {
|
||||||
|
$('#footer_text').text("Live update enabled. (Data might be delayed up to a minute)");
|
||||||
|
clearChartData();
|
||||||
|
$.when(getServerData($('input[name=samples]').val())).done(function(retdata) {
|
||||||
|
updateChart(retdata);
|
||||||
|
updateGauge(retdata);
|
||||||
|
});
|
||||||
|
getStatusAndDecide.statusWas = "alive";
|
||||||
|
}
|
||||||
|
else if (retdata.status == "dead" && getStatusAndDecide.statusWas == "alive") {
|
||||||
|
$('#footer_text').text("Source not online, live update disabled. Showing latest available data.");
|
||||||
|
getStatusAndDecide.statusWas = "dead";
|
||||||
|
}
|
||||||
|
else if (retdata.status == "dead" && getStatusAndDecide.statusWas == "dead") {
|
||||||
|
getStatusAndDecide.statusWas == "dead";
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
location.reload(forceGet);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
};
|
||||||
|
//functions run after clicking the refresh button
|
||||||
|
$('#refresh').click(function() {
|
||||||
|
console.log("refreshing all");
|
||||||
|
clearChartData();
|
||||||
|
$.when(getServerData($('input[name=samples]').val())).done(function(retdata) {
|
||||||
|
updateChart(retdata);
|
||||||
|
updateGauge(retdata);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//functions run after choosing sample amount from the gui
|
||||||
|
$('#sample_select_form').submit(function (f) {
|
||||||
|
f.preventDefault();
|
||||||
|
console.log("selected range from form");
|
||||||
|
clearChartData();
|
||||||
|
$.when(getServerData($('input[name=samples]').val())).done(function(retdata) {
|
||||||
|
updateChart(retdata);
|
||||||
|
updateGauge(retdata);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//function run on page load, fetches and dislpays the latest data
|
||||||
|
$(document).ready(function() {
|
||||||
|
$.when(getServerData($('input[name=samples]').val())).done(function(retdata) {
|
||||||
|
if (retdata.status == "alive") {
|
||||||
|
$('#footer_text').text("Live update enabled. (Data might be delayed up to a minute)");
|
||||||
|
}
|
||||||
|
else if (retdata.status == "dead") {
|
||||||
|
$('#footer_text').text("Source not online, live update disabled. Showing latest available data.");
|
||||||
|
}
|
||||||
|
updateChart(retdata);
|
||||||
|
updateGauge(retdata);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
//90 second timer to refresh contents
|
||||||
|
setInterval("getStatusAndDecide()", 90000);
|
File diff suppressed because one or more lines are too long
0
webapp/static/raphael.min.js → webapp/static/js/raphael.min.js
vendored
Normal file → Executable file
0
webapp/static/raphael.min.js → webapp/static/js/raphael.min.js
vendored
Normal file → Executable file
|
@ -1,76 +0,0 @@
|
||||||
#header_bar {
|
|
||||||
}
|
|
||||||
|
|
||||||
#footer_bar {
|
|
||||||
padding-bottom: 5px;
|
|
||||||
padding-left: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gauge_container {
|
|
||||||
text-align: center;
|
|
||||||
display: block;
|
|
||||||
margin: auto;
|
|
||||||
margin-top: 15px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gauge {
|
|
||||||
display: inline-block;
|
|
||||||
width: 240px;
|
|
||||||
height: 160px;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
||||||
|
|
||||||
.gauge_desc {
|
|
||||||
margin: 0;
|
|
||||||
margin-top: -30px;
|
|
||||||
margin-bottom: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
.graph_name {
|
|
||||||
margin-top: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#sample_select {
|
|
||||||
margin-top: 40px;
|
|
||||||
margin-left: 20px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#uberschrift {
|
|
||||||
margin: 0;
|
|
||||||
padding: 2px;
|
|
||||||
margin-left: 20px;
|
|
||||||
margin-top: 10px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content_wrapper {
|
|
||||||
display: flex;
|
|
||||||
flex-flow: wrap;
|
|
||||||
margin-left: 5px;
|
|
||||||
margin-right: 5px;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content_left {
|
|
||||||
flex: 0 0 20%;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content_center {
|
|
||||||
flex: 0 0 33%;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
#content_right {
|
|
||||||
flex: 0 0 33%;
|
|
||||||
justify-content: center;
|
|
||||||
}
|
|
||||||
|
|
||||||
body {
|
|
||||||
padding: 0;
|
|
||||||
margin: 0;
|
|
||||||
font-family: 'Noto Sans', Noto, sans-serif;
|
|
||||||
}
|
|
||||||
|
|
||||||
svg {
|
|
||||||
margin-left: auto;
|
|
||||||
margin-right: auto;
|
|
||||||
}
|
|
|
@ -0,0 +1,188 @@
|
||||||
|
#header_bar {
|
||||||
|
}
|
||||||
|
#uberschrift {
|
||||||
|
margin: 0;
|
||||||
|
padding: 0 2px 2px;
|
||||||
|
margin-left: 20px;
|
||||||
|
margin-top: 10px;
|
||||||
|
}
|
||||||
|
#footer_bar {
|
||||||
|
padding-bottom: 5px;
|
||||||
|
padding-left: 5px;
|
||||||
|
}
|
||||||
|
#footer_text {
|
||||||
|
margin-bottom: 2px;
|
||||||
|
}
|
||||||
|
#recent_vals {
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
#timestamp {
|
||||||
|
margin-top: -10px;
|
||||||
|
margin-left:20px;
|
||||||
|
}
|
||||||
|
.gauge_container {
|
||||||
|
text-align: center;
|
||||||
|
display: block;
|
||||||
|
margin: auto;
|
||||||
|
margin-top: 15px;
|
||||||
|
}
|
||||||
|
.gauge {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: 20px;
|
||||||
|
|
||||||
|
}
|
||||||
|
.gauge_desc {
|
||||||
|
margin-left: -50px;
|
||||||
|
margin-top: -30px;
|
||||||
|
margin-bottom: 20px;
|
||||||
|
}
|
||||||
|
.graph_name {
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
.graph_canvas {
|
||||||
|
display: block;
|
||||||
|
width: 100%;
|
||||||
|
}
|
||||||
|
#tempchart {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
#multichart {
|
||||||
|
margin-bottom: 25px;
|
||||||
|
}
|
||||||
|
#sample_select {
|
||||||
|
margin-top: 40px;
|
||||||
|
margin-left: 20px;
|
||||||
|
}
|
||||||
|
#content_wrapper {
|
||||||
|
display: flex;
|
||||||
|
flex-flow: wrap;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-right: 5px;
|
||||||
|
}
|
||||||
|
#content_left {
|
||||||
|
flex: 0 0 18%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
|
||||||
|
#content_center {
|
||||||
|
/*margin-left: 10px;*/
|
||||||
|
flex: 0 0 40%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
#content_right {
|
||||||
|
flex: 0 0 40%;
|
||||||
|
justify-content: center;
|
||||||
|
}
|
||||||
|
body {
|
||||||
|
padding: 0;
|
||||||
|
margin: 0;
|
||||||
|
font-family: 'Noto Sans', Noto, sans-serif;
|
||||||
|
}
|
||||||
|
svg {
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
|
||||||
|
@media (max-width: 1280px) {
|
||||||
|
#content_left {
|
||||||
|
display: flex !important;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1 1 100%;
|
||||||
|
}
|
||||||
|
#content_center {
|
||||||
|
flex: 0 0 95%;
|
||||||
|
justify-content: center;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
#content_right {
|
||||||
|
flex: 0 0 95%;
|
||||||
|
justify-content: center;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.gauge_container_top {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.gauge_container_bottom {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
}
|
||||||
|
.gauge_container {
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.gauge {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
.gauge_desc {
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: -30px;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
#sample_select {
|
||||||
|
margin-top: auto;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@media (max-width: 1000px) {
|
||||||
|
#recent_vals {
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
#uberschrift {
|
||||||
|
margin: 0;
|
||||||
|
padding: 2px;
|
||||||
|
margin-left: 5px;
|
||||||
|
margin-top: 5px;
|
||||||
|
}
|
||||||
|
#content_left {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
justify-content: center;
|
||||||
|
flex: 1 1 100%;
|
||||||
|
}
|
||||||
|
#content_center {
|
||||||
|
flex: 0 0 95%;
|
||||||
|
justify-content: center;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
#content_right {
|
||||||
|
flex: 0 0 95%;
|
||||||
|
justify-content: center;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.gauge_container_top {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: auto;
|
||||||
|
margin-left: -15px;
|
||||||
|
}
|
||||||
|
.gauge_container_bottom {
|
||||||
|
display: flex;
|
||||||
|
flex-direction: row;
|
||||||
|
margin: auto;
|
||||||
|
margin-left: -15px;
|
||||||
|
}
|
||||||
|
.gauge_container {
|
||||||
|
text-align: center;
|
||||||
|
display: flex;
|
||||||
|
flex-direction: column;
|
||||||
|
margin: auto;
|
||||||
|
}
|
||||||
|
.gauge {
|
||||||
|
display: inline-block;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
.gauge_desc {
|
||||||
|
justify-content: center;
|
||||||
|
margin-left: auto;
|
||||||
|
margin-right: auto;
|
||||||
|
}
|
||||||
|
#sample_select {
|
||||||
|
margin-top: auto;
|
||||||
|
margin-left: 5px;
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,31 +1,45 @@
|
||||||
<!doctype html>
|
<!DOCTYPE html>
|
||||||
<html>
|
<html>
|
||||||
<head>
|
<head>
|
||||||
<title>IAQ dash</title>
|
<title>IAQ dash</title>
|
||||||
<link rel='shortcut icon' type='image/ico' href='static/favicon.ico'>
|
<link rel='shortcut icon' type='image/ico' href='static/favicon.ico'>
|
||||||
<link rel='stylesheet' href='static/styles/chartstyle.css'>
|
<link rel='stylesheet' href='static/styles/style.css'>
|
||||||
<link rel='stylesheet' href='static/styles/Chart.min.css'>
|
<link rel='stylesheet' href='static/styles/Chart.min.css'>
|
||||||
<script type='text/javascript' src='static/Chart.bundle.min.js'></script>
|
<script type='text/javascript' src='static/js/moment.min.js'></script>
|
||||||
<script type='text/javascript' src='static/jquery.min.js'></script>
|
<script type='text/javascript' src='static/js/Chart.min.js'></script>
|
||||||
<script type='text/javascript' src='static/raphael.min.js'></script>
|
<script type='text/javascript' src='static/js/jquery.min.js'></script>
|
||||||
<script type='text/javascript' src='static/justgage.min.js'></script>
|
<script type='text/javascript' src='static/js/raphael.min.js'></script>
|
||||||
|
<script type='text/javascript' src='static/js/justgage.min.js'></script>
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
<div id='header_bar'>
|
<div id='header_bar'>
|
||||||
<h1 id='uberschrift'>Indoor enviro data</h1>
|
<h2 id='uberschrift'>Indoor enviro data</h2>
|
||||||
</div>
|
</div>
|
||||||
<div id='content_wrapper' class='column'>
|
<div id='content_wrapper' class='column'>
|
||||||
<div id='content_left' class='column'>
|
<div id='content_left' class='column'>
|
||||||
|
<h3 id='recent_vals'>Most recent values</h3>
|
||||||
|
<h4 id='timestamp'></h4>
|
||||||
<div class='gauge_container'>
|
<div class='gauge_container'>
|
||||||
<h3>Most recent values</h3>
|
<div class='gauge_container_top'>
|
||||||
<div id='temp_gauge' class='gauge' align='center'></div>
|
<div class='gauge_block'>
|
||||||
<h5 align='center' class='gauge_desc'>Temperature</h5>
|
<div id='temp_gauge' class='gauge' align='center'></div>
|
||||||
<div id='hum_gauge' class='gauge' align='center'></div>
|
<h5 align='center' class='gauge_desc'>Temperature</h5>
|
||||||
<h5 align='center' class='gauge_desc'>Relative humidity</h5>
|
</div>
|
||||||
<div id='pres_gauge' class='gauge' align='center'></div>
|
<div class='gauge_block'>
|
||||||
<h5 align='center' class='gauge_desc'>Pressure</h5>
|
<div id='hum_gauge' class='gauge' align='center'></div>
|
||||||
<div id='co2_gauge' class='gauge' align='center'></div>
|
<h5 align='center' class='gauge_desc'>Relative humidity</h5>
|
||||||
<h5 align='center' class='gauge_desc'>CO2 concentration</h5>
|
</div>
|
||||||
|
</div>
|
||||||
|
<div class='gauge_container_bottom'>
|
||||||
|
<div class='gauge_block'>
|
||||||
|
<div id='pres_gauge' class='gauge' align='center'></div>
|
||||||
|
<h5 align='center' class='gauge_desc'>Pressure</h5>
|
||||||
|
</div>
|
||||||
|
<div class='gauge_block'>
|
||||||
|
<div id='co2_gauge' class='gauge' align='center'></div>
|
||||||
|
<h5 align='center' class='gauge_desc'>CO2 concentration</h5>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id='sample_select'>
|
<div id='sample_select'>
|
||||||
<form id='sample_select_form' action='/'>
|
<form id='sample_select_form' action='/'>
|
||||||
|
@ -37,21 +51,21 @@
|
||||||
</div>
|
</div>
|
||||||
<div id='content_center' class='column'>
|
<div id='content_center' class='column'>
|
||||||
<h3 class='graph_name'>Temperature</h3>
|
<h3 class='graph_name'>Temperature</h3>
|
||||||
<canvas id='tempchart' width='600' height='380' style='display: block;'></canvas>
|
<canvas class='graph_canvas' id='tempchart'></canvas>
|
||||||
<h3 class='graph_name'>Humidity</h3>
|
<h3 class='graph_name'>Humidity</h3>
|
||||||
<canvas id='humchart' width='600' height='380' style='display: block;'></canvas>
|
<canvas class='graph_canvas' id='humchart'></canvas>
|
||||||
</div>
|
</div>
|
||||||
<div id='content_right' class='column'>
|
<div id='content_right' class='column'>
|
||||||
<h3 class='graph_name'>Combined</h3>
|
<h3 class='graph_name'>Combined</h3>
|
||||||
<canvas id='multichart' width='600' height='380' style='display: block'></canvas>
|
<canvas class='graph_canvas' id='multichart'></canvas>
|
||||||
<h3 class='graph_name'>CO2 concentration</h3>
|
<h3 class='graph_name'>CO2 concentration</h3>
|
||||||
<canvas id='co2chart' width='600' height='380' style='display: block;'></canvas>
|
<canvas class='graph_canvas' id='co2chart'></canvas>
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
<div id='footer_bar'>
|
<div id='footer_bar'>
|
||||||
<p id='footer_text'>Source not online, live update disabled. Showing latest available data.</p>
|
<p id='footer_text'>Source not online, live update disabled. Showing latest available data.</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<script type='text/javascript' src='static/main.js'></script>
|
<script type='text/javascript' src='static/js/main.js'></script>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
|
|
Loading…
Reference in New Issue