# HG changeset patch # User Daniel O'Connor # Date 1638683371 -37800 # Node ID 446cfe74827b019ba51bba304732fabef8dfdd13 # Parent 9c0435a617db2492c1e3f4a8a19fdd61f046b768 Add program to report epro status to DBus for Venus tools. Move sqlite3 logging to here as well diff -r 9c0435a617db -r 446cfe74827b eprodbus.py --- /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() diff -r 9c0435a617db -r 446cfe74827b eprodbus.sh --- /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