318 lines
13 KiB
Python
Executable File
318 lines
13 KiB
Python
Executable File
#!/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
|