Cleanup
This commit is contained in:
@@ -1,4 +1,6 @@
|
|||||||
#!/usr/bin/python3
|
#!/usr/bin/python3
|
||||||
|
"""Our cherrypy server, it all starts here
|
||||||
|
"""
|
||||||
import os
|
import os
|
||||||
import cherrypy
|
import cherrypy
|
||||||
import OpenSSL
|
import OpenSSL
|
||||||
@@ -6,27 +8,37 @@ import sys
|
|||||||
|
|
||||||
PATH = os.path.abspath(os.path.dirname(__file__))
|
PATH = os.path.abspath(os.path.dirname(__file__))
|
||||||
|
|
||||||
|
# Where are we?
|
||||||
_SCRIPT_PATH = os.path.dirname(sys.argv[0])
|
_SCRIPT_PATH = os.path.dirname(sys.argv[0])
|
||||||
|
# So we can load static libraries
|
||||||
sys.path.append(_SCRIPT_PATH)
|
sys.path.append(_SCRIPT_PATH)
|
||||||
|
# Certificates reside here
|
||||||
|
_CERT_PATH = _SCRIPT_PATH + '/.cert'
|
||||||
|
|
||||||
import voltage
|
"""Let's look at the statically linked stuff (i.e. the files that will
|
||||||
|
serve us data). These imports are static files in the same directory. Each
|
||||||
|
a spearate application with it's own face and behavior
|
||||||
|
"""
|
||||||
|
# UI for weather ./weather.py
|
||||||
import weather
|
import weather
|
||||||
|
# CSV "API" for graphs, or individual pulling ./dynamic.py
|
||||||
import dynamic
|
import dynamic
|
||||||
|
# UI for RasPi / davis status ./status.py
|
||||||
import status
|
import status
|
||||||
|
|
||||||
|
#basic config for the server, the SSL part is questionable...
|
||||||
server_config={
|
server_config={
|
||||||
'server.socket_host': '0.0.0.0',
|
'server.socket_host': '0.0.0.0',
|
||||||
'server.socket_port':443,
|
'server.socket_port': 443,
|
||||||
'server.ssl_module':'builtin',
|
'server.ssl_module': 'builtin',
|
||||||
'server.ssl_certificate':'/home/www/.cert/fullchain1.pem',
|
'server.ssl_certificate': _CERT_PATH + '/fullchain1.pem',
|
||||||
'server.ssl_private_key':'/home/www/.cert/privkey1.pem'
|
'server.ssl_private_key': _CERT_PATH + '/privkey1.pem'
|
||||||
}
|
}
|
||||||
|
# commit the config settings
|
||||||
cherrypy.config.update(server_config)
|
cherrypy.config.update(server_config)
|
||||||
|
|
||||||
|
# If launched directly, let's go
|
||||||
if __name__ == '__main__':
|
if __name__ == '__main__':
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
conf = {
|
conf = {
|
||||||
'/': {
|
'/': {
|
||||||
'tools.sessions.on': True,
|
'tools.sessions.on': True,
|
||||||
@@ -42,9 +54,11 @@ if __name__ == '__main__':
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
cherrypy.tree.mount(voltage.EnergyInfo(), "/", conf)
|
# Here are the different served mounts
|
||||||
cherrypy.tree.mount(weather.WeatherInfo(), "/weather", conf)
|
cherrypy.tree.mount(weather.WeatherInfo(), "/", conf)
|
||||||
cherrypy.tree.mount(status.StatusInfo(), "/status", conf)
|
cherrypy.tree.mount(status.StatusInfo(), "/status", conf)
|
||||||
cherrypy.tree.mount(dynamic.DynamicData(), "/data", conf)
|
cherrypy.tree.mount(dynamic.DynamicData(), "/data", conf)
|
||||||
|
|
||||||
|
# Run the server, lock it in place.
|
||||||
cherrypy.engine.start()
|
cherrypy.engine.start()
|
||||||
cherrypy.engine.block()
|
cherrypy.engine.block()
|
||||||
|
|||||||
19
web/readme.md
Normal file
19
web/readme.md
Normal file
@@ -0,0 +1,19 @@
|
|||||||
|
# The GUI. Web based. Python.
|
||||||
|
This folder should contain all the files related to displaying and manipulating data via the UI.
|
||||||
|
That means, everything related to the connections to the outside.
|
||||||
|
|
||||||
|
# UPDATE: a complete rewrite of current to accommodate all new developers.
|
||||||
|
By this I mean, we'll be slowly going through the whole code class by class to build a better world.
|
||||||
|
|
||||||
|
## Server
|
||||||
|
I'd say cherrypy. It is lightweight and relatively simple to use. Pure python, of course
|
||||||
|
|
||||||
|
## DB
|
||||||
|
Python calls to Influx. NOT using the influxdb library might be a good idea. We'll see.
|
||||||
|
Currently the library is being used.
|
||||||
|
|
||||||
|
## HTML
|
||||||
|
Bootstrap 3, default admin / dasboard theme
|
||||||
|
|
||||||
|
## JS graphs
|
||||||
|
[Dygraphs](http://dygraphs.com/)
|
||||||
317
web/voltage.py
317
web/voltage.py
@@ -1,317 +0,0 @@
|
|||||||
#!/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
|
|
||||||
Reference in New Issue
Block a user