dreadnought / python-daly-bms

Python module for Daly BMS devices
MIT License
82 stars 37 forks source link

Daly readings over Bluetooth #28

Open KevinEeckman opened 2 years ago

KevinEeckman commented 2 years ago

Hi,

Thanks for your code, it's very helpful. Where did you find the doc to interact with the BMS through Bluetooth ? I was able to find that my Daly is using this module to turn serial data into bluetooth, but it still doesn't really describe how to interact with the module or which characteristics to read/write from.

In any case, my current problem is that I can't read the cell voltages using a Raspberry Pi Zero W. Bleak calls the notification_callback() twice, first with 200 bytes of data, then another time with 8 bytes. The first 200 byes start well with a succession of 13 bytes frames, but the last ~20% is garbage. I couldn't make sense of the 2nd set of 8 bytes.

I put the data here for your reference (I have a 16s daly), all concatenated together, with an attempt at formatting it along the 13-bytes frames :

\xa5\x01\x95\x08\x01\x0c\x89\x0c\x86\x0c\x89\xd3\xd3
\xa5\x01\x95\x08\x02\x0c\x86\x0c\x8a\x0c\x88\xd3\xd4
\xa5\x01\x95\x08\x03\x0c\x89\x0c\x86\x0c\x94\xd3\xe0
\xa5\x01\x95\x08\x04\x0c\x8e\x0c\x8f\x0c\x8e\xd3\xe9
\xa5\x01\x95\x08\x05\x0c\x8f\x0c\x8a\x0c\x8d\xd3\xe5
\xa5\x01\x95\x08\x06\x0c\x8c\x00\x00\x00\x00\xd3\xb4
\xa5\x01\x95\x08\x07\x00\x00\x00\x00\x00\x00\xd3\x1d
\xa5\x01\x95\x08\x08\x00\x00\x00\x00\x00\x00\xd3\x1e
\xa5\x01\x95\x08\t\x00\x00\x00\x00\x00\x00\xd3\x1f
\xa5\x01\x95\x08\n\x00\x00\x00\x00\x00\x00\xd3
\xa5\x01\x95\x08\x0b\x00\x00\x00\x00\x00\x00\xd3!
\xa5\xa8\x00@\x00\x00 @\x00\r0\x00\x00\x00 @\x00\xdel\x00\x00m2\x00\x00\x00 @\x00S1\x00\x00\x00\x00\x00\x00\xa8\x00@\x00\x00\x00\x00\x000\x00@\x00\x00\x00\x00
\xa5\x01\x95\x08\x10\x00\x00\x00\x00\x00\x00\xd3&

Seems like the problem is not coming from your lib, but more from Bleak, Bluez or my Raspberry itself. I spent a couple of hours on the net, but couldn't find anything meaninful. My expertise with BLE is very limited so I create this issue here as a last attempt solving the probblem

dreadnought commented 2 years ago

Hi, the bytes that you receive look already quite familiar to me. \xa5\x01\x95\x08 is the "header" for the cell voltage responses, the next byte (\x01-\x08) mark the eight responses, with 3 voltages in each.

Here are a few lines of code that process your data:

from dalybms import DalyBMS

# your data, everything in one line
input_data = "\xa5\x01\x95\x08\x01\x0c\x89\x0c\x86\x0c\x89\xd3\xd3\xa5\x01\x95\x08\x02\x0c\x86\x0c\x8a\x0c\x88\xd3\xd4\xa5\x01\x95\x08\x03\x0c\x89\x0c\x86\x0c\x94\xd3\xe0\xa5\x01\x95\x08\x04\x0c\x8e\x0c\x8f\x0c\x8e\xd3\xe9\xa5\x01\x95\x08\x05\x0c\x8f\x0c\x8a\x0c\x8d\xd3\xe5\xa5\x01\x95\x08\x06\x0c\x8c\x00\x00\x00\x00\xd3\xb4\xa5\x01\x95\x08\x07\x00\x00\x00\x00\x00\x00\xd3\x1d\xa5\x01\x95\x08\x08\x00\x00\x00\x00\x00\x00\xd3\x1e\xa5\x01\x95\x08\t\x00\x00\x00\x00\x00\x00\xd3\x1f\xa5\x01\x95\x08\n\x00\x00\x00\x00\x00\x00\xd3"

