Cleanup
This commit is contained in:
@@ -1,4 +1,6 @@
|
||||
#!/usr/bin/python3
|
||||
"""Our cherrypy server, it all starts here
|
||||
"""
|
||||
import os
|
||||
import cherrypy
|
||||
import OpenSSL
|
||||
@@ -6,27 +8,37 @@ import sys
|
||||
|
||||
PATH = os.path.abspath(os.path.dirname(__file__))
|
||||
|
||||
# Where are we?
|
||||
_SCRIPT_PATH = os.path.dirname(sys.argv[0])
|
||||
# So we can load static libraries
|
||||
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
|
||||
# CSV "API" for graphs, or individual pulling ./dynamic.py
|
||||
import dynamic
|
||||
# UI for RasPi / davis status ./status.py
|
||||
import status
|
||||
|
||||
#basic config for the server, the SSL part is questionable...
|
||||
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'
|
||||
'server.socket_port': 443,
|
||||
'server.ssl_module': 'builtin',
|
||||
'server.ssl_certificate': _CERT_PATH + '/fullchain1.pem',
|
||||
'server.ssl_private_key': _CERT_PATH + '/privkey1.pem'
|
||||
}
|
||||
# commit the config settings
|
||||
cherrypy.config.update(server_config)
|
||||
|
||||
# If launched directly, let's go
|
||||
if __name__ == '__main__':
|
||||
|
||||
|
||||
|
||||
conf = {
|
||||
'/': {
|
||||
'tools.sessions.on': True,
|
||||
@@ -42,9 +54,11 @@ if __name__ == '__main__':
|
||||
}
|
||||
}
|
||||
|
||||
cherrypy.tree.mount(voltage.EnergyInfo(), "/", conf)
|
||||
cherrypy.tree.mount(weather.WeatherInfo(), "/weather", conf)
|
||||
# Here are the different served mounts
|
||||
cherrypy.tree.mount(weather.WeatherInfo(), "/", conf)
|
||||
cherrypy.tree.mount(status.StatusInfo(), "/status", conf)
|
||||
cherrypy.tree.mount(dynamic.DynamicData(), "/data", conf)
|
||||
|
||||
# Run the server, lock it in place.
|
||||
cherrypy.engine.start()
|
||||
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