I have a battery with a jbd bms and I plan to expand with another battery and another bms the same, I think this is possible but I would like you to confirm it, is there a limit of BMS or some restriction or minor problem?
I do not see that it is possible to edit the name of the device, in future versions is this contemplated?
At the moment you can add more BMS in parallel and in the details in your Remote Console you will see both. However only 1 will be displayed on the dashboard and on VRM. They both will source power to the system. This will change when I have the multi bank feature finished. Handle multiple battery modules · Issue #8 · Louisvdw/dbus-serialbattery · GitHub
There is also a ticket open to rename different BMS in the system. Create option to add custom name · Issue #100 · Louisvdw/dbus-serialbattery · GitHub
You can subscript to those tickets to get notifications if you want.
Hi, @Louisvdw 'm slowly progressing in rolling out your unit to a low cost battery pack.
Now I can read voltage, current and temperature.
I’m having trouble publishing the maximum and minimum voltage and identifying which cells have it. The BMS sends me the voltages of 16 cells, and my battery only has 15, the 16 is equal to zero.
So I created a list and got the max and min voltage non-zero. And I’m getting this error that I can’t solve:
What is the link to publish the min and max tensions? self.cell_max_voltage and self.cell_min_voltage? I used the ANT battery code as an example.
code snippet:
def read_all_data(self):
all_data = self.read_serial_data_lifepo4(self.command_geral)
if all_data is False or len(all_data) < 39:
logger.warning("PTI_erro_ao_Ler_Tensoes")
return False
# 42 registradores (com id funcao e length)
id, funcao, length, tensao, corrente, cell_voltage1, cell_voltage2, cell_voltage3, cell_voltage4, cell_voltage5, cell_voltage6,\
cell_voltage7, cell_voltage8, cell_voltage9, cell_voltage10, cell_voltage11, cell_voltage12, cell_voltage13, cell_voltage14,\
cell_voltage15, cell_voltage16, = unpack_from(">BBBHhHHHHHHHHHHHHHHHH", all_data)
self.id = id
self.funcao = funcao
self.length = length
# unpack_from os bytes HhHH são as tensões, corrente, etc.
self.voltage = tensao / 100
self.current = corrente / 100
# são 16 celulas 2 bytes por célula 32 byte
self.cells = [cell_voltage1, cell_voltage2, cell_voltage3, cell_voltage4, cell_voltage5, cell_voltage6, cell_voltage7, cell_voltage8,\
cell_voltage9, cell_voltage10, cell_voltage11, cell_voltage12, cell_voltage13, cell_voltage14, cell_voltage15, cell_voltage16]
minimo = self.cells[0]
indicemin = 0
contador = 0
for num in self.cells:
if num < minimo and num != 0:
minimo = num
indicemin = contador
contador += 1
self.cell_min_voltage = minimo /1000
self.cell_max_no = (self.cells.index(max(self.cells)) +1)
self.cell_min_no = (indicemin + 1)
self.cell_max_voltage = max(self.cells) / 1000
Hi
If you populate the self.cells object and you have inherited your battery class from Battery like all the examples, then the min/max voltages will be done for you by the Battery class. Don’t calculate it yourself.
The ANT driver does not read the individual cells, but only read the min/max values from the BMS, so it is not the best example for your BMS to copy. Rather look at lttjbd.py and the function read_cell_data which would be closer to what you need to do.
Good luck with your work. I hope to be seeing a Pull Request from you soon
@Louisvdw your a celeb on my favourite youtuber How to connect your BMS to Victron Venus OS. Easy setup and works with JK, JBP, DALY and Heltec. - YouTube
Nah, that’s some lady called Louise…
@Louisvdw, I’d like you to consider a suggestion, which may or may not suit or refine your driver.
It is regarding the hard-coded limits, ( I realize they can be changed but will be over-written with a new revision which is a PITA).
My suggestion is that if they have to be hard-coded they are hard-coded in terms of the C rating of the battery. In other words, set to 1/2 C, I think that that is recognized as a good practice charging limitation amongst all brands.
For example, a 280Ah would attract a 140A limit and so on.
Of course, this could still be altered by the user just as before.
I realize your driver has to try and be brand agnostic and as generic as possible, and this suggestion may not suit.
Another option, or maybe an additional option on top of the default, is to store the value in localsettings (with the default possibly being C/2). Then the user can modify it with a suitable command too, and it will persist across updates.
Or when @Louisvdw gets his multiple banks working C/2 * “No. of banks connected”.
The future plan is to have a GUI in VenusOS where people can change all the settings that is in utils.py with those moving to localsettings.
I have hoped for a non destructive way to add to the qml gui where I don’t need to change existing files. I don’t want to have to fix menus on every new Venus release. But I don’t see that as an option at the moment.
The one thing about the C/2 option is that the BMS also has a current limit. That should be incorporated as well.
The GUI is also not going to stay the same, and as new GX-hardware becomes available the older ones may retain the present GUI while the others may not. It is always going to be a game of catch-up.
@Louisvdw When it’s working I will share files
I have a question, why does the buffer size vary? From the images we can see that there is a difference between one request and another, is it a limitation of cerbo gx? The manufacturer of my BMS uses a single frame to receive all the data in its software, however if I try it on cerbo gx or venus gx there is this difference in the buffer size.
If I use several frames (voltage, current, SOC, temperature) the following error occurs: “device reports readiness to read but returned no data (device disconnected or multiple access on port?”
I have no option on how to proceed.
Att.
Cristian.
Hi
A lot of times the amount of data that we want to retrieve is too much for the protocol and speed that the BMS has implemented. This is the case for the Daly BMS as well. So what happens is that the BMS is still in the previous data loop and then the next poll triggers.
To test if this is the case for your BMS increase the variable self.poll_interval = 5000 for your code. This will then ask a poll trigger every 5 seconds. If this solves your issue then perhaps look at the Daly BMS and the code that reference the poll_step variable. In short we retrieve the important information every second, but other data only every 2 seconds.
@Louisvdw I reviewed Daly’s file several times, and I did the same in my file, but I didn’t understand the following error:
“”"
Traceback (most recent call last):
File “/opt/victronenergy/dbus-serialbattery/dbushelper.py”, line 162, in publish_battery
success = self.battery.refresh_data()
File “/opt/victronenergy/dbus-serialbattery/lifepo4.py”, line 67, in refresh_data
result = self.read_tensao(ser)
File “/opt/victronenergy/dbus-serialbattery/lifepo4.py”, line 203, in read_tensao
all_data = self.read_serial_data_lifepo4_2(ser, self.command_geral)
File “/opt/victronenergy/dbus-serialbattery/lifepo4.py”, line 289, in read_serial_data_lifepo4_2
data = read_serial_data(ser, command, self.port, self.baud_rate, self.LENGTH_POS, self.LENGTH_CHECK)
File “/opt/victronenergy/dbus-serialbattery/utils.py”, line 70, in read_serial_data
with serial.Serial(port, baudrate=baud, timeout=0.1) as ser:
File “/usr/lib/python3.8/site-packages/serial/serialutil.py”, line 218, in init
self.port = port
File “/usr/lib/python3.8/site-packages/serial/serialutil.py”, line 264, in port
raise ValueError(‘“port” must be None or a string, not {}’.format(type(port)))
ValueError: “port” must be None or a string, not <class ‘bytes’>
“”"
Sorry for my lack of knowledge, as I understand it, it opens the port to be used several times, but it seems to me that the port is incorrect.
Looks like a python3 porting issue. In python2 several file operations returned a string, because in python2 a string was the same an array of bytes, and for unicode strings you used a unicode object instead.
In python3, a string is the same as a unicode object, and file operations now return bytes
objects.
To turn it into a string, call b.decode("utf-8")
, or if you are absolutely sure it must be ascii, b.decode("ascii")
.
@plonkster Would it be one of these two functions that do this conversion (“utf-8”)?
I get port = “/dev/ttyUSB0”
def open_serial_port(port, baud):
ser = None
tries = 3
while tries > 0:
try:
ser = serial.Serial(port, baudrate=baud, timeout=0.1)
tries = 0
except serial.SerialException as e:
logger.error(e)
tries -= 1
return ser
# Read data from previously openned serial port
def read_serialport_data(ser, command, length_pos, length_check, length_fixed=None, length_size=None):
try:
ser.flushOutput()
ser.flushInput()
ser.write(command)
length_byte_size = 1
if length_size is not None:
if length_size.upper() == 'H':
length_byte_size = 2
elif length_size.upper() == 'I' or length_size.upper() == 'L':
length_byte_size = 4
count = 0
toread = ser.inWaiting()
while toread < (length_pos+length_byte_size):
sleep(0.005)
toread = ser.inWaiting()
count += 1
if count > 50:
logger.error(">>> ERROR: No reply - returning")
return False
#logger.info('serial data toread ' + str(toread))
res = ser.read(toread)
if length_fixed is not None:
length = length_fixed
else:
if len(res) < (length_pos+length_byte_size):
logger.error(">>> ERROR: No reply - returning [len:" + str(len(res)) + "]")
return False
length_size = length_size if length_size is not None else 'B'
length = unpack_from('>'+length_size, res,length_pos)[0]
#logger.info('serial data length ' + str(length))
count = 0
data = bytearray(res)
while len(data) <= length + length_check:
res = ser.read(length + length_check)
data.extend(res)
#logger.info('serial data length ' + str(len(data)))
sleep(0.005)
count += 1
if count > 150:
logger.error(">>> ERROR: No reply - returning [len:" + str(len(data)) + "/" + str(length + length_check) + "]")
return False
return data
except serial.SerialException as e:
logger.error(e)
return False
This is the function that is reading the data from the serial port, but what calls this takes the result and converts it. That part use the covertion.
Look at what is the value you have set your port variable too.
I don’t know this code base at all. I just ported plenty of things to python3 so I know the common pitfalls.
You’re passing a bytes object instead of a string. Normally this doesn’t happen by accident. You need to figure out where the “port” comes from that’s of the wrong type.
Btw, you python guys, I’m not sure if you know this, but you can always jump into a debugger by inserting:
import pdb; pdb.set_trace()
… right before the line that errors. And then you can inspect your variables. There are plenty of cheat sheets on the interwebz on how to use the debugger. The one you want here is bt
(backtrace), which will show you who called that function with the wrong data type. Then you can u
(up) to step up in the call stack and inspect the caller… and in doing that you can figure out where this erroneous value comes from. Then you can either fix that… or call .decode('ascii')
or whatever if a legitimate type conversion is needed.
Good luck!
Edit: I doubt it will be THIS obvious, but it could be as simple as port = b'/dev/ttyUSB0'
instead of port = '/dev/ttyUSB0'
. The b
prefix is shorthand to make a bytes object. The reason why I say this usually doesn’t happen by accident, is that normally you would not put that prefix there, and python2 will do its thing and python3 will do something a little different and nobody is bothered…
This sort of “wrong type” stuff usually happens when you’re reading it from a file opened in binary mode. Eg…
>>> f = open('/etc/passwd', 'rb')
>>> type(f.read())
<class 'bytes'>
vs in non-binary mode, where python3 applies the default encoding for your platform:
>>> f = open('/etc/passwd', 'r')
>>> type(f.read())
<class 'str'>
Or you can be explicit
>>> f = open('/etc/passwd', 'r', encoding='UTF-8')
>>> type(f.read())
<class 'str'>
>>> f = open('/etc/passwd', 'r', encoding='latin1')
>>> type(f.read())
<class 'str'>
So again, don’t know the code base, but wherever port
came from, something went wrong there.
The drive is working, picking up the port correctly, however, it has a problem due to the BMS not supporting the different frames that are sent simultaneously. I used the battery Daly file as an example so that the frames are not sent simultaneously.
What was entered in the code was:
ser = open_serial_port(self.port, self.baud_rate)
What I don’t understand is why the port is different now than it was before.
To keep it simple, increase the poll_interval value for your BMS. 1000 means every 1 second. So make that 5000 to see that it all works fine. Then you can reduce that again once you know it is working.
If it is too fast the BMS might still be returning a reply while the new event is triggered the get the data again.