response_data = []
# here we are splitting it with a simple string-based approach, but splitting it by 13 bytes and cutting the first 4 bytes should work too
for part in input_data.split("\xa5\x01\x95\x08"): 
    if len(part) == 0:
        continue
    response_data.append(part.encode()[:8])

bms = DalyBMS()
bms.status = {"cells": 16} # normally the module sets this from get_status, but you can force it manually too
cell_voltages = bms.get_cell_voltages(response_data=response_data)
print(cell_voltages)

The output: {1: 3.266, 2: -30.452, 3: -15.738, 4: 3.266, 5: -31.22, 6: -15.734, 7: 3.266, 8: -30.452, 9: -15.738, 10: 3.266, 11: -29.172, 12: -15.729, 13: 3.266, 14: -28.916, 15: -15.734, 16: 3.266}

You can see that every 3rd cell (or the first of every response package) is in the range of a normal battery cell. Try to compare them to the Bluetooth App, when one value matches you're quite close a solution.

The BMS that you're using should be quite similar to mine, have a look here: #26

The code for the service that I'm using to read the cell voltages 24/7 is here: https://github.com/dreadnought/energy-storage-controller/blob/master/smart_bms.py

Maybe it helps you to understand how you can use the DalyBMSBluetooth.

For debugging/development it can also help to test your code on a machine with a standard USB Bluetooth module and desktop environment.

KevinEeckman commented 2 years ago

That put me on the righ track, thanks. I thought that I had to receive 16 frames since I have 16 cells, didn't realize that there were 3 voltages per frame.

KevinEeckman commented 2 years ago

Sent you a pull request that fixes this particular issue and some others as well

bergyla commented 2 years ago

@[KevinEeckman I tested your patches and coul succesfully read Cell-Voltages from one of mine BMS (200A - 16S). But sometimes I got only "Timeout while waiting for 95 response" .

Furthermore from 2 other BMS I have (400A + 500A both as well 16S) I get Timeouts all the time. Unfortunately I do not know how to generally extend Timeout on asyncio transfers.. I found hardcoded timeout in the used asyncio.wait_for() and extend them for testing. But it seems there is no change in behaviour at all. Even with timeout of 30 secs, I could face up to four retries. So I lowered the timeout to 0.5 and extended retries to 10 which is more then enought to get a 100% successrate at my 100A BMS. Unfortunately even with high timeout plus high retries wether my 400A nor the 500A BMS will ever respond cell-voltages.

KevinEeckman commented 2 years ago

Interesting - this definitely needs more work. Consider uploading the BMS you use to Issue #26 . I also noted that the individual cell voltages I retrieve from get_cell_voltages() don't match the high/low voltages I get from get_cell_voltage_range(). This is especially visible when the batteries is close to max charge and the voltages start to differ a lot. How is it for you ?

bergyla commented 2 years ago

This is especially visible when the batteries is close to max charge and the voltages start to differ a lot. How is it for you ?

In the little batterie pack, all cells seems to be perfectly matched. Max cell differences are abou 0,007V. With that setup I noticed no big differences between get_cell_voltages() and get_cell_voltage_range().

For BMS presentation, I had to unconnect and open the batteriepacks from working setup, eventually I could build some Micro-USB-female Connector to JST-PH-6pin Male for the UART readout via PC-software. Is the pinout of the BT-device noted anywhere ? I received new BT-Transceivers and measured its wiring, if anybody is interessted: JST-GH female connector (Pin numbering from DALY datasheet - might be reversed from default!) - Micro USB (type only, NO USB protocol) - USB function only provided for better orientation 6 (red) UART RX2 - 1 ( VBUS) 5 (white) UART_TX2 - 2 (D-) 4 (yellow) BLU_DRV (Button) - 4 (ID) 3 - open 2 (green) POW_3V3 - 3 (D+) 1 (black) GND - 5 GND

bergyla commented 2 years ago

'JST-GH female connector (Pin numbering from DALY datasheet - might be reversed from default!) - Micro USB (type only, NO USB protocol) - USB function only provided for better orientation 6 (red) UART RX2 - 1 ( VBUS) 5 (white) UART_TX2 - 2 (D-) 4 (yellow) BLU_DRV (Button) - 4 (ID) 3 - open 2 (green) POW_3V3 - 3 (D+) 1 (black) GND - 5 GND

matthiaslink77 commented 2 years ago

I am trying since quite some time now to get the bluetooth connection setup, but I am failing.

When I try to pair the BMS Bluetooth via bluetoothctl pair I always get: ... Failed to pair: org.bluez.Error.AuthenticationCanceled

Is there a good documentation on how to connect properly to the Daly BMS via bluetooth and then how to connect with this daly-bms-cli to the bluethooth device?

Very much appreciated and best regards, Matt.

SamuelBrucksch commented 1 year ago

I just tried the same and get a similiar error when trying to pair. Would be really happy to get some help on establishing the BT connection.

Error is

Failed to pair: org.bluez.Error.AuthenticationFailed
dreadnought commented 1 year ago

Here are a few hints that I can provide you:

I hope they help you get one step further.

FrancescoTalotta commented 1 year ago

Hello. Is there anybody that has a simple script that reads data over the Bluetooth? I tried https://github.com/dreadnought/energy-storage-controller/blob/master/smart_bms.py but I can't let it work with Python 3.9.

Thank you :)

