initial commit
2
web/TODO.txt
Executable file
@@ -0,0 +1,2 @@
|
||||
Absolute web path throughout the project
|
||||
Graphs share JS variables, separate them.
|
||||
50
web/chttpd.py
Executable file
@@ -0,0 +1,50 @@
|
||||
#!/usr/bin/python3
|
||||
import os
|
||||
import cherrypy
|
||||
import OpenSSL
|
||||
import sys
|
||||
|
||||
PATH = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
_SCRIPT_PATH = os.path.dirname(sys.argv[0])
|
||||
sys.path.append(_SCRIPT_PATH)
|
||||
|
||||
import voltage
|
||||
import weather
|
||||
import dynamic
|
||||
import status
|
||||
|
||||
server_config={
|
||||
'server.socket_host': '0.0.0.0',
|
||||
'server.socket_port':443,
|
||||
'server.ssl_module':'builtin',
|
||||
'server.ssl_certificate':'/home/www/.cert/fullchain1.pem',
|
||||
'server.ssl_private_key':'/home/www/.cert/privkey1.pem'
|
||||
}
|
||||
cherrypy.config.update(server_config)
|
||||
|
||||
if __name__ == '__main__':
|
||||
|
||||
|
||||
|
||||
conf = {
|
||||
'/': {
|
||||
'tools.sessions.on': True,
|
||||
'tools.staticdir.root': os.path.abspath(_SCRIPT_PATH + '/')
|
||||
},
|
||||
'/static': {
|
||||
'tools.staticdir.on': True,
|
||||
'tools.staticdir.dir': './static'
|
||||
},
|
||||
'/data': {
|
||||
'tools.staticdir.on': False,
|
||||
'tools.staticdir.dir': './dynamic'
|
||||
}
|
||||
}
|
||||
|
||||
cherrypy.tree.mount(voltage.EnergyInfo(), "/", conf)
|
||||
cherrypy.tree.mount(weather.WeatherInfo(), "/weather", conf)
|
||||
cherrypy.tree.mount(status.StatusInfo(), "/status", conf)
|
||||
cherrypy.tree.mount(dynamic.DynamicData(), "/data", conf)
|
||||
cherrypy.engine.start()
|
||||
cherrypy.engine.block()
|
||||
497
web/dynamic.py
Executable file
@@ -0,0 +1,497 @@
|
||||
#!/usr/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import cherrypy
|
||||
import influxdb
|
||||
import time
|
||||
import datetime
|
||||
import json
|
||||
from cherrypy.lib.httputil import parse_query_string
|
||||
|
||||
|
||||
# Universal variables
|
||||
_SCRIPT_PATH = os.path.dirname(sys.argv[0])
|
||||
|
||||
influx_host = 'localhost'
|
||||
influx_port = 8086
|
||||
influx_user = 'pi'
|
||||
influx_pwd = 'freedavis'
|
||||
influx_db = 'voltage'
|
||||
influx_weather_db = 'weather'
|
||||
influx_status_db = 'status'
|
||||
|
||||
variables_known = ["range", "granularity", "start", "end"]
|
||||
default_variables = {"range": "1h", "granularity": "30s", "end": "1s"}
|
||||
|
||||
|
||||
class DynamicData(object):
|
||||
def __init__(self):
|
||||
self.influx_client = influxdb.client.InfluxDBClient(
|
||||
influx_host, influx_port, influx_user, influx_pwd, influx_db
|
||||
)
|
||||
self.influx_weather_client = influxdb.client.InfluxDBClient(
|
||||
influx_host, influx_port, influx_user, influx_pwd, influx_weather_db
|
||||
)
|
||||
self.influx_status_client = influxdb.client.InfluxDBClient(
|
||||
influx_host, influx_port, influx_user, influx_pwd, influx_status_db
|
||||
)
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
return "Index, MOFO"
|
||||
|
||||
def check_GET(self, arguments):
|
||||
''' No other purpose than to make it more secure and only process the
|
||||
values that are meant to be processed. Hardcoded on top, mate!
|
||||
|
||||
sets:
|
||||
list(): key values of those which should be processed, merged
|
||||
with deault values
|
||||
'''
|
||||
self.q = eval(str(arguments))
|
||||
keys_to_process = {
|
||||
key:self.q[key] for key in self.q.keys() if key in variables_known}
|
||||
resulting_variables = default_variables.copy()
|
||||
resulting_variables.update(keys_to_process)
|
||||
self.q = resulting_variables
|
||||
|
||||
@cherrypy.expose
|
||||
def solar_monitor(self, **kwargs):
|
||||
'''
|
||||
Function to get solar readings from InfluxDB.
|
||||
These parsed into a CSV
|
||||
|
||||
yields: csv in raw, text format
|
||||
time, V_solar, I_solar, P_solar
|
||||
|
||||
'''
|
||||
|
||||
# GET variables now set, ready to reference them
|
||||
self.check_GET(kwargs)
|
||||
query1 = "SELECT mean(V_solar) as Usol, mean(I_solar) as Isol, "
|
||||
query2 = "mean(V_array) as Varr, mean(I_consumption) as Icons FROM voltage "
|
||||
query3 = "WHERE time > NOW() - {} AND time < NOW() - {} GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
query4 = "ORDER BY time DESC"
|
||||
query = "{} {} {} {}".format(query1, query2, query3, query4)
|
||||
measures = self.influx_client.query(query)
|
||||
# Let's get the data from DB
|
||||
header = "time,V_solar,I_solar,P_solar,P_cons\n"
|
||||
yield header
|
||||
for datapoint in measures["voltage"]:
|
||||
tm = str(datapoint["time"]).strip()
|
||||
solar_voltage = str(datapoint["Usol"]).strip()
|
||||
solar_current = str(datapoint["Isol"]).strip()
|
||||
array_voltage = str(datapoint["Varr"]).strip()
|
||||
consumption_current = str(datapoint["Icons"]).strip()
|
||||
if solar_voltage != 'None' and solar_current != 'None' \
|
||||
and array_voltage != 'None' and consumption_current != 'None':
|
||||
solar_voltage = float(solar_voltage) / 1000.00
|
||||
solar_current = float(solar_current) / 1000.00
|
||||
array_voltage = float(array_voltage) / 1000.00
|
||||
consumption_current = float(consumption_current) / 1000.00
|
||||
else:
|
||||
solar_voltage = 0.00
|
||||
solar_current = 0.00
|
||||
array_voltage = 0.00
|
||||
consumption_current = 0.00
|
||||
# The 8W is the approximate internal consumption of the mppt controller ~ 0.15A
|
||||
# This value was removed, No idea why it appeared there in the first place.
|
||||
solar_power = round(solar_voltage * solar_current, 2)
|
||||
consumption_power = round(array_voltage * consumption_current, 2)
|
||||
yield "{},{},{},{},{}\n".format(tm, solar_voltage,
|
||||
solar_current, solar_power,
|
||||
consumption_power)
|
||||
|
||||
|
||||
@cherrypy.expose
|
||||
def wind_monitor(self, **kwargs):
|
||||
'''
|
||||
Function to get wind value readings from InfluxDB.
|
||||
These parsed into a CSV
|
||||
|
||||
yields: csv in raw, text format
|
||||
time, Speed, Gusts, Direction
|
||||
|
||||
'''
|
||||
|
||||
# GET variables now set, ready to reference them
|
||||
self.check_GET(kwargs)
|
||||
speed_q1 = "SELECT mean(value) as w_speed FROM wind"
|
||||
gust_q1 = "SELECT mean(value) as w_gust FROM wind"
|
||||
direction_q1 = "SELECT mean(value) as w_dir FROM wind"
|
||||
speed_q2 = "WHERE time > NOW() - {} AND time < NOW() - {} AND type = 'speed' GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
gust_q2 = "WHERE time > NOW() - {} AND time < NOW() - {} AND type = 'windgust' GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
direction_q2 = "WHERE time > NOW() - {} AND time < NOW() - {} AND type = 'direction' GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
q3 = "ORDER BY time DESC"
|
||||
speed_query = "{} {} {}".format(speed_q1, speed_q2, q3)
|
||||
gust_query = "{} {} {}".format(gust_q1, gust_q2, q3)
|
||||
direction_query = "{} {} {}".format(direction_q1, direction_q2, q3)
|
||||
rs_speed = self.influx_weather_client.query(speed_query)
|
||||
rs_gust = self.influx_weather_client.query(gust_query)
|
||||
rs_direction = self.influx_weather_client.query(direction_query)
|
||||
# Let's get the data from DB
|
||||
header = "time,Speed,Gusts,Direction\n"
|
||||
yield header
|
||||
for speed, gust, direction in zip(rs_speed['wind'], rs_gust['wind'], rs_direction['wind']):
|
||||
tm_speed = str(speed["time"]).strip()
|
||||
tm_gust = str(gust["time"]).strip()
|
||||
tm_direction = str(direction["time"]).strip()
|
||||
speed_value = str(speed["w_speed"]).strip()
|
||||
gust_value = str(gust["w_gust"]).strip()
|
||||
direction_value = str(direction["w_dir"]).strip()
|
||||
#if tm_speed == tm_gust and tm_speed == tm_direction:
|
||||
#tm = strptime(speed["time"]).strip(), "%Y-%m-%dT%H:%M:%SZ")
|
||||
yield "{},{},{},{}\n".format(tm_speed, speed_value, gust_value, direction_value)
|
||||
|
||||
@cherrypy.expose
|
||||
def temphumi_monitor(self, **kwargs):
|
||||
'''
|
||||
Function to get temperature and humidity readings from InfluxDB.
|
||||
These parsed into a CSV
|
||||
|
||||
yields: csv in raw, text format
|
||||
time,
|
||||
|
||||
'''
|
||||
|
||||
# GET variables now set, ready to reference them
|
||||
self.check_GET(kwargs)
|
||||
temp_q1 = "SELECT mean(temperature) as temp FROM temphumi"
|
||||
hum_q1 = "SELECT mean(humidity) as hum FROM temphumi"
|
||||
in_q2 = "WHERE time > NOW() - {} AND time < NOW() - {} AND type = 'internal' GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
out_q2 = "WHERE time > NOW() - {} AND time < NOW() - {} AND type = 'external' GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
q3 = "ORDER BY time DESC"
|
||||
temp_in_query = "{} {} {}".format(temp_q1, in_q2, q3)
|
||||
temp_out_query = "{} {} {}".format(temp_q1, out_q2, q3)
|
||||
hum_in_query = "{} {} {}".format(hum_q1, in_q2, q3)
|
||||
hum_out_query = "{} {} {}".format(hum_q1, out_q2, q3)
|
||||
rs_temp_in = self.influx_weather_client.query(temp_in_query)
|
||||
rs_temp_out = self.influx_weather_client.query(temp_out_query)
|
||||
rs_hum_in = self.influx_weather_client.query(hum_in_query)
|
||||
rs_hum_out = self.influx_weather_client.query(hum_out_query)
|
||||
|
||||
# Let's get the data from DB
|
||||
header = "time,T(ins),T(out),Humi(ins),Humi(out)\n"
|
||||
yield header
|
||||
for Tin, Tout, Hin, Hout in zip(rs_temp_in['temphumi'],
|
||||
rs_temp_out['temphumi'],
|
||||
rs_hum_in['temphumi'],
|
||||
rs_hum_out['temphumi']):
|
||||
tm_temp = str(Tin["time"]).strip()
|
||||
temp_in_val = str(Tin["temp"]).strip()
|
||||
temp_out_val = str(Tout["temp"]).strip()
|
||||
hum_in_val = str(Hin["hum"]).strip()
|
||||
hum_out_val = str(Hout["hum"]).strip()
|
||||
#if tm_speed == tm_gust and tm_speed == tm_direction:
|
||||
#tm = strptime(speed["time"]).strip(), "%Y-%m-%dT%H:%M:%SZ")
|
||||
yield "{},{},{},{},{}\n".format(tm_temp, temp_in_val, temp_out_val, hum_in_val, hum_out_val)
|
||||
|
||||
@cherrypy.expose
|
||||
def pressure_monitor(self, **kwargs):
|
||||
'''
|
||||
Function to get pressure readings from InfluxDB.
|
||||
These parsed into a CSV
|
||||
|
||||
yields: csv in raw, text format
|
||||
time, Pressure
|
||||
|
||||
'''
|
||||
|
||||
# GET variables now set, ready to reference them
|
||||
self.check_GET(kwargs)
|
||||
query1 = "SELECT mean(pressure) as pressure FROM temphumi"
|
||||
query2 = "WHERE type = 'adjusted' AND time > NOW() - {} AND time < NOW() - {} GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
query3 = "ORDER BY time DESC"
|
||||
query = "{} {}".format(query1, query2, query3)
|
||||
measures = self.influx_weather_client.query(query)
|
||||
# Let's get the data from DB
|
||||
header = "time,Pressure\n"
|
||||
yield header
|
||||
for datapoint in measures["temphumi"]:
|
||||
tm = str(datapoint["time"]).strip()
|
||||
pressure = str(datapoint["pressure"]).strip()
|
||||
yield "{},{}\n".format(tm, pressure)
|
||||
|
||||
|
||||
@cherrypy.expose
|
||||
def solcap_monitor(self, **kwargs):
|
||||
'''
|
||||
Function to get solar and supercap readings from InfluxDB.
|
||||
These parsed into a CSV
|
||||
|
||||
yields: csv in raw, text format
|
||||
time, Solar Irradiance, Capacitor
|
||||
|
||||
'''
|
||||
|
||||
# GET variables now set, ready to reference them
|
||||
self.check_GET(kwargs)
|
||||
solar_query1 = "SELECT mean(voltage) as solar FROM iss"
|
||||
solar_query2 = "WHERE type = 'solar' AND time > NOW() - {} AND time < NOW() - {} GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
cap_query1 = "SELECT mean(voltage) as cap FROM iss"
|
||||
cap_query2 = "WHERE type = 'capcaitor' AND time > NOW() - {} AND time < NOW() - {} GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
query3 = "ORDER BY time DESC"
|
||||
query_solar = "{} {} {}".format(solar_query1, solar_query2, query3)
|
||||
query_cap = "{} {} {}".format(cap_query1, cap_query2, query3)
|
||||
measures_sol = self.influx_status_client.query(query_solar)
|
||||
measures_cap = self.influx_status_client.query(query_cap)
|
||||
# Let's get the data from DB
|
||||
header = "time,Solar,Capacitor\n"
|
||||
yield header
|
||||
for Sol, Cap in zip(measures_sol['iss'], measures_cap['iss']):
|
||||
tm = str(Sol["time"]).strip()
|
||||
try:
|
||||
solar_value = float(str(Sol["solar"]).strip()) / 100
|
||||
except:
|
||||
solar_value = ''
|
||||
try:
|
||||
cap_value = float(str(Cap["cap"]).strip())
|
||||
except:
|
||||
cap_value = ''
|
||||
yield "{},{},{}\n".format(tm, solar_value, cap_value)
|
||||
|
||||
|
||||
@cherrypy.expose
|
||||
def cpumem_monitor(self, **kwargs):
|
||||
'''
|
||||
Function to get cpu,mem, disk % from InfluxDB.
|
||||
These parsed into a CSV
|
||||
|
||||
yields: csv in raw, text format
|
||||
time, Cpu Mem, Disk
|
||||
|
||||
'''
|
||||
|
||||
# GET variables now set, ready to reference them
|
||||
self.check_GET(kwargs)
|
||||
cpu_query1 = "SELECT mean(usage) as Cpu "
|
||||
mem_query1 = "SELECT mean(usage) as Mem "
|
||||
disk_query1 = "SELECT mean(usage) as Disk "
|
||||
query2 = "FROM RasPI "
|
||||
cpu_query3 = "WHERE type = 'cpu' AND time > NOW() - {} AND time < NOW() - {} GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
mem_query3 = "WHERE type = 'mem' AND time > NOW() - {} AND time < NOW() - {} GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
disk_query3 = "WHERE type = 'disk' AND time > NOW() - {} AND time < NOW() - {} GROUP BY time({})".format(
|
||||
self.q['range'],
|
||||
self.q['end'],
|
||||
self.q['granularity'])
|
||||
query4 = "ORDER BY time DESC"
|
||||
query_cpu = "{} {} {} {}".format(cpu_query1, query2, cpu_query3, query4)
|
||||
query_mem = "{} {} {} {}".format(mem_query1, query2, mem_query3, query4)
|
||||
query_disk = "{} {} {} {}".format(disk_query1, query2, disk_query3, query4)
|
||||
measures_cpu = self.influx_status_client.query(query_cpu)
|
||||
measures_mem = self.influx_status_client.query(query_mem)
|
||||
measures_disk = self.influx_status_client.query(query_disk)
|
||||
# Let's get the data from DB
|
||||
header = "time,Cpu,Mem,Disk\n"
|
||||
yield header
|
||||
for cpu, mem, disk in zip(measures_cpu['RasPI'], measures_mem['RasPI'], measures_disk['RasPI']):
|
||||
tm = str(cpu["time"]).strip()
|
||||
try:
|
||||
cpu_value = float(str(cpu["Cpu"]).strip())
|
||||
except:
|
||||
solar_value = ''
|
||||
try:
|
||||
mem_value = float(str(mem["Mem"]).strip())
|
||||
except:
|
||||
cap_value = ''
|
||||
try:
|
||||
disk_value = float(str(disk["Disk"]).strip())
|
||||
except:
|
||||
disk_value = ''
|
||||
yield "{},{},{},{}\n".format(tm, cpu_value, mem_value, disk_value)
|
||||
|
||||
|
||||
@cherrypy.expose
|
||||
def historical_values(self, **kwargs):
|
||||
'''
|
||||
Function to all historical readings from InfluxDB.
|
||||
These parsed into a CSV? Dict?
|
||||
|
||||
returns: csv in raw, text format
|
||||
time, V_solar, I_solar
|
||||
|
||||
select mean(I_solar) as I_solar from voltage where time > now() - 10m group by time(30s) order by time desc
|
||||
'''
|
||||
|
||||
# GET variables now set, ready to reference them
|
||||
|
||||
measure_range = kwargs['range']
|
||||
query1 = "SELECT mean(V_array) as Uarr, mean(I_consumption) as Icon, "
|
||||
query2 = "mean(V_solar) as Usol, mean(I_solar) as Isol, "
|
||||
query3 = "mean(V_unit1) as Us1, mean(V_unit2) as Us2, max(charging) as charge FROM voltage"
|
||||
query4 = "WHERE time > NOW() - {} GROUP BY time(30s)".format(measure_range)
|
||||
query5 = "ORDER BY time DESC"
|
||||
query = "{} {} {} {} {}".format(query1, query2, query3, query4, query5)
|
||||
measures = self.influx_client.query(query)
|
||||
|
||||
# Let's get the data from DB
|
||||
result = []
|
||||
for datapoint in measures["voltage"]:
|
||||
tm = datapoint['time']
|
||||
try:
|
||||
array_voltage = round(float(datapoint["Uarr"]), 2)
|
||||
current_consumed = round(float(datapoint["Icon"]) / array_voltage, 2)
|
||||
|
||||
solar_voltage = round(float(datapoint["Usol"]), 2)
|
||||
solar_current = round(float(datapoint["Isol"]) / solar_voltage , 2)
|
||||
|
||||
Us1 = round(float(datapoint["Us1"]), 2)
|
||||
Us2 = round(float(datapoint["Us2"]), 2)
|
||||
except:
|
||||
continue
|
||||
|
||||
charging = int(datapoint["charge"])
|
||||
row = {"time": tm,
|
||||
"V_array": array_voltage,
|
||||
"I_consumption": current_consumed,
|
||||
"V_solar": solar_voltage,
|
||||
"I_solar": solar_current,
|
||||
"V_unit1": Us1,
|
||||
"V_unit2": Us2,
|
||||
"charging": charging}
|
||||
result.append(row)
|
||||
return result
|
||||
|
||||
@cherrypy.expose
|
||||
def stat_values(self, **kwargs):
|
||||
'''
|
||||
Function to all historical readings from InfluxDB.
|
||||
These parsed into a CSV? Dict?
|
||||
|
||||
returns: csv in raw, text format
|
||||
24Wh_consumed, 24Wh_solar
|
||||
|
||||
select mean(I_solar) as I_solar from voltage where time > now() - 10m group by time(30s) order by time desc
|
||||
'''
|
||||
|
||||
# GET variables now set, ready to reference them
|
||||
|
||||
_days = kwargs['days']
|
||||
d = datetime.datetime.now()
|
||||
measures = []
|
||||
result = []
|
||||
|
||||
for t_range in range(1, _days*24, 24):
|
||||
day = (d - datetime.timedelta(hours = t_range)).strftime("%Y-%m-%d")
|
||||
query1 = "SELECT mean(V_array) as Uarr, mean(I_consumption) as Icon, "
|
||||
query2 = "mean(V_solar) as Usol, mean(I_solar) as Isol, "
|
||||
query3 = "mean(V_unit1) as Us1, mean(V_unit2) as Us2, max(charging) as charge FROM voltage"
|
||||
query4 = "WHERE time > '{} 00:00:00' AND time < '{} 23:59:59' GROUP BY time(1h) fill(0)".format(day, day)
|
||||
query5 = "ORDER BY time DESC"
|
||||
query = "{} {} {} {} {}".format(query1, query2, query3, query4, query5)
|
||||
measure = self.influx_client.query(query)
|
||||
|
||||
# Let's get the data from DB
|
||||
tm = []
|
||||
Ubat = []
|
||||
Icon = []
|
||||
Usol = []
|
||||
Isol = []
|
||||
Uss1 = []
|
||||
Uss2 = []
|
||||
P_cons = []
|
||||
P_sol = []
|
||||
charging = 0
|
||||
day_result = []
|
||||
|
||||
for datapoint in measure["voltage"]:
|
||||
#print(datapoint)
|
||||
|
||||
tstamp = datapoint['time']
|
||||
array_voltage = round(float(datapoint["Uarr"]), 2)
|
||||
current_consumed = round(float(datapoint["Icon"]), 2)
|
||||
solar_voltage = round(float(datapoint["Usol"]), 2)
|
||||
solar_current = round(float(datapoint["Isol"]), 2)
|
||||
Us1 = round(float(datapoint["Us1"]), 2)
|
||||
Us2 = round(float(datapoint["Us2"]), 2)
|
||||
charge = int(datapoint["charge"])
|
||||
p_consumed = round(float(array_voltage / 1000.00 \
|
||||
* current_consumed / 1000.00), 2)
|
||||
p_solar = round(float(solar_voltage / 1000.00 \
|
||||
* solar_current / 1000.00), 2)
|
||||
|
||||
tm.append(tstamp),
|
||||
Ubat.append(array_voltage),
|
||||
Icon.append(current_consumed),
|
||||
Usol.append(solar_voltage),
|
||||
Isol.append(solar_current),
|
||||
Uss1.append(Us1),
|
||||
Uss2.append(Us2),
|
||||
charging = charging + charge
|
||||
P_cons.append(p_consumed)
|
||||
P_sol.append(p_solar)
|
||||
|
||||
row = {"time": tm,
|
||||
"V_array": Ubat,
|
||||
"I_consumption": Icon,
|
||||
"V_solar": Usol,
|
||||
"I_solar": Isol,
|
||||
"V_unit1": Uss1,
|
||||
"V_unit2": Uss2,
|
||||
"charging": charging,
|
||||
"P_cons": P_cons,
|
||||
"P_sol": P_sol}
|
||||
|
||||
try:
|
||||
day_result = [
|
||||
row['time'][0][0:10],
|
||||
round(min(row['V_array']) / 1000, 2),
|
||||
round(sum(row['I_consumption']) / 1000, 2),
|
||||
round(max(row['V_solar']) / 1000, 2),
|
||||
round(sum(row['I_solar']) / 1000, 2),
|
||||
round(min(row['V_unit1']) / 1000, 2),
|
||||
round(min(row['V_unit2']) / 1000, 2),
|
||||
row['charging'],
|
||||
round(sum(row['P_cons']),2),
|
||||
round(sum(row['P_sol']), 2)
|
||||
]
|
||||
except:
|
||||
day_result = [
|
||||
d.strftime("%Y-%m-%d"),
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0,
|
||||
0
|
||||
]
|
||||
# need to compute averages, mate...
|
||||
print(day_result)
|
||||
result.append(day_result)
|
||||
return result
|
||||
1339
web/static/css/bootstrap-grid.css
vendored
Normal file
1
web/static/css/bootstrap-grid.css.map
Normal file
1
web/static/css/bootstrap-grid.min.css
vendored
Normal file
1
web/static/css/bootstrap-grid.min.css.map
Normal file
459
web/static/css/bootstrap-reboot.css
vendored
Normal file
@@ -0,0 +1,459 @@
|
||||
/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */
|
||||
html {
|
||||
font-family: sans-serif;
|
||||
line-height: 1.15;
|
||||
-ms-text-size-adjust: 100%;
|
||||
-webkit-text-size-adjust: 100%;
|
||||
}
|
||||
|
||||
body {
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
article,
|
||||
aside,
|
||||
footer,
|
||||
header,
|
||||
nav,
|
||||
section {
|
||||
display: block;
|
||||
}
|
||||
|
||||
h1 {
|
||||
font-size: 2em;
|
||||
margin: 0.67em 0;
|
||||
}
|
||||
|
||||
figcaption,
|
||||
figure,
|
||||
main {
|
||||
display: block;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 1em 40px;
|
||||
}
|
||||
|
||||
hr {
|
||||
-webkit-box-sizing: content-box;
|
||||
box-sizing: content-box;
|
||||
height: 0;
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
pre {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
a {
|
||||
background-color: transparent;
|
||||
-webkit-text-decoration-skip: objects;
|
||||
}
|
||||
|
||||
a:active,
|
||||
a:hover {
|
||||
outline-width: 0;
|
||||
}
|
||||
|
||||
abbr[title] {
|
||||
border-bottom: none;
|
||||
text-decoration: underline;
|
||||
text-decoration: underline dotted;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: inherit;
|
||||
}
|
||||
|
||||
b,
|
||||
strong {
|
||||
font-weight: bolder;
|
||||
}
|
||||
|
||||
code,
|
||||
kbd,
|
||||
samp {
|
||||
font-family: monospace, monospace;
|
||||
font-size: 1em;
|
||||
}
|
||||
|
||||
dfn {
|
||||
font-style: italic;
|
||||
}
|
||||
|
||||
mark {
|
||||
background-color: #ff0;
|
||||
color: #000;
|
||||
}
|
||||
|
||||
small {
|
||||
font-size: 80%;
|
||||
}
|
||||
|
||||
sub,
|
||||
sup {
|
||||
font-size: 75%;
|
||||
line-height: 0;
|
||||
position: relative;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
sub {
|
||||
bottom: -0.25em;
|
||||
}
|
||||
|
||||
sup {
|
||||
top: -0.5em;
|
||||
}
|
||||
|
||||
audio,
|
||||
video {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
audio:not([controls]) {
|
||||
display: none;
|
||||
height: 0;
|
||||
}
|
||||
|
||||
img {
|
||||
border-style: none;
|
||||
}
|
||||
|
||||
svg:not(:root) {
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
button,
|
||||
input,
|
||||
optgroup,
|
||||
select,
|
||||
textarea {
|
||||
font-family: sans-serif;
|
||||
font-size: 100%;
|
||||
line-height: 1.15;
|
||||
margin: 0;
|
||||
}
|
||||
|
||||
button,
|
||||
input {
|
||||
overflow: visible;
|
||||
}
|
||||
|
||||
button,
|
||||
select {
|
||||
text-transform: none;
|
||||
}
|
||||
|
||||
button,
|
||||
html [type="button"],
|
||||
[type="reset"],
|
||||
[type="submit"] {
|
||||
-webkit-appearance: button;
|
||||
}
|
||||
|
||||
button::-moz-focus-inner,
|
||||
[type="button"]::-moz-focus-inner,
|
||||
[type="reset"]::-moz-focus-inner,
|
||||
[type="submit"]::-moz-focus-inner {
|
||||
border-style: none;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
button:-moz-focusring,
|
||||
[type="button"]:-moz-focusring,
|
||||
[type="reset"]:-moz-focusring,
|
||||
[type="submit"]:-moz-focusring {
|
||||
outline: 1px dotted ButtonText;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
border: 1px solid #c0c0c0;
|
||||
margin: 0 2px;
|
||||
padding: 0.35em 0.625em 0.75em;
|
||||
}
|
||||
|
||||
legend {
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
color: inherit;
|
||||
display: table;
|
||||
max-width: 100%;
|
||||
padding: 0;
|
||||
white-space: normal;
|
||||
}
|
||||
|
||||
progress {
|
||||
display: inline-block;
|
||||
vertical-align: baseline;
|
||||
}
|
||||
|
||||
textarea {
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
[type="checkbox"],
|
||||
[type="radio"] {
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
padding: 0;
|
||||
}
|
||||
|
||||
[type="number"]::-webkit-inner-spin-button,
|
||||
[type="number"]::-webkit-outer-spin-button {
|
||||
height: auto;
|
||||
}
|
||||
|
||||
[type="search"] {
|
||||
-webkit-appearance: textfield;
|
||||
outline-offset: -2px;
|
||||
}
|
||||
|
||||
[type="search"]::-webkit-search-cancel-button,
|
||||
[type="search"]::-webkit-search-decoration {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
::-webkit-file-upload-button {
|
||||
-webkit-appearance: button;
|
||||
font: inherit;
|
||||
}
|
||||
|
||||
details,
|
||||
menu {
|
||||
display: block;
|
||||
}
|
||||
|
||||
summary {
|
||||
display: list-item;
|
||||
}
|
||||
|
||||
canvas {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
template {
|
||||
display: none;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none;
|
||||
}
|
||||
|
||||
html {
|
||||
-webkit-box-sizing: border-box;
|
||||
box-sizing: border-box;
|
||||
}
|
||||
|
||||
*,
|
||||
*::before,
|
||||
*::after {
|
||||
-webkit-box-sizing: inherit;
|
||||
box-sizing: inherit;
|
||||
}
|
||||
|
||||
@-ms-viewport {
|
||||
width: device-width;
|
||||
}
|
||||
|
||||
html {
|
||||
-ms-overflow-style: scrollbar;
|
||||
-webkit-tap-highlight-color: transparent;
|
||||
}
|
||||
|
||||
body {
|
||||
font-family: -apple-system, system-ui, BlinkMacSystemFont, "Segoe UI", Roboto, "Helvetica Neue", Arial, sans-serif;
|
||||
font-size: 1rem;
|
||||
font-weight: normal;
|
||||
line-height: 1.5;
|
||||
color: #292b2c;
|
||||
background-color: #fff;
|
||||
}
|
||||
|
||||
[tabindex="-1"]:focus {
|
||||
outline: none !important;
|
||||
}
|
||||
|
||||
h1, h2, h3, h4, h5, h6 {
|
||||
margin-top: 0;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
p {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
abbr[title],
|
||||
abbr[data-original-title] {
|
||||
cursor: help;
|
||||
}
|
||||
|
||||
address {
|
||||
margin-bottom: 1rem;
|
||||
font-style: normal;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
ol,
|
||||
ul,
|
||||
dl {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
}
|
||||
|
||||
ol ol,
|
||||
ul ul,
|
||||
ol ul,
|
||||
ul ol {
|
||||
margin-bottom: 0;
|
||||
}
|
||||
|
||||
dt {
|
||||
font-weight: bold;
|
||||
}
|
||||
|
||||
dd {
|
||||
margin-bottom: .5rem;
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
blockquote {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
a {
|
||||
color: #0275d8;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:focus, a:hover {
|
||||
color: #014c8c;
|
||||
text-decoration: underline;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]) {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):focus, a:not([href]):not([tabindex]):hover {
|
||||
color: inherit;
|
||||
text-decoration: none;
|
||||
}
|
||||
|
||||
a:not([href]):not([tabindex]):focus {
|
||||
outline: 0;
|
||||
}
|
||||
|
||||
pre {
|
||||
margin-top: 0;
|
||||
margin-bottom: 1rem;
|
||||
overflow: auto;
|
||||
}
|
||||
|
||||
figure {
|
||||
margin: 0 0 1rem;
|
||||
}
|
||||
|
||||
img {
|
||||
vertical-align: middle;
|
||||
}
|
||||
|
||||
[role="button"] {
|
||||
cursor: pointer;
|
||||
}
|
||||
|
||||
a,
|
||||
area,
|
||||
button,
|
||||
[role="button"],
|
||||
input,
|
||||
label,
|
||||
select,
|
||||
summary,
|
||||
textarea {
|
||||
-ms-touch-action: manipulation;
|
||||
touch-action: manipulation;
|
||||
}
|
||||
|
||||
table {
|
||||
border-collapse: collapse;
|
||||
background-color: transparent;
|
||||
}
|
||||
|
||||
caption {
|
||||
padding-top: 0.75rem;
|
||||
padding-bottom: 0.75rem;
|
||||
color: #636c72;
|
||||
text-align: left;
|
||||
caption-side: bottom;
|
||||
}
|
||||
|
||||
th {
|
||||
text-align: left;
|
||||
}
|
||||
|
||||
label {
|
||||
display: inline-block;
|
||||
margin-bottom: .5rem;
|
||||
}
|
||||
|
||||
button:focus {
|
||||
outline: 1px dotted;
|
||||
outline: 5px auto -webkit-focus-ring-color;
|
||||
}
|
||||
|
||||
input,
|
||||
button,
|
||||
select,
|
||||
textarea {
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
input[type="radio"]:disabled,
|
||||
input[type="checkbox"]:disabled {
|
||||
cursor: not-allowed;
|
||||
}
|
||||
|
||||
input[type="date"],
|
||||
input[type="time"],
|
||||
input[type="datetime-local"],
|
||||
input[type="month"] {
|
||||
-webkit-appearance: listbox;
|
||||
}
|
||||
|
||||
textarea {
|
||||
resize: vertical;
|
||||
}
|
||||
|
||||
fieldset {
|
||||
min-width: 0;
|
||||
padding: 0;
|
||||
margin: 0;
|
||||
border: 0;
|
||||
}
|
||||
|
||||
legend {
|
||||
display: block;
|
||||
width: 100%;
|
||||
padding: 0;
|
||||
margin-bottom: .5rem;
|
||||
font-size: 1.5rem;
|
||||
line-height: inherit;
|
||||
}
|
||||
|
||||
input[type="search"] {
|
||||
-webkit-appearance: none;
|
||||
}
|
||||
|
||||
output {
|
||||
display: inline-block;
|
||||
}
|
||||
|
||||
[hidden] {
|
||||
display: none !important;
|
||||
}
|
||||
/*# sourceMappingURL=bootstrap-reboot.css.map */
|
||||
1
web/static/css/bootstrap-reboot.css.map
Normal file
1
web/static/css/bootstrap-reboot.min.css
vendored
Normal file
@@ -0,0 +1 @@
|
||||
/*! normalize.css v5.0.0 | MIT License | github.com/necolas/normalize.css */html{font-family:sans-serif;line-height:1.15;-ms-text-size-adjust:100%;-webkit-text-size-adjust:100%}body{margin:0}article,aside,footer,header,nav,section{display:block}h1{font-size:2em;margin:.67em 0}figcaption,figure,main{display:block}figure{margin:1em 40px}hr{-webkit-box-sizing:content-box;box-sizing:content-box;height:0;overflow:visible}pre{font-family:monospace,monospace;font-size:1em}a{background-color:transparent;-webkit-text-decoration-skip:objects}a:active,a:hover{outline-width:0}abbr[title]{border-bottom:none;text-decoration:underline;text-decoration:underline dotted}b,strong{font-weight:inherit}b,strong{font-weight:bolder}code,kbd,samp{font-family:monospace,monospace;font-size:1em}dfn{font-style:italic}mark{background-color:#ff0;color:#000}small{font-size:80%}sub,sup{font-size:75%;line-height:0;position:relative;vertical-align:baseline}sub{bottom:-.25em}sup{top:-.5em}audio,video{display:inline-block}audio:not([controls]){display:none;height:0}img{border-style:none}svg:not(:root){overflow:hidden}button,input,optgroup,select,textarea{font-family:sans-serif;font-size:100%;line-height:1.15;margin:0}button,input{overflow:visible}button,select{text-transform:none}[type=reset],[type=submit],button,html [type=button]{-webkit-appearance:button}[type=button]::-moz-focus-inner,[type=reset]::-moz-focus-inner,[type=submit]::-moz-focus-inner,button::-moz-focus-inner{border-style:none;padding:0}[type=button]:-moz-focusring,[type=reset]:-moz-focusring,[type=submit]:-moz-focusring,button:-moz-focusring{outline:1px dotted ButtonText}fieldset{border:1px solid silver;margin:0 2px;padding:.35em .625em .75em}legend{-webkit-box-sizing:border-box;box-sizing:border-box;color:inherit;display:table;max-width:100%;padding:0;white-space:normal}progress{display:inline-block;vertical-align:baseline}textarea{overflow:auto}[type=checkbox],[type=radio]{-webkit-box-sizing:border-box;box-sizing:border-box;padding:0}[type=number]::-webkit-inner-spin-button,[type=number]::-webkit-outer-spin-button{height:auto}[type=search]{-webkit-appearance:textfield;outline-offset:-2px}[type=search]::-webkit-search-cancel-button,[type=search]::-webkit-search-decoration{-webkit-appearance:none}::-webkit-file-upload-button{-webkit-appearance:button;font:inherit}details,menu{display:block}summary{display:list-item}canvas{display:inline-block}template{display:none}[hidden]{display:none}html{-webkit-box-sizing:border-box;box-sizing:border-box}*,::after,::before{-webkit-box-sizing:inherit;box-sizing:inherit}@-ms-viewport{width:device-width}html{-ms-overflow-style:scrollbar;-webkit-tap-highlight-color:transparent}body{font-family:-apple-system,system-ui,BlinkMacSystemFont,"Segoe UI",Roboto,"Helvetica Neue",Arial,sans-serif;font-size:1rem;font-weight:400;line-height:1.5;color:#292b2c;background-color:#fff}[tabindex="-1"]:focus{outline:0!important}h1,h2,h3,h4,h5,h6{margin-top:0;margin-bottom:.5rem}p{margin-top:0;margin-bottom:1rem}abbr[data-original-title],abbr[title]{cursor:help}address{margin-bottom:1rem;font-style:normal;line-height:inherit}dl,ol,ul{margin-top:0;margin-bottom:1rem}ol ol,ol ul,ul ol,ul ul{margin-bottom:0}dt{font-weight:700}dd{margin-bottom:.5rem;margin-left:0}blockquote{margin:0 0 1rem}a{color:#0275d8;text-decoration:none}a:focus,a:hover{color:#014c8c;text-decoration:underline}a:not([href]):not([tabindex]){color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus,a:not([href]):not([tabindex]):hover{color:inherit;text-decoration:none}a:not([href]):not([tabindex]):focus{outline:0}pre{margin-top:0;margin-bottom:1rem;overflow:auto}figure{margin:0 0 1rem}img{vertical-align:middle}[role=button]{cursor:pointer}[role=button],a,area,button,input,label,select,summary,textarea{-ms-touch-action:manipulation;touch-action:manipulation}table{border-collapse:collapse;background-color:transparent}caption{padding-top:.75rem;padding-bottom:.75rem;color:#636c72;text-align:left;caption-side:bottom}th{text-align:left}label{display:inline-block;margin-bottom:.5rem}button:focus{outline:1px dotted;outline:5px auto -webkit-focus-ring-color}button,input,select,textarea{line-height:inherit}input[type=checkbox]:disabled,input[type=radio]:disabled{cursor:not-allowed}input[type=date],input[type=time],input[type=datetime-local],input[type=month]{-webkit-appearance:listbox}textarea{resize:vertical}fieldset{min-width:0;padding:0;margin:0;border:0}legend{display:block;width:100%;padding:0;margin-bottom:.5rem;font-size:1.5rem;line-height:inherit}input[type=search]{-webkit-appearance:none}output{display:inline-block}[hidden]{display:none!important}/*# sourceMappingURL=bootstrap-reboot.min.css.map */
|
||||
1
web/static/css/bootstrap-reboot.min.css.map
Normal file
@@ -0,0 +1 @@
|
||||
{"version":3,"sources":["../../scss/_normalize.scss","bootstrap-reboot.css","../../scss/_reboot.scss","../../scss/_variables.scss","../../scss/mixins/_hover.scss"],"names":[],"mappings":"4EAYA,KACE,YAAA,WACA,YAAA,KACA,qBAAA,KACA,yBAAA,KAUF,KACE,OAAA,EAOF,QAAA,MAAA,OAAA,OAAA,IAAA,QAME,QAAA,MAQF,GACE,UAAA,IACA,OAAA,MAAA,EAWF,WAAA,OAAA,KAGE,QAAA,MAOF,OACE,OAAA,IAAA,KAQF,GACE,mBAAA,YAAA,WAAA,YACA,OAAA,EACA,SAAA,QAQF,IACE,YAAA,UAAA,UACA,UAAA,IAWF,EACE,iBAAA,YACA,6BAAA,QAQF,SAAA,QAEE,cAAA,EAQF,YACE,cAAA,KACA,gBAAA,UACA,gBAAA,UAAA,OAOF,EAAA,OAEE,YAAA,QAOF,EAAA,OAEE,YAAA,OAQF,KAAA,IAAA,KAGE,YAAA,UAAA,UACA,UAAA,IAOF,IACE,WAAA,OAOF,KACE,iBAAA,KACA,MAAA,KAOF,MACE,UAAA,IAQF,IAAA,IAEE,UAAA,IACA,YAAA,EACA,SAAA,SACA,eAAA,SAGF,IACE,OAAA,OAGF,IACE,IAAA,MAUF,MAAA,MAEE,QAAA,aAOF,sBACE,QAAA,KACA,OAAA,EAOF,IACE,aAAA,KAOF,eACE,SAAA,OAWF,OAAA,MAAA,SAAA,OAAA,SAKE,YAAA,WACA,UAAA,KACA,YAAA,KACA,OAAA,EAQF,OAAA,MAEE,SAAA,QAQF,OAAA,OAEE,eAAA,KASF,aAAA,cAAA,OAAA,mBAIE,mBAAA,OAOF,gCAAA,+BAAA,gCAAA,yBAIE,aAAA,KACA,QAAA,EAOF,6BAAA,4BAAA,6BAAA,sBAIE,QAAA,IAAA,OAAA,WAOF,SACE,OAAA,IAAA,MAAA,OACA,OAAA,EAAA,IACA,QAAA,MAAA,OAAA,MAUF,OACE,mBAAA,WAAA,WAAA,WACA,MAAA,QACA,QAAA,MACA,UAAA,KACA,QAAA,EACA,YAAA,OAQF,SACE,QAAA,aACA,eAAA,SAOF,SACE,SAAA,KCrKF,gBAAA,aD+KE,mBAAA,WAAA,WAAA,WACA,QAAA,EC1KF,yCAAA,yCDmLE,OAAA,KC9KF,cDuLE,mBAAA,UACA,eAAA,KCnLF,4CAAA,yCD4LE,mBAAA,KAQF,6BACE,mBAAA,OACA,KAAA,QAWF,QAAA,KAEE,QAAA,MAOF,QACE,QAAA,UAUF,OACE,QAAA,aAOF,SACE,QAAA,KCnNF,SD8NE,QAAA,KEtbF,KACE,mBAAA,WAAA,WAAA,WAGF,EAAA,QAAA,SAGE,mBAAA,QAAA,WAAA,QAoBA,cAAgB,MAAA,aAQlB,KAYE,mBAAA,UAGA,4BAAA,YAGF,KACE,YAAA,cAAA,UAAA,mBAAA,WAAA,OC2K4H,iBD3K5H,MAAA,WACA,UAAA,KACA,YAAA,IACA,YAAA,IAEA,MAAA,QAEA,iBAAA,KD2LF,sBClLE,QAAA,YAYF,GAAI,GAAI,GAAI,GAAI,GAAI,GAClB,WAAA,EACA,cAAA,MAOF,EACE,WAAA,EACA,cAAA,KAIF,0BAAA,YAGE,OAAA,KAGF,QACE,cAAA,KACA,WAAA,OACA,YAAA,QAGF,GAAA,GAAA,GAGE,WAAA,EACA,cAAA,KAGF,MAAA,MAAA,MAAA,MAIE,cAAA,EAGF,GACE,YAAA,IAGF,GACE,cAAA,MACA,YAAA,EAGF,WACE,OAAA,EAAA,EAAA,KAQF,EACE,MAAA,QACA,gBAAA,KEhJE,QAAA,QFmJA,MAAA,QACA,gBAAA,UAUJ,8BACE,MAAA,QACA,gBAAA,KEhKE,oCAAA,oCFmKA,MAAA,QACA,gBAAA,KANJ,oCAUI,QAAA,EASJ,IAEE,WAAA,EAEA,cAAA,KAEA,SAAA,KAQF,OAGE,OAAA,EAAA,EAAA,KAQF,IAGE,eAAA,ODsIF,cCzHE,OAAA,QAcF,cAAA,EAAA,KAAA,OAAA,MAAA,MAAA,OAAA,QAAA,SASE,iBAAA,aAAA,aAAA,aAQF,MAEE,gBAAA,SAEA,iBAAA,YAGF,QACE,YAAA,OACA,eAAA,OACA,MAAA,QACA,WAAA,KACA,aAAA,OAGF,GAEE,WAAA,KAQF,MAEE,QAAA,aACA,cAAA,MAOF,aACE,QAAA,IAAA,OACA,QAAA,IAAA,KAAA,yBAGF,OAAA,MAAA,OAAA,SAME,YAAA,QAGF,8BAAA,2BAMI,OAAA,YAKJ,iBAAA,iBAAA,2BAAA,kBASE,mBAAA,QAGF,SAEE,OAAA,SAGF,SAME,UAAA,EAEA,QAAA,EACA,OAAA,EACA,OAAA,EAGF,OAEE,QAAA,MACA,MAAA,KACA,QAAA,EACA,cAAA,MACA,UAAA,OACA,YAAA,QAGF,mBAKE,mBAAA,KAIF,OACE,QAAA,aDsEF,SC9DE,QAAA"}
|
||||
9320
web/static/css/bootstrap.css
vendored
Normal file
1
web/static/css/bootstrap.css.map
Normal file
6
web/static/css/bootstrap.min.css
vendored
Normal file
1
web/static/css/bootstrap.min.css.map
Normal file
70
web/static/css/dashboard.css
Normal file
@@ -0,0 +1,70 @@
|
||||
/*
|
||||
* Base structure
|
||||
*/
|
||||
|
||||
/* Move down content because we have a fixed navbar that is 50px tall */
|
||||
body {
|
||||
padding-top: 50px;
|
||||
}
|
||||
|
||||
/*
|
||||
* Typography
|
||||
*/
|
||||
|
||||
h1 {
|
||||
margin-bottom: 20px;
|
||||
padding-bottom: 9px;
|
||||
border-bottom: 1px solid #eee;
|
||||
}
|
||||
|
||||
/*
|
||||
* Sidebar
|
||||
*/
|
||||
|
||||
.sidebar {
|
||||
position: fixed;
|
||||
top: 51px;
|
||||
bottom: 0;
|
||||
left: 0;
|
||||
z-index: 1000;
|
||||
padding: 20px;
|
||||
overflow-x: hidden;
|
||||
overflow-y: auto; /* Scrollable contents if viewport is shorter than content. */
|
||||
border-right: 1px solid #eee;
|
||||
}
|
||||
|
||||
/* Sidebar navigation */
|
||||
.sidebar {
|
||||
padding-left: 0;
|
||||
padding-right: 0;
|
||||
}
|
||||
|
||||
.sidebar .nav {
|
||||
margin-bottom: 20px;
|
||||
}
|
||||
|
||||
.sidebar .nav-item {
|
||||
width: 100%;
|
||||
}
|
||||
|
||||
.sidebar .nav-item + .nav-item {
|
||||
margin-left: 0;
|
||||
}
|
||||
|
||||
.sidebar .nav-link {
|
||||
border-radius: 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Dashboard
|
||||
*/
|
||||
|
||||
/* Placeholders */
|
||||
.placeholders {
|
||||
padding-bottom: 3rem;
|
||||
}
|
||||
|
||||
.placeholder img {
|
||||
padding-top: 1.5rem;
|
||||
padding-bottom: 1.5rem;
|
||||
}
|
||||
75
web/static/css/def_style.css
Normal file
@@ -0,0 +1,75 @@
|
||||
/*BODY{font-family:monospace; background-color: #fafafa; padding: 0px; margin:0px;}*/
|
||||
div.textbox{
|
||||
width: 50em;
|
||||
margin-left: auto;
|
||||
margin-right: auto;
|
||||
margin-top: 2em;
|
||||
padding: 3em;
|
||||
border: 1px solid #ccc;
|
||||
border-top: 1px solid #ddd;
|
||||
border-bottom: 2px solid #000;
|
||||
border-right: 2px solid #000;
|
||||
background-color: #fff;
|
||||
}
|
||||
/*H1{font-size: 2em;}
|
||||
H2{font-size: 1em; color: #666;}
|
||||
h3{font-size: 0.8em; coloe: #eee;}
|
||||
TABLE{border:1px solid grey;}
|
||||
TD{padding: 2px;}
|
||||
TH{background-color: #eee;}*/
|
||||
IMG#profile{
|
||||
float: left;
|
||||
margin-right: 2em;
|
||||
margin-bottom: 4em;
|
||||
margin-top:4em;
|
||||
}
|
||||
#header{
|
||||
border-bottom: 2px solid #000;
|
||||
margin-bottom: 4em;
|
||||
padding: 8em;
|
||||
padding-top: 2em;
|
||||
padding-bottom: 1em;
|
||||
background-color: #fff;
|
||||
}
|
||||
#footer{
|
||||
font-size: 0.5em;
|
||||
color: #777;
|
||||
margin: 0px;
|
||||
margin-top: 4em;
|
||||
padding: 10em;
|
||||
padding-top: 2em;
|
||||
padding-bottom: 4em;
|
||||
border-top: 1px solid #ddd;
|
||||
background-color: #fefefe;
|
||||
}
|
||||
#menu{
|
||||
float: right;
|
||||
}
|
||||
.menuitem{
|
||||
float: left;
|
||||
font-size: 1.5em;
|
||||
color: #000;
|
||||
}
|
||||
.menuitem_current{
|
||||
float: left;
|
||||
font-size: 1.5em;
|
||||
color: #000;
|
||||
font-weight: bold;
|
||||
}
|
||||
.menuitem a{
|
||||
color: #666;
|
||||
text-decoration: none;
|
||||
}
|
||||
.menuitem a:hover{
|
||||
color: #600;
|
||||
}
|
||||
.cleaner{
|
||||
clear: both;
|
||||
display: inline;
|
||||
height: 0px;
|
||||
}
|
||||
#logo{
|
||||
|
||||
}
|
||||
.left{float: left;}
|
||||
.right{float: right;}
|
||||
117
web/static/css/dygraph.css
Normal file
@@ -0,0 +1,117 @@
|
||||
/**
|
||||
* Default styles for the dygraphs charting library.
|
||||
*/
|
||||
|
||||
.dygraph-legend {
|
||||
position: absolute;
|
||||
font-size: 14px;
|
||||
z-index: 10;
|
||||
width: 250px; /* labelsDivWidth */
|
||||
/*
|
||||
dygraphs determines these based on the presence of chart labels.
|
||||
It might make more sense to create a wrapper div around the chart proper.
|
||||
top: 0px;
|
||||
right: 2px;
|
||||
*/
|
||||
background: white;
|
||||
line-height: normal;
|
||||
text-align: left;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* styles for a solid line in the legend */
|
||||
.dygraph-legend-line {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
bottom: .5ex;
|
||||
padding-left: 1em;
|
||||
height: 1px;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-style: solid;
|
||||
/* border-bottom-color is set based on the series color */
|
||||
}
|
||||
|
||||
/* styles for a dashed line in the legend, e.g. when strokePattern is set */
|
||||
.dygraph-legend-dash {
|
||||
display: inline-block;
|
||||
position: relative;
|
||||
bottom: .5ex;
|
||||
height: 1px;
|
||||
border-bottom-width: 2px;
|
||||
border-bottom-style: solid;
|
||||
/* border-bottom-color is set based on the series color */
|
||||
/* margin-right is set based on the stroke pattern */
|
||||
/* padding-left is set based on the stroke pattern */
|
||||
}
|
||||
|
||||
.dygraph-roller {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
}
|
||||
|
||||
/* This class is shared by all annotations, including those with icons */
|
||||
.dygraph-annotation {
|
||||
position: absolute;
|
||||
z-index: 10;
|
||||
overflow: hidden;
|
||||
}
|
||||
|
||||
/* This class only applies to annotations without icons */
|
||||
/* Old class name: .dygraphDefaultAnnotation */
|
||||
.dygraph-default-annotation {
|
||||
border: 1px solid black;
|
||||
background-color: white;
|
||||
text-align: center;
|
||||
}
|
||||
|
||||
.dygraph-axis-label {
|
||||
/* position: absolute; */
|
||||
/* font-size: 14px; */
|
||||
z-index: 10;
|
||||
line-height: normal;
|
||||
overflow: hidden;
|
||||
color: black; /* replaces old axisLabelColor option */
|
||||
}
|
||||
|
||||
.dygraph-axis-label-x {
|
||||
}
|
||||
|
||||
.dygraph-axis-label-y {
|
||||
}
|
||||
|
||||
.dygraph-axis-label-y2 {
|
||||
}
|
||||
|
||||
.dygraph-title {
|
||||
font-weight: bold;
|
||||
z-index: 10;
|
||||
text-align: center;
|
||||
/* font-size: based on titleHeight option */
|
||||
}
|
||||
|
||||
.dygraph-xlabel {
|
||||
text-align: center;
|
||||
/* font-size: based on xLabelHeight option */
|
||||
}
|
||||
|
||||
/* For y-axis label */
|
||||
.dygraph-label-rotate-left {
|
||||
text-align: center;
|
||||
/* See http://caniuse.com/#feat=transforms2d */
|
||||
transform: rotate(90deg);
|
||||
-webkit-transform: rotate(90deg);
|
||||
-moz-transform: rotate(90deg);
|
||||
-o-transform: rotate(90deg);
|
||||
-ms-transform: rotate(90deg);
|
||||
}
|
||||
|
||||
/* For y2-axis label */
|
||||
.dygraph-label-rotate-right {
|
||||
text-align: center;
|
||||
/* See http://caniuse.com/#feat=transforms2d */
|
||||
transform: rotate(-90deg);
|
||||
-webkit-transform: rotate(-90deg);
|
||||
-moz-transform: rotate(-90deg);
|
||||
-o-transform: rotate(-90deg);
|
||||
-ms-transform: rotate(-90deg);
|
||||
}
|
||||
BIN
web/static/img/53096.jpg
Normal file
|
After Width: | Height: | Size: 15 KiB |
52
web/static/img/53096.svg
Normal file
@@ -0,0 +1,52 @@
|
||||
<?xml version="1.0" encoding="iso-8859-1"?>
|
||||
<!-- Generator: Adobe Illustrator 16.0.0, SVG Export Plug-In . SVG Version: 6.00 Build 0) -->
|
||||
<!DOCTYPE svg PUBLIC "-//W3C//DTD SVG 1.1//EN" "http://www.w3.org/Graphics/SVG/1.1/DTD/svg11.dtd">
|
||||
<svg version="1.1" id="Capa_1" xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" x="0px" y="0px"
|
||||
width="938px" height="938px" viewBox="0 0 938 938" style="enable-background:new 0 0 938 938;" xml:space="preserve">
|
||||
<g>
|
||||
<g>
|
||||
<path d="M50.792,747.965c0,13.415,10.875,24.29,24.29,24.29h787.836c13.415,0,24.29-10.875,24.29-24.29v-408.36H50.792V747.965z
|
||||
M649.792,476.871c0-6.419,5.203-11.622,11.622-11.622h112.098c6.419,0,11.622,5.203,11.622,11.622v27.54
|
||||
c0,6.419-5.203,11.622-11.622,11.622H661.414c-6.419,0-11.622-5.203-11.622-11.622V476.871z M152.866,476.295
|
||||
c0-6.091,4.938-11.029,11.029-11.029h36.764v-36.8c0-6.091,4.938-11.028,11.029-11.028h28.725c6.091,0,11.029,4.938,11.029,11.028
|
||||
v36.8h36.764c6.091,0,11.029,4.938,11.029,11.029v28.727c0,6.091-4.938,11.028-11.029,11.028h-36.764v36.765
|
||||
c0,6.091-4.938,11.028-11.029,11.028h-28.725c-6.091,0-11.029-4.938-11.029-11.028V516.05h-36.764
|
||||
c-6.091,0-11.029-4.938-11.029-11.028V476.295L152.866,476.295z"/>
|
||||
<path d="M915.285,314.605c12.545,0,22.715-10.17,22.715-22.715v-38.164c0-12.545-10.17-22.715-22.715-22.715H788.537v-42.552
|
||||
c0-12.545-10.17-22.715-22.715-22.715h-97.83c-12.545,0-22.715,10.17-22.715,22.715v42.552H301.611v-42.552
|
||||
c0-12.545-10.17-22.715-22.715-22.715h-97.83c-12.545,0-22.715,10.17-22.715,22.715v42.552H22.715
|
||||
C10.17,231.01,0,241.181,0,253.727v38.165c0,12.545,10.17,22.715,22.715,22.715h28.076h836.417L915.285,314.605L915.285,314.605z"
|
||||
/>
|
||||
</g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
<g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 1.9 KiB |
BIN
web/static/img/53096_s.jpg
Normal file
|
After Width: | Height: | Size: 921 B |
BIN
web/static/img/battery_0.png
Normal file
|
After Width: | Height: | Size: 4.0 KiB |
BIN
web/static/img/battery_20.png
Normal file
|
After Width: | Height: | Size: 5.9 KiB |
BIN
web/static/img/battery_40.png
Normal file
|
After Width: | Height: | Size: 8.6 KiB |
BIN
web/static/img/battery_60.png
Normal file
|
After Width: | Height: | Size: 11 KiB |
BIN
web/static/img/battery_80.png
Normal file
|
After Width: | Height: | Size: 13 KiB |
BIN
web/static/img/battery_charging.png
Normal file
|
After Width: | Height: | Size: 12 KiB |
BIN
web/static/img/battery_current.png
Normal file
|
After Width: | Height: | Size: 10 KiB |
BIN
web/static/img/battery_full.png
Normal file
|
After Width: | Height: | Size: 15 KiB |
BIN
web/static/img/battery_power.png
Normal file
|
After Width: | Height: | Size: 19 KiB |
BIN
web/static/img/humidity_icon.png
Normal file
|
After Width: | Height: | Size: 9.2 KiB |
BIN
web/static/img/inside_temp_icon.png
Normal file
|
After Width: | Height: | Size: 29 KiB |
BIN
web/static/img/outside_temp_icon.png
Normal file
|
After Width: | Height: | Size: 23 KiB |
BIN
web/static/img/pressure_icon.png
Normal file
|
After Width: | Height: | Size: 7.3 KiB |
BIN
web/static/img/temperature_icon.png
Normal file
|
After Width: | Height: | Size: 17 KiB |
BIN
web/static/img/wind_direction_icon.png
Normal file
|
After Width: | Height: | Size: 90 KiB |
BIN
web/static/img/wind_gust_icon.png
Normal file
|
After Width: | Height: | Size: 3.1 KiB |
BIN
web/static/img/wind_icon.png
Normal file
|
After Width: | Height: | Size: 4.6 KiB |
3535
web/static/js/bootstrap.js
vendored
Normal file
7
web/static/js/bootstrap.min.js
vendored
Normal file
9383
web/static/js/dygraph.js
Normal file
24
web/static/js/ie10bugfix.js
Normal file
@@ -0,0 +1,24 @@
|
||||
/*!
|
||||
* IE10 viewport hack for Surface/desktop Windows 8 bug
|
||||
* Copyright 2014-2017 The Bootstrap Authors
|
||||
* Copyright 2014-2017 Twitter, Inc.
|
||||
* Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)
|
||||
*/
|
||||
|
||||
// See the Getting Started docs for more information:
|
||||
// https://getbootstrap.com/getting-started/#support-ie10-width
|
||||
|
||||
(function () {
|
||||
'use strict'
|
||||
|
||||
if (navigator.userAgent.match(/IEMobile\/10\.0/)) {
|
||||
var msViewportStyle = document.createElement('style')
|
||||
msViewportStyle.appendChild(
|
||||
document.createTextNode(
|
||||
'@-ms-viewport{width:auto!important}'
|
||||
)
|
||||
)
|
||||
document.head.appendChild(msViewportStyle)
|
||||
}
|
||||
|
||||
}())
|
||||
120
web/static/js/solar_graph.js
Normal file
@@ -0,0 +1,120 @@
|
||||
var hours = 24;
|
||||
var granularity = '2m';
|
||||
var end = 0;
|
||||
var graphdata = "https://bastart.spoton.cz/data/solar_monitor?range=24h&granularity=2m&end=0h";
|
||||
sol = new Dygraph(
|
||||
// containing div
|
||||
document.getElementById("solar"),
|
||||
// CSV or path to a CSV file.
|
||||
graphdata
|
||||
,{
|
||||
//labels: ['time','V_solar','Isolar', P_solar, P_cons],
|
||||
axes : {
|
||||
x : {
|
||||
drawGrid: true,
|
||||
drawAxis : true
|
||||
},
|
||||
y : {
|
||||
drawGrid: false,
|
||||
drawAxis : true
|
||||
},
|
||||
y2 : {
|
||||
drawGrid: false,
|
||||
drawAxis: true,
|
||||
independentTicks: true
|
||||
}
|
||||
},
|
||||
rollPeriod: 5,
|
||||
visibility: [true, false, true, true],
|
||||
interactionModel: {},
|
||||
connectSeparatedPoints: true,
|
||||
series:{
|
||||
'V_solar': {
|
||||
axis: 'y',
|
||||
color: '#ffd020',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.4
|
||||
},
|
||||
'I_solar': {
|
||||
axis: 'y',
|
||||
color: '#ff1100'
|
||||
},
|
||||
'P_solar': {
|
||||
axis: 'y2',
|
||||
color: '#1111ff',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.4
|
||||
},
|
||||
'P_cons': {
|
||||
axis: 'y2',
|
||||
color: '#ff1111',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.4
|
||||
}
|
||||
|
||||
},
|
||||
ylabel: '<span style="color:#ffd020;">[V]</span>/<span style="color:#ff1100;">[A]</span>',
|
||||
y2label: '<span style="color:#111177;">Solar / Consumption [W]</span>',
|
||||
labelsDiv: 'solar_labels',
|
||||
legend: 'always'
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
function refreshGraph(){
|
||||
graphdata = "https://bastart.spoton.cz/data/solar_monitor?range=" + hours + "h&granularity=" + granularity + "&end=" + end + "h";
|
||||
sol.updateOptions({'file': graphdata});
|
||||
//power.updateOptions({'file': graphdata});
|
||||
}
|
||||
|
||||
function setHours(hours_to_set){
|
||||
hours = hours_to_set;
|
||||
switch(hours){
|
||||
case '1':
|
||||
granularity = '30s';
|
||||
break;
|
||||
case '6':
|
||||
granularity = '1m';
|
||||
break;
|
||||
case '12':
|
||||
granularity = '1m';
|
||||
break;
|
||||
case '24':
|
||||
granularity = '2m';
|
||||
break;
|
||||
case '168':
|
||||
granularity = '20m';
|
||||
break;
|
||||
case '720':
|
||||
granularity = '1h';
|
||||
break;
|
||||
default:
|
||||
granularity = '10m';
|
||||
}
|
||||
end = 0;
|
||||
//document.getElementById('xxx').innerHTML = graphdata;
|
||||
refreshGraph();
|
||||
}
|
||||
|
||||
function setBack(){
|
||||
// range=1h -> range=2h&end=1h
|
||||
disp_range = hours*1 - end*1;
|
||||
hours = hours*1 + disp_range;
|
||||
end = end*1 + disp_range;
|
||||
//document.getElementById('xxx').innerHTML = graphdata;
|
||||
refreshGraph();
|
||||
}
|
||||
|
||||
function setForth(){
|
||||
disp_range = hours*1 - end*1;
|
||||
hours = hours*1 - disp_range;
|
||||
if(hours < disp_range){
|
||||
hours = disp_range;
|
||||
}
|
||||
end = end*1 - disp_range;
|
||||
if(end < 0){
|
||||
end = 0;
|
||||
}
|
||||
//document.getElementById('xxx').innerHTML = graphdata;
|
||||
refreshGraph();
|
||||
}
|
||||
174
web/static/js/solcap_graph.js
Normal file
@@ -0,0 +1,174 @@
|
||||
var hours = 24;
|
||||
var granularity = '2m';
|
||||
var end = 0;
|
||||
var solcapdata = "https://bastart.spoton.cz/data/solcap_monitor?range=24h&granularity=2m&end=0h";
|
||||
var cpumemdata = "https://bastart.spoton.cz/data/cpumem_monitor?range=24h&granularity=2m&end=0h";
|
||||
|
||||
solcap = new Dygraph(
|
||||
// containing div
|
||||
document.getElementById("solcap"),
|
||||
// CSV or path to a CSV file.
|
||||
solcapdata
|
||||
,{
|
||||
//labels: ['time','Solar','Capacitor'],
|
||||
axes : {
|
||||
x : {
|
||||
drawGrid: true,
|
||||
drawAxis : true
|
||||
},
|
||||
y : {
|
||||
drawGrid: false,
|
||||
drawAxis : true,
|
||||
valueRange: [0,4.6]
|
||||
},
|
||||
y2 : {
|
||||
drawGrid: false,
|
||||
drawAxis: true,
|
||||
valueRange: [0,8.6],
|
||||
independentTicks: true
|
||||
}
|
||||
},
|
||||
rollPeriod: 5,
|
||||
interactionModel: {},
|
||||
connectSeparatedPoints: true,
|
||||
series:{
|
||||
'Solar': {
|
||||
axis: 'y',
|
||||
color: '#888844',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.4
|
||||
},
|
||||
'Capacitor': {
|
||||
axis: 'y2',
|
||||
color: '#884444'
|
||||
}
|
||||
|
||||
},
|
||||
ylabel: '<span style="color:#888844;">Solar Irradiance</span>',
|
||||
y2label: '<span style="color:#884444;">Capacitor [V]</span>',
|
||||
labelsDiv: 'solcap_labels',
|
||||
legend: 'always'
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
cpumem = new Dygraph(
|
||||
// containing div
|
||||
document.getElementById("cpumem"),
|
||||
// CSV or path to a CSV file.
|
||||
cpumemdata
|
||||
,{
|
||||
//labels: ['time','cpu','mem','disk'],
|
||||
axes : {
|
||||
x : {
|
||||
drawGrid: true,
|
||||
drawAxis : true
|
||||
},
|
||||
y : {
|
||||
drawGrid: false,
|
||||
drawAxis : true,
|
||||
valueRange: [0,100]
|
||||
},
|
||||
y2 : {
|
||||
drawGrid: false,
|
||||
drawAxis: true,
|
||||
valueRange: [0,100],
|
||||
independentTicks: true
|
||||
}
|
||||
},
|
||||
rollPeriod: 5,
|
||||
interactionModel: {},
|
||||
connectSeparatedPoints: true,
|
||||
series:{
|
||||
'Cpu': {
|
||||
axis: 'y',
|
||||
color: '#888844',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.4
|
||||
},
|
||||
'Mem': {
|
||||
axis: 'y2',
|
||||
color: '#884444'
|
||||
},
|
||||
'Disk': {
|
||||
axis: 'y2',
|
||||
color: '#448844'
|
||||
}
|
||||
|
||||
},
|
||||
ylabel: '<span style="color:#888844;">CPU [%]</span>',
|
||||
y2label: '<span style="color:#884444;">MEM / DISK [%]</span>',
|
||||
labelsDiv: 'cpumem_labels',
|
||||
legend: 'always'
|
||||
}
|
||||
);
|
||||
|
||||
function refreshGraph(source){
|
||||
graphdata = "https://bastart.spoton.cz/data/" + source + "?range=" + hours + "h&granularity=" + granularity + "&end=" + end + "h";
|
||||
if(source == 'solcap_monitor'){
|
||||
solcap.updateOptions({'file': graphdata});
|
||||
}
|
||||
if(source == 'cpumem_monitor'){
|
||||
cpumem.updateOptions({'file': graphdata});
|
||||
}
|
||||
//document.getElementById('xxx').innerHTML = source;
|
||||
//power.updateOptions({'file': graphdata});
|
||||
}
|
||||
|
||||
function setHours(hours_to_set, target){
|
||||
hours = hours_to_set;
|
||||
switch(hours){
|
||||
case '1':
|
||||
granularity = '10s';
|
||||
break;
|
||||
case '6':
|
||||
granularity = '2m';
|
||||
break;
|
||||
case '12':
|
||||
granularity = '2m';
|
||||
break;
|
||||
case '24':
|
||||
granularity = '5m';
|
||||
break;
|
||||
case '168':
|
||||
granularity = '10m';
|
||||
break;
|
||||
case '720':
|
||||
granularity = '1h';
|
||||
break;
|
||||
case '4320':
|
||||
granularity = '6h';
|
||||
break;
|
||||
case '8640':
|
||||
granularity = '1d';
|
||||
break;
|
||||
default:
|
||||
granularity = '10m';
|
||||
}
|
||||
end = 0;
|
||||
//document.getElementById('xxx').innerHTML = target;
|
||||
refreshGraph(target);
|
||||
}
|
||||
|
||||
function setBack(target){
|
||||
// range=1h -> range=2h&end=1h
|
||||
disp_range = hours*1 - end*1;
|
||||
hours = hours*1 + disp_range;
|
||||
end = end*1 + disp_range;
|
||||
//document.getElementById('xxx').innerHTML = graphdata;
|
||||
refreshGraph(target);
|
||||
}
|
||||
|
||||
function setForth(target){
|
||||
disp_range = hours*1 - end*1;
|
||||
hours = hours*1 - disp_range;
|
||||
if(hours < disp_range){
|
||||
hours = disp_range;
|
||||
}
|
||||
end = end*1 - disp_range;
|
||||
if(end < 0){
|
||||
end = 0;
|
||||
}
|
||||
//document.getElementById('xxx').innerHTML = graphdata;
|
||||
refreshGraph(target);
|
||||
}
|
||||
296
web/static/js/weather_graph.js
Normal file
@@ -0,0 +1,296 @@
|
||||
var hours = 24;
|
||||
var granularity = '1m';
|
||||
var end = 0;
|
||||
var winddata = "https://bastart.spoton.cz/data/wind_monitor?range=24h&granularity=1m&end=0h";
|
||||
var temphumidata = "https://bastart.spoton.cz/data/temphumi_monitor?range=24h&granularity=2m&end=0h";
|
||||
var pressuredata = "https://bastart.spoton.cz/data/pressure_monitor?range=168h&granularity=2m&end=0h";
|
||||
wind = new Dygraph(
|
||||
// containing div
|
||||
document.getElementById("wind"),
|
||||
// CSV or path to a CSV file.
|
||||
winddata
|
||||
,{
|
||||
//labels: ['time','Speed','Gust', 'Direction'],
|
||||
axes : {
|
||||
x : {
|
||||
drawGrid: true,
|
||||
drawAxis : true
|
||||
},
|
||||
y : {
|
||||
drawGrid: false,
|
||||
drawAxis : true,
|
||||
},
|
||||
y2 : {
|
||||
drawGrid: false,
|
||||
drawAxis: true,
|
||||
valueRange: [0,360],
|
||||
independentTicks: true
|
||||
}
|
||||
},
|
||||
rollPeriod: 20,
|
||||
interactionModel: {},
|
||||
connectSeparatedPoints: true,
|
||||
series:{
|
||||
'Speed': {
|
||||
axis: 'y',
|
||||
color: '#444444',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.4
|
||||
},
|
||||
'Gusts': {
|
||||
axis: 'y',
|
||||
color: '#ff5555'
|
||||
},
|
||||
'Direction': {
|
||||
axis: 'y2',
|
||||
color: '#999999'
|
||||
}
|
||||
|
||||
},
|
||||
ylabel: '<span style="color:#444444;">Speed<span style="color:#444444;"> / <span style="color:#ff5555;">Gusts</span> [km/h]',
|
||||
y2label: '<span style="color:#999999;">Direction [°]</span>',
|
||||
labelsDiv: 'wind_labels',
|
||||
legend: 'always'
|
||||
}
|
||||
);
|
||||
|
||||
temphumi_out = new Dygraph(
|
||||
// containing div
|
||||
document.getElementById("temphumi"),
|
||||
// CSV or path to a CSV file.
|
||||
temphumidata
|
||||
,{
|
||||
//labels: [time,T(ins),T(out),Humi(ins),Humi(out)],
|
||||
axes : {
|
||||
x : {
|
||||
drawGrid: true,
|
||||
drawAxis : false
|
||||
},
|
||||
y : {
|
||||
drawGrid: false,
|
||||
drawAxis : true,
|
||||
},
|
||||
y2 : {
|
||||
drawGrid: false,
|
||||
drawAxis: true,
|
||||
independentTicks: true
|
||||
}
|
||||
},
|
||||
rollPeriod: 10,
|
||||
visibility: [false, true, false, true],
|
||||
interactionModel: {},
|
||||
connectSeparatedPoints: true,
|
||||
series:{
|
||||
'T(out)': {
|
||||
axis: 'y',
|
||||
color: '#705555',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.2
|
||||
},
|
||||
'T(ins)': {
|
||||
axis: 'y',
|
||||
color: '#705555',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.2
|
||||
},
|
||||
'Humi(out)': {
|
||||
axis: 'y2',
|
||||
color: '#222288'
|
||||
},
|
||||
'Humi(ins)': {
|
||||
axis: 'y2',
|
||||
color: '#222288'
|
||||
}
|
||||
|
||||
},
|
||||
ylabel: '<span style="color:#555555;">Outside [°C]</span>',
|
||||
y2label: '<span style="color:#222288;">Humidity [%]</span>',
|
||||
labelsDiv: 'temphumi_labels',
|
||||
legend: 'always'
|
||||
}
|
||||
);
|
||||
|
||||
temphumi_in = new Dygraph(
|
||||
// containing div
|
||||
document.getElementById("temphumi_in"),
|
||||
// CSV or path to a CSV file.
|
||||
temphumidata
|
||||
,{
|
||||
//labels: [time,T(ins),T(out),Humi(ins),Humi(out)],
|
||||
axes : {
|
||||
x : {
|
||||
drawGrid: true,
|
||||
drawAxis : true
|
||||
},
|
||||
y : {
|
||||
drawGrid: false,
|
||||
drawAxis : true,
|
||||
valueRange: [10,35]
|
||||
},
|
||||
y2 : {
|
||||
drawGrid: false,
|
||||
drawAxis: true,
|
||||
independentTicks: true,
|
||||
valueRange: [0,100]
|
||||
}
|
||||
},
|
||||
rollPeriod: 10,
|
||||
visibility: [true, false, true, false],
|
||||
interactionModel: {},
|
||||
connectSeparatedPoints: true,
|
||||
series:{
|
||||
'T(out)': {
|
||||
axis: 'y',
|
||||
color: '#705555',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.2
|
||||
},
|
||||
'T(ins)': {
|
||||
axis: 'y',
|
||||
color: '#705555',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.2
|
||||
},
|
||||
'Humi(out)': {
|
||||
axis: 'y2',
|
||||
color: '#222288'
|
||||
},
|
||||
'Humi(ins)': {
|
||||
axis: 'y2',
|
||||
color: '#222288'
|
||||
}
|
||||
|
||||
},
|
||||
ylabel: '<span style="color:#555555;">Inside [°C]</span>',
|
||||
y2label: '<span style="color:#222288;">Humidity [%]</span>',
|
||||
labelsDiv: 'temphumi_labels_in',
|
||||
legend: 'always'
|
||||
}
|
||||
);
|
||||
|
||||
pressure = new Dygraph(
|
||||
// containing div
|
||||
document.getElementById("pressure"),
|
||||
// CSV or path to a CSV file.
|
||||
pressuredata
|
||||
,{
|
||||
//labels: [time,Pressure],
|
||||
axes : {
|
||||
x : {
|
||||
drawGrid: true,
|
||||
drawAxis : true
|
||||
},
|
||||
y : {
|
||||
drawGrid: false,
|
||||
drawAxis : true,
|
||||
valueRange: [970,1055]
|
||||
},
|
||||
y2 : {
|
||||
drawGrid: false,
|
||||
drawAxis: true,
|
||||
valueRange: [970,1055]
|
||||
}
|
||||
},
|
||||
rollPeriod: 10,
|
||||
interactionModel: {},
|
||||
connectSeparatedPoints: true,
|
||||
visibility: [true, false],
|
||||
series:{
|
||||
'Pressure': {
|
||||
axis: 'y',
|
||||
color: '#557070',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.5
|
||||
},
|
||||
'Pressure': {
|
||||
axis: 'y2',
|
||||
color: '#557070',
|
||||
fillGraph: true,
|
||||
fillAlpha: 0.5
|
||||
}
|
||||
|
||||
},
|
||||
ylabel: '<span style="color:#555555;">Pressure [hPa]</span>',
|
||||
labelsDiv: 'pressure_labels',
|
||||
legend: 'always'
|
||||
}
|
||||
);
|
||||
|
||||
|
||||
function refreshGraph(source){
|
||||
graphdata = "https://bastart.spoton.cz/data/" + source + "?range=" + hours + "h&granularity=" + granularity + "&end=" + end + "h";
|
||||
if(source == 'wind_monitor'){
|
||||
wind.updateOptions({'file': graphdata});
|
||||
}
|
||||
if(source == 'temphumi_monitor'){
|
||||
temphumi_out.updateOptions({'file': graphdata});
|
||||
temphumi_in.updateOptions({'file': graphdata});
|
||||
}
|
||||
if(source == 'pressure_monitor'){
|
||||
pressure.updateOptions({'file': graphdata});
|
||||
}
|
||||
//document.getElementById('xxx').innerHTML = source;
|
||||
//power.updateOptions({'file': graphdata});
|
||||
}
|
||||
|
||||
function setHours(hours_to_set, target){
|
||||
hours = hours_to_set;
|
||||
switch(hours){
|
||||
case '1':
|
||||
granularity = '1m';
|
||||
if(target == 'temphumi_monitor'){ granularity = '2m';}
|
||||
break;
|
||||
case '6':
|
||||
granularity = '1m';
|
||||
if(target == 'temphumi_monitor'){ granularity = '2m';}
|
||||
break;
|
||||
case '12':
|
||||
granularity = '1m';
|
||||
if(target == 'temphumi_monitor'){ granularity = '2m';}
|
||||
break;
|
||||
case '24':
|
||||
granularity = '1m';
|
||||
if(target == 'temphumi_monitor'){ granularity = '2m';}
|
||||
break;
|
||||
case '168':
|
||||
granularity = '10m';
|
||||
break;
|
||||
case '720':
|
||||
granularity = '1h';
|
||||
break;
|
||||
case '4320':
|
||||
granularity = '6h';
|
||||
break;
|
||||
case '8640':
|
||||
granularity = '1d';
|
||||
break;
|
||||
default:
|
||||
granularity = '10m';
|
||||
}
|
||||
end = 0;
|
||||
//document.getElementById('xxx').innerHTML = target;
|
||||
refreshGraph(target);
|
||||
}
|
||||
|
||||
function setBack(target){
|
||||
// range=1h -> range=2h&end=1h
|
||||
disp_range = hours*1 - end*1;
|
||||
hours = hours*1 + disp_range;
|
||||
end = end*1 + disp_range;
|
||||
//document.getElementById('xxx').innerHTML = graphdata;
|
||||
refreshGraph(target);
|
||||
}
|
||||
|
||||
function setForth(target){
|
||||
disp_range = hours*1 - end*1;
|
||||
hours = hours*1 - disp_range;
|
||||
if(hours < disp_range){
|
||||
hours = disp_range;
|
||||
}
|
||||
end = end*1 - disp_range;
|
||||
if(end < 0){
|
||||
end = 0;
|
||||
}
|
||||
//document.getElementById('xxx').innerHTML = graphdata;
|
||||
refreshGraph(target);
|
||||
}
|
||||
106
web/status.py
Executable file
@@ -0,0 +1,106 @@
|
||||
#!/usr/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import cherrypy
|
||||
import influxdb
|
||||
import time
|
||||
import json
|
||||
|
||||
# Universal variables
|
||||
_SCRIPT_PATH = os.path.dirname(sys.argv[0])
|
||||
_STATIC_DIR = '/templates/' # Needs to have trailing and leading slash '/'
|
||||
|
||||
influx_host = 'localhost'
|
||||
influx_port = 8086
|
||||
influx_user = 'pi'
|
||||
influx_pwd = 'freedavis'
|
||||
influx_status_db = 'status'
|
||||
|
||||
# Icons
|
||||
fs_sol_icon = '../static/img/iss_solar_icon.png'
|
||||
fs_cap_icon = '../static/img/iss_capacitor_icon.png'
|
||||
|
||||
# Functions
|
||||
def read_html(filename):
|
||||
read_path = _SCRIPT_PATH + _STATIC_DIR + filename + '.html'
|
||||
try:
|
||||
with open(read_path, 'r') as handle:
|
||||
return handle.read()
|
||||
except:
|
||||
return """<div>ERROR!</div>""" + read_path
|
||||
|
||||
# Set default structure
|
||||
|
||||
html_start = '<html>'
|
||||
body_start = '<body>'
|
||||
body_close = '</body>'
|
||||
html_close = '</html>'
|
||||
|
||||
class StatusInfo(object):
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
header = read_html('header')
|
||||
menu_raw = read_html('top_menu')
|
||||
menu = menu_raw.format(energy = '', weather = '', status = 'active')
|
||||
body = self.body()
|
||||
footer = read_html('footer')
|
||||
result = header\
|
||||
+ menu\
|
||||
+ body\
|
||||
+ footer
|
||||
return result
|
||||
|
||||
def LastKnownState(self):
|
||||
'''
|
||||
Function to get energy readings from InfluxDB.
|
||||
|
||||
returns:
|
||||
dict(): {"time": str(time_stamp),
|
||||
"solar": solar,
|
||||
"cap": cap}
|
||||
'''
|
||||
|
||||
influx_status_client = influxdb.client.InfluxDBClient(
|
||||
influx_host, influx_port, influx_user, influx_pwd, influx_status_db
|
||||
)
|
||||
|
||||
# General query
|
||||
query1 = "SELECT time, voltage FROM iss WHERE type = "
|
||||
query2 = "ORDER BY time DESC LIMIT 1"
|
||||
|
||||
# now parse the tag in the middle of the two q from above
|
||||
solar_q = "{} '{}' {}".format(query1, "solar", query2)
|
||||
cap_q = "{} '{}' {}".format(query1, "capcaitor", query2)
|
||||
|
||||
# the actual query to DB
|
||||
solar = influx_status_client.query(solar_q)
|
||||
cap = influx_status_client.query(cap_q)
|
||||
|
||||
# returned is a list, in this case, we just need one value [0]
|
||||
result_solar = [sol for sol in solar][0][0]
|
||||
result_cap = [cap_ for cap_ in cap][0][0]
|
||||
|
||||
# Put the time to a uhman readable format, strip nanosecs
|
||||
time_stamp = time.strptime(result_solar['time'].split('.')[0],
|
||||
"%Y-%m-%dT%H:%M:%S")
|
||||
result = {}
|
||||
|
||||
# Construct the result to return
|
||||
result.update({"time": time_stamp})
|
||||
result.update({"solar": round(result_solar['voltage'], 1)})
|
||||
result.update({"cap": round(result_cap['voltage'], 1)})
|
||||
return result
|
||||
|
||||
|
||||
def body(self):
|
||||
admin_preformat = read_html('status_admin')
|
||||
current_status = self.LastKnownState()
|
||||
admin_html = admin_preformat.format(
|
||||
timestamp = time.strftime("%d.%m.%Y %H:%M:%S",
|
||||
current_status['time']),
|
||||
sol_icon = fs_sol_icon,
|
||||
cap_icon = fs_cap_icon,
|
||||
sol_value = round(current_status['solar'] / 100, 2),
|
||||
cap_value = current_status['cap']
|
||||
)
|
||||
return admin_html
|
||||
7
web/templates/footer.html
Normal file
@@ -0,0 +1,7 @@
|
||||
<div id="footer">
|
||||
Created in 101 minutes, in VI, while sitting in a train Wien -> Bratislava.
|
||||
Uploeaded over EDGE with a lot of signal-less territory on roaming. Cheers.
|
||||
Done by Ventil.
|
||||
</div>
|
||||
</body>
|
||||
</html>
|
||||
23
web/templates/header.html
Normal file
@@ -0,0 +1,23 @@
|
||||
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
|
||||
"http://www.w3.org/TR/html4/loose.dtd">
|
||||
<html>
|
||||
<head>
|
||||
<title>Energy reporter</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1, shrink-to-fit=no">
|
||||
<meta http-equiv="Content-Type" content="text/html; charset=utf-8">
|
||||
<meta http-equiv="Content-Language" content="en">
|
||||
<meta name="Robots" content="all">
|
||||
<meta name="Keywords" content="automation, python, development, arduino, measurement">
|
||||
<meta name="description" content="RasPi automation hardcore">
|
||||
<meta name="Author" content="Spoton s.r.o. | Milan Toman">
|
||||
<link rel="stylesheet" type="text/css" href="./static/css/def_style.css">
|
||||
<!-- Bootstrap core CSS -->
|
||||
<link href="../static/css/bootstrap.min.css" rel="stylesheet">
|
||||
<!-- Custom styles for this template -->
|
||||
<link href="../static/css/dashboard.css" rel="stylesheet">
|
||||
<!-- Dygraph css and js -->
|
||||
<link href="../static/css/dygraph.css" rel="stylesheet">
|
||||
<script type="text/javascript" src="../static/js/dygraph.js"></script>
|
||||
<link rel="icon" href="../../favicon.ico">
|
||||
</head>
|
||||
<body>
|
||||
65
web/templates/status_admin.html
Normal file
@@ -0,0 +1,65 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
|
||||
<div class="col"></div>
|
||||
<main class="pt-4 col-10">
|
||||
<h1>Status Dashboard</h1>
|
||||
<h5>{timestamp} UTC</h5>
|
||||
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col placeholder">
|
||||
<img src="{sol_icon}" width="20" class="img-fluid" alt="ISS solar irradiation">
|
||||
<h3>{sol_value}</h3>
|
||||
<div class="text-muted">[V]</div>
|
||||
</div>
|
||||
<div class="col placeholder">
|
||||
<img src="{cap_icon}" width="20" class="img-fluid" alt="ISS Capacitor state">
|
||||
<h3>{cap_value}</h3>
|
||||
<div class="text-muted">[V]</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col">
|
||||
<div id="solcap_labels"></div>
|
||||
<div id="solcap" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<button type="button" class="btn btn-secondary" onclick="setBack('solcap_monitor')"><<</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('6','solcap_monitor')">6h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('12','solcap_monitor')">12h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('24','solcap_monitor')">24h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('168','solcap_monitor')">Week</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('720','solcap_monitor')">Month</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('4320','solcap_monitor')">6Mo</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('8640','solcap_monitor')">Year</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth('solcap_monitor')">>></button>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col">
|
||||
<div id="cpumem_labels"></div>
|
||||
<div id="cpumem" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<button type="button" class="btn btn-secondary" onclick="setBack('cpumem_monitor')"><<</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('1','cpumem_monitor')">1h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('6','cpumem_monitor')">6h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('12','cpumem_monitor')">12h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('24','cpumem_monitor')">24h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('168','cpumem_monitor')">Week</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('720','cpumem_monitor')">Month</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('4320','cpumem_monitor')">6Mo</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('8640','cpumem_monitor')">Year</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth('cpumem_monitor')">>></button>
|
||||
</div>
|
||||
<script type="text/javascript" src="../static/js/solcap_graph.js"></script>
|
||||
</section>
|
||||
</main>
|
||||
<div class="col"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bootstrap core JavaScript
|
||||
================================================== -->
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
|
||||
<script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
|
||||
<script src="../static/js/bootstrap.min.js"></script>
|
||||
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
|
||||
<script src="../static/js/ie10bugfix.js"></script>
|
||||
20
web/templates/top_menu.html
Normal file
@@ -0,0 +1,20 @@
|
||||
<nav class="navbar navbar-toggleable-md navbar-inverse fixed-top bg-inverse">
|
||||
<button class="navbar-toggler navbar-toggler-right hidden-lg-up" type="button" data-toggle="collapse" data-target="#navbarsExampleDefault" aria-controls="navbarsExampleDefault" aria-expanded="false" aria-label="Toggle navigation">
|
||||
<span class="navbar-toggler-icon"></span>
|
||||
</button>
|
||||
<a class="navbar-brand" href="#">Monitoring</a>
|
||||
|
||||
<div class="collapse navbar-collapse" id="navbarsExampleDefault">
|
||||
<ul class="navbar-nav mr-auto">
|
||||
<li class="nav-item {energy}">
|
||||
<a class="nav-link" href="https://bastart.spoton.cz">Energy</a>
|
||||
</li>
|
||||
<li class="nav-item {weather}">
|
||||
<a class="nav-link" href="https://bastart.spoton.cz/weather">Weather</a>
|
||||
</li>
|
||||
<li class="nav-item {status}">
|
||||
<a class="nav-link" href="https://bastart.spoton.cz/status">Status</a>
|
||||
</li>
|
||||
</ul>
|
||||
</div>
|
||||
</nav>
|
||||
74
web/templates/voltage_admin.html
Normal file
@@ -0,0 +1,74 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
|
||||
<div class="col"></div>
|
||||
<main class="pt-4 col-10">
|
||||
<h1>Energy Dashboard</h1>
|
||||
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col placeholder">
|
||||
<img src="{array_voltage_icon}" width="20" class="img-fluid" alt="Battery voltage">
|
||||
<h3>{array_voltage} [V]</h3>
|
||||
<div class="text-muted">{array_percent} [%]</div>
|
||||
</div>
|
||||
<div class="col placeholder">
|
||||
<img src="../static/img/battery_current.png" width="20" class="img-fluid" alt="Battery current">
|
||||
<h3>{charge_current}</h3>
|
||||
<div class="text-muted">Charging [A]</div>
|
||||
</div>
|
||||
<div class="col placeholder">
|
||||
<img src="../static/img/solar_power.png" width="20" class="img-fluid" alt="Solar array power">
|
||||
<h3>{solar_power}</h3>
|
||||
<span class="text-muted"> Solar PV [W]</span>
|
||||
</div>
|
||||
<div class="col placeholder">
|
||||
<img src="../static/img/solar_voltage.png" width="20" class="img-fluid" alt="Solar array voltage">
|
||||
<h3>{voltage_solar}</h3>
|
||||
<div class="text-muted">Solar [V]</div>
|
||||
</div>
|
||||
<div class="col placeholder">
|
||||
<img src="../static/img/pmax_day.png" width="20" class="img-fluid" alt="Max power / day">
|
||||
<h3>{pmax_day}</h3>
|
||||
<div class="text-muted">Pmax / day [W]</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col">
|
||||
<div id="solar_labels"></div>
|
||||
<div id="solar" class="container-fluid" style="width:100%;"></div>
|
||||
<div id="power_labels"></div>
|
||||
<div id="power" class="container-fluid" style="width:100%;"></div>
|
||||
<script type="text/javascript" src="../static/js/solar_graph.js"></script>
|
||||
<button type="button" class="btn btn-secondary" onclick="setBack()"><<</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('6')">6h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('12')">12h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('24')">24h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('168')">Week</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('720')">Month</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth()">>></button>
|
||||
</div>
|
||||
<div id="xxx"></div>
|
||||
</section>
|
||||
|
||||
<div class="table-responisve">
|
||||
<h2>Historical values by day</h2>
|
||||
{history_table}
|
||||
</div>
|
||||
<div class="table-responsive">
|
||||
{stats_table}
|
||||
{fresh_table}
|
||||
|
||||
</div>
|
||||
</main>
|
||||
<div class="col"></div>
|
||||
</div>
|
||||
</div>
|
||||
<!-- Bootstrap core JavaScript
|
||||
================================================== -->
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
|
||||
<script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
|
||||
<script src="../static/js/bootstrap.min.js"></script>
|
||||
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
|
||||
<script src="../static/js/ie10bugfix.js"></script>
|
||||
108
web/templates/weather_admin.html
Normal file
@@ -0,0 +1,108 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
|
||||
<div class="col"></div>
|
||||
<main class="pt-4 col-10">
|
||||
<h1>Weather Dashboard</h1>
|
||||
<h5>{timestamp} UTC</h5>
|
||||
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{w_speed_icon}" width="50" class="img-fluid" alt="Wind Speed">
|
||||
<h4>{w_speed_km} [km/h]</h4>
|
||||
<div class="text-muted">{w_speed_ms} [m/s]</div>
|
||||
</div>
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{w_gust_icon}" width="50" class="img-fluid" alt="Wind Gusts">
|
||||
<h4>{w_gust_km} [km/h]</h4>
|
||||
<div class="text-muted">{w_gust_ms} [m/s]</div>
|
||||
</div>
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{w_dir_icon}" width="50" class="img-fluid" alt="Wind Direction">
|
||||
<h4>{w_dir_name}</h4>
|
||||
<div class="text-muted">{w_dir_deg} °</div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{out_temp_icon}" width="50" class="img-fluid" alt="Outside Temperature">
|
||||
<h4>{out_temp} °C</h4>
|
||||
<div class="text-muted">[Outside]</div>
|
||||
</div>
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{in_temp_icon}" width="50" class="img-fluid" alt="Inside Temperature">
|
||||
<h4>{in_temp} °C</h4>
|
||||
<div class="text-muted">[Inside]</div>
|
||||
</div>
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{pressure_icon}" width="50" class="img-fluid" alt="Atmospheric pressure">
|
||||
<h4>{pressure} [hPa]</h4>
|
||||
<div class="text-muted">{raw_pressure} [hPa] raw</div>
|
||||
</div>
|
||||
</section>
|
||||
|
||||
<section class="row text-center">
|
||||
<div class="col">
|
||||
<h2>Wind speed and direction</h2>
|
||||
<div id="wind_labels"></div>
|
||||
<div id="wind" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<div class="row"><div class="col py-5">
|
||||
<button type="button" class="btn btn-secondary" onclick="setBack('wind_monitor')"><<</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('6','wind_monitor')">6h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('12','wind_monitor')">12h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('24','wind_monitor')">24h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('168','wind_monitor')">Week</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('720','wind_monitor')">Month</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('4320','wind_monitor')">6Mo</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('8640','wind_monitor')">Year</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth('wind_monitor')">>></button>
|
||||
</div></div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col">
|
||||
<h2>Temperature and Humidity</h2>
|
||||
<div id="temphumi_labels"></div><div id="temphumi_labels_in"></div>
|
||||
<div id="temphumi" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<div id="temphumi_in" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<button type="button" class="btn btn-secondary" onclick="setBack('temphumi_monitor')"><<</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('6','temphumi_monitor')">6h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('12','temphumi_monitor')">12h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('24','temphumi_monitor')">24h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('168','temphumi_monitor')">Week</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('720','temphumi_monitor')">Month</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('4320','temphumi_monitor')">6Mo</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('8640','temphumi_monitor')">Year</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth('temphumi_monitor')">>></button>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col">
|
||||
<div id="pressure_labels"></div>
|
||||
<div id="pressure" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<button type="button" class="btn btn-secondary" onclick="setBack('pressure_monitor')"><<</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('12','pressure_monitor')">12h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('24','pressure_monitor')">24h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('168','pressure_monitor')">Week</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('720','pressure_monitor')">Month</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('4320','pressure_monitor')">6Mo</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('8640','pressure_monitor')">Year</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth('pressure_monitor')">>></button>
|
||||
</div>
|
||||
<script type="text/javascript" src="../static/js/weather_graph.js"></script>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
<div class="col" id="xxx"></div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap core JavaScript
|
||||
================================================== -->
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
|
||||
<script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
|
||||
<script src="../static/js/bootstrap.min.js"></script>
|
||||
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
|
||||
<script src="../static/js/ie10bugfix.js"></script>
|
||||
280
web/templates/weather_admin.html.orig
Normal file
@@ -0,0 +1,280 @@
|
||||
<div class="container-fluid">
|
||||
<div class="row">
|
||||
|
||||
<!--
|
||||
--------------------------- Nahodou menu na levo -----------------------
|
||||
--
|
||||
<nav class="col-sm-3 col-md-2 hidden-xs-down bg-faded sidebar">
|
||||
<ul class="nav nav-pills flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link active" href="#">Overview <span class="sr-only">(current)</span></a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Reports</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Analytics</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Export</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav nav-pills flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Nav item</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Nav item again</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">One more nav</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Another nav item</a>
|
||||
</li>
|
||||
</ul>
|
||||
|
||||
<ul class="nav nav-pills flex-column">
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Nav item again</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">One more nav</a>
|
||||
</li>
|
||||
<li class="nav-item">
|
||||
<a class="nav-link" href="#">Another nav item</a>
|
||||
</li>
|
||||
</ul>
|
||||
</nav>
|
||||
|
||||
-->
|
||||
<div class="col"></div>
|
||||
<main class="pt-4 col-10">
|
||||
<h1>Weather Dashboard</h1>
|
||||
<h5>{timestamp}</h5>
|
||||
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{w_speed_icon}" width="50" class="img-fluid" alt="Wind Speed">
|
||||
<h4>{w_speed_km} [km/h]</h4>
|
||||
<div class="text-muted">{w_speed_ms} [m/s]</div>
|
||||
</div>
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{w_gust_icon}" width="50" class="img-fluid" alt="Wind Gusts">
|
||||
<h4>{w_gust_km} [km/h]</h4>
|
||||
<div class="text-muted">{w_gust_ms} [m/s]</div>
|
||||
</div>
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{w_dir_icon}" width="50" class="img-fluid" alt="Wind Direction">
|
||||
<h4>{w_dir_name}</h4>
|
||||
<div class="text-muted">{w_dir_deg} °</div>
|
||||
</section>
|
||||
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{out_temp_icon}" width="50" class="img-fluid" alt="Outside Temperature">
|
||||
<h4>{out_temp} °C</h4>
|
||||
<div class="text-muted">[Outside]</div>
|
||||
</div>
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{in_temp_icon}" width="50" class="img-fluid" alt="Inside Temperature">
|
||||
<h4>{in_temp} °C</h4>
|
||||
<div class="text-muted">[Inside]</div>
|
||||
</div>
|
||||
<div class="col-md-4 placeholder col-sm-6">
|
||||
<img src="{pressure_icon}" width="50" class="img-fluid" alt="Atmospheric pressure">
|
||||
<h4>{pressure} [hPa] Adjusted</h4>
|
||||
<div class="text-muted">{raw_pressure} [hPa] Actual</div>
|
||||
</section>
|
||||
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col">
|
||||
<h2>Wind speed and direction</h2>
|
||||
<div id="wind_labels"></div>
|
||||
<div id="wind" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<div class="row"><div class="col py-5">
|
||||
<button type="button" class="btn btn-secondary" onclick="setBack('wind_monitor')"><<</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('1','wind_monitor')">1h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('6','wind_monitor')">6h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('12','wind_monitor')">12h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('24','wind_monitor')">24h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('168','wind_monitor')">Week</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('5040','wind_monitor')">Month</button>
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth('wind_monitor')">>></button>
|
||||
</div></div>
|
||||
</div>
|
||||
</section>
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col">
|
||||
<h2>Temperature and Humidity</h2>
|
||||
<div id="temphumi_labels"></div><div id="temphumi_labels_in"></div>
|
||||
<div id="temphumi" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<div id="temphumi_in" class="container-fluid" style="width:100%;height:200px;"></div>
|
||||
<button type="button" class="btn btn-secondary" onclick="setBack('temphumi_monitor')"><<</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('1','temphumi_monitor')">1h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('6','temphumi_monitor')">6h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('12','temphumi_monitor')">12h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('24','temphumi_monitor')">24h</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('168','temphumi_monitor')">Week</button>
|
||||
<button type="button" class="btn btn-secondary btn-sm" onclick="setHours('5040','temphumi_monitor')">Month</button>
|
||||
<<<<<<< HEAD
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth('temphumi_monitor');">>></button>
|
||||
=======
|
||||
<button type="button" class="btn btn-secondary" onclick="setForth('temphumi_monitor')">>></button>
|
||||
>>>>>>> ca62f04757f1010262e5559bbd8753bfa49bf34e
|
||||
</div>
|
||||
</section>
|
||||
<section class="row text-center placeholders">
|
||||
<div class="col">
|
||||
<div id="pressure_labels"></div>
|
||||
<div id="pressure" class="container-fluid" style="width:100%;"></div>
|
||||
</div>
|
||||
<script type="text/javascript" src="../static/js/weather_graph.js"></script>
|
||||
</section>
|
||||
|
||||
</main>
|
||||
<div class="col" id="xxx"></div>
|
||||
|
||||
<!--
|
||||
<h2>Section title</h2>
|
||||
<div class="table-responsive">
|
||||
<table class="table table-striped">
|
||||
<thead>
|
||||
<tr>
|
||||
<th>#</th>
|
||||
<th>Header</th>
|
||||
<th>Header</th>
|
||||
<th>Header</th>
|
||||
<th>Header</th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td>1,001</td>
|
||||
<td>Lorem</td>
|
||||
<td>ipsum</td>
|
||||
<td>dolor</td>
|
||||
<td>sit</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,002</td>
|
||||
<td>amet</td>
|
||||
<td>consectetur</td>
|
||||
<td>adipiscing</td>
|
||||
<td>elit</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,003</td>
|
||||
<td>Integer</td>
|
||||
<td>nec</td>
|
||||
<td>odio</td>
|
||||
<td>Praesent</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,003</td>
|
||||
<td>libero</td>
|
||||
<td>Sed</td>
|
||||
<td>cursus</td>
|
||||
<td>ante</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,004</td>
|
||||
<td>dapibus</td>
|
||||
<td>diam</td>
|
||||
<td>Sed</td>
|
||||
<td>nisi</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,005</td>
|
||||
<td>Nulla</td>
|
||||
<td>quis</td>
|
||||
<td>sem</td>
|
||||
<td>at</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,006</td>
|
||||
<td>nibh</td>
|
||||
<td>elementum</td>
|
||||
<td>imperdiet</td>
|
||||
<td>Duis</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,007</td>
|
||||
<td>sagittis</td>
|
||||
<td>ipsum</td>
|
||||
<td>Praesent</td>
|
||||
<td>mauris</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,008</td>
|
||||
<td>Fusce</td>
|
||||
<td>nec</td>
|
||||
<td>tellus</td>
|
||||
<td>sed</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,009</td>
|
||||
<td>augue</td>
|
||||
<td>semper</td>
|
||||
<td>porta</td>
|
||||
<td>Mauris</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,010</td>
|
||||
<td>massa</td>
|
||||
<td>Vestibulum</td>
|
||||
<td>lacinia</td>
|
||||
<td>arcu</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,011</td>
|
||||
<td>eget</td>
|
||||
<td>nulla</td>
|
||||
<td>Class</td>
|
||||
<td>aptent</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,012</td>
|
||||
<td>taciti</td>
|
||||
<td>sociosqu</td>
|
||||
<td>ad</td>
|
||||
<td>litora</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,013</td>
|
||||
<td>torquent</td>
|
||||
<td>per</td>
|
||||
<td>conubia</td>
|
||||
<td>nostra</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,014</td>
|
||||
<td>per</td>
|
||||
<td>inceptos</td>
|
||||
<td>himenaeos</td>
|
||||
<td>Curabitur</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>1,015</td>
|
||||
<td>sodales</td>
|
||||
<td>ligula</td>
|
||||
<td>in</td>
|
||||
<td>libero</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
-->
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Bootstrap core JavaScript
|
||||
================================================== -->
|
||||
<!-- Placed at the end of the document so the pages load faster -->
|
||||
<script src="https://code.jquery.com/jquery-3.1.1.slim.min.js" integrity="sha384-A7FZj7v+d/sdmMqp/nOQwliLvUsJfDHW+k9Omg/a/EheAdgtzNs3hpfag6Ed950n" crossorigin="anonymous"></script>
|
||||
<script>window.jQuery || document.write('<script src="../../assets/js/vendor/jquery.min.js"><\/script>')</script>
|
||||
<script src="https://cdnjs.cloudflare.com/ajax/libs/tether/1.4.0/js/tether.min.js" integrity="sha384-DztdAPBWPRXSA/3eYEEUWrWCy7G5KFbe8fFjk5JAIxUYHKkDx6Qin1DkWx51bBrb" crossorigin="anonymous"></script>
|
||||
<script src="../static/js/bootstrap.min.js"></script>
|
||||
<!-- IE10 viewport hack for Surface/desktop Windows 8 bug -->
|
||||
<script src="../static/js/ie10bugfix.js"></script>
|
||||
317
web/voltage.py
Executable file
@@ -0,0 +1,317 @@
|
||||
#!/usr/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import cherrypy
|
||||
import influxdb
|
||||
import time
|
||||
import datetime
|
||||
import json
|
||||
from dynamic import DynamicData
|
||||
|
||||
# Universal variables
|
||||
_SCRIPT_PATH = os.path.dirname(sys.argv[0])
|
||||
_STATIC_DIR = '/templates/' # Needs to have trailing and leading slash '/'
|
||||
|
||||
influx_host = 'localhost'
|
||||
influx_port = 8086
|
||||
influx_user = 'pi'
|
||||
influx_pwd = 'freedavis'
|
||||
influx_db = 'voltage'
|
||||
|
||||
_MEASURES_IN_MINUTE = 11
|
||||
|
||||
|
||||
# Functions
|
||||
def read_html(filename):
|
||||
read_path = _SCRIPT_PATH + _STATIC_DIR + filename + '.html'
|
||||
try:
|
||||
with open(read_path, 'r') as handle:
|
||||
return handle.read()
|
||||
except:
|
||||
return """<div>ERROR!</div>""" + read_path
|
||||
|
||||
# Set default structure
|
||||
|
||||
html_start = '<html>'
|
||||
body_start = '<body>'
|
||||
body_close = '</body>'
|
||||
html_close = '</html>'
|
||||
|
||||
class EnergyInfo(object):
|
||||
def __init__(self):
|
||||
self.influx_client = influxdb.client.InfluxDBClient(
|
||||
influx_host, influx_port, influx_user, influx_pwd, influx_db
|
||||
)
|
||||
self.array_full = '../static/img/battery_full.png' #100-90
|
||||
self.array_80 = '../static/img/battery_80.png' # 90-70
|
||||
self.array_60 = '../static/img/battery_60.png' # 70-50
|
||||
self.array_40 = '../static/img/battery_40.png' # 50-30
|
||||
self.array_20 = '../static/img/battery_20.png' # 30-10
|
||||
self.array_0 = '../static/img/battery_0.png' # 10-0
|
||||
self.array_ch = '../static/img/battery_charging.png'
|
||||
self.battery_icon = self.array_full
|
||||
self.measures_obj = DynamicData()
|
||||
|
||||
def set_battery_icon(self, percentage, state):
|
||||
''' Interprets % of battery state in icon changes
|
||||
|
||||
expects:
|
||||
float(percentage): Percentage -> battery icon
|
||||
|
||||
sets:
|
||||
str(self.battery_icon): the path to appropriate icon image
|
||||
'''
|
||||
if state == 1:
|
||||
self.battery_icon = self.array_ch
|
||||
else:
|
||||
if percentage > 70.0 and percentage < 89.9:
|
||||
self.battery_icon = self.array_80
|
||||
if percentage > 50.0 and percentage < 69.9:
|
||||
self.battery_icon = self.array_60
|
||||
if percentage > 30.0 and percentage < 49.9:
|
||||
self.battery_icon = self.array_40
|
||||
if percentage > 10.0 and percentage < 29.9:
|
||||
self.battery_icon = self.array_20
|
||||
if percentage > 0.0 and percentage < 9.9:
|
||||
self.battery_icon = self.array_0
|
||||
|
||||
def percentageCalc(self, voltage, system):
|
||||
''' Turns current charge for lead acid batteries into a human readable %
|
||||
|
||||
expects:
|
||||
float(voltage): Voltage in V
|
||||
int(system): nominal system voltage, e.g. 12, 24, 48 etc
|
||||
|
||||
returns:
|
||||
float(percentage): Two decimal state of battery in percentage
|
||||
'''
|
||||
if system is 12:
|
||||
percentage = round(24.5724168782\
|
||||
* voltage * voltage - 521.9890329784 * voltage\
|
||||
+ 2771.1828105637, 1)
|
||||
elif system is 24:
|
||||
percentage = 2.4442 * voltage * voltage\
|
||||
- 82.004 * voltage + 602.91
|
||||
percentage = 100.00 if percentage > 100.00 else percentage
|
||||
return percentage
|
||||
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
header = read_html('header')
|
||||
menu_raw = read_html('top_menu')
|
||||
menu = menu_raw.format(energy = 'active', weather = '', status = '')
|
||||
body = self.body()
|
||||
footer = read_html('footer')
|
||||
result = header\
|
||||
+ menu\
|
||||
+ body\
|
||||
+ footer
|
||||
return result
|
||||
|
||||
def FreshValues(self):
|
||||
''' Get most up-to-date energy reading.
|
||||
|
||||
returns:
|
||||
dict(): {'I_solar': float(mA),
|
||||
'V_unit2': float(mV),
|
||||
'time': 'YYYY-mm-DDTHH:MM:SS.149636706Z',
|
||||
'I_array': None, # ((deprecated, will be removed))
|
||||
'V_unit1': float(mV),
|
||||
'V_solar': float(mV),
|
||||
'I_consumption': float(mA),
|
||||
'V_array': float(mV),
|
||||
'charging': bool(0/1)}
|
||||
'''
|
||||
query = "SELECT * FROM voltage ORDER BY time DESC LIMIT 1"
|
||||
last_measure = self.influx_client.query(query)
|
||||
charge_current = self.influx_client.query("select current from mppt where type = 'bat' order by time desc limit 1")
|
||||
pmax_day = self.influx_client.query("select Pmax from mppt where type = 'day' order by time desc limit 1")
|
||||
wh_day = self.influx_client.query("select Wh from mppt where type = 'day' order by time desc limit 1")
|
||||
power_solar = self.influx_client.query("select power from mppt where type = 'solar' order by time desc limit 1")
|
||||
state_r = self.influx_client.query("select * from mppt order by time desc limit 1")
|
||||
result = [measure for measure in last_measure["voltage"]][0]
|
||||
charge_current_result = [ch_curr for ch_curr in charge_current["mppt"]][0]["current"]
|
||||
pmax_day_result = [pmax for pmax in pmax_day["mppt"]][0]["Pmax"]
|
||||
wh_day_result = [wh for wh in wh_day["mppt"]][0]["Wh"]
|
||||
power_solar_result = [Psol for Psol in power_solar["mppt"]][0]["power"]
|
||||
state_result = [state for state in state_r["mppt"]][0]["state"]
|
||||
time_stamp = time.strptime(result['time'].split('.')[0],
|
||||
"%Y-%m-%dT%H:%M:%S")
|
||||
result['time'] = time_stamp
|
||||
result['state'] = state_result
|
||||
result['Psol'] = power_solar_result
|
||||
result['Wh_day'] = wh_day_result
|
||||
result['Pmax_day'] = pmax_day_result
|
||||
result['ChCurr'] = charge_current_result
|
||||
return result
|
||||
|
||||
|
||||
def HistoryValues(self, _INTERVAL):
|
||||
''' Function to get energy readings from InfluxDB.
|
||||
|
||||
returns:
|
||||
dict(): {"voltage_data": [{time: str(), V_array: str()...}, {...}],
|
||||
"voltage_data_cli": [str(line1), str(line2)...]}
|
||||
'''
|
||||
|
||||
table_list = []
|
||||
query1 = "SELECT * FROM voltage"
|
||||
query2 = "ORDER BY time DESC".format(_INTERVAL)
|
||||
query = "{} {}".format(query1, query2)
|
||||
neasured_range = {'range': '1h'}
|
||||
measures = self.measures_obj.historical_values(range = '1h')
|
||||
|
||||
# Let's get the data from DB
|
||||
for datapoint in measures:
|
||||
time_stamp = time.strptime(datapoint["time"].split('.')[0],
|
||||
"%Y-%m-%dT%H:%M:%SZ")
|
||||
array_volts = round(datapoint["V_array"] / 1000, 2)
|
||||
current_consumption = round(datapoint["I_consumption"] / 1000, 2)
|
||||
solar_volts = round(datapoint["V_solar"] / 1000, 2)
|
||||
current_solar = round(datapoint["I_solar"] / 1000, 2)
|
||||
unit1_volts = round(datapoint["V_unit1"] / 1000, 2)
|
||||
unit2_volts = round(datapoint["V_unit2"] / 1000, 2)
|
||||
charging = datapoint["charging"]
|
||||
power_consumption = round(current_consumption * 48, 2)
|
||||
power_solar = round(current_solar * solar_volts, 2)
|
||||
percent_array = self.percentageCalc(array_volts, 24)
|
||||
percent_unit1 = self.percentageCalc(unit1_volts, 12)
|
||||
percent_unit2 = self.percentageCalc(unit2_volts, 12)
|
||||
table_list.append([time.strftime("%Y/%m/%d %H:%M:%S", time_stamp),
|
||||
array_volts,
|
||||
current_consumption,
|
||||
solar_volts,
|
||||
current_solar,
|
||||
unit1_volts,
|
||||
unit2_volts,
|
||||
charging,
|
||||
power_consumption,
|
||||
power_solar])
|
||||
print(table_list)
|
||||
return {"voltage_data": table_list}
|
||||
|
||||
def tableConstructor(self, header, body):
|
||||
''' The idea behind is to have a method of constructing <table> html
|
||||
element and to separate data and html code.
|
||||
|
||||
expects:
|
||||
list(header): [[str(header1), str(header2), ...], row2, row3, ...]
|
||||
list(body): [str(td1), str(td2), ...]
|
||||
|
||||
returns:
|
||||
str(html_table)
|
||||
'''
|
||||
table_header = ''
|
||||
table_body = ''
|
||||
table_begin = '<table class="table table-striped">'
|
||||
thead = '<thead>'
|
||||
thead_end = '</thead>'
|
||||
tbody = '<tbody>'
|
||||
tbody_end = '</tbody>'
|
||||
table_end = '</table>'
|
||||
table_header = '</tr>'
|
||||
for th in header:
|
||||
table_header = table_header + '<th>' + th + '</th>'
|
||||
table_header = table_header + '</tr>'
|
||||
for tr in body:
|
||||
table_body = table_body + '<tr>'
|
||||
for td in tr:
|
||||
table_body = table_body + '<td>' + str(td) + '</td>'
|
||||
table_body = table_body + '</tr>'
|
||||
|
||||
html_table = "{} {} {} {} {}".format(table_begin + thead,
|
||||
table_header,
|
||||
thead_end + tbody,
|
||||
table_body,
|
||||
tbody_end + table_end)
|
||||
return html_table
|
||||
|
||||
def body(self):
|
||||
lastState = self.FreshValues()
|
||||
fresh_time = time.strftime("%Y-%m-%d %H:%M:%S", lastState['time'])
|
||||
try:
|
||||
fresh_voltage_array = round(float(lastState['V_array'] / 1000), 2)
|
||||
except:
|
||||
fresh_voltage_array = -99
|
||||
fresh_ch_current = lastState['ChCurr']
|
||||
try:
|
||||
fresh_voltage_solar = round(float(lastState['V_solar'] / 1000), 2)
|
||||
except:
|
||||
fresh_voltage_solar = -99
|
||||
fresh_current_solar = 10 #round(float(lastState['I_solar'] / 1000), 2)
|
||||
try:
|
||||
fresh_pmax_day = lastState['Pmax_day']
|
||||
except:
|
||||
fresh_pmax_day = -99
|
||||
fresh_voltage_unit2 = 10 #round(float(lastState['V_unit2'] / 1000), 2)
|
||||
fresh_percent_array = self.percentageCalc(fresh_voltage_array, 24)
|
||||
fresh_percent_unit2 = self.percentageCalc(fresh_voltage_unit2, 12)
|
||||
fresh_power_consumption = round(48\
|
||||
* fresh_ch_current, 2)
|
||||
fresh_power_solar = round(lastState['Psol'])
|
||||
fresh_charge_state = lastState['charging']
|
||||
fresh_table_header = ['Date Time',
|
||||
'Uarr [V]',
|
||||
'Icons [A]',
|
||||
'Usol [V]',
|
||||
'Isol [A]',
|
||||
'Us1 [V]',
|
||||
'Us2 [V]',
|
||||
'Charge',
|
||||
'Pcons [W]',
|
||||
'Psol [W]']
|
||||
|
||||
fresh_table_contents = [[fresh_time,
|
||||
fresh_voltage_array,
|
||||
fresh_ch_current,
|
||||
fresh_voltage_solar,
|
||||
fresh_current_solar,
|
||||
fresh_voltage_unit2,
|
||||
fresh_charge_state,
|
||||
fresh_power_consumption,
|
||||
fresh_power_solar]]
|
||||
|
||||
fresh_table = self.tableConstructor(fresh_table_header,
|
||||
fresh_table_contents)
|
||||
fresh_table = ''
|
||||
|
||||
#history_table_header = fresh_table_header
|
||||
#history_table_contents = self.HistoryValues(10)["voltage_data"]
|
||||
#history_table = self.tableConstructor(history_table_header,
|
||||
# history_table_contents)
|
||||
|
||||
history_table = ''
|
||||
|
||||
# Statistical table preparation
|
||||
stat_header = ['Date',
|
||||
'Uarr min [V]',
|
||||
'Icons [Ah]',
|
||||
'Usol max [V]',
|
||||
'Isol [Ah]',
|
||||
'Us1 min [V]',
|
||||
'Us2 min [V]',
|
||||
'Charge [h]',
|
||||
'Pcons [Wh]',
|
||||
'Psol [Wh]']
|
||||
|
||||
stats_content = self.measures_obj.stat_values(days = 7)
|
||||
stats_table = self.tableConstructor(stat_header, stats_content)
|
||||
|
||||
#self.set_battery_icon(array_percent_adjusted, fresh_charge_state)
|
||||
admin_preformat = read_html('voltage_admin')
|
||||
admin_html = admin_preformat.format(array_voltage = fresh_voltage_array,
|
||||
charge_current = fresh_ch_current,
|
||||
solar_power = fresh_power_solar,
|
||||
voltage_solar = fresh_voltage_solar,
|
||||
pmax_day = fresh_pmax_day,
|
||||
array_voltage_icon = '../static/img/battery_full.png',
|
||||
array_percent = 0,
|
||||
unit1_percentage = 0,
|
||||
unit2_percentage = 0,
|
||||
fresh_table = fresh_table,
|
||||
history_table = history_table,
|
||||
stats_table = stats_table)
|
||||
|
||||
|
||||
|
||||
return admin_html
|
||||
200
web/weather.py
Executable file
@@ -0,0 +1,200 @@
|
||||
#!/usr/bin/python3
|
||||
import os
|
||||
import sys
|
||||
import cherrypy
|
||||
import influxdb
|
||||
import time
|
||||
import json
|
||||
|
||||
# Universal variables
|
||||
_SCRIPT_PATH = os.path.dirname(sys.argv[0])
|
||||
_STATIC_DIR = '/templates/' # Needs to have trailing and leading slash '/'
|
||||
|
||||
influx_host = 'localhost'
|
||||
influx_port = 8086
|
||||
influx_user = 'pi'
|
||||
influx_pwd = 'freedavis'
|
||||
influx_weather_db = 'weather'
|
||||
influx_status_db = 'status'
|
||||
|
||||
# Icons
|
||||
fs_wind_icon = '../static/img/wind_icon.png'
|
||||
fs_pressure_icon = '../static/img/pressure_icon.png'
|
||||
fs_in_temperature_icon = '../static/img/inside_temp_icon.png'
|
||||
fs_out_temperature_icon = '../static/img/outside_temp_icon.png'
|
||||
fs_windgust_icon = '../static/img/wind_gust_icon.png'
|
||||
fs_winddirection_icon = '../static/img/wind_direction_icon.png'
|
||||
|
||||
# Functions
|
||||
def read_html(filename):
|
||||
read_path = _SCRIPT_PATH + _STATIC_DIR + filename + '.html'
|
||||
try:
|
||||
with open(read_path, 'r') as handle:
|
||||
return handle.read()
|
||||
except:
|
||||
return """<div>ERROR!</div>""" + read_path
|
||||
|
||||
# Set default structure
|
||||
|
||||
html_start = '<html>'
|
||||
body_start = '<body>'
|
||||
body_close = '</body>'
|
||||
html_close = '</html>'
|
||||
|
||||
class WeatherInfo(object):
|
||||
@cherrypy.expose
|
||||
def index(self):
|
||||
header = read_html('header')
|
||||
menu_raw = read_html('top_menu')
|
||||
menu = menu_raw.format(energy = '', weather = 'active', status = '')
|
||||
body = self.body()
|
||||
footer = read_html('footer')
|
||||
result = header\
|
||||
+ menu\
|
||||
+ body\
|
||||
+ footer
|
||||
return result
|
||||
|
||||
def winddirName(self, direction):
|
||||
'''
|
||||
Function to get energy readings from InfluxDB.
|
||||
|
||||
returns:
|
||||
dict(): {"last": str(last_entry),
|
||||
"details": list(detailed_table),
|
||||
"watthours": str(watthours)}
|
||||
'''
|
||||
if 10.5 <= direction < 34.5:
|
||||
result = "NNE"
|
||||
elif 34.5 <= direction < 58.5:
|
||||
result = "NE"
|
||||
elif 58.5 <= direction < 82.5:
|
||||
result = "ENE"
|
||||
elif 82.5 <= direction < 106.5:
|
||||
result = "E"
|
||||
elif 106.5 <= direction < 130.5:
|
||||
result = "ESE"
|
||||
elif 130.5 <= direction < 154.5:
|
||||
result = "SE"
|
||||
elif 154.5 <= direction < 178.5:
|
||||
result = "SSE"
|
||||
elif 178.5 <= direction < 202.5:
|
||||
result = "S"
|
||||
elif 202.5 <= direction < 226.5:
|
||||
result = "SSW"
|
||||
elif 226.5 <= direction < 250.5:
|
||||
result = "SW"
|
||||
elif 250.5 <= direction < 274.5:
|
||||
result = "W"
|
||||
elif 274.5 <= direction < 298.5:
|
||||
result = "WNW"
|
||||
elif 298.5 <= direction < 322.5:
|
||||
result = "NW"
|
||||
elif 322.5 <= direction < 346.5:
|
||||
result = "NNW"
|
||||
elif 346.5 <= direction <= 359.9 or 0 < direction < 10.5:
|
||||
result = "N"
|
||||
elif direction == 0:
|
||||
result = "ERROR, windvan broken"
|
||||
else:
|
||||
result = "NaN"
|
||||
return result
|
||||
|
||||
def LastKnownState(self):
|
||||
'''
|
||||
Function to get energy readings from InfluxDB.
|
||||
|
||||
returns:
|
||||
dict(): {"last": str(last_entry),
|
||||
"details": list(detailed_table),
|
||||
"watthours": str(watthours)}
|
||||
'''
|
||||
influx_weather_client = influxdb.client.InfluxDBClient(
|
||||
influx_host, influx_port, influx_user, influx_pwd, influx_weather_db
|
||||
)
|
||||
influx_status_client = influxdb.client.InfluxDBClient(
|
||||
influx_host, influx_port, influx_user, influx_pwd, influx_status_db
|
||||
)
|
||||
|
||||
# General query
|
||||
query1 = "SELECT time, value FROM wind WHERE type = "
|
||||
query2 = "AND time > NOW() - 5m ORDER BY time DESC LIMIT 1"
|
||||
|
||||
# now parse the tag in the middle of the two q from above
|
||||
wind_speed_q = "{} '{}' {}".format(query1, "speed", query2)
|
||||
wind_direction_q = "{} '{}' {}".format(query1, "direction", query2)
|
||||
wind_gust_q = "{} '{}' {}".format(query1, "windgust", query2)
|
||||
hum_ext_q = "SELECT time, humidity FROM temphumi WHERE type = 'external' {}".format(query2)
|
||||
hum_int_q = "SELECT time, humidity FROM temphumi WHERE type = 'internal' {}".format(query2)
|
||||
press_q = "SELECT time, pressure FROM temphumi WHERE type = 'adjusted' {}".format(query2)
|
||||
presr_q = "SELECT time, pressure FROM temphumi WHERE type = 'raw' {}".format(query2)
|
||||
t_ext_q = "SELECT time, temperature FROM temphumi WHERE type = 'external' {}".format(query2)
|
||||
t_int_q = "SELECT time, temperature FROM temphumi WHERE type = 'internal' {}".format(query2)
|
||||
|
||||
|
||||
# the actual query to DB
|
||||
wind_speed = influx_weather_client.query(wind_speed_q)
|
||||
wind_direction = influx_weather_client.query(wind_direction_q)
|
||||
wind_gust = influx_weather_client.query(wind_gust_q)
|
||||
hum_ext = influx_weather_client.query(hum_ext_q)
|
||||
hum_int = influx_weather_client.query(hum_int_q)
|
||||
press = influx_weather_client.query(press_q)
|
||||
presr = influx_weather_client.query(presr_q)
|
||||
t_ext = influx_weather_client.query(t_ext_q)
|
||||
t_int = influx_weather_client.query(t_int_q)
|
||||
|
||||
|
||||
# returned is a list, in this case, we just need one value [0]
|
||||
result_windspeed = [speed for speed in wind_speed][0][0]
|
||||
result_winddir = [direction for direction in wind_direction][0][0]
|
||||
result_windgust = [gust for gust in wind_gust][0][0]
|
||||
result_hum_int = [y for y in hum_int][0][0]
|
||||
result_t_int = [c for c in t_int][0][0]
|
||||
result_press = [z for z in press][0][0]
|
||||
result_presr = [a for a in presr][0][0]
|
||||
result_t_ext = [b for b in t_ext][0][0]
|
||||
result_hum_ext = [x for x in hum_ext][0][0]
|
||||
|
||||
# Put the time to a uhman readable format, strip nanosecs
|
||||
time_stamp = time.strptime(result_windspeed['time'].split('.')[0],
|
||||
"%Y-%m-%dT%H:%M:%S")
|
||||
result = {}
|
||||
|
||||
# Construct the result to return
|
||||
result.update({"time": time_stamp})
|
||||
result.update({"speed": round(result_windspeed['value'], 1)})
|
||||
result.update({"direction": round(result_winddir['value'], 1)})
|
||||
result.update({"windgust": round(result_windgust['value'], 1)})
|
||||
result.update({"humidity_ext": round(result_hum_ext['humidity'], 1)})
|
||||
result.update({"humidity_int": round(result_hum_int['humidity'], 1)})
|
||||
result.update({"pressure": round(result_press['pressure'], 1)})
|
||||
result.update({"pressure_raw": round(result_presr['pressure'], 1)})
|
||||
result.update({"temp_ext": round(result_t_ext['temperature'], 1)})
|
||||
result.update({"temp_int": round(result_t_int['temperature'], 1)})
|
||||
return result
|
||||
|
||||
|
||||
def body(self):
|
||||
admin_preformat = read_html('weather_admin')
|
||||
current_weather = self.LastKnownState()
|
||||
admin_html = admin_preformat.format(
|
||||
timestamp = time.strftime("%d.%m.%Y %H:%M:%S",
|
||||
current_weather['time']),
|
||||
w_speed_icon = fs_wind_icon,
|
||||
w_speed_km = current_weather['speed'],
|
||||
w_speed_ms = round(current_weather['speed'] / 3.6, 1),
|
||||
w_gust_icon = fs_windgust_icon,
|
||||
w_gust_km = current_weather['windgust'],
|
||||
w_gust_ms = round(current_weather['windgust'] / 3.6, 1),
|
||||
w_dir_icon = fs_winddirection_icon,
|
||||
w_dir_name = self.winddirName(current_weather['direction']),
|
||||
w_dir_deg = current_weather['direction'],
|
||||
out_temp_icon = fs_out_temperature_icon,
|
||||
out_temp = current_weather['temp_ext'],
|
||||
in_temp_icon = fs_in_temperature_icon,
|
||||
in_temp = current_weather['temp_int'],
|
||||
pressure_icon = fs_pressure_icon,
|
||||
pressure = current_weather['pressure'],
|
||||
raw_pressure = current_weather['pressure_raw']
|
||||
)
|
||||
return admin_html
|
||||