comparison eprodbus.py @ 9:446cfe74827b

Add program to report epro status to DBus for Venus tools. Move sqlite3 logging to here as well
author Daniel O'Connor <darius@dons.net.au>
date Sun, 05 Dec 2021 16:19:31 +1030
parents
children 08b61687b75f
comparison
equal deleted inserted replaced
8:9c0435a617db 9:446cfe74827b
1 #!/usr/bin/env python
2
3 # Read enerdrive ePro packets from serial port and update DBus with them
4 # Also logs to an sqlite3 DB every 60 seconds
5
6 import datetime
7 from dbus.mainloop.glib import DBusGMainLoop
8 import epro
9 import gobject
10 import logging
11 import os
12 import serial
13 import signal
14 import sqlite3
15 import sys
16
17 sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'velib_python'))
18 from vedbus import VeDbusService
19
20 logging.basicConfig(format = '%(asctime)s %(message)s', level = logging.DEBUG)
21 logging.info(__file__ + " is starting up")
22
23 port = 'ttyepro'
24 servicename = 'com.victronenergy.battery.' + port
25 instance = 0
26
27 class eProUpdater:
28 def __init__(self, dbusservice, s, dbh):
29 self.log_queue = []
30 self.dbusservice = dbusservice
31 self.p = epro.Processor()
32 self.s = s
33 self.dbh = dbh
34 gobject.io_add_watch(s.fileno(), gobject.IO_IN, self.read_serial)
35 gobject.timeout_add(60000, self.log_epro)
36
37 def read_serial(self, fd, userdata):
38 try:
39 data = self.s.read(1024)
40 except Exception as e:
41 logging.error('Failed to read from serial port: %s', str(e))
42 return False
43
44 logging.debug('Read %d bytes from serial port', len(data))
45 self.p.process(data)
46
47 while len(self.p.packets) > 0:
48 # Process oldest packets first
49 p = self.p.packets.pop(0)
50 self.log_queue.append(p)
51 logging.debug('%s', str(p))
52 if type(p) == epro.StateOfCharge:
53 self.dbusservice['/Soc'] = p.soc
54 elif type(p) == epro.MainVoltage:
55 self.dbusservice['/Dc/0/Voltage'] = p.volts
56 elif type(p) == epro.BatteryCurrent:
57 self.dbusservice['/Dc/0/Current'] = p.amps
58 elif type(p) == epro.AmpHours:
59 self.dbusservice['/ConsumedAmphours'] = p.amphrs
60 elif type(p) == epro.TimeRemaining:
61 # ePro reports in minutes, Venus expects seconds
62 self.dbusservice['/TimeToGo'] = p.time * 60
63 return True
64
65 def log_epro(self):
66 logging.debug('Logging epro data')
67 # Check we have all the packets we need in the queue
68 msgtypes = set([x.msgtype for x in self.log_queue])
69 wantedtypes = set([
70 epro.MainVoltage.MSGTYPE,
71 epro.AmpHours.MSGTYPE,
72 epro.BatteryCurrent.MSGTYPE,
73 epro.StateOfCharge.MSGTYPE,
74 epro.TimeRemaining.MSGTYPE,
75 epro.BatteryTemperature.MSGTYPE,
76 epro.MonitorStatus.MSGTYPE,
77 epro.AuxVoltage.MSGTYPE,
78 ])
79 if msgtypes < wantedtypes:
80 logging.debug('Didn\'t get all packet types required to log')
81 return
82
83 row = {}
84 usedtypes = set()
85 while len(self.log_queue) > 0:
86 pkt = self.log_queue.pop() # Read latest packets first
87 if pkt.msgtype == epro.MainVoltage.MSGTYPE:
88 row['main_voltage'] = pkt.volts
89 elif pkt.msgtype == epro.AmpHours.MSGTYPE:
90 row['amp_hours'] = pkt.amphrs
91 elif pkt.msgtype == epro.BatteryCurrent.MSGTYPE:
92 row['battery_curr'] = pkt.amps
93 elif pkt.msgtype == epro.StateOfCharge.MSGTYPE:
94 row['state_of_charge'] = pkt.soc
95 elif pkt.msgtype == epro.TimeRemaining.MSGTYPE:
96 row['time_remaining'] = pkt.time
97 elif pkt.msgtype == epro.BatteryTemperature.MSGTYPE:
98 row['battery_temp'] = pkt.temp
99 elif pkt.msgtype == epro.MonitorStatus.MSGTYPE:
100 row['auto_sync_volts'] = pkt.autosyncvolt
101 row['auto_sync_curr'] = pkt.autosyncamp
102 row['e501'] = pkt.e501compat
103 row['alarm_test'] = pkt.alarmtst
104 row['light'] = pkt.backlight
105 row['display_test'] = pkt.disptst
106 row['temp_sensor'] = pkt.tempsense
107 row['aux_hv'] = pkt.auxhv
108 row['aux_lv'] = pkt.auxlv
109 row['installer_lock'] = pkt.lock
110 row['main_hv'] = pkt.mainhv
111 row['main_lv'] = pkt.mainlv
112 row['low_battery'] = pkt.lowbatalarm
113 row['battery_flat'] = pkt.batflat
114 row['battery_full'] = pkt.batfull
115 row['battery_charged'] = pkt.charged
116 row['no_sync'] = pkt.nosync
117 row['monitor_reset'] = pkt.monreset
118 elif pkt.msgtype == epro.AuxVoltage.MSGTYPE:
119 row['aux_voltage'] = pkt.volts
120
121 usedtypes.add(pkt.msgtype)
122 if usedtypes >= wantedtypes:
123 self.log_queue = []
124 break
125
126 logging.info('Got all packets, logging')
127 cur = self.dbh.cursor()
128 row['tstamp'] = int(datetime.datetime.now().strftime('%s'))
129 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)
130 self.dbh.commit()
131
132 def doexit():
133 sys.exit(1)
134
135 def main():
136 # Add signal handler to exit, otherwise we have to press ctrl-c twice to quit
137 signal.signal(signal.SIGINT, doexit)
138
139 DBusGMainLoop(set_as_default = True)
140
141 dbusservice = VeDbusService(servicename)
142 dbusservice.add_path('/Connected', value = True)
143 dbusservice.add_path('/ProductName', value = 'Enerdrive ePro')
144 dbusservice.add_path('/Mgmt/Connection', value = '/dev/' + port)
145 dbusservice.add_path('/DeviceInstance', value = instance)
146 dbusservice.add_path('/ProductId', value = 'unknown')
147 dbusservice.add_path('/Dc/0/Voltage', value = None)
148 dbusservice.add_path('/Dc/0/Current', value = None)
149 dbusservice.add_path('/Soc', value = None)
150 dbusservice.add_path('/TimeToGo', value = None)
151 dbusservice.add_path('/ConsumedAmphours', value = None)
152
153 s = serial.Serial('/dev/' + port, 2400, parity = 'E')
154 s.timeout = 0.1
155
156 dbh = sqlite3.connect('/home/root/vanlogger/log.db')
157
158 updater = eProUpdater(dbusservice, s, dbh)
159
160 logging.info('Starting main loop')
161 mainloop = gobject.MainLoop()
162 mainloop.run()
163
164 if __name__ == '__main__':
165 main()