changeset 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 9c0435a617db
children d3624c2b7c92
files eprodbus.py eprodbus.sh
diffstat 2 files changed, 246 insertions(+), 0 deletions(-) [+]
line wrap: on
line diff
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eprodbus.py	Sun Dec 05 16:19:31 2021 +1030
@@ -0,0 +1,165 @@
+#!/usr/bin/env python
+
+# Read enerdrive ePro packets from serial port and update DBus with them
+# Also logs to an sqlite3 DB every 60 seconds
+
+import datetime
+from dbus.mainloop.glib import DBusGMainLoop
+import epro
+import gobject
+import logging
+import os
+import serial
+import signal
+import sqlite3
+import sys
+
+sys.path.insert(1, os.path.join(os.path.dirname(__file__), 'velib_python'))
+from vedbus import VeDbusService
+
+logging.basicConfig(format = '%(asctime)s %(message)s', level = logging.DEBUG)
+logging.info(__file__ + " is starting up")
+
+port = 'ttyepro'
+servicename = 'com.victronenergy.battery.' + port
+instance = 0
+
+class eProUpdater:
+    def __init__(self, dbusservice, s, dbh):
+        self.log_queue = []
+        self.dbusservice = dbusservice
+        self.p = epro.Processor()
+        self.s = s
+        self.dbh = dbh
+        gobject.io_add_watch(s.fileno(), gobject.IO_IN, self.read_serial)
+        gobject.timeout_add(60000, self.log_epro)
+
+    def read_serial(self, fd, userdata):
+        try:
+            data = self.s.read(1024)
+        except Exception as e:
+            logging.error('Failed to read from serial port: %s', str(e))
+            return False
+
+        logging.debug('Read %d bytes from serial port', len(data))
+        self.p.process(data)
+
+        while len(self.p.packets) > 0:
+            # Process oldest packets first
+            p = self.p.packets.pop(0)
+            self.log_queue.append(p)
+            logging.debug('%s', str(p))
+            if type(p) == epro.StateOfCharge:
+                self.dbusservice['/Soc'] = p.soc
+            elif type(p) == epro.MainVoltage:
+                self.dbusservice['/Dc/0/Voltage'] = p.volts
+            elif type(p) == epro.BatteryCurrent:
+                self.dbusservice['/Dc/0/Current'] = p.amps
+            elif type(p) == epro.AmpHours:
+                self.dbusservice['/ConsumedAmphours'] = p.amphrs
+            elif type(p) == epro.TimeRemaining:
+                # ePro reports in minutes, Venus expects seconds
+                self.dbusservice['/TimeToGo'] = p.time * 60
+        return True
+
+    def log_epro(self):
+        logging.debug('Logging epro data')
+        # Check we have all the packets we need in the queue
+        msgtypes = set([x.msgtype for x in self.log_queue])
+        wantedtypes = set([
+            epro.MainVoltage.MSGTYPE,
+            epro.AmpHours.MSGTYPE,
+            epro.BatteryCurrent.MSGTYPE,
+            epro.StateOfCharge.MSGTYPE,
+            epro.TimeRemaining.MSGTYPE,
+            epro.BatteryTemperature.MSGTYPE,
+            epro.MonitorStatus.MSGTYPE,
+            epro.AuxVoltage.MSGTYPE,
+            ])
+        if msgtypes < wantedtypes:
+            logging.debug('Didn\'t get all packet types required to log')
+            return
+
+        row = {}
+        usedtypes = set()
+        while len(self.log_queue) > 0:
+            pkt = self.log_queue.pop() # Read latest packets first
+            if pkt.msgtype == epro.MainVoltage.MSGTYPE:
+                row['main_voltage'] = pkt.volts
+            elif pkt.msgtype == epro.AmpHours.MSGTYPE:
+                row['amp_hours'] = pkt.amphrs
+            elif pkt.msgtype == epro.BatteryCurrent.MSGTYPE:
+                row['battery_curr'] = pkt.amps
+            elif pkt.msgtype == epro.StateOfCharge.MSGTYPE:
+                row['state_of_charge'] = pkt.soc
+            elif pkt.msgtype == epro.TimeRemaining.MSGTYPE:
+                row['time_remaining'] = pkt.time
+            elif pkt.msgtype == epro.BatteryTemperature.MSGTYPE:
+                row['battery_temp'] = pkt.temp
+            elif pkt.msgtype == epro.MonitorStatus.MSGTYPE:
+                row['auto_sync_volts'] = pkt.autosyncvolt
+                row['auto_sync_curr'] = pkt.autosyncamp
+                row['e501'] = pkt.e501compat
+                row['alarm_test'] = pkt.alarmtst
+                row['light'] = pkt.backlight
+                row['display_test'] = pkt.disptst
+                row['temp_sensor'] = pkt.tempsense
+                row['aux_hv'] = pkt.auxhv
+                row['aux_lv'] = pkt.auxlv
+                row['installer_lock'] = pkt.lock
+                row['main_hv'] = pkt.mainhv
+                row['main_lv'] = pkt.mainlv
+                row['low_battery'] = pkt.lowbatalarm
+                row['battery_flat'] = pkt.batflat
+                row['battery_full'] = pkt.batfull
+                row['battery_charged'] = pkt.charged
+                row['no_sync'] = pkt.nosync
+                row['monitor_reset'] = pkt.monreset
+            elif pkt.msgtype == epro.AuxVoltage.MSGTYPE:
+                row['aux_voltage'] = pkt.volts
+
+            usedtypes.add(pkt.msgtype)
+            if usedtypes >= wantedtypes:
+                self.log_queue = []
+                break
+
+        logging.info('Got all packets, logging')
+        cur = self.dbh.cursor()
+        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)
+        self.dbh.commit()
+
+def doexit():
+    sys.exit(1)
+
+def main():
+    # Add signal handler to exit, otherwise we have to press ctrl-c twice to quit
+    signal.signal(signal.SIGINT, doexit)
+
+    DBusGMainLoop(set_as_default = True)
+
+    dbusservice = VeDbusService(servicename)
+    dbusservice.add_path('/Connected', value = True)
+    dbusservice.add_path('/ProductName', value = 'Enerdrive ePro')
+    dbusservice.add_path('/Mgmt/Connection', value = '/dev/' + port)
+    dbusservice.add_path('/DeviceInstance', value = instance)
+    dbusservice.add_path('/ProductId', value = 'unknown')
+    dbusservice.add_path('/Dc/0/Voltage', value = None)
+    dbusservice.add_path('/Dc/0/Current', value = None)
+    dbusservice.add_path('/Soc', value = None)
+    dbusservice.add_path('/TimeToGo', value = None)
+    dbusservice.add_path('/ConsumedAmphours', value = None)
+
+    s = serial.Serial('/dev/' + port, 2400, parity = 'E')
+    s.timeout = 0.1
+
+    dbh = sqlite3.connect('/home/root/vanlogger/log.db')
+
+    updater = eProUpdater(dbusservice, s, dbh)
+
+    logging.info('Starting main loop')
+    mainloop = gobject.MainLoop()
+    mainloop.run()
+
+if __name__ == '__main__':
+    main()
--- /dev/null	Thu Jan 01 00:00:00 1970 +0000
+++ b/eprodbus.sh	Sun Dec 05 16:19:31 2021 +1030
@@ -0,0 +1,81 @@
+#! /bin/sh
+
+### BEGIN INIT INFO
+# Provides:         eprodbus
+# Required-Start:   $remote_fs $syslog
+# Required-Stop:    $remote_fs $syslog
+# Default-Start:    2 3 4 5
+# Default-Stop:     0 1 6
+# Short-Description:    eprodbus
+# Description:
+#   Log van stats
+### END INIT INFO
+
+set -e
+
+PIDFILE=/var/run/eprodbus.pid
+PYTHON=/usr/bin/python
+DAEMON=/home/root/vanlogger/epro/eprodbus.py
+
+test -x ${DAEMON} || exit 0
+
+umask 022
+
+. /etc/init.d/functions
+
+export PATH="${PATH:+$PATH:}/usr/sbin:/sbin"
+
+case "$1" in
+    start)
+        echo "Starting eprodbus"
+        if start-stop-daemon --start --quiet --oknodo --background --make-pidfile --pidfile ${PIDFILE} --exec ${PYTHON} -- ${DAEMON} ; then
+            exit 0
+        else
+            exit 1
+        fi
+        ;;
+    stop)
+        echo "Stopping eprodbus"
+        if start-stop-daemon --stop --quiet --oknodo --pidfile ${PIDFILE}; then
+            rm -f ${PIDFILE}
+            exit 0
+        else
+            exit 1
+        fi
+        ;;
+
+    restart)
+        echo "Restarting eprodbus"
+        if start-stop-daemon --stop --quiet --oknodo --retry 30 --pidfile ${PIDFILE}; then
+            rm -f ${PIDFILE}
+        fi
+        if start-stop-daemon --start --quiet --oknodo --background --make-pidfile --pidfile ${PIDFILE} --exec ${PYTHON} -- ${DAEMON} ; then
+            exit 0
+        else
+            exit 1
+        fi
+        ;;
+
+    status)
+	if [ ! -e ${PIDFILE} ]; then
+	  running=0
+	else
+	    pid=$(cat ${PIDFILE})
+	    ps -w | grep -v grep | grep -q "$pid"
+	    running=$((!$?))
+	fi
+	if [ $running -ne 0 ]; then
+	  echo "eprodbus (pid $pid) is running..."
+	  exit 0
+	else
+	    echo "eprodbus is stopped"
+	    exit 1
+	fi
+        ;;
+
+    *)
+        echo "Usage: $0 {start|stop|restart|status}"
+        exit 1
+esac
+
+exit 0