For those integrating VE-DIRECT Victron USB Devices.
I have (with the help of others on the net and HA Community) a working system for reading a USB victron device plugged into a Linux machine and displaying in HA front end VIA MQTT.
The setup is rather convoluted and I’m sure could be streamlined by someone clever., I have just strung a few things together…
I have scripts run from systemd at boot which reload once an hour to purge the ever growing printed files.
#############################################
The BMV700 Battery Meter to MQTT files:
#############################################
These three files are located in folder /startup/bmv
1st script
“BMV-serial-run.sh”
is to initiate the USB VE-Direct cable to print data using a VE-Direct script (vedirect.py) to file “bmv700.txt”
File: BMV-serial-run.sh
#!/bin/sh
cd /startup/bmv ;
python vedirect.py --port /dev/ttyUSB_BMV1 > bmv700.txt
This setup relies on you to reserve USB device IDs using /etc/udev/rules.d/ for the VE-Direct Serial devices.
File which does the clever serial printing:
vedirect.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os, serial, argparse
class vedirect:
def __init__(self, serialport, timeout):
self.serialport = serialport
self.ser = serial.Serial(serialport, 19200, timeout=timeout)
self.header1 = '\r'
self.header2 = '\n'
self.hexmarker = ':'
self.delimiter = '\t'
self.key = ''
self.value = ''
self.bytes_sum = 0;
self.state = self.WAIT_HEADER
self.dict = {}
(HEX, WAIT_HEADER, IN_KEY, IN_VALUE, IN_CHECKSUM) = range(5)
def input(self, byte):
if byte == self.hexmarker and self.state != self.IN_CHECKSUM:
self.state = self.HEX
if self.state == self.WAIT_HEADER:
self.bytes_sum += ord(byte)
if byte == self.header1:
self.state = self.WAIT_HEADER
elif byte == self.header2:
self.state = self.IN_KEY
return None
elif self.state == self.IN_KEY:
self.bytes_sum += ord(byte)
if byte == self.delimiter:
if (self.key == 'Checksum'):
self.state = self.IN_CHECKSUM
else:
self.state = self.IN_VALUE
else:
self.key += byte
return None
elif self.state == self.IN_VALUE:
self.bytes_sum += ord(byte)
if byte == self.header1:
self.state = self.WAIT_HEADER
self.dict[self.key] = self.value;
self.key = '';
self.value = '';
else:
self.value += byte
return None
elif self.state == self.IN_CHECKSUM:
self.bytes_sum += ord(byte)
self.key = ''
self.value = ''
self.state = self.WAIT_HEADER
if (self.bytes_sum % 256 == 0):
self.bytes_sum = 0
return self.dict
else:
print 'Malformed packet'
self.bytes_sum = 0
elif self.state == self.HEX:
self.bytes_sum = 0
if byte == self.header2:
self.state = self.WAIT_HEADER
else:
raise AssertionError()
def read_data(self):
while True:
byte = self.ser.read(1)
packet = self.input(byte)
def read_data_single(self):
while True:
byte = self.ser.read(1)
packet = self.input(byte)
if (packet != None):
return packet
def read_data_callback(self, callbackFunction):
while True:
byte = self.ser.read(1)
if byte:
packet = self.input(byte)
if (packet != None):
callbackFunction(packet)
else:
break
def print_data_callback(data):
print data
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Process VE.Direct protocol')
parser.add_argument('--port', help='Serial port')
parser.add_argument('--timeout', help='Serial port read timeout', type=int, default='60')
args = parser.parse_args()
ve = vedirect(args.port, args.timeout)
ve.read_data_callback(print_data_callback)
#print(ve.read_data_single())
Below, with the help of user DAP35
I use these two files to print the VE-DIRECT BMV700 Battery meter FILE info to MQTT.
bmv-to-mqtt.py
#!/usr/bin/python
import json
import paho.mqtt.client as mqtt
import time
import os
broker_url = "192.168.1.4"
broker_port = 1883
delay = 10
mq_client = "bmv700host"
mq_user = "YOUR_MQTT_USER"
mq_pw = "YOUR_MQTT_PASSWORD"
topic1 = "bmv700/SOC"
topic2 = "bmv700/TTG_MINS"
topic3 = "bmv700/AMNT_CHARGE_ENERGY_KWH"
topic4 = "bmv700/BAT_CURR_mA"
topic5 = "bmv700/CONS_ENERGY_mAH"
topic6 = "bmv700/AMNT_DISCHARGE_ENERGY_KWH"
topic7 = "bmv700/CURRENT_BATT_VOLTS_mV"
topic8 = "bmv700/MAX_BATT_VOLTS_mV"
topic9 = "bmv700/SEC_SINCE_LAST_F_CHARGE"
topic10 = "bmv700/LAST_DISCHARGE_mAH"
topic11 = "bmv700/DEEPEST_DISCHARGE_mAH"
topic12 = "bmv700/TOTAL_AH_DRAWN_mAH"
topic13 = "bmv700/MIN_BATT_VOLTS_mV"
topic14 = "bmv700/FULL_DISCHARGES"
status = "bmv700/connection_status"
lwm = "offline"
#
client = mqtt.Client(mq_client)
client.will_set(status, lwm, qos=1, retain=True)
client.username_pw_set(mq_user, password=mq_pw)
client.connect(broker_url, broker_port)
client.publish(topic=status, payload="online", qos=1, retain=True)
#
client.loop_start()
rc=1
while rc > 0:
#### Edit path to grab.sh to match your environment ####
os.popen('/startup/bmv/grab.sh')
with open('bmv700Parsed1.txt', 'r') as sensordata:
data=sensordata.read()
obj = json.loads(data)
SOC = int(obj['SOC'])
TTG = int(obj['TTG'])
H18 = int(obj['H18'])
I = int(obj['I'])
CE = int(obj['CE'])
H17 = int(obj['H17'])
V = int(obj['V'])
H8 = int(obj['H8'])
H9 = int(obj['H9'])
H2 = int(obj['H2'])
H1 = int(obj['H1'])
H6 = int(obj['H6'])
H7 = int(obj['H7'])
H5 = int(obj['H5'])
client.publish(topic=topic1, payload=SOC, qos=1, retain=True)
client.publish(topic=topic2, payload=TTG, qos=1, retain=True)
client.publish(topic=topic3, payload=H18, qos=1, retain=True)
client.publish(topic=topic4, payload=I, qos=1, retain=True)
client.publish(topic=topic5, payload=CE, qos=1, retain=True)
client.publish(topic=topic6, payload=H17, qos=1, retain=True)
client.publish(topic=topic7, payload=V, qos=1, retain=True)
client.publish(topic=topic8, payload=H8, qos=1, retain=True)
client.publish(topic=topic9, payload=H9, qos=1, retain=True)
client.publish(topic=topic10, payload=H2, qos=1, retain=True)
client.publish(topic=topic11, payload=H1, qos=1, retain=True)
client.publish(topic=topic12, payload=H6, qos=1, retain=True)
client.publish(topic=topic13, payload=H7, qos=1, retain=True)
client.publish(topic=topic14, payload=H5, qos=1, retain=True)
print("Parsing data", SOC, TTG, H18, I, CE, H17, V, H8, H9, H2, H1, H6, H7, H5)
time.sleep(delay)
Which uses grab.sh to parse and publish to MQTT.
grab.sh
#!/bin/sh
tail -2 bmv700.txt | head -1 | sed s/\'/\"/g > bmv700Parsed1.txt
####################################################
After that we have the VE-Direct MPPT (Solar controller setup)
####################################################
3 Files located in folder: /startup/mppt
MPPT-serial-run.sh
#!/bin/sh
cd /startup/mppt ;
python vedirect.py --port /dev/ttyUSB_MPPT1 > mppt-log.txt
Which also references vedirect.py
#!/usr/bin/python
# -*- coding: utf-8 -*-
import os, serial, argparse
class vedirect:
def __init__(self, serialport, timeout):
self.serialport = serialport
self.ser = serial.Serial(serialport, 19200, timeout=timeout)
self.header1 = '\r'
self.header2 = '\n'
self.hexmarker = ':'
self.delimiter = '\t'
self.key = ''
self.value = ''
self.bytes_sum = 0;
self.state = self.WAIT_HEADER
self.dict = {}
(HEX, WAIT_HEADER, IN_KEY, IN_VALUE, IN_CHECKSUM) = range(5)
def input(self, byte):
if byte == self.hexmarker and self.state != self.IN_CHECKSUM:
self.state = self.HEX
if self.state == self.WAIT_HEADER:
self.bytes_sum += ord(byte)
if byte == self.header1:
self.state = self.WAIT_HEADER
elif byte == self.header2:
self.state = self.IN_KEY
return None
elif self.state == self.IN_KEY:
self.bytes_sum += ord(byte)
if byte == self.delimiter:
if (self.key == 'Checksum'):
self.state = self.IN_CHECKSUM
else:
self.state = self.IN_VALUE
else:
self.key += byte
return None
elif self.state == self.IN_VALUE:
self.bytes_sum += ord(byte)
if byte == self.header1:
self.state = self.WAIT_HEADER
self.dict[self.key] = self.value;
self.key = '';
self.value = '';
else:
self.value += byte
return None
elif self.state == self.IN_CHECKSUM:
self.bytes_sum += ord(byte)
self.key = ''
self.value = ''
self.state = self.WAIT_HEADER
if (self.bytes_sum % 256 == 0):
self.bytes_sum = 0
return self.dict
else:
print 'Malformed packet'
self.bytes_sum = 0
elif self.state == self.HEX:
self.bytes_sum = 0
if byte == self.header2:
self.state = self.WAIT_HEADER
else:
raise AssertionError()
def read_data(self):
while True:
byte = self.ser.read(1)
packet = self.input(byte)
def read_data_single(self):
while True:
byte = self.ser.read(1)
packet = self.input(byte)
if (packet != None):
return packet
def read_data_callback(self, callbackFunction):
while True:
byte = self.ser.read(1)
if byte:
packet = self.input(byte)
if (packet != None):
callbackFunction(packet)
else:
break
def print_data_callback(data):
print data
if __name__ == '__main__':
parser = argparse.ArgumentParser(description='Process VE.Direct protocol')
parser.add_argument('--port', help='Serial port')
parser.add_argument('--timeout', help='Serial port read timeout', type=int, default='60')
args = parser.parse_args()
ve = vedirect(args.port, args.timeout)
ve.read_data_callback(print_data_callback)
#print(ve.read_data_single())
Then these two files below print the VE-DIRECT BMV700 Battery meter FILE info to MQTT.
mppt-to-mqtt.py
#!/usr/bin/python3
import json
import paho.mqtt.client as mqtt
import time
import os
broker_url = "192.168.1.4"
broker_port = 1883
delay = 10
mq_client = "victronhost"
mq_user = "YOUR_MQTT_USER"
mq_pw = "YOUR_MQTT_PASS"
topic1 = "victron/YIELD_TOTAL_mAH"
topic2 = "victron/PANEL_VOLTS_mV"
topic3 = "victron/SOLAR_AMPS_mA"
topic4 = "victron/MAX_POWER_TODAY_WATTS"
topic5 = "victron/YIELD_TODAY_KWH"
topic6 = "victron/AMAX_POWER_YESTRD_WATTS"
topic7 = "victron/BATT_VOLTS_mV"
topic8 = "victron/YIELD_YESTRD_KWH"
status = "victron/connection_status"
lwm = "offline"
#
client = mqtt.Client(mq_client)
client.will_set(status, lwm, qos=1, retain=True)
client.username_pw_set(mq_user, password=mq_pw)
client.connect(broker_url, broker_port)
client.publish(topic=status, payload="online", qos=0, retain=False)
#
client.loop_start()
rc=1
while rc > 0:
#### Edit path to grab.sh to match your environment ####
os.popen('/startup/mppt/mppt-grab.sh')
with open('mppt-log-parsed1.txt', 'r') as sensordata:
data=sensordata.read()
obj = json.loads(data)
H19 = int(obj['H19'])
VPV = int(obj['VPV'])
I = int(obj['I'])
H21 = int(obj['H21'])
H20 = int(obj['H20'])
H23 = int(obj['H23'])
V = int(obj['V'])
H22 = int(obj['H22'])
client.publish(topic=topic1, payload=H19, qos=0, retain=False)
client.publish(topic=topic2, payload=VPV, qos=0, retain=False)
client.publish(topic=topic3, payload=I, qos=0, retain=False)
client.publish(topic=topic4, payload=H21, qos=0, retain=False)
client.publish(topic=topic5, payload=H20, qos=0, retain=False)
client.publish(topic=topic6, payload=H23, qos=0, retain=False)
client.publish(topic=topic7, payload=V, qos=0, retain=False)
client.publish(topic=topic8, payload=H22, qos=0, retain=False)
print("Parsing data", H19, VPV, I, H21, H20, H23, V, H22)
time.sleep(delay)
And the grab script referenced in the file above to parse printed file:
mppt-grab.sh
#!/bin/sh
tail -2 mppt-log.txt | head -1 | sed s/\'/\"/g > mppt-log-parsed1.txt
To a proper coder this is probably rather basic! BUT its been working for months on end.
It will hopefully help some people get started!
If someone can consolidate and improve the process. That’d also be useful for others…