dreadnought commented 1 year ago

Please share a bit more about your issue, the commands that you run, the output that you get, the BMS that you have.

FrancescoTalotta commented 1 year ago

Hello, thank you for your reply. I have a Daly BMS 150A with 16 LiPoFe4 batteries. I tried that script but now I get some errors with the config library. What I would like to see is how to connect via bluetooth and how to read values using the library. Here is the code I'm using now but it does not work:

#!/usr/bin/python3
import time
from cysystemd.daemon import notify, Notification

from dalybms import DalyBMSBluetooth
import logging

import config

#logger = get_logger(level='info')
logger = logging.getLogger(__name__)
received_data = False
time.sleep(3)

import asyncio
import multiprocessing

class DalyBMSConnection():
    def __init__(self, mac_address, logger):
        self.logger = logger
        self.bt_bms = DalyBMSBluetooth(logger=logger)
        self.mac_address = mac_address
        self.last_data_received = None

    async def connect(self):
        await self.bt_bms.connect(mac_address=self.mac_address)

    async def update_cell_voltages(self):
        cell_voltages = await self.bt_bms.get_cell_voltages()
        logger.debug(cell_voltages)
        points = []

        if not cell_voltages:
            logger.warning("failed to receive cell voltages")
            return
        cell_voltages_time = time.time()
        for cell, voltage in cell_voltages.items():
            points.append({
                "measurement": "SmartBMSCellVoltages",
                "tags": {
                    "mac_address": self.mac_address,
                    "cell": cell,
                },
                "time": cell_voltages_time,
                "fields": {'voltage': voltage},
            })

        self.last_data_received = time.time()

    async def update_soc(self):
        soc = await self.bt_bms.get_soc()
        self.logger.debug(soc)
        if not soc:
            logger.warning("failed to receive SOC")
            return
        point = {
            "measurement": "SmartBMSStatus",
            "tags": {
                "mac_address": self.mac_address,
            },
            "time": time.time(),
            "fields": soc,
        }
        self.last_data_received = time.time()

