mpp-solar
mpp-solar copied to clipboard
jkbms continuous polling
Is there any possibility to perfom continuous polling and sending to MQTT? By continuous polling I'm referring the phone app working mode. The BLE setup is extremely slow and prone to error (sometimes I get a stacktrace instead of data).
I have the same problem, getting data over ble is very slow and not very reliable (up to 50% error). Don't know if it can be faster but it would be nice...
same here
I've switched to the serial interface and everything is smooth, except that some data is missing (but can be inferred).
How did you do it @sigxcpu76 ?
I have the same problem, getting data over ble is very slow and not very reliable (up to 50% error). Don't know if it can be faster but it would be nice...
for those interested in getting data over bluetooth every X seconds, here is a python script I have found on github, and slitglty modified to my needs. It is outputing data every 5 seconds in a file indefinitely. You need a good bluetooth connection (short distance). For me it works great, 720 samples per hour and almost no error. Jkbms.py.txt
Thank you, i'm trying it out since the RS485 seems to be not working at all. I've read from another post that the new 11.XW hardware now supports 32 cells so the readings are a bit off. How do you think i can integrate this modification in your script? I got this output now:
{'model_nbr': 'BK-BLE-1.0',
'device_info': {'hw_rev': '11.XW', 'sw_rev': '11.26', 'uptime': 1218900, 'vendor_id': 'JK_B2A24S15P', 'manufacturing_date': '230409'}, 'last_update': 1682243168.0821383, 'settings': {'cell_uvp': 2.8000000000000003, 'cell_uvpr': 2.85, 'cell_ovp': 3.66, 'cell_ovpr': 3.64, 'balance_trigger_voltage': 0.003, 'power_off_voltage': 2.5, 'max_charge_current': 120.0, 'max_discharge_current': 150.0, 'max_balance_current': 120.0, 'cell_count': 16, 'charging_switch': True, 'discharging_switch': True, 'balancing_switch': True}, 'cell_info':
{'voltages': [3.378, 3.362, 3.359, 3.356, 3.356, 3.361, 3.36, 3.359, 3.356, 3.355, 3.355, 3.356, 3.358, 3.356, 3.359, 3.364], 'average_cell_voltage': 0.0, 'delta_cell_voltage': 0.0, 'max_voltage_cell': 0, 'min_voltage_cell': 0, 'resistances': [0.0, 0.0, 0.0, 65.535, 0.0, 3.359, 0.023, 0.768, 0.061, 0.062, 0.06, 0.06, 0.059000000000000004, 0.059000000000000004, 0.058, 0.058], 'total_voltage': 0.0, 'current': 0.0, 'temperature_sensor_1': 0.0, 'temperature_sensor_2': 0.0, 'temperature_mos': 0.0, 'balancing_current': 0.0, 'balancing_action': 0.0, 'battery_soc': 0, 'capacity_remain': 19857.408, 'capacity_nominal': 0.0, 'cycle_count': 53745, 'cycle_capacity': 2736.213, 'charging_switch_enabled': False, 'discharging_switch_enabled': False, 'balancing_active': False, 'error_bitmask_16': '0x0', 'error_bitmask_2': '0000000000000000', 'power': 0.0},
'warnings': {'resistance_too_high': False, 'cell_count_wrong': False, 'charge_overtemp': False, 'charge_undertemp': False, 'discharge_overtemp': False, 'cell_overvoltage': False, 'cell_undervoltage': False, 'charge_overcurrent': False, 'discharge_overcurrent': False}}
Maybe the protcol has slightly changed between my v10 hardware version and your V11, I think it can be corrected because the script seems to get some good values like the cells voltage and other stuff.
I think your best bet is to modify this section to try to get the good values where they are missing:
TRANSLATE_DEVICE_INFO = [ [["device_info", "hw_rev"], 22, "8s"], [["device_info", "sw_rev"], 30, "8s"], [["device_info", "uptime"], 38, "<L"], [["device_info", "vendor_id"], 6, "16s"], [["device_info", "manufacturing_date"], 78, "8s"], ]
TRANSLATE_SETTINGS = [ [["settings", "cell_uvp"], 10, "<L", 0.001], [["settings", "cell_uvpr"], 14, "<L", 0.001], [["settings", "cell_ovp"], 18, "<L", 0.001], [["settings", "cell_ovpr"], 22, "<L", 0.001], [["settings", "balance_trigger_voltage"], 26, "<L", 0.001], [["settings", "power_off_voltage"], 46, "<L", 0.001], [["settings", "max_charge_current"], 50, "<L", 0.001], [["settings", "max_discharge_current"], 62, "<L", 0.001], [["settings", "max_balance_current"], 50, "<L", 0.001], [["settings", "cell_count"], 114, "<L"], [["settings", "charging_switch"], 118, "4?"], [["settings", "discharging_switch"], 122, "4?"], [["settings", "balancing_switch"], 126, "4?"], ]
TRANSLATE_CELL_INFO = [ [["cell_info", "voltages", 16], 6, "<H", 0.001], [["cell_info", "average_cell_voltage"], 58, "<H", 0.001], [["cell_info", "delta_cell_voltage"], 60, "<H", 0.001], [["cell_info", "max_voltage_cell"], 62, "<B"], [["cell_info", "min_voltage_cell"], 63, "<B"], [["cell_info", "resistances", 16], 64, "<H", 0.001], [["cell_info", "total_voltage"], 118, "<H", 0.001], [["cell_info", "current"], 126, "<l", 0.001], [["cell_info", "temperature_sensor_1"], 130, "<H", 0.1], [["cell_info", "temperature_sensor_2"], 132, "<H", 0.1], [["cell_info", "temperature_mos"], 134, "<H", 0.1], [["cell_info", "balancing_current"], 138, "<H", 0.001], [["cell_info", "balancing_action"], 140, "<B", 0.001], [["cell_info", "battery_soc"], 141, "B"], [["cell_info", "capacity_remain"], 142, "<L", 0.001], [["cell_info", "capacity_nominal"], 146, "<L", 0.001], [["cell_info", "cycle_count"], 150, "<L"], [["cell_info", "cycle_capacity"], 154, "<L", 0.001], [["cell_info", "charging_switch_enabled"], 166, "1?"], [["cell_info", "discharging_switch_enabled"], 167, "1?"], [["cell_info", "balancing_active"], 191, "1?"], ]
I also have a spare V11 hardware with V11.25 software, maybe i'll give it a try to see if I have the same behavior than yours.
Maybe the protcol has slightly changed between my v10 hardware version and your V11, I think it can be corrected because the script seems to get some good values like the cells voltage and other stuff.
I think your best bet is to modify this section to try to get the good values where they are missing:
TRANSLATE_DEVICE_INFO = [ [["device_info", "hw_rev"], 22, "8s"], [["device_info", "sw_rev"], 30, "8s"], [["device_info", "uptime"], 38, "<L"], [["device_info", "vendor_id"], 6, "16s"], [["device_info", "manufacturing_date"], 78, "8s"], ]
TRANSLATE_SETTINGS = [ [["settings", "cell_uvp"], 10, "<L", 0.001], [["settings", "cell_uvpr"], 14, "<L", 0.001], [["settings", "cell_ovp"], 18, "<L", 0.001], [["settings", "cell_ovpr"], 22, "<L", 0.001], [["settings", "balance_trigger_voltage"], 26, "<L", 0.001], [["settings", "power_off_voltage"], 46, "<L", 0.001], [["settings", "max_charge_current"], 50, "<L", 0.001], [["settings", "max_discharge_current"], 62, "<L", 0.001], [["settings", "max_balance_current"], 50, "<L", 0.001], [["settings", "cell_count"], 114, "<L"], [["settings", "charging_switch"], 118, "4?"], [["settings", "discharging_switch"], 122, "4?"], [["settings", "balancing_switch"], 126, "4?"], ]
TRANSLATE_CELL_INFO = [ [["cell_info", "voltages", 16], 6, "<H", 0.001], [["cell_info", "average_cell_voltage"], 58, "<H", 0.001], [["cell_info", "delta_cell_voltage"], 60, "<H", 0.001], [["cell_info", "max_voltage_cell"], 62, "<B"], [["cell_info", "min_voltage_cell"], 63, "<B"], [["cell_info", "resistances", 16], 64, "<H", 0.001], [["cell_info", "total_voltage"], 118, "<H", 0.001], [["cell_info", "current"], 126, "<l", 0.001], [["cell_info", "temperature_sensor_1"], 130, "<H", 0.1], [["cell_info", "temperature_sensor_2"], 132, "<H", 0.1], [["cell_info", "temperature_mos"], 134, "<H", 0.1], [["cell_info", "balancing_current"], 138, "<H", 0.001], [["cell_info", "balancing_action"], 140, "<B", 0.001], [["cell_info", "battery_soc"], 141, "B"], [["cell_info", "capacity_remain"], 142, "<L", 0.001], [["cell_info", "capacity_nominal"], 146, "<L", 0.001], [["cell_info", "cycle_count"], 150, "<L"], [["cell_info", "cycle_capacity"], 154, "<L", 0.001], [["cell_info", "charging_switch_enabled"], 166, "1?"], [["cell_info", "discharging_switch_enabled"], 167, "1?"], [["cell_info", "balancing_active"], 191, "1?"], ]
Yep, i tried already simply adding "8" starting from "average_cell_voltage" ) but with no luck
I also have a spare V11 hardware with V11.25 software, maybe i'll give it a try to see if I have the same behavior than yours.
from row 21 there's this, it seems to be implemented already?:
FRAME_VERSION_JK04 = 0x01 FRAME_VERSION_JK02 = 0x02 FRAME_VERSION_JK02_32S = 0x03 PROTOCOL_VERSION_JK02 = 0x02
protocol_version = PROTOCOL_VERSION_JK02
I also have a spare V11 hardware with V11.25 software, maybe i'll give it a try to see if I have the same behavior than yours.
from row 21 there's this, it seems to be implemented already?:
FRAME_VERSION_JK04 = 0x01 FRAME_VERSION_JK02 = 0x02 FRAME_VERSION_JK02_32S = 0x03 PROTOCOL_VERSION_JK02 = 0x02
protocol_version = PROTOCOL_VERSION_JK02
Do you have tried modifying protocol_version = PROTOCOL_VERSION_JK02 to protocol_version = PROTOCOL_VERSION_JK02_32S ? Does it works ?
Yes i tried. I think it's the FRAME parte that Need to be modified
Il lun 24 apr 2023, 11:57 maxxximatoze @.***> ha scritto:
I also have a spare V11 hardware with V11.25 software, maybe i'll give it a try to see if I have the same behavior than yours.
from row 21 there's this, it seems to be implemented already?:
FRAME_VERSION_JK04 = 0x01 FRAME_VERSION_JK02 = 0x02 FRAME_VERSION_JK02_32S = 0x03 PROTOCOL_VERSION_JK02 = 0x02
protocol_version = PROTOCOL_VERSION_JK02
Do you have tried modifying protocol_version = PROTOCOL_VERSION_JK02 to protocol_version = PROTOCOL_VERSION_JK02_32S ? Does it works ?
— Reply to this email directly, view it on GitHub https://github.com/jblance/mpp-solar/issues/251#issuecomment-1519800061, or unsubscribe https://github.com/notifications/unsubscribe-auth/AMYZNQJ67FNLWCPN2CLCSUDXCZFAHANCNFSM57ICCTIQ . You are receiving this because you commented.Message ID: @.***>
I also have a spare V11 hardware with V11.25 software, maybe i'll give it a try to see if I have the same behavior than yours.
from row 21 there's this, it seems to be implemented already?: FRAME_VERSION_JK04 = 0x01 FRAME_VERSION_JK02 = 0x02 FRAME_VERSION_JK02_32S = 0x03 PROTOCOL_VERSION_JK02 = 0x02 protocol_version = PROTOCOL_VERSION_JK02
Do you have tried modifying protocol_version = PROTOCOL_VERSION_JK02 to protocol_version = PROTOCOL_VERSION_JK02_32S ? Does it works ?
any luck with this ?
Is there any further development on this? I have been unsseucfeul in getting the above script to work, as my device uses JK04 protocotol version and I cannot figure out how to define a new protocol in the linked script. If anyone has a source for that script, I may be able to fix it with the right documentation.
I am looping the script round and round to control charging and low cell volts on an EV motorbike, but the delay causes issues at low battery voltage when sag is not detected. A live stream like the JKBMS app would alleviate this.
Is there any further development on this? I have been unsseucfeul in getting the above script to work, as my device uses JK04 protocotol version and I cannot figure out how to define a new protocol in the linked script. If anyone has a source for that script, I may be able to fix it with the right documentation.
I am looping the script round and round to control charging and low cell volts on an EV motorbike, but the delay causes issues at low battery voltage when sag is not detected. A live stream like the JKBMS app would alleviate this.
Here is the version I am using right now for for some time and it works really well. It is configured to stay connected forever and to write data to a file every 5s, but you can modify it to your needs. Don't know if it works with JK04, not tried yet. Give some feedback here if you get some succes with it, it could help others...
import os import asyncio from bleak import BleakScanner, BleakClient import time from logging import info, debug import logging from struct import unpack_from, calcsize import threading
logging.basicConfig(level=logging.INFO) #logging.basicConfig(level=logging.DEBUG)
#zero means parse all incoming data (every second) CELL_INFO_REFRESH_S = 0 DEVICE_INFO_REFRESH_S = 43200 # every 12 Hours CHAR_HANDLE = "0000ffe1-0000-1000-8000-00805f9b34fb" MODEL_NBR_UUID = "00002a24-0000-1000-8000-00805f9b34fb"
COMMAND_CELL_INFO = 0x96 COMMAND_DEVICE_INFO = 0x97
FRAME_VERSION_JK04 = 0x01 FRAME_VERSION_JK02 = 0x02 FRAME_VERSION_JK02_32S = 0x03 PROTOCOL_VERSION_JK02 = 0x02
protocol_version = PROTOCOL_VERSION_JK02
MIN_RESPONSE_SIZE = 300 MAX_RESPONSE_SIZE = 320
TRANSLATE_DEVICE_INFO = [ [["device_info", "hw_rev"], 22, "8s"], [["device_info", "sw_rev"], 30, "8s"], [["device_info", "uptime"], 38, "<L"], [["device_info", "vendor_id"], 6, "16s"], [["device_info", "manufacturing_date"], 78, "8s"], ]
TRANSLATE_SETTINGS = [ [["settings", "cell_uvp"], 10, "<L", 0.001], [["settings", "cell_uvpr"], 14, "<L", 0.001], [["settings", "cell_ovp"], 18, "<L", 0.001], [["settings", "cell_ovpr"], 22, "<L", 0.001], [["settings", "balance_trigger_voltage"], 26, "<L", 0.001], [["settings", "power_off_voltage"], 46, "<L", 0.001], [["settings", "max_charge_current"], 50, "<L", 0.001], [["settings", "max_discharge_current"], 62, "<L", 0.001], [["settings", "max_balance_current"], 50, "<L", 0.001], [["settings", "cell_count"], 114, "<L"], [["settings", "charging_switch"], 118, "4?"], [["settings", "discharging_switch"], 122, "4?"], [["settings", "balancing_switch"], 126, "4?"], ]
TRANSLATE_CELL_INFO = [ [["cell_info", "voltages", 16], 6, "<H", 0.001], [["cell_info", "average_cell_voltage"], 58, "<H", 0.001], [["cell_info", "delta_cell_voltage"], 60, "<H", 0.001], [["cell_info", "max_voltage_cell"], 62, "<B"], [["cell_info", "min_voltage_cell"], 63, "<B"], [["cell_info", "resistances", 16], 64, "<H", 0.001], [["cell_info", "total_voltage"], 118, "<H", 0.001], [["cell_info", "current"], 126, "<l", 0.001], [["cell_info", "temperature_sensor_1"], 130, "<H", 0.1], [["cell_info", "temperature_sensor_2"], 132, "<H", 0.1], [["cell_info", "temperature_mos"], 134, "<H", 0.1], [["cell_info", "balancing_current"], 138, "<H", 0.001], [["cell_info", "balancing_action"], 140, "<B", 0.001], [["cell_info", "battery_soc"], 141, "B"], [["cell_info", "capacity_remain"], 142, "<L", 0.001], [["cell_info", "capacity_nominal"], 146, "<L", 0.001], [["cell_info", "cycle_count"], 150, "<L"], [["cell_info", "cycle_capacity"], 154, "<L", 0.001], [["cell_info", "charging_switch_enabled"], 166, "1?"], [["cell_info", "discharging_switch_enabled"], 167, "1?"], [["cell_info", "balancing_active"], 191, "1?"], ]
class JkBmsBle: #entries for translating the bytearray to py-object via unpack #[[py dict entry as list, each entry ] ] frame_buffer = bytearray() bms_status = {}
waiting_for_response = ""
last_cell_info = 0
def __init__(self, addr):
self.address = addr
self.bt_thread = threading.Thread(target=self.connect_and_scrape)
async def scanForDevices(self):
devices = await BleakScanner.discover()
for d in devices:
print(d)
# iterative implementation maybe later due to referencing
def translate(self, fb, translation, o, i=0):
if i == len(translation[0]) - 1:
# keep things universal by using an n=1 list
kees = (
range(0, translation[0][i])
if isinstance(translation[0][i], int)
else [translation[0][i]]
)
i = 0
for j in kees:
if isinstance(translation[2], int):
# handle raw bytes without unpack_from;
# 3. param gives no format but number of bytes
val = bytearray(
fb[translation[1] + i : translation[1] + i + translation[2]]
)
i += translation[2]
else:
val = unpack_from(
translation[2], bytearray(fb), translation[1] + i
)[0]
# calculate stepping in case of array
i = i + calcsize(translation[2])
if isinstance(val, bytes):
val = val.decode("utf-8").rstrip(" \t\n\r\0")
elif isinstance(val, int) and len(translation) == 4:
val = val * translation[3]
o[j] = val
else:
if translation[0][i] not in o:
if len(translation[0]) == i + 2 and isinstance(
translation[0][i + 1], int
):
o[translation[0][i]] = [None] * translation[0][i + 1]
else:
o[translation[0][i]] = {}
self.translate(fb, translation, o[translation[0][i]], i + 1)
def decode_warnings(self, fb):
val = unpack_from("<H", bytearray(fb), 136)[0]
self.bms_status["cell_info"]["error_bitmask_16"] = hex(val)
self.bms_status["cell_info"]["error_bitmask_2"] = format(val, "016b")
if "warnings" not in self.bms_status:
self.bms_status["warnings"] = {}
self.bms_status["warnings"]["resistance_too_high"] = bool(val & (1 << 0))
self.bms_status["warnings"]["cell_count_wrong"] = bool(val & (1 << 2)) # ?
self.bms_status["warnings"]["charge_overtemp"] = bool(val & (1 << 8))
self.bms_status["warnings"]["charge_undertemp"] = bool(val & (1 << 9))
self.bms_status["warnings"]["discharge_overtemp"] = bool(val & (1 << 15))
self.bms_status["warnings"]["cell_overvoltage"] = bool(val & (1 << 4))
self.bms_status["warnings"]["cell_undervoltage"] = bool(val & (1 << 11))
self.bms_status["warnings"]["charge_overcurrent"] = bool(val & (1 << 6))
self.bms_status["warnings"]["discharge_overcurrent"] = bool(val & (1 << 13))
# bis hierhin verifiziert, rest zu testen
def decode_device_info_jk02(self):
fb = self.frame_buffer
for t in TRANSLATE_DEVICE_INFO:
self.translate(fb, t, self.bms_status)
def decode_cellinfo_jk02(self):
fb = self.frame_buffer
for t in TRANSLATE_CELL_INFO:
self.translate(fb, t, self.bms_status)
self.decode_warnings(fb)
debug(self.bms_status)
def decode_settings_jk02(self):
fb = self.frame_buffer
for t in TRANSLATE_SETTINGS:
self.translate(fb, t, self.bms_status)
debug(self.bms_status)
def decode(self):
# check what kind of info the frame contains
info_type = self.frame_buffer[4]
if info_type == 0x01:
info("Processing frame with settings info")
if protocol_version == PROTOCOL_VERSION_JK02:
self.decode_settings_jk02()
self.bms_status["last_update"] = time.time()
elif info_type == 0x02:
if (
CELL_INFO_REFRESH_S == 0
or time.time() - self.last_cell_info > CELL_INFO_REFRESH_S
):
self.last_cell_info = time.time()
info("processing frame with battery cell info")
if protocol_version == PROTOCOL_VERSION_JK02:
self.decode_cellinfo_jk02()
self.bms_status["last_update"] = time.time()
# power is calculated from voltage x current as
# register 122 contains unsigned power-value
self.bms_status["cell_info"]["power"] = (
self.bms_status["cell_info"]["current"]
* self.bms_status["cell_info"]["total_voltage"]
)
if self.waiting_for_response == "cell_info":
self.waiting_for_response = ""
elif info_type == 0x03:
info("processing frame with device info")
if protocol_version == PROTOCOL_VERSION_JK02:
self.decode_device_info_jk02()
self.bms_status["last_update"] = time.time()
else:
return
if self.waiting_for_response == "device_info":
self.waiting_for_response = ""
def assemble_frame(self, data: bytearray):
if len(self.frame_buffer) > MAX_RESPONSE_SIZE:
info("data dropped because it alone was longer than max frame length")
self.frame_buffer = []
if data[0] == 0x55 and data[1] == 0xAA and data[2] == 0xEB and data[3] == 0x90:
# beginning of new frame, clear buffer
self.frame_buffer = []
self.frame_buffer.extend(data)
if len(self.frame_buffer) >= MIN_RESPONSE_SIZE:
# check crc; always at position 300, independent of
# actual frame-lentgh, so crc up to 299
ccrc = self.crc(self.frame_buffer, 300 - 1)
rcrc = self.frame_buffer[300 - 1]
debug(f"compair recvd. crc: {rcrc} vs calc. crc: {ccrc}")
if ccrc == rcrc:
debug("great success! frame complete and sane, lets decode")
self.decode()
self.frame_buffer = []
def ncallback(self, sender: int, data: bytearray):
debug(f"------> NEW PACKAGE!laenge: {len(data)}")
self.assemble_frame(data)
def crc(self, arr: bytearray, length: int) -> int:
crc = 0
for a in arr[:length]:
crc = crc + a
return crc.to_bytes(2, "little")[0]
async def write_register(
self, address, vals: bytearray, length: int, bleakC: BleakClient
):
frame = bytearray(20)
frame[0] = 0xAA # start sequence
frame[1] = 0x55 # start sequence
frame[2] = 0x90 # start sequence
frame[3] = 0xEB # start sequence
frame[4] = address # holding register
frame[5] = length # size of the value in byte
frame[6] = vals[0]
frame[7] = vals[1]
frame[8] = vals[2]
frame[9] = vals[3]
frame[10] = 0x00
frame[11] = 0x00
frame[12] = 0x00
frame[13] = 0x00
frame[14] = 0x00
frame[15] = 0x00
frame[16] = 0x00
frame[17] = 0x00
frame[18] = 0x00
frame[19] = self.crc(frame, len(frame) - 1)
debug("Write register: ", frame)
await bleakC.write_gatt_char(CHAR_HANDLE, frame, False)
async def request_bt(self, rtype: str, client):
timeout = time.time()
while self.waiting_for_response != "" and time.time() - timeout < 10:
await asyncio.sleep(1)
print(self.waiting_for_response)
if rtype == "cell_info":
cmd = COMMAND_CELL_INFO
self.waiting_for_response = "cell_info"
elif rtype == "device_info":
cmd = COMMAND_DEVICE_INFO
self.waiting_for_response = "device_info"
else:
return
await self.write_register(cmd, b"\0\0\0\0", 0x00, client)
def get_status(self):
if "settings" in self.bms_status and "cell_info" in self.bms_status:
return self.bms_status
else:
return None
def connect_and_scrape(self):
asyncio.run(self.asy_connect_and_scrape())
async def asy_connect_and_scrape(self):
#print("connect and scrape on address: " + self.address)
self.run = True
#while self.run and self.main_thread.is_alive(): # autoreconnect
while self.run: # autoreconnect
client = BleakClient(self.address)
#print("btloop")
try:
#print("reconnect")
await client.connect()
self.bms_status["model_nbr"] = (
await client.read_gatt_char(MODEL_NBR_UUID)
).decode("utf-8")
await client.start_notify(CHAR_HANDLE, self.ncallback)
await self.request_bt("device_info", client)
await self.request_bt("cell_info", client)
# await self.enable_charging(client)
last_dev_info = time.time()
#while client.is_connected and self.run and self.main_thread.is_alive():
while client.is_connected and self.run():
await asyncio.sleep(0.01)
except Exception as e:
info("error while connecting to bt: " + str(e))
#self.run = False
#finally:
# if client.is_connected:
# try:
# await client.disconnect()
# except Exception as e:
# info("error while disconnecting")
#print("Exiting bt-loop")
def start_scraping(self):
self.main_thread = threading.current_thread()
if self.is_running():
return
self.bt_thread.start()
info(
"scraping thread started -> main thread id: "
+ str(self.main_thread.ident)
+ " scraping thread: "
+ str(self.bt_thread.ident)
)
def stop_scraping(self):
self.run = False
stop = time.time()
while self.is_running():
time.sleep(0.5)
if time.time() - stop > 10:
return False
return True
def is_running(self):
return self.bt_thread.is_alive()
async def enable_charging(self, c):
# these are the registers for the control-buttons:
# data is 01 00 00 00 for on 00 00 00 00 for off;
# the following bytes up to 19 are unclear and changing
# dynamically -> auth-mechanism?
await self.write_register(0x1D, b"\x01\x00\x00\x00", 4, c)
await self.write_register(0x1E, b"\x01\x00\x00\x00", 4, c)
await self.write_register(0x1F, b"\x01\x00\x00\x00", 4, c)
await self.write_register(0x40, b"\x01\x00\x00\x00", 4, c)
#if name == "main": #jk = JkBmsBle("C8:47:8C:E4:54:0E") #jk = JkBmsBle("C8:47:8C:E1:FA:99") #info("sss") #jk.start_scraping() #while True: #print("asdf") #bms_status = {} #print(jk.get_status()) #out = jk.get_status() #file = open('jkbmsout.txt', 'wb') #pickle.dump(out, file) #file.close() #with open('jkbmsout.txt', 'w', encoding='utf-8' ) as my_file: # my_file.write(text.decode('utf-8')) # my_file.close() #clear = lambda: os.system('clear') #clear() #time.sleep(1)
if name == "main": jk = JkBmsBle("C8:47:XX:XX:XX:XX") jk.start_scraping() while True: out = jk.get_status() print(out) with open('/root/jkbms/tmpramfs/jkbmsout.txt', 'w') as file: file.write(str(out)) #time.sleep(0.5) time.sleep(5)
Thanks!
This version, and the one linked, generate the following error, I have updated my MAC ID to my BMS.
error while connecting to bt: Characteristic 00002a24-0000-1000-8000-00805f9b34fb was not found!
I queired the details with a BLE Scanner, and updated them:
However, it generates the same error below
Could not read characteristic handle 9: Protocol Error 0x02: Read Not Permitted
I have dug through the JKBMS and MPP_Solar codebases to see how they are identifying them, but as far as I am aware, they are defining each protocol manually, wheras I think this script is trying to decipher the protocol based on the answer.
I suspect the issue is that the "JK04" Protcol is not implemented in this script
I now understand that MODEL_NBR_UUID is the UUID address of the devices model name. and that CHAR_HANDLE is the UUID of the charachter mapping?
in any case, all variations did not produce a ususable result.
for those interested in getting data over bluetooth every X seconds, here is a python script I have found on github, and slitglty modified to my needs. It is outputing data every 5 seconds in a file indefinitely. You need a good bluetooth connection (short distance). For me it works great, 720 samples per hour and almost no error. Jkbms.py.txt
Hey thanks for this, it works very well with my JK-B1A24S15P. :)
Small improvement to the above jkbms.py. I was wondering why it keeps reconnecting, you could hear constant "beep, beep" every few seconds from the BMS. And when looking at the console I saw this line when it reconnected:
INFO:root:error while connecting to bt: 'bool' object is not callable
The error is near line 330:
while client.is_connected and self.run():
self.run is a bool, not a function! So you after removing the parenthesis, error no longer appears and the script stays connected to the BMS forever. Works very well now, and no longer beeps annoyingly. :)
What if I have multiple BMSes
Do I put each bms MAC on a 'jk = JkBmsBle("XX:XX:XX:XX:XX:XX")' line?
I'm trying to get a reliable monitor for my 5 and increasing jkbmses. Tried batmon in ha but it doesn't keep a stable connection. Also tried installing mpp-solar[ble] but compilation keeps failing for bluepy.py I think.