This commit is contained in:
Milan Toman
2018-07-14 23:13:24 +02:00
parent bc873b7d1c
commit 2d3cf7b56b
3 changed files with 43 additions and 327 deletions

View File

@@ -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
View 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/)

View File

@@ -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