async def main(con):
    logger.info("Connecting")
    await con.connect()
    logger.info("Starting loop")
    received_data = False
    while con.bt_bms.client.is_connected:
        logger.debug("run start")
        await con.update_soc()
        await con.update_cell_voltages()
        #all = await con.bt_bms.get_all()
        #print(all)

        if con.last_data_received is None:
            logger.warning("Failed receive data")
            await asyncio.sleep(10)
            continue
        time_diff = time.time() - con.last_data_received
        if time_diff > 30:
            logger.error("BMS thread didn't receive data for %0.1f seconds" % time_diff)
        else:
            if not received_data:
                logger.info("First received data")
                notify(Notification.READY)
                received_data = True
            notify(Notification.WATCHDOG)

        logger.debug("run done")
        await asyncio.sleep(10)
    await con.bt_bms.disconnect()
    logger.info("Loop ended")

con = DalyBMSConnection(mac_address=config.config['bms']['mac_address'], logger=logger)
loop = asyncio.get_event_loop()
asyncio.ensure_future(main(con))
try:
    loop.run_forever()
except KeyboardInterrupt:
    pass

loop.run_until_complete(con.bt_bms.disconnect())
logger.info("Final End")
FrancescoTalotta commented 1 year ago

I figured out a simple script that can be used to read data over Bluetooth:

#!/usr/bin/python3
from dalybms import DalyBMSBluetooth
import asyncio

class DalyBMSConnection():
    def __init__(self, mac_address):
        self.bt_bms = DalyBMSBluetooth()
        self.mac_address = mac_address

    async def connect(self):
        await self.bt_bms.connect(mac_address=self.mac_address)

    async def update_cell_voltages(self):
        cell_voltages = await self.bt_bms.get_cell_voltages()

    async def update_cell_voltage_range(self):
        cell_v_range = await self.bt_bms.get_cell_voltage_range()
        print(cell_v_range)

    async def update_status(self):
        status = await self.bt_bms.get_status()
        print(status)

    async def update_temps2(self):
        temp2 = await self.bt_bms.get_max_min_temperature()
        print(temp2)

    async def update_soc(self):
        soc = await self.bt_bms.get_soc()
        print(soc)

    async def update_mosfet(self):
        mosfet = await self.bt_bms.get_mosfet_status()
        print(mosfet)

    async def update_temps(self):
        temps = await self.bt_bms.get_temperatures()
        print(temps)

    async def update_bal(self):
        bal = await self.bt_bms.get_balancing_status()
        print(bal)

    async def update_errors(self):
        errors = await self.bt_bms.get_errors()
        print(errors)

async def main(con):
    await con.connect()
    await con.update_status()
    await con.update_soc()
    await con.update_temps2()
    await con.update_cell_voltage_range()
    await con.update_mosfet()
    await con.update_bal()
    await con.update_errors()
    await con.bt_bms.disconnect()

con = DalyBMSConnection(mac_address='C6:6C:09:02:0E:4C')
asyncio.run(main(con))

I am using @KevinEeckman version but I am unable to read cells voltages. Balancing status always shows false. I also noticed that with the original version of @dreadnought I sometimes gets some BMS error messages from the update_errors() function that I don't see in the SmartBMS app. With @KevinEeckman version I get no BMS error messages.

tomatensaus commented 1 year ago

I am also using @KevinEeckman changes and they work well on my bms. Thanks @FrancescoTalotta for the script, saved me some time. All cell voltages are present and appear to be correct. Guess I will be adding some MQTT code next

tomatensaus commented 1 year ago

@FrancescoTalotta @KevinEeckman I forked the project and published a executable (for bluetooth) that can read config from the command line and/or config file, and is able to output to mqtt & home assistant. I also added systemd instructions & integration. Mine is now running from systemd and automatically starts after a boot. If we can get the @KevinEeckman changes merged I can add a pull request to bring all the changes upstream. I also fixed some minor issues that I found. Working for my 16S 200A Daly. PR created https://github.com/KevinEeckman/python-daly-bms/pull/1

JanusHL commented 1 year ago

class DalyBMSConnection

Thank you for this piece of code... JanusHL

dg1kwa commented 3 months ago

I use script from Franceso, but only errors:

Traceback (most recent call last): File "/home/pi/daly-bms/daly-bms.py", line 60, in asyncio.run(main(con)) File "/usr/lib/python3.11/asyncio/runners.py", line 190, in run return runner.run(main) ^^^^^^^^^^^^^^^^ File "/usr/lib/python3.11/asyncio/runners.py", line 118, in run return self._loop.run_until_complete(task) ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^ File "/usr/lib/python3.11/asyncio/base_events.py", line 653, in run_until_complete return future.result() ^^^^^^^^^^^^^^^ File "/home/pi/daly-bms/daly-bms.py", line 49, in main await con.connect() File "/home/pi/daly-bms/daly-bms.py", line 11, in connect await self.bt_bms.connect(mac_address=self.mac_address) File "/home/pi/.local/lib/python3.11/site-packages/dalybms/daly_bms_bluetooth.py", line 45, in connect await self.client.start_notify(17, self._notification_callback) File "/home/pi/.local/lib/python3.11/site-packages/bleak/init.py", line 822, in start_notify raise BleakError(f"Characteristic {char_specifier} not found!") bleak.exc.BleakError: Characteristic 17 not found!

Raspbian 12 32Bit

dg1kwa commented 2 months ago

I figured out a simple script that can be used to read data over Bluetooth:

#!/usr/bin/python3
from dalybms import DalyBMSBluetooth
import asyncio

class DalyBMSConnection():
    def __init__(self, mac_address):
        self.bt_bms = DalyBMSBluetooth()
        self.mac_address = mac_address

    async def connect(self):
        await self.bt_bms.connect(mac_address=self.mac_address)

    async def update_cell_voltages(self):
        cell_voltages = await self.bt_bms.get_cell_voltages()

    async def update_cell_voltage_range(self):
        cell_v_range = await self.bt_bms.get_cell_voltage_range()
        print(cell_v_range)

    async def update_status(self):
        status = await self.bt_bms.get_status()
        print(status)

    async def update_temps2(self):
        temp2 = await self.bt_bms.get_max_min_temperature()
        print(temp2)

    async def update_soc(self):
        soc = await self.bt_bms.get_soc()
        print(soc)

    async def update_mosfet(self):
        mosfet = await self.bt_bms.get_mosfet_status()
        print(mosfet)

    async def update_temps(self):
        temps = await self.bt_bms.get_temperatures()
        print(temps)

    async def update_bal(self):
        bal = await self.bt_bms.get_balancing_status()
        print(bal)

    async def update_errors(self):
        errors = await self.bt_bms.get_errors()
        print(errors)

async def main(con):
    await con.connect()
    await con.update_status()
    await con.update_soc()
    await con.update_temps2()
    await con.update_cell_voltage_range()
    await con.update_mosfet()
    await con.update_bal()
    await con.update_errors()
    await con.bt_bms.disconnect()

con = DalyBMSConnection(mac_address='C6:6C:09:02:0E:4C')
asyncio.run(main(con))

I am using @KevinEeckman version but I am unable to read cells voltages. Balancing status always shows false. I also noticed that with the original version of @dreadnought I sometimes gets some BMS error messages from the update_errors() function that I don't see in the SmartBMS app. With @KevinEeckman version I get no BMS error messages.

Raspbian 11 same Problem: bleak.exc.BleakError: Characteristic 17 not found!

KevinEeckman commented 2 months ago

Hi @dg1kwa, I don't use a Daly BMS anymore so I'm not maintaining this code anymore. Your error message seems to indicate that you are trying to poll a bluetooth characteristic that doesn't exist. That could point to a version of your Daly BMS that's not supported by this code. Is it very old or very recent ?

dg1kwa commented 2 months ago

Hi @dg1kwa, I don't use a Daly BMS anymore so I'm not maintaining this code anymore. Your error message seems to indicate that you are trying to poll a bluetooth characteristic that doesn't exist. That could point to a version of your Daly BMS that's not supported by this code. Is it very old or very recent ?

This is only from con.connect() ....

I must ask my friend how old the Daly BMS