diff --git a/web/chttpd.py b/web/chttpd.py index 50a5645..d46d279 100755 --- a/web/chttpd.py +++ b/web/chttpd.py @@ -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() diff --git a/web/readme.md b/web/readme.md new file mode 100644 index 0000000..46cb9e7 --- /dev/null +++ b/web/readme.md @@ -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/) diff --git a/web/voltage.py b/web/voltage.py deleted file mode 100755 index e6c66ea..0000000 --- a/web/voltage.py +++ /dev/null @@ -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 """
ERROR!
""" + read_path - -# Set default structure - -html_start = '' -body_start = '' -body_close = '' -html_close = '' - -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 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 = '
' - thead = '' - thead_end = '' - tbody = '' - tbody_end = '' - table_end = '
' - table_header = '' - for th in header: - table_header = table_header + '' + th + '' - table_header = table_header + '' - for tr in body: - table_body = table_body + '' - for td in tr: - table_body = table_body + '' + str(td) + '' - table_body = table_body + '' - - 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