diy solar

diy solar

Overkill remote monitoring.

Dmarkwa

New Member
Joined
Apr 24, 2021
Messages
29
I have two 8 cell 24 volt batteries at a remote cabin. They both use Overkill BMS’. In the summer it’s set it and forget it. In the winter I need more careful monitoring.

I bought a solar assistant and USB converter. I removed the Overkill blue tooth plug from the BMS and plugged in the USB converter to the BMS and can now see the “battery” as a whole remotely, but not the fine tuned control i had with Overkill Bluetooth. But overkill bluetooth is not remote. And if I need to make a change while at the cabin I’m also stuck.

Any suggestions?

(Yes, To conserve fuel running the generator in the winter I only want to run it just enough to get To next sun which is variable of course).
 
Right now I'm monitoring mine over bluetooth using a Raspberry Pi which sends the data to InfluxDB.

Based on https://github.com/tgalarneau/bms

app.py

Python:
#!/bin/env python3

import binascii
from collections import namedtuple
from datetime import datetime, timezone
import logging
import os
import struct
import sys
import time
import traceback

from bluepy.btle import Peripheral, DefaultDelegate, BTLEException
from influxdb import InfluxDBClient


CellVoltages = namedtuple('CellVoltages', ['dt', 'cell1', 'cell2', 'cell3', 'cell4', 'cell5', 'cell6', 'cell7', 'cell8', 'cellmin', 'cellmax', 'delta'])
SummaryStats = namedtuple('SummaryStats', ['dt', 'volts', 'amps', 'watts', 'remain', 'capacity', 'cycles', 'c01', 'c02', 'c03', 'c04', 'c05', 'c06', 'c07', 'c08'])
DetailInfo = namedtuple('DetailInfo', ['dt', 'ovp', 'uvp', 'bov', 'buv', 'cot', 'cut', 'dot', 'dut', 'coc', 'duc', 'sc', 'ic', 'cnf', 'protect', 'percent', 'fet', 'cells', 'temp1', 'temp2'])

FORMAT = ('%(asctime)-15s %(threadName)-15s'
          ' %(levelname)-8s %(module)-15s:%(lineno)-8s %(message)s')
logging.basicConfig(format=FORMAT)
log = logging.getLogger()
log.setLevel(logging.WARNING)


_logger = logging.getLogger(__name__)

DEVICE_ADDRESS = os.environ.get('DEVICE_ADDRESS') # "A4:C1:38:DC:71:F8"
DEVICE_NAME = os.environ.get('DEVICE_NAME') # "lfp0"

def cellinfo1(data):            # process pack info
    infodata = data
    i = 4                       # Unpack into variables, skipping header bytes 0-3
    volts, amps, remain, capacity, cycles, mdate, balance1, balance2 = struct.unpack_from('>HhHHHHHH', infodata, i)
    volts=volts/100
    amps = amps/100
    capacity = capacity/100
    remain = remain/100
    watts = volts*amps                              # adding watts field for dbase
    bal1 = (format(balance1, "b").zfill(16))       
    c16 = int(bal1[0:1])                           
    c15 = int(bal1[1:2])                            # using balance1 bits for 16 cells
    c14 = int(bal1[2:3])                            # balance2 is for next 17-32 cells - not using
    c13 = int(bal1[3:4])
    c12 = int(bal1[4:5])                            # bit shows (0,1) charging on-off
    c11 = int(bal1[5:6])
    c10 = int(bal1[6:7])
    c09 = int(bal1[7:8])
    c08 = int(bal1[8:9])
    c07 = int(bal1[9:10])
    c06 = int(bal1[10:11])
    c05 = int(bal1[11:12])
    c04 = int(bal1[12:13])
    c03 = int(bal1[13:14])
    c02 = int(bal1[14:15])
    c01 = int(bal1[15:16])

    return SummaryStats(datetime.now(), float(volts), float(amps), float(watts), float(remain), float(capacity), int(cycles), int(c01), int(c02), int(c03), int(c04), int(c05), int(c06), int(c07), int(c08))

def cellinfo2(data):
    infodata = data 
    i = 0                          # unpack into variables, ignore end of message byte '77'
    protect,vers,percent,fet,cells,sensors,temp1,temp2,b77 = struct.unpack_from('>HBBBBBHHB', infodata, i)
    temp1 = (temp1-2731)/10
    temp2 = (temp2-2731)/10            # fet 0011 = 3 both on ; 0010 = 2 disch on ; 0001 = 1 chrg on ; 0000 = 0 both off
    prt = (format(protect, "b").zfill(16))        # protect trigger (0,1)(off,on)
    ovp = int(prt[0:1])            # overvoltage
    uvp = int(prt[1:2])            # undervoltage
    bov = int(prt[2:3])            # pack overvoltage
    buv = int(prt[3:4])            # pack undervoltage
    cot = int(prt[4:5])            # current over temp
    cut = int(prt[5:6])            # current under temp
    dot = int(prt[6:7])            # discharge over temp
    dut = int(prt[7:8])            # discharge under temp
    coc = int(prt[8:9])            # charge over current
    duc = int(prt[9:10])        # discharge under current
    sc = int(prt[10:11])        # short circuit
    ic = int(prt[11:12])        # ic failure
    cnf = int(prt[12:13])        # fet config problem

    return DetailInfo(datetime.now(), int(ovp), int(uvp), int(bov), int(buv), int(cot), int(cut), int(dot), int(dut), int(coc), int(duc), int(sc), int(ic), int(cnf), int(protect), int(percent), int(fet), int(cells), float(temp1), float(temp2))

def cellvolts1(data):                            # process cell voltages
    celldata = data
    i = 4                       # Unpack into variables, skipping header bytes 0-3
    cell1, cell2, cell3, cell4, cell5, cell6, cell7, cell8 = struct.unpack_from('>HHHHHHHH', celldata, i)
    cells1 = [cell1, cell2, cell3, cell4, cell5, cell6, cell7, cell8]     # needed for max, min, delta calculations

    cellmin = min(cells1)
    cellmax = max(cells1)
    delta = cellmax-cellmin

    return CellVoltages(datetime.now(), int(cell1) / 1000.0, int(cell2) / 1000.0, int(cell3) / 1000.0, int(cell4) / 1000.0, int(cell5) / 1000.0, int(cell6) / 1000.0, int(cell7) / 1000.0, int(cell8) / 1000.0, int(cellmin) / 1000.0, int(cellmax) / 1000.0, int(delta) / 1000.0)

def dict_less_dt(d):
    del d['dt']
    return d

class MyDelegate(DefaultDelegate):
    def __init__(self, influx_client):
        DefaultDelegate.__init__(self)
        self.influx_client = influx_client

    def handleNotification(self, cHandle, data):
        hex_data = binascii.hexlify(data)         # Given raw bytes, get an ASCII string representing the hex values
        text_string = hex_data.decode('utf-8')  # check incoming data for routing to decoding routines

        data_line = None

        if text_string.find('dd04') != -1:                                 # x04 (1-8 cells)   
            data_line = cellvolts1(data)
        elif text_string.find('dd03') != -1:                             # x03
            data_line = cellinfo1(data)
        elif text_string.find('77') != -1 and len(text_string) == 28 or len(text_string) == 36:     # x03
            data_line = cellinfo2(data)

        if data_line:
            self.publish(data_line)

    def publish(self, data_line):
        influx_data = [
            {
                "measurement": "battery_data0",
                "tags": {
                    "battery": DEVICE_NAME,
                },
                "time": data_line.dt.astimezone(timezone.utc).isoformat(),
                "fields": dict_less_dt(data_line._asdict())
            }
        ]

        try:
            self.influx_client.write_points(influx_data)
        except:
            print("Failed to write data to influxdb:", influx_data)
            traceback.print_exc()

def main():
    influx_client = InfluxDBClient('myinflux.example', 443, 'usernameZZZ', 'passwordZZZ', 'my_db_name', ssl=True, verify_ssl=True)

    try:
        print('attempting to connect')       
        bms = Peripheral(DEVICE_ADDRESS, addrType="public")
    except BTLEException as ex:
        time.sleep(10)
        print('2nd try connect')
        bms = Peripheral(DEVICE_ADDRESS, addrType="public")
    except BTLEException as ex:
        print('cannot connect')
        exit()
    else:
        print('connected ', DEVICE_ADDRESS)

    bms.setDelegate(MyDelegate(influx_client))

    # write empty data to 0x15 for notification request   --  address x03 handle for info & x04 handle for cell voltage
    # using waitForNotifications(5) as less than 5 seconds has caused some missed notifications

    # TODO: See https://gitlab.com/bms-tools/bms-tools/-/blob/master/JBD_REGISTER_MAP.md

    while True:
        result = bms.writeCharacteristic(0x15, b'\xdd\xa5\x03\x00\xff\xfd\x77', False)        # write x03 w/o response cell info
        bms.waitForNotifications(5)
        result = bms.writeCharacteristic(0x15, b'\xdd\xa5\x04\x00\xff\xfc\x77', False)        # write x04 w/o response cell voltages
        bms.waitForNotifications(5)
        time.sleep(5)

if __name__ == "__main__":
    main()

Dockerfile

Code:
FROM arm64v8/python:3.8.4-slim-buster
MAINTAINER Symbioquine <symbioquine@gmail.com>
RUN apt-get update && apt-get install build-essential libffi-dev libssl-dev python-dev libglib2.0-dev -y && pip install --upgrade pip && pip install poetry
COPY ./pyproject.toml /app/
WORKDIR /app
RUN poetry install --no-dev
COPY . /app
RUN poetry install --no-dev
ENV PYTHONUNBUFFERED=1
CMD ["poetry", "run", "python", "./app/app.py"]

pyproject.toml

Code:
[tool.poetry]
name = "report-jbd-bms-data"
version = "0.1.0"
description = ""
authors = ["Your Name <you@example.com>"]

[tool.poetry.dependencies]
python = "^3.8"
bluepy = "^1.3.0"
influxdb = "^5.3.1"

[tool.poetry.dev-dependencies]

[build-system]
requires = ["poetry>=0.12"]
build-backend = "poetry.masonry.api"
 
Not sure what you mean “charge termination”. 2200 watts of panels —> Midnight Solar 250 —> battery bank 2 each 8 cell 24 volts —> Dimensions 2000 watt pure sine wave inverter —> cabin panel.

Does that help?
 
Not sure what you mean “charge termination”. 2200 watts of panels —> Midnight Solar 250 —> battery bank 2 each 8 cell 24 volts —> Dimensions 2000 watt pure sine wave inverter —> cabin panel.

Does that help?
Nope.

By charge termination I mean opening/closing the charge path through the BMS.
Are you doing that?

Are you just monitoring the BMS or are you changing the runtime configuration.
If the latter, what changes are you making?
 
Thank you. For the first 8 cell 24 volt batteries for a 24v system, after top balancing we still, noticed two runaway cells that caused the BMS to shut down charging and report an error.

We bought 8 more cells that were top balanced and installed them. We noticed that near the end of the charge cycle we ended up with one runaway cell.

It makes me nervous. And I can’t stay at the cabin forever.

The first set, over time balanced out, so that year around we are good. No runaways. I suppose that the second set of cells will do the same.

Because it’s a drive to the cabin we were hoping that the Overkill software could somehow be remotely administered.

Replacing the Bluetooth on the Overkill with a USB converter and hooking up to a Solar-Assistant was a step forward in terms of monitoring but now I lack any control over the BMS settings without disconnecting and reconnecting to blue tooth.

Hence my looking for a way to still monitor/control my Overkills either at the cabin or remote from home.
 
We replicated the BMS settings from BMS 1 and I dont have those settings here at home.

Mostly I’m wanting the peace of mind of remote adMin of everything so that when family uses the remote cabin and something comes up I have Options without driving up there.

Yes I know it is great when it’s set and forget. But stuff still comes up. Just want options. If there aren’t any there aren’t any.

It’s ok to ask here I think.
 
I can't help you if you don't answer my questions.
Best of luck.
 
For the first 8 cell 24 volt batteries for a 24v system, after top balancing we still, noticed two runaway cells that caused the BMS to shut down charging and report an error.
Maybe your charge settings (too high?) and your BMS settings are not compatible with your cells?
Can you post your charge settings and BMS settings?

My guess, with no data points to base it on, would be that you are charging higher than you need to be (over 14V?) and you have a cell or 2 that run away. Often reigning in charging to just reaching 100% (and maybe a little absorb time) will allow your cells to charge close to 100% yet remain within balance.

Charging cells to 3.45V is pretty much 99% charged. We often see folks trying to charge to 3.65V and of course there are issues with some cells reaching BMS cutoff.
 
I can't help you if you don't answer my questions.
Best of luck.
FWIW, It’s not about answering you. You could have said “can’t do much without the data. Would like to though”. The etiquette on some Forums sucks when experts need to display their ego centric expertness. Liked MisterSandals response. That is helpful at least with runaways. I will look at the Linux based software option for remote monitoring. Sheesh.
 
FWIW, It’s not about answering you.
What are you talking about?
You could have said “can’t do much without the data. Would like to though”.
Why would I say that?
The etiquette on some Forums sucks when experts need to display their ego centric expertness.
What are you talking about?
Liked MisterSandals response. That is helpful at least with runaways. I will look at the Linux based software option for remote monitoring. Sheesh.
You asked for help.
What an ingrate.
 
I"m not an ingrate. How could I be when you gave no help due to a lack of information as you said? Mr. Sandals offered what help he could, also without information, for which I'm thankful. I did ask for help and got some from Mr. Sandals. Compare your remarks to Mr. Sandals. The difference is clear. I should have thanked him and ignored you. Next time that is what I'll do.
 
Back
Top