changeset 27:718b963b0dfa

merge
author Daniel O'Connor <darius@dons.net.au>
date Wed, 25 Sep 2019 21:36:49 +0930
parents efe1954da8ca (diff) 00845d271007 (current diff)
children 1da02c79b458
files vanlogger.py
diffstat 3 files changed, 122 insertions(+), 93 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/README	Wed Sep 25 21:36:49 2019 +0930
@@ -0,0 +1,6 @@
+sudo cp vanlogger.service /lib/systemd/system/
+sudo cp 99-giant-ips.rules /lib/udev/rules.d/
+sudo systemctl daemon-reload
+sudo udevadm control --reload
+sudo systemctl start vanllogger.service
+
--- a/graph.py	Wed Sep 25 21:35:40 2019 +0930
+++ b/graph.py	Wed Sep 25 21:36:49 2019 +0930
@@ -23,8 +23,8 @@
         self.annofn = annofn
 
 columns = [
-    Column('main_voltage', 'Battery Voltage', 'eprolog', 'Vdc'),
-    Column('aux_voltage', 'Aux Voltage', 'eprolog', 'Vdc'),
+    Column('main_voltage', 'Battery Voltage', 'eprolog', 'Vdc', (10, 30)),
+    Column('aux_voltage', 'Aux Voltage', 'eprolog', 'Vdc', (10, 30)),
     Column('battery_curr', 'Battery Current', 'eprolog', 'A'),
     Column('amp_hours', 'Battery Amp Hours', 'eprolog', 'Ah'),
     Column('state_of_charge', 'State of Charge', 'eprolog', '%', (0, 100), annofn = lambda xdata, ydata: 'DoD: %.1f' % (100 - ydata.min())),
@@ -66,27 +66,30 @@
     if args.days is not None and args.days < 0:
         parser.error('days must be non-negative')
 
-    # Can specify..
-    # Start and end
-    # Start and days or Start
-    # End and days or End
-    # Nothing
-    # Want to end up with a start & end
+    # If it's before 6am then plot yesterday
+    today = datetime.date.today()
+    if datetime.datetime.now().hour < 6:
+        today -= datetime.timedelta(days = 1)
+
     selector = [args.start is not None, args.end is not None, args.days is not None]
-    if selector == [True, True, False]:
+    if selector == [True, True, False]: # Start and end
         pass
-    elif selector == [True, False, True] or selector == [True, False, False]:
+    elif selector == [True, False, True] or selector == [True, False, False]: # Start and days or start
         if args.days == None:
             args.days = 1
         args.end = args.start + datetime.timedelta(days = args.days)
-    elif selector == [False, True, True] or selector == [False, True, False]:
+    elif selector == [False, True, True] or selector == [False, True, False]: # End and days or end
         if args.days == None:
             args.days = 1
         args.start = args.end - datetime.timedelta(days = args.days)
-    elif selector == [False, False, True]:
-        end = datetime.date.today()
-        end = datetime.datetime(start.year, start.month, start.day)
+    elif selector == [False, False, True]: # Days
+        args.end = today + datetime.timedelta(days = 1)
+        args.end = datetime.datetime(args.end.year, args.end.month, args.end.day)
         args.start = args.end - datetime.timedelta(days = args.days)
+    elif selector == [False, False, False]: # Nothing
+        args.start = today
+        args.start = datetime.datetime(args.start.year, args.start.month, args.start.day)
+        args.end = args.start + datetime.timedelta(days = 1)
     else:
         parser.error('can\'t specify days, start and end simultaneously')
 
@@ -116,10 +119,6 @@
         args.start = args.start.replace(tzinfo = lt)
     if args.end.tzinfo == None:
         args.end = args.end.replace(tzinfo = lt)
-    startlt = args.start
-    endlt = args.end
-    args.start = args.start.astimezone(utc)
-    args.end = args.end.astimezone(utc)
     graph(args.graphfn, cur, cols, args.start, args.end, lt, utc)
 
 def graph(fname, cur, _cols, start, end, lt, utc):
@@ -165,10 +164,10 @@
         ary = numpy.array(cur.fetchall())
         if ary.shape[0] == 0:
             print('No data for ' + c.rowname)
-            return
+            continue
 
-        # Create TZ naive from POSIX stamp, then convert to TZ aware UTC then adjust to local time
-        c.xdata = map(lambda f: datetime.datetime.fromtimestamp(f).replace(tzinfo = utc).astimezone(lt), ary[:,0])
+        # Convert epoch stamp to datetime with UTC timezone
+        c.xdata = map(lambda f: datetime.datetime.utcfromtimestamp(f).replace(tzinfo = utc), ary[:,0])
         c.ydata = ary[:,1]
         if c.conv != None:
             c.ydata = map(c.conv, c.ydata)
@@ -190,6 +189,10 @@
         colouridx += 1
         ax.append(c)
 
+    if len(ax1lines) == 0 and len(ax2lines) == 0:
+        print('Nothing to plot')
+        return
+
     # Load the right backend for display or save
     if fname == None:
         import matplotlib.pylab
@@ -209,7 +212,7 @@
             ax1.set_ylim(line.limits[0], line.limits[1])
         if line.annotation != None:
             annotations.append(line.annotation)
-    ax1.legend(loc = 'upper left')
+    ax1.legend(loc = 'center left')
 
     if len(ax2lines) > 0:
         ax2 = ax1.twinx()
@@ -222,21 +225,24 @@
             if line.annotation != None:
                 annotations.append(line.annotation)
 
-        ax2.legend(loc = 'upper right')
+        ax2.legend(loc = 'center right')
 
     if len(annotations) > 0:
-        ax1.text(0.02, 0.5, reduce(lambda a, b: a + '\n' + b, annotations),
+        ax1.text(0.02, 0.3, reduce(lambda a, b: a + '\n' + b, annotations),
                     transform = ax1.transAxes, bbox = dict(facecolor = 'blue', alpha = 0.5),
                     ha = 'left', va = 'top')
     ndays = int(max(1, round(((end - start).total_seconds()) / 86400)))
+    once = False
     for ax in fig.get_axes():
-        if ndays > 1:
-            ax.set_title('%s to %s' % (start.strftime('%Y-%m-%d'), end.strftime('%Y-%m-%d')))
-        else:
-            ax.set_title('%s' % (start.strftime('%Y-%m-%d')))
-        ax.set_xlim([start, end])
-        ax.format_xdata = lambda d: matplotlib.dates.num2date(d).strftime('%d %b %H:%M')
+        if not once:
+            once = True
+            if ndays > 1:
+                ax.set_title('%s to %s' % (start.astimezone(lt).strftime('%Y-%m-%d'), end.astimezone(lt).strftime('%Y-%m-%d')))
+            else:
+                ax.set_title('%s' % (start.astimezone(lt).strftime('%Y-%m-%d')))
+        ax.set_xlim([start.astimezone(utc), end.astimezone(utc)])
         ax.xaxis.grid(True)
+        ax.xaxis.axis_date(lt)
         ax.xaxis.set_major_formatter(matplotlib.dates.DateFormatter('%d %b\n%H:%M'))
         ax.xaxis.set_major_locator(matplotlib.dates.HourLocator(interval = 2 * ndays))
         ax.xaxis.set_minor_locator(matplotlib.dates.MinuteLocator(interval = 5 * ndays))
@@ -250,52 +256,7 @@
         matplotlib.pyplot.show()
     else:
         canvas = matplotlib.backends.backend_agg.FigureCanvasAgg(fig) # Sets canvas in fig too
-        fig.savefig(startdt.strftime(fname))
-
-def updatedb(cur, data):
-    mkdb(cur)
-    for d in data['reads']['data']:
-        ts = datetime.datetime.strptime(d['t_stamp'], '%Y-%m-%dT%H:%M:%SZ')
-        # Note we rename *energy* to *power* here to match what it actually means
-        vals = [ts, d['battery_charge'], d['battery_energy'], d['energy_consumed'], d['energy_expected'], d['energy_exported'], d['energy_generated'],
-                    d['energy_imported'], d['estimated_savings'], d['pv_forecast'], d['pv_generation']['battery_energy'],
-                    d['pv_generation']['grid_energy'], d['pv_generation']['site_energy'], d['site_consumption']['battery_energy'],
-                    d['site_consumption']['grid_energy'], d['site_consumption']['pv_energy']]
-        skip = True
-        for v in vals[1:]:
-            if v != None:
-                skip = False
-                break
-        if skip:
-            print('Skipping empty record at ' + str(ts))
-            continue
-        cur.execute('INSERT OR IGNORE INTO agl(t_stamp, battery_charge, battery_power, power_consumed, power_expected, power_exported, power_generated, power_imported, estimated_savings, pv_forecast, pv_gen_battery, pv_gen_grid, pv_gen_site, site_cons_battery, site_cons_grid, site_cons_pv) VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)', vals)
-
-def gettoken(username, password):
-    authblob = json.encoder.JSONEncoder().encode({'email' : username, 'password' : password})
-    reply = requests.request('POST', loginurl, data = authblob, headers = {'Content-Type' : 'application/json'})
-    if reply.status_code != 200:
-        return None
-    return json.decoder.JSONDecoder().decode(reply.content)['access_token']
-
-def getdata(token, startdate, enddate):
-    #print('getting ' + startdate.strftime('%Y-%m-%d'))
-    reply = requests.request('GET', dataurl, params = {
-        'startDate' :	startdate.strftime('%Y-%m-%d'),
-        'endDate' :		enddate.strftime('%Y-%m-%d'),
-        'granularity' :	'Minute',
-        'metrics' :		'read',
-        'units' :		'W',
-        }, headers = { 'Authorization' : 'Bearer ' + token})
-
-    if reply.status_code != 200:
-        return None
-
-    return json.decoder.JSONDecoder().decode(reply.content)
-
-def logout(token):
-    reply = requests.request('GET', logouturl, headers = { 'Authorization' : 'Bearer ' + token})
-    return reply.status_code == 200
+        fig.savefig(start.astimezone(lt).strftime(fname))
 
 if __name__ == '__main__':
     main()
--- a/vanlogger.py	Wed Sep 25 21:35:40 2019 +0930
+++ b/vanlogger.py	Wed Sep 25 21:36:49 2019 +0930
@@ -5,10 +5,9 @@
 sys.path.append('/home/pi/logger')
 import datetime
 import epro.epro as epro
-import json
+import giant.giant as giant
 import serial
 import sqlite3
-import subprocess
 import sys
 
 def create(cur):
@@ -44,21 +43,35 @@
 ''')
 
     cur.execute('''
-CREATE TABLE IF NOT EXISTS victron(
-    tstamp		INTEGER NOT NULL,
-    ACIn_L1_volts	REAL NOT NULL,
-    ACIn_L1_freq	REAL NOT NULL,
-    ACIn_L1_curent	REAL NOT NULL,
-    ACIn_active		BOOLEAN NOT NULL,
-    ACOut_L1_volts	REAL NOT NULL,
-    ACOut_L1_freq	REAL NOT NULL,
-    ACOut_L1_curent	REAL NOT NULL,
-    Battery_Voltage	REAL NOT NULL,
-    Battery_Current	REAL NOT NULL
+CREATE TABLE IF NOT EXISTS giantlog(
+    tstamp			INTEGER NOT NULL,
+	ac_act_power	REAL NOT NULL,
+	ac_app_power	REAL NOT NULL,
+	ac_frequency	REAL NOT NULL,
+	ac_volts		REAL NOT NULL,
+	batt_chr_curr	REAL NOT NULL,
+	batt_dis_curr	REAL NOT NULL,
+    battery_cap		REAL NOT NULL,
+	battery_volts	REAL NOT NULL,
+	batt_volt_ofs	REAL NOT NULL,
+	bus_voltage		REAL NOT NULL,
+	grid_frequency	REAL NOT NULL,
+	grid_volts		REAL NOT NULL,
+	hs_temperature	REAL NOT NULL,
+	load_pct		REAL NOT NULL,
+	pv1_chrg_pow	REAL NOT NULL,
+	pv1_current		REAL NOT NULL,
+	pv1_volts		REAL NOT NULL,
+	scc1_volts		REAL NOT NULL,
+    scc1_charging	BOOLEAN NOT NULL,
+    switch			BOOLEAN NOT NULL,
+	float_charge	BOOLEAN NOT NULL,
+    ac_charging 	BOOLEAN NOT NULL,
+	sbu_prio		BOOLEAN NOT NULL,
+	b_volt_steady	BOOLEAN NOT NULL,
+    charging		BOOLEAN NOT NULL
 );
 ''')
- 
-
 
 def log_epro(p, cur):
     # Check we have all the packets we need in the queue
@@ -122,23 +135,61 @@
     row['tstamp'] = int(datetime.datetime.now().strftime('%s'))
     cur.execute('INSERT INTO eprolog VALUES (:tstamp, :main_voltage, :aux_voltage, :battery_curr, :amp_hours, :state_of_charge, :time_remaining, :battery_temp, :auto_sync_volts, :auto_sync_curr, :e501, :alarm_test, :light, :display_test, :temp_sensor, :aux_hv, :aux_lv, :installer_lock, :main_hv, :main_lv, :low_battery, :battery_flat, :battery_full, :battery_charged, :no_sync, :monitor_reset)', row)
 
+def log_giant(gstat, cur):
+    row = {}
+    row['ac_act_power'] = gstat['ACActPower']
+    row['ac_app_power'] = gstat['ACAppPower']
+    row['ac_frequency'] = gstat['ACFreq']
+    row['ac_volts'] = gstat['ACVolts']
+    row['batt_chr_curr'] = gstat['BattChrCurr']
+    row['batt_dis_curr'] = gstat['BattDisCurr']
+    row['battery_cap'] = gstat['BattCap']
+    row['battery_volts'] = gstat['BattVolts']
+    row['batt_volt_ofs'] = gstat['BattVoltOfs']
+    row['bus_voltage'] = gstat['BusVolts']
+    row['grid_frequency'] = gstat['GridFreq']
+    row['grid_volts'] = gstat['GridVolts']
+    row['hs_temperature'] = gstat['HSTemp']
+    row['load_pct'] = gstat['LoadPct']
+    row['pv1_chrg_pow'] = gstat['PVChrgPow1']
+    row['pv1_current'] = gstat['PVCurr1']
+    row['pv1_volts'] = gstat['PVVolt1']
+    row['scc1_volts'] = gstat['SCC1Volt']
+    row['scc1_charging'] = gstat['Status']['SCC1Charging']
+    row['switch'] = gstat['Status']['Switch']
+    row['float_charge'] = gstat['Status']['FloatCharge']
+    if gstat['Status']['ChargeType'] == 'Both' or gstat['Status']['ChargeType'] == 'AC':
+        row['ac_charging'] = True
+    else:
+        row['ac_charging'] = False
+    row['sbu_prio'] = gstat['Status']['SBUPrio']
+    row['b_volt_steady'] = gstat['Status']['BattVoltSteady']
+    row['charging'] = gstat['Status']['Charging']
+
+    row['tstamp'] = int(datetime.datetime.now().strftime('%s'))
+    cur.execute('INSERT INTO giantlog VALUES(:tstamp, :ac_act_power, :ac_app_power, :ac_frequency, :ac_volts, :batt_chr_curr, :batt_dis_curr, :battery_cap, :battery_volts, :batt_volt_ofs, :bus_voltage, :grid_frequency, :grid_volts, :hs_temperature, :load_pct, :pv1_chrg_pow, :pv1_current, :pv1_volts, :scc1_volts, :scc1_charging, :switch, :float_charge, :ac_charging, :sbu_prio, :b_volt_steady, :charging)', row)
+
 def main():
     print 'Started'
     dbh = sqlite3.connect('/home/pi/vanlogger/log.db')
     cur = dbh.cursor()
     create(cur)
-    #s = serial.Serial('/dev/ttyS0', 2400, parity='E')
-    s = serial.Serial('/dev/ttyS0', 2400)
+    s = serial.Serial('/dev/ttyS0', 2400, parity='E')
     s.timeout = 0.2
 
     p = epro.Processor()
+    ips = giant.GiantIPS()
 
     then = None
     lasteprolog = datetime.datetime.now()
+    lastgiantlog = datetime.datetime.now()
     while True:
         if datetime.datetime.now() - lasteprolog > datetime.timedelta(hours = 1):
 	    print('Stale ePro data')
 	    sys.exit(1)
+        if datetime.datetime.now() - lastgiantlog > datetime.timedelta(hours = 1):
+	    print('Stale Giant data')
+	    sys.exit(1)
         dolog = False
         if then == None or datetime.datetime.now() - then > datetime.timedelta(seconds = 60):
             dolog = True
@@ -149,5 +200,16 @@
             log_epro(p, cur)
             dbh.commit()
 
+        gstat = None
+        try:
+            gstat = ips.getStatus()
+        except:
+            pass
+        if gstat != None and dolog:
+            lastgiantlog = datetime.datetime.now()
+            log_giant(gstat, cur)
+            dbh.commit()
+            #print(gstat)
+
 if __name__ == '__main__':
     main()