From 1e6d4100e1d1360b579dd0caeae93ff51f0d839a Mon Sep 17 00:00:00 2001 From: Manuel <mr-manuel@outlook.it> Date: Thu, 27 Apr 2023 14:14:46 +0200 Subject: [PATCH 01/17] Improved JBD BMS soc calculation https://github.com/Louisvdw/dbus-serialbattery/pull/439 --- CHANGELOG.md | 1 + etc/dbus-serialbattery/lltjbd.py | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 391dfecb..e6243b61 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -44,6 +44,7 @@ * Changed: Fixed black lint errors * Changed: Fixed cell balancing background for cells 17-24 * Changed: Fixed Time-To-Go is not working, if `TIME_TO_SOC_VALUE_TYPE` is set to other than `1` https://github.com/Louisvdw/dbus-serialbattery/pull/424#issuecomment-1440511018 +* Changed: Improved JBD BMS soc calculation https://github.com/Louisvdw/dbus-serialbattery/pull/439 * Changed: Logging to get relevant data * Changed: Moved ble part to `installble.sh` * Changed: Optimized installation scripts diff --git a/etc/dbus-serialbattery/lltjbd.py b/etc/dbus-serialbattery/lltjbd.py index 2a3d536c..a8222fd6 100644 --- a/etc/dbus-serialbattery/lltjbd.py +++ b/etc/dbus-serialbattery/lltjbd.py @@ -138,13 +138,14 @@ def read_gen_data(self): balance2, protection, version, - self.soc, + soc, fet, self.cell_count, self.temp_sensors, ) = unpack_from(">HhHHHHhHHBBBBB", gen_data) self.voltage = voltage / 100 self.current = current / 100 + self.soc = 100 * capacity_remain / capacity self.capacity_remain = capacity_remain / 100 self.capacity = capacity / 100 self.to_cell_bits(balance, balance2) From 922b82c42599f5c19b7df23e5f7d56d119e9d82c Mon Sep 17 00:00:00 2001 From: Manuel <mr-manuel@outlook.it> Date: Thu, 27 Apr 2023 14:20:39 +0200 Subject: [PATCH 02/17] Fix for #397 https://github.com/Louisvdw/dbus-serialbattery/pull/484 --- CHANGELOG.md | 3 ++- etc/dbus-serialbattery/daly.py | 32 ++++++++++++++++++++------------ etc/dbus-serialbattery/utils.py | 6 ++++-- 3 files changed, 26 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index e6243b61..12d2ee32 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -40,11 +40,12 @@ * Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/239 * Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/311 * Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/351 +* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/397 by @transistorgit * Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/421 * Changed: Fixed black lint errors * Changed: Fixed cell balancing background for cells 17-24 * Changed: Fixed Time-To-Go is not working, if `TIME_TO_SOC_VALUE_TYPE` is set to other than `1` https://github.com/Louisvdw/dbus-serialbattery/pull/424#issuecomment-1440511018 -* Changed: Improved JBD BMS soc calculation https://github.com/Louisvdw/dbus-serialbattery/pull/439 +* Changed: Improved JBD BMS soc calculation https://github.com/Louisvdw/dbus-serialbattery/pull/439 by @aaronreek * Changed: Logging to get relevant data * Changed: Moved ble part to `installble.sh` * Changed: Optimized installation scripts diff --git a/etc/dbus-serialbattery/daly.py b/etc/dbus-serialbattery/daly.py index ae96c723..492b0500 100644 --- a/etc/dbus-serialbattery/daly.py +++ b/etc/dbus-serialbattery/daly.py @@ -20,7 +20,7 @@ def __init__(self, port, baud, address): # command bytes [StartFlag=A5][Address=40][Command=94][DataLength=8][8x zero bytes][checksum] command_base = b"\xA5\x40\x94\x08\x00\x00\x00\x00\x00\x00\x00\x00\x81" - cellvolt_buffer = b"\xA5\x40\x94\x08\x00\x00\x00\x00\x00\x00\x00\x00\x82\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" + cellvolt_buffer = b"\xA5\x40\x94\x08\x00\x00\x00\x00\x00\x00\x00\x00\x82" command_soc = b"\x90" command_minmax_cell_volts = b"\x91" command_minmax_temp = b"\x92" @@ -31,7 +31,7 @@ def __init__(self, port, baud, address): command_cell_balance = b"\x97" command_alarm = b"\x98" BATTERYTYPE = "Daly" - LENGTH_CHECK = 4 + LENGTH_CHECK = 1 LENGTH_POS = 3 CURRENT_ZERO_CONSTANT = 30000 TEMP_ZERO_CONSTANT = 40 @@ -246,13 +246,16 @@ def read_cells_volts(self, ser): buffer[1] = self.command_address[0] # Always serial 40 or 80 buffer[2] = self.command_cell_volts[0] - maxFrame = int(self.cell_count / 3) + 1 + if (int(self.cell_count) % 3) == 0: + maxFrame = (int(self.cell_count / 3)) + else: + maxFrame = (int(self.cell_count / 3) + 1) lenFixed = ( - maxFrame * 12 - ) # 0xA5, 0x01, 0x95, 0x08 + 1 byte frame + 6 byte data + 1byte reserved + maxFrame * 13 + ) # 0xA5, 0x01, 0x95, 0x08 + 1 byte frame + 6 byte data + 1byte reserved + chksum cells_volts_data = read_serialport_data( - ser, buffer, self.LENGTH_POS, self.LENGTH_CHECK, lenFixed + ser, buffer, self.LENGTH_POS, 0, lenFixed ) if cells_volts_data is False: logger.warning("read_cells_volts") @@ -269,14 +272,19 @@ def read_cells_volts(self, ser): for idx in range(self.cell_count): self.cells.append(Cell(True)) + # logger.warning("data " + bytes(cells_volts_data).hex()) + while ( bufIdx < len(cells_volts_data) - 4 ): # we at least need 4 bytes to extract the identifiers b1, b2, b3, b4 = unpack_from(">BBBB", cells_volts_data, bufIdx) if b1 == 0xA5 and b2 == 0x01 and b3 == 0x95 and b4 == 0x08: - frame, frameCell[0], frameCell[1], frameCell[2] = unpack_from( - ">Bhhh", cells_volts_data, bufIdx + 4 + frame, frameCell[0], frameCell[1], frameCell[2], _, chk = unpack_from( + ">BhhhBB", cells_volts_data, bufIdx + 4 ) + if sum(cells_volts_data[bufIdx:bufIdx+12]) & 0xFF != chk: + logger.warning("bad cell voltages checksum") + return False for idx in range(3): cellnum = ( (frame - 1) * 3 @@ -287,9 +295,9 @@ def read_cells_volts(self, ser): self.cells[cellnum].voltage = ( None if cellVoltage < lowMin else cellVoltage ) - bufIdx += 10 # BBBBBhhh -> 11 byte - bufIdx += 1 - + bufIdx += 13 # BBBBBhhhBB -> 13 byte + else: + logger.warning("bad cell voltages header") return True def read_cell_voltage_range_data(self, ser): @@ -359,7 +367,7 @@ def read_serial_data_daly(self, ser, command): start, flag, command_ret, length = unpack_from("BBBB", data) checksum = sum(data[:-1]) & 0xFF - if start == 165 and length == 8 and checksum == data[12]: + if start == 165 and length == 8 and len(data)>12 and checksum == data[12]: return data[4 : length + 4] else: logger.error(">>> ERROR: Incorrect Reply") diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 5bd66408..7cf6eac6 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -441,8 +441,10 @@ def read_serialport_data( count = 0 data = bytearray(res) - while len(data) <= length + length_check: - res = ser.read(length + length_check) + + packetlen = length_fixed if length_fixed is not None else length_pos + length_byte_size + length + length_check + while len(data) < packetlen: + res = ser.read(packetlen - len(data)) data.extend(res) # logger.info('serial data length ' + str(len(data))) sleep(0.005) From 23356f6fbe4d6668e685a49e989b71c97264e304 Mon Sep 17 00:00:00 2001 From: Manuel <mr-manuel@outlook.it> Date: Thu, 27 Apr 2023 14:20:57 +0200 Subject: [PATCH 03/17] small fixes --- .github/workflows/release.yml | 1 + etc/dbus-serialbattery/battery.py | 4 ++-- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 0b6a57da..a9ad92fb 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -24,6 +24,7 @@ jobs: --exclude restartservice.sh \ --exclude revov.py \ --exclude test_max17853.py \ + --exclude util_max17853.py \ etc/dbus-serialbattery/ - name: Release diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 54417720..1578a8c9 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -828,8 +828,8 @@ def log_settings(self) -> None: ) logger.info(f"> LINEAR LIMITATION ENABLE: {utils.LINEAR_LIMITATION_ENABLE}") logger.info( - f"> MAX BATTERY CHARGE CURRENT: {utils.MAX_BATTERY_CHARGE_CURRENT}V | " - + f"MAX BATTERY DISCHARGE CURRENT: {utils.MAX_BATTERY_DISCHARGE_CURRENT}V" + f"> MAX BATTERY CHARGE CURRENT: {utils.MAX_BATTERY_CHARGE_CURRENT}A | " + + f"MAX BATTERY DISCHARGE CURRENT: {utils.MAX_BATTERY_DISCHARGE_CURRENT}A" ) logger.info(f"> CVCM: {utils.CVCM_ENABLE}") logger.info( From 4bd257f157d889378cedaa4e654f2fb7102edff9 Mon Sep 17 00:00:00 2001 From: Manuel <mr-manuel@outlook.it> Date: Thu, 27 Apr 2023 14:53:15 +0200 Subject: [PATCH 04/17] sort bms imports --- etc/dbus-serialbattery/dbus-serialbattery.py | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index fdcc05a5..bb0df250 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -19,28 +19,29 @@ from utils import logger import utils from battery import Battery -from lltjbd import LltJbd + +# import battery classes +from ant import Ant from daly import Daly +from ecs import Ecs from ant import Ant from jkbms import Jkbms - -# from sinowealth import Sinowealth -from renogy import Renogy -from ecs import Ecs from lifepower import Lifepower - +from lltjbd import LltJbd +from renogy import Renogy +# from sinowealth import Sinowealth supported_bms_types = [ - {"bms": LltJbd, "baud": 9600}, {"bms": Ant, "baud": 19200}, {"bms": Daly, "baud": 9600, "address": b"\x40"}, {"bms": Daly, "baud": 9600, "address": b"\x80"}, + {"bms": Ecs, "baud": 19200}, {"bms": Jkbms, "baud": 115200}, - # {"bms" : Sinowealth}, {"bms": Lifepower, "baud": 9600}, + {"bms": LltJbd, "baud": 9600}, {"bms": Renogy, "baud": 9600, "address": b"\x30"}, {"bms": Renogy, "baud": 9600, "address": b"\xF7"}, - {"bms": Ecs, "baud": 19200}, + # {"bms": Sinowealth}, ] expected_bms_types = [ battery_type From c56a9d107946313b4509c1f0c60d919a32c05636 Mon Sep 17 00:00:00 2001 From: Manuel <mr-manuel@outlook.it> Date: Thu, 27 Apr 2023 14:56:15 +0200 Subject: [PATCH 05/17] Add support for HLPdata BMS4S https://github.com/Louisvdw/dbus-serialbattery/pull/505 --- etc/dbus-serialbattery/dbus-serialbattery.py | 3 +- etc/dbus-serialbattery/hlpdatabms4s.py | 240 ++++ .../hlpdatabms4s_miniterm.py | 1189 +++++++++++++++++ 3 files changed, 1431 insertions(+), 1 deletion(-) create mode 100644 etc/dbus-serialbattery/hlpdatabms4s.py create mode 100644 etc/dbus-serialbattery/hlpdatabms4s_miniterm.py diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index bb0df250..b1a034bd 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -24,7 +24,7 @@ from ant import Ant from daly import Daly from ecs import Ecs -from ant import Ant +from hlpdatabms4s import HLPdataBMS4S from jkbms import Jkbms from lifepower import Lifepower from lltjbd import LltJbd @@ -36,6 +36,7 @@ {"bms": Daly, "baud": 9600, "address": b"\x40"}, {"bms": Daly, "baud": 9600, "address": b"\x80"}, {"bms": Ecs, "baud": 19200}, + {'bms': HLPdataBMS4S, "baud": 9600}, {"bms": Jkbms, "baud": 115200}, {"bms": Lifepower, "baud": 9600}, {"bms": LltJbd, "baud": 9600}, diff --git a/etc/dbus-serialbattery/hlpdatabms4s.py b/etc/dbus-serialbattery/hlpdatabms4s.py new file mode 100644 index 00000000..63995357 --- /dev/null +++ b/etc/dbus-serialbattery/hlpdatabms4s.py @@ -0,0 +1,240 @@ +# -*- coding: utf-8 -*- +from battery import Protection, Battery, Cell +from utils import * +from struct import * +import serial +from time import sleep + + +class HLPdataBMS4S(Battery): + def __init__(self, port, baud): + super(HLPdataBMS4S, self).__init__(port, baud) + self.type = self.BATTERYTYPE + + BATTERYTYPE = "HLPdataBMS4S" + + def test_connection(self): + # call a function that will connect to the battery, send a command and retrieve the result. + # The result or call should be unique to this BMS. Battery name or version, etc. + # Return True if success, False for failure + result = False + try: + result = self.read_test_data() + except Exception as e: + # logger.error(e, exc_info=True) + pass + return result + + def get_settings(self): + # After successful connection get_settings will be call to set up the battery. + # Set the current limits, populate cell count, etc + # Return True if success, False for failure + result = False + try: + result = self.read_settings_data() + except Exception as e: + # logger.error(e, exc_info=True) + pass + return result + + def refresh_data(self): + # call all functions that will refresh the battery data. + # This will be called for every iteration (1 second) + # Return True if success, False for failure + result = False + try: + result = self.read_status_data() + except Exception as e: + # logger.error(e, exc_info=True) + pass + return result + + # def log_settings(self): + # logger.info(f'Battery {self.type} connected to dbus from {self.port}') + # logger.info(f'=== Settings ===') + # cell_counter = len(self.cells) + # logger.info(f'> Connection voltage {self.voltage}V | current {self.current}A | SOC {self.soc}%') + # logger.info(f'> Cell count {self.cell_count} | cells populated {cell_counter}') + # logger.info(f'> CCCM SOC {CCCM_SOC_ENABLE} | DCCM SOC {DCCM_SOC_ENABLE}') + # logger.info(f'> CCCM CV {CCCM_CV_ENABLE} | DCCM CV {DCCM_CV_ENABLE}') + # logger.info(f'> CCCM T {CCCM_T_ENABLE} | DCCM T {DCCM_T_ENABLE}') + # logger.info(f'> MIN_CELL_VOLTAGE {MIN_CELL_VOLTAGE}V | MAX_CELL_VOLTAGE {MAX_CELL_VOLTAGE}V') + + return + + def read_test_data(self): + test_data = self.read_serial_data_HLPdataBMS4S(b"pv\n", 1, 15) + if test_data is False: + return False + s1 = str(test_data) + ix = s1.find("BMS4S") + if ix > 0: + self.hardware_version = s1[ix : len(s1) - 1] + self.version = self.hardware_version + self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT + self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT + self.poll_interval = 10000 + self.control_discharge_current = 1000 + self.control_charge_current = 1000 + self.soc = 50 + self.voltage = 13.2 + self.current = 0 + self.min_battery_voltage = 12.0 + self.max_battery_voltage = 14.4 + + if self.cell_count is None: + self.cell_count = 4 + for c in range(self.cell_count): + self.cells.append(Cell(False)) + return True + return False + + def read_settings_data(self): + test_data = self.read_serial_data_HLPdataBMS4S(b"ps\n", 3, 700) + if test_data is False: + return False + s = str(test_data) + s = s.replace(",", ".") + par = get_par("BatterySize= ", s) + if par is False: + return False + self.capacity = int(par) + v = get_par("VoltHigh= ", s) + if v is False: + return False + self.max_battery_voltage = float(v) * float(4) + v = get_par("VoltLow= ", s) + if v is False: + return False + self.min_battery_voltage = float(v) * float(4) + + return True + + def read_status_data(self): + status_data = self.read_serial_data_HLPdataBMS4S(b"m1\n", 0.2, 40) + if status_data is False: + return False + par1 = str(status_data) + par = par1.split(",") + if len(par) < 8: + return False + if len(par[0]) < 7: + return False + p0 = str(par[0]) + ix = p0.find(".") + par0 = p0[ix - 1 : len(p0)] + + # v1,v2,v3,v4,current,soc,chargeoff,loadoff,vbat2,socnow,adj,beep,led,temp1,temp2... + # 0 1 2 3 4 5 6 7 8 9 10 11 12 13 14... + + self.voltage = float(par0) + float(par[1]) + float(par[2]) + float(par[3]) + self.cells[0].voltage = float(par0) + self.cells[1].voltage = float(par[1]) + self.cells[2].voltage = float(par[2]) + self.cells[3].voltage = float(par[3]) + self.current = float(par[4]) + self.soc = int(par[5]) + self.control_allow_charge = par[6] + self.charge_fet = par[6] + self.control_allow_discharge = par[7] + self.discharge_fet = par[7] + + beep = int(par[11]) + if beep == 2: + self.protection.temp_low_charge = 1 + else: + self.protection.temp_low_charge = 0 + if beep == 3: + self.protection.temp_high_charge = 1 + else: + self.protection.temp_high_charge = 0 + if beep == 4: + self.protection.voltage_low = 2 + else: + self.protection.voltage_low = 0 + if beep == 5: + self.protection.voltage_high = 2 + else: + self.protection.voltage_high = 0 + + if len(par) > 13: + nb = 0 + min = int(1000) + max = int(-1000) + ix = 13 + while ix < len(par): + tmp = par[ix].split(" ") + ix += 1 + if len(tmp) == 2: + name = tmp[0] + temp = int("".join(filter(str.isdigit, tmp[1]))) + if name[0] == "b": + nb += 1 + if temp > max: + max = temp + if temp < min: + min = temp + if nb == 1: + self.temp1 = max + if nb > 1: + self.temp1 = max + self.temp2 = min + + return True + + def manage_charge_voltage(self): + self.allow_max_voltage = True + self.control_voltage = self.max_battery_voltage + + def manage_charge_current(self): + self.control_charge_current = 1000 + self.control_discharge_current = 1000 + + def read_serial_data_HLPdataBMS4S(self, command, time, min_len): + data = read_serial_data2(command, self.port, self.baud_rate, time, min_len) + if data is False: + return False + return data + + +def read_serial_data2(command, port, baud, time, min_len): + try: + with serial.Serial(port, baudrate=baud, timeout=0.5) as ser: + ret = read_serialport_data2(ser, command, time, min_len) + if not ret is False: + return ret + return False + + except serial.SerialException as e: + logger.error(e) + return False + + +def read_serialport_data2(ser, command, time, min_len): + try: + cnt = 0 + while cnt < 3: + cnt += 1 + ser.flushOutput() + ser.flushInput() + ser.write(command) + sleep(time) + res = ser.read(1000) + if len(res) >= min_len: + return res + return False + + except serial.SerialException as e: + logger.error(e) + return False + + +def get_par(p, s): + ix = s.find(p) + if ix > 0: + ix += len(p) + for i in range(ix, len(s)): + if s[i] == " " or s[i] == 10 or s[i] == 13: + ret = s[ix:i] + return ret + return False diff --git a/etc/dbus-serialbattery/hlpdatabms4s_miniterm.py b/etc/dbus-serialbattery/hlpdatabms4s_miniterm.py new file mode 100644 index 00000000..741ac75a --- /dev/null +++ b/etc/dbus-serialbattery/hlpdatabms4s_miniterm.py @@ -0,0 +1,1189 @@ +#!/usr/bin/env python +# +# Very simple serial terminal +# +# This file is part of pySerial. https://github.com/pyserial/pyserial +# (C)2002-2020 Chris Liechti <cliechti@gmx.net> +# +# SPDX-License-Identifier: BSD-3-Clause +# +# Modified to enable managing HLPdataBMS4S in Venus OS + +from __future__ import absolute_import + +import codecs +import os +import sys +import threading + +import serial +from serial.tools.list_ports import comports +from serial.tools import hexlify_codec +from time import sleep + +# pylint: disable=wrong-import-order,wrong-import-position + +codecs.register(lambda c: hexlify_codec.getregentry() if c == "hexlify" else None) + +try: + raw_input +except NameError: + # pylint: disable=redefined-builtin,invalid-name + raw_input = input # in python3 it's "raw" + unichr = chr + + +def key_description(character): + """generate a readable description for a key""" + ascii_code = ord(character) + if ascii_code < 32: + return "Ctrl+{:c}".format(ord("@") + ascii_code) + else: + return repr(character) + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +class ConsoleBase(object): + """OS abstraction for console (input/output codec, no echo)""" + + def __init__(self, miniterm): + self.miniterm = miniterm + if sys.version_info >= (3, 0): + self.byte_output = sys.stdout.buffer + else: + self.byte_output = sys.stdout + self.output = sys.stdout + + def setup(self): + """Set console to read single characters, no echo""" + + def cleanup(self): + """Restore default console settings""" + + def getkey(self): + """Read a single key from the console""" + return None + + def write_bytes(self, byte_string): + """Write bytes (already encoded)""" + self.byte_output.write(byte_string) + self.byte_output.flush() + + def write(self, text): + """Write string""" + self.output.write(text) + self.output.flush() + + def cancel(self): + """Cancel getkey operation""" + + # - - - - - - - - - - - - - - - - - - - - - - - - + # context manager: + # switch terminal temporary to normal mode (e.g. to get user input) + + def __enter__(self): + self.cleanup() + return self + + def __exit__(self, *args, **kwargs): + self.setup() + + +if os.name == "nt": # noqa + import msvcrt + import ctypes + import platform + + class Out(object): + """file-like wrapper that uses os.write""" + + def __init__(self, fd): + self.fd = fd + + def flush(self): + pass + + def write(self, s): + os.write(self.fd, s) + + class Console(ConsoleBase): + fncodes = { + ";": "\x1bOP", # F1 + "<": "\x1bOQ", # F2 + "=": "\x1bOR", # F3 + ">": "\x1bOS", # F4 + "?": "\x1b[15~", # F5 + "@": "\x1b[17~", # F6 + "A": "\x1b[18~", # F7 + "B": "\x1b[19~", # F8 + "C": "\x1b[20~", # F9 + "D": "\x1b[21~", # F10 + } + navcodes = { + "H": "\x1b[A", # UP + "P": "\x1b[B", # DOWN + "K": "\x1b[D", # LEFT + "M": "\x1b[C", # RIGHT + "G": "\x1b[H", # HOME + "O": "\x1b[F", # END + "R": "\x1b[2~", # INSERT + "S": "\x1b[3~", # DELETE + "I": "\x1b[5~", # PAGE UP + "Q": "\x1b[6~", # PAGE DOWN + } + + def __init__(self, miniterm): + super(Console, self).__init__(miniterm) + self._saved_ocp = ctypes.windll.kernel32.GetConsoleOutputCP() + self._saved_icp = ctypes.windll.kernel32.GetConsoleCP() + ctypes.windll.kernel32.SetConsoleOutputCP(65001) + ctypes.windll.kernel32.SetConsoleCP(65001) + # ANSI handling available through SetConsoleMode since Windows 10 v1511 + # https://en.wikipedia.org/wiki/ANSI_escape_code#cite_note-win10th2-1 + if ( + platform.release() == "10" + and int(platform.version().split(".")[2]) > 10586 + ): + ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x0004 + import ctypes.wintypes as wintypes + + if not hasattr(wintypes, "LPDWORD"): # PY2 + wintypes.LPDWORD = ctypes.POINTER(wintypes.DWORD) + SetConsoleMode = ctypes.windll.kernel32.SetConsoleMode + GetConsoleMode = ctypes.windll.kernel32.GetConsoleMode + GetStdHandle = ctypes.windll.kernel32.GetStdHandle + mode = wintypes.DWORD() + GetConsoleMode(GetStdHandle(-11), ctypes.byref(mode)) + if (mode.value & ENABLE_VIRTUAL_TERMINAL_PROCESSING) == 0: + SetConsoleMode( + GetStdHandle(-11), + mode.value | ENABLE_VIRTUAL_TERMINAL_PROCESSING, + ) + self._saved_cm = mode + self.output = codecs.getwriter("UTF-8")(Out(sys.stdout.fileno()), "replace") + # the change of the code page is not propagated to Python, manually fix it + sys.stderr = codecs.getwriter("UTF-8")(Out(sys.stderr.fileno()), "replace") + sys.stdout = self.output + self.output.encoding = "UTF-8" # needed for input + + def __del__(self): + ctypes.windll.kernel32.SetConsoleOutputCP(self._saved_ocp) + ctypes.windll.kernel32.SetConsoleCP(self._saved_icp) + try: + ctypes.windll.kernel32.SetConsoleMode( + ctypes.windll.kernel32.GetStdHandle(-11), self._saved_cm + ) + except AttributeError: # in case no _saved_cm + pass + + def getkey(self): + while True: + z = msvcrt.getwch() + if z == unichr(13): + return unichr(10) + elif z is unichr(0) or z is unichr(0xE0): + try: + code = msvcrt.getwch() + if z is unichr(0): + return self.fncodes[code] + else: + return self.navcodes[code] + except KeyError: + pass + else: + return z + + def cancel(self): + # CancelIo, CancelSynchronousIo do not seem to work when using + # getwch, so instead, send a key to the window with the console + hwnd = ctypes.windll.kernel32.GetConsoleWindow() + ctypes.windll.user32.PostMessageA(hwnd, 0x100, 0x0D, 0) + +elif os.name == "posix": + import atexit + import termios + import fcntl + import signal + + class Console(ConsoleBase): + def __init__(self, miniterm): + super(Console, self).__init__(miniterm) + self.fd = sys.stdin.fileno() + self.old = termios.tcgetattr(self.fd) + atexit.register(self.cleanup) + signal.signal(signal.SIGINT, self.sigint) + if sys.version_info < (3, 0): + self.enc_stdin = codecs.getreader(sys.stdin.encoding)(sys.stdin) + else: + self.enc_stdin = sys.stdin + + def setup(self): + new = termios.tcgetattr(self.fd) + new[3] = new[3] & ~termios.ICANON & ~termios.ECHO & ~termios.ISIG + new[6][termios.VMIN] = 1 + new[6][termios.VTIME] = 0 + termios.tcsetattr(self.fd, termios.TCSANOW, new) + + def getkey(self): + c = self.enc_stdin.read(1) + if c == unichr(0x7F): + c = unichr(8) # map the BS key (which yields DEL) to backspace + return c + + def cancel(self): + fcntl.ioctl(self.fd, termios.TIOCSTI, b"\0") + + def cleanup(self): + termios.tcsetattr(self.fd, termios.TCSAFLUSH, self.old) + + def sigint(self, sig, frame): + """signal handler for a clean exit on SIGINT""" + self.miniterm.stop() + self.cancel() + +else: + raise NotImplementedError( + "Sorry no implementation for your platform ({}) available.".format(sys.platform) + ) + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + +class Transform(object): + """do-nothing: forward all data unchanged""" + + def rx(self, text): + """text received from serial port""" + return text + + def tx(self, text): + """text to be sent to serial port""" + return text + + def echo(self, text): + """text to be sent but displayed on console""" + return text + + +class CRLF(Transform): + """ENTER sends CR+LF""" + + def tx(self, text): + return text.replace("\n", "\r\n") + + +class CR(Transform): + """ENTER sends CR""" + + def rx(self, text): + return text.replace("\r", "\n") + + def tx(self, text): + return text.replace("\n", "\r") + + +class LF(Transform): + """ENTER sends LF""" + + +class NoTerminal(Transform): + """remove typical terminal control codes from input""" + + REPLACEMENT_MAP = dict( + (x, 0x2400 + x) for x in range(32) if unichr(x) not in "\r\n\b\t" + ) + REPLACEMENT_MAP.update( + { + 0x7F: 0x2421, # DEL + 0x9B: 0x2425, # CSI + } + ) + + def rx(self, text): + return text.translate(self.REPLACEMENT_MAP) + + echo = rx + + +class NoControls(NoTerminal): + """Remove all control codes, incl. CR+LF""" + + REPLACEMENT_MAP = dict((x, 0x2400 + x) for x in range(32)) + REPLACEMENT_MAP.update( + { + 0x20: 0x2423, # visual space + 0x7F: 0x2421, # DEL + 0x9B: 0x2425, # CSI + } + ) + + +class Printable(Transform): + """Show decimal code for all non-ASCII characters and replace most control codes""" + + def rx(self, text): + r = [] + for c in text: + if " " <= c < "\x7f" or c in "\r\n\b\t": + r.append(c) + elif c < " ": + r.append(unichr(0x2400 + ord(c))) + else: + r.extend(unichr(0x2080 + ord(d) - 48) for d in "{:d}".format(ord(c))) + r.append(" ") + return "".join(r) + + echo = rx + + +class Colorize(Transform): + """Apply different colors for received and echo""" + + def __init__(self): + # XXX make it configurable, use colorama? + self.input_color = "\x1b[37m" + self.echo_color = "\x1b[31m" + + def rx(self, text): + return self.input_color + text + + def echo(self, text): + return self.echo_color + text + + +class DebugIO(Transform): + """Print what is sent and received""" + + def rx(self, text): + sys.stderr.write(" [RX:{!r}] ".format(text)) + sys.stderr.flush() + return text + + def tx(self, text): + sys.stderr.write(" [TX:{!r}] ".format(text)) + sys.stderr.flush() + return text + + +# other ideas: +# - add date/time for each newline +# - insert newline after: a) timeout b) packet end character + +EOL_TRANSFORMATIONS = { + "crlf": CRLF, + "cr": CR, + "lf": LF, +} + +TRANSFORMATIONS = { + "direct": Transform, # no transformation + "default": NoTerminal, + "nocontrol": NoControls, + "printable": Printable, + "colorize": Colorize, + "debug": DebugIO, +} + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +def ask_for_port(): + """\ + Show a list of ports and ask the user for a choice. To make selection + easier on systems with long device names, also allow the input of an + index. + """ + sys.stderr.write("\n--- Available ports:\n") + ports = [] + for n, (port, desc, hwid) in enumerate(sorted(comports()), 1): + sys.stderr.write("--- {:2}: {:20} {!r}\n".format(n, port, desc)) + ports.append(port) + while True: + sys.stderr.write("--- Enter port index or full name: ") + port = raw_input("") + try: + index = int(port) - 1 + if not 0 <= index < len(ports): + sys.stderr.write("--- Invalid index!\n") + continue + except ValueError: + pass + else: + port = ports[index] + return port + + +class Miniterm(object): + """\ + Terminal application. Copy data from serial port to console and vice versa. + Handle special keys from the console to show menu etc. + """ + + def __init__(self, serial_instance, echo=False, eol="crlf", filters=()): + self.console = Console(self) + self.serial = serial_instance + self.echo = True + self.raw = False + self.input_encoding = "UTF-8" + self.output_encoding = "UTF-8" + self.eol = eol + self.filters = filters + self.update_transformations() + self.exit_character = unichr(0x1D) # GS/CTRL+] + self.menu_character = unichr(0x14) # Menu: CTRL+T + self.alive = None + self._reader_alive = None + self.receiver_thread = None + self.rx_decoder = None + self.tx_decoder = None + self.tx_encoder = None + self.no_write = False + self.remove_no_write = False + + def _start_reader(self): + """Start reader thread""" + self._reader_alive = True + # start serial->console thread + self.receiver_thread = threading.Thread(target=self.reader, name="rx") + self.receiver_thread.daemon = True + self.receiver_thread.start() + + def _stop_reader(self): + """Stop reader thread only, wait for clean exit of thread""" + self._reader_alive = False + if hasattr(self.serial, "cancel_read"): + self.serial.cancel_read() + self.receiver_thread.join() + + def start(self): + """start worker threads""" + self.alive = True + self._start_reader() + # enter console->serial loop + self.transmitter_thread = threading.Thread(target=self.writer, name="tx") + self.transmitter_thread.daemon = True + self.transmitter_thread.start() + self.console.setup() + + def stop(self): + """set flag to stop worker threads""" + self.alive = False + + def join(self, transmit_only=False): + """wait for worker threads to terminate""" + self.transmitter_thread.join() + if not transmit_only: + if hasattr(self.serial, "cancel_read"): + self.serial.cancel_read() + self.receiver_thread.join() + + def close(self): + self.serial.close() + + def update_transformations(self): + """take list of transformation classes and instantiate them for rx and tx""" + transformations = [EOL_TRANSFORMATIONS[self.eol]] + [ + TRANSFORMATIONS[f] for f in self.filters + ] + self.tx_transformations = [t() for t in transformations] + self.rx_transformations = list(reversed(self.tx_transformations)) + + def set_rx_encoding(self, encoding, errors="replace"): + """set encoding for received data""" + self.input_encoding = encoding + self.rx_decoder = codecs.getincrementaldecoder(encoding)(errors) + + def set_tx_encoding(self, encoding, errors="replace"): + """set encoding for transmitted data""" + self.output_encoding = encoding + self.tx_encoder = codecs.getincrementalencoder(encoding)(errors) + + def dump_port_settings(self): + """Write current settings to sys.stderr""" + sys.stderr.write( + "\n--- Settings: {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits}\n".format( + p=self.serial + ) + ) + sys.stderr.write( + "--- RTS: {:8} DTR: {:8} BREAK: {:8}\n".format( + ("active" if self.serial.rts else "inactive"), + ("active" if self.serial.dtr else "inactive"), + ("active" if self.serial.break_condition else "inactive"), + ) + ) + try: + sys.stderr.write( + "--- CTS: {:8} DSR: {:8} RI: {:8} CD: {:8}\n".format( + ("active" if self.serial.cts else "inactive"), + ("active" if self.serial.dsr else "inactive"), + ("active" if self.serial.ri else "inactive"), + ("active" if self.serial.cd else "inactive"), + ) + ) + except serial.SerialException: + # on RFC 2217 ports, it can happen if no modem state notification was + # yet received. ignore this error. + pass + sys.stderr.write( + "--- software flow control: {}\n".format( + "active" if self.serial.xonxoff else "inactive" + ) + ) + sys.stderr.write( + "--- hardware flow control: {}\n".format( + "active" if self.serial.rtscts else "inactive" + ) + ) + sys.stderr.write("--- serial input encoding: {}\n".format(self.input_encoding)) + sys.stderr.write( + "--- serial output encoding: {}\n".format(self.output_encoding) + ) + sys.stderr.write("--- EOL: {}\n".format(self.eol.upper())) + sys.stderr.write("--- filters: {}\n".format(" ".join(self.filters))) + + def reader(self): + """loop and copy serial->console""" + data = b"" + while self.alive and self._reader_alive: + # read all that is there or wait for one byte + try: + data2 = self.serial.read(self.serial.in_waiting or 1) + except: + data2 = b"" + if data2: + if self.remove_no_write == True: + self.remove_no_write = False + self.no_write = False + data = data2 + else: + data += data2 + if b"\n" in data: + if self.raw: + self.console.write_bytes(data) + else: + text = self.rx_decoder.decode(data) + for transformation in self.rx_transformations: + text = transformation.rx(text) + if b"m1\n" in data: + self.no_write = True + if self.no_write == False: + self.console.write(text) + data = b"" + + def writer(self): + """\ + Loop and copy console->serial until self.exit_character character is + found. When self.menu_character is found, interpret the next key + locally. + """ + menu_active = False + try: + while self.alive: + try: + c = self.console.getkey() + self.remove_no_write = True + except KeyboardInterrupt: + c = "\x03" + if not self.alive: + break + if menu_active: + self.handle_menu_key(c) + menu_active = False + elif c == self.menu_character: + menu_active = True # next char will be for menu + elif c == self.exit_character: + self.stop() # exit app + break + else: + # ~ if self.raw: + text = c + for transformation in self.tx_transformations: + text = transformation.tx(text) + self.serial.write(self.tx_encoder.encode(text)) + if self.echo: + echo_text = c + for transformation in self.tx_transformations: + echo_text = transformation.echo(echo_text) + self.console.write(echo_text) + except: + self.alive = False + raise + + def handle_menu_key(self, c): + """Implement a simple menu / settings""" + if c == self.menu_character or c == self.exit_character: + # Menu/exit character again -> send itself + self.serial.write(self.tx_encoder.encode(c)) + if self.echo: + self.console.write(c) + elif c == "\x15": # CTRL+U -> upload file + self.upload_file() + elif c in "\x08hH?": # CTRL+H, h, H, ? -> Show help + sys.stderr.write(self.get_help_text()) + elif c == "\x12": # CTRL+R -> Toggle RTS + self.serial.rts = not self.serial.rts + sys.stderr.write( + "--- RTS {} ---\n".format("active" if self.serial.rts else "inactive") + ) + elif c == "\x04": # CTRL+D -> Toggle DTR + self.serial.dtr = not self.serial.dtr + sys.stderr.write( + "--- DTR {} ---\n".format("active" if self.serial.dtr else "inactive") + ) + elif c == "\x02": # CTRL+B -> toggle BREAK condition + self.serial.break_condition = not self.serial.break_condition + sys.stderr.write( + "--- BREAK {} ---\n".format( + "active" if self.serial.break_condition else "inactive" + ) + ) + elif c == "\x05": # CTRL+E -> toggle local echo + self.echo = not self.echo + sys.stderr.write( + "--- local echo {} ---\n".format("active" if self.echo else "inactive") + ) + elif c == "\x06": # CTRL+F -> edit filters + self.change_filter() + elif c == "\x0c": # CTRL+L -> EOL mode + modes = list(EOL_TRANSFORMATIONS) # keys + eol = modes.index(self.eol) + 1 + if eol >= len(modes): + eol = 0 + self.eol = modes[eol] + sys.stderr.write("--- EOL: {} ---\n".format(self.eol.upper())) + self.update_transformations() + elif c == "\x01": # CTRL+A -> set encoding + self.change_encoding() + elif c == "\x09": # CTRL+I -> info + self.dump_port_settings() + # ~ elif c == '\x01': # CTRL+A -> cycle escape mode + # ~ elif c == '\x0c': # CTRL+L -> cycle linefeed mode + elif c in "pP": # P -> change port + self.change_port() + elif c in "zZ": # S -> suspend / open port temporarily + self.suspend_port() + elif c in "bB": # B -> change baudrate + self.change_baudrate() + elif c == "8": # 8 -> change to 8 bits + self.serial.bytesize = serial.EIGHTBITS + self.dump_port_settings() + elif c == "7": # 7 -> change to 8 bits + self.serial.bytesize = serial.SEVENBITS + self.dump_port_settings() + elif c in "eE": # E -> change to even parity + self.serial.parity = serial.PARITY_EVEN + self.dump_port_settings() + elif c in "oO": # O -> change to odd parity + self.serial.parity = serial.PARITY_ODD + self.dump_port_settings() + elif c in "mM": # M -> change to mark parity + self.serial.parity = serial.PARITY_MARK + self.dump_port_settings() + elif c in "sS": # S -> change to space parity + self.serial.parity = serial.PARITY_SPACE + self.dump_port_settings() + elif c in "nN": # N -> change to no parity + self.serial.parity = serial.PARITY_NONE + self.dump_port_settings() + elif c == "1": # 1 -> change to 1 stop bits + self.serial.stopbits = serial.STOPBITS_ONE + self.dump_port_settings() + elif c == "2": # 2 -> change to 2 stop bits + self.serial.stopbits = serial.STOPBITS_TWO + self.dump_port_settings() + elif c == "3": # 3 -> change to 1.5 stop bits + self.serial.stopbits = serial.STOPBITS_ONE_POINT_FIVE + self.dump_port_settings() + elif c in "xX": # X -> change software flow control + self.serial.xonxoff = c == "X" + self.dump_port_settings() + elif c in "rR": # R -> change hardware flow control + self.serial.rtscts = c == "R" + self.dump_port_settings() + elif c in "qQ": + self.stop() # Q -> exit app + else: + sys.stderr.write( + "--- unknown menu character {} --\n".format(key_description(c)) + ) + + def upload_file(self): + """Ask user for filename and send its contents""" + sys.stderr.write("\n--- File to upload: ") + sys.stderr.flush() + with self.console: + filename = sys.stdin.readline().rstrip("\r\n") + if filename: + try: + with open(filename, "rb") as f: + sys.stderr.write("--- Sending file {} ---\n".format(filename)) + while True: + block = f.read(1024) + if not block: + break + self.serial.write(block) + # Wait for output buffer to drain. + self.serial.flush() + sys.stderr.write(".") # Progress indicator. + sys.stderr.write("\n--- File {} sent ---\n".format(filename)) + except IOError as e: + sys.stderr.write( + "--- ERROR opening file {}: {} ---\n".format(filename, e) + ) + + def change_filter(self): + """change the i/o transformations""" + sys.stderr.write("\n--- Available Filters:\n") + sys.stderr.write( + "\n".join( + "--- {:<10} = {.__doc__}".format(k, v) + for k, v in sorted(TRANSFORMATIONS.items()) + ) + ) + sys.stderr.write( + "\n--- Enter new filter name(s) [{}]: ".format(" ".join(self.filters)) + ) + with self.console: + new_filters = sys.stdin.readline().lower().split() + if new_filters: + for f in new_filters: + if f not in TRANSFORMATIONS: + sys.stderr.write("--- unknown filter: {!r}\n".format(f)) + break + else: + self.filters = new_filters + self.update_transformations() + sys.stderr.write("--- filters: {}\n".format(" ".join(self.filters))) + + def change_encoding(self): + """change encoding on the serial port""" + sys.stderr.write( + "\n--- Enter new encoding name [{}]: ".format(self.input_encoding) + ) + with self.console: + new_encoding = sys.stdin.readline().strip() + if new_encoding: + try: + codecs.lookup(new_encoding) + except LookupError: + sys.stderr.write("--- invalid encoding name: {}\n".format(new_encoding)) + else: + self.set_rx_encoding(new_encoding) + self.set_tx_encoding(new_encoding) + sys.stderr.write("--- serial input encoding: {}\n".format(self.input_encoding)) + sys.stderr.write( + "--- serial output encoding: {}\n".format(self.output_encoding) + ) + + def change_baudrate(self): + """change the baudrate""" + sys.stderr.write("\n--- Baudrate: ") + sys.stderr.flush() + with self.console: + backup = self.serial.baudrate + try: + self.serial.baudrate = int(sys.stdin.readline().strip()) + except ValueError as e: + sys.stderr.write("--- ERROR setting baudrate: {} ---\n".format(e)) + self.serial.baudrate = backup + else: + self.dump_port_settings() + + def change_port(self): + """Have a conversation with the user to change the serial port""" + with self.console: + try: + port = ask_for_port() + except KeyboardInterrupt: + port = None + if port and port != self.serial.port: + # reader thread needs to be shut down + self._stop_reader() + # save settings + settings = self.serial.getSettingsDict() + try: + new_serial = serial.serial_for_url(port, do_not_open=True) + # restore settings and open + new_serial.applySettingsDict(settings) + new_serial.rts = self.serial.rts + new_serial.dtr = self.serial.dtr + new_serial.open() + new_serial.break_condition = self.serial.break_condition + except Exception as e: + sys.stderr.write("--- ERROR opening new port: {} ---\n".format(e)) + new_serial.close() + else: + self.serial.close() + self.serial = new_serial + sys.stderr.write( + "--- Port changed to: {} ---\n".format(self.serial.port) + ) + # and restart the reader thread + self._start_reader() + + def suspend_port(self): + """\ + open port temporarily, allow reconnect, exit and port change to get + out of the loop + """ + # reader thread needs to be shut down + self._stop_reader() + self.serial.close() + sys.stderr.write("\n--- Port closed: {} ---\n".format(self.serial.port)) + do_change_port = False + while not self.serial.is_open: + sys.stderr.write( + "--- Quit: {exit} | p: port change | any other key to reconnect ---\n".format( + exit=key_description(self.exit_character) + ) + ) + k = self.console.getkey() + if k == self.exit_character: + self.stop() # exit app + break + elif k in "pP": + do_change_port = True + break + try: + self.serial.open() + except Exception as e: + sys.stderr.write("--- ERROR opening port: {} ---\n".format(e)) + if do_change_port: + self.change_port() + else: + # and restart the reader thread + self._start_reader() + sys.stderr.write("--- Port opened: {} ---\n".format(self.serial.port)) + + def get_help_text(self): + """return the help text""" + # help text, starts with blank line! + return """ +--- pySerial ({version}) - miniterm - help +--- +--- {exit:8} Exit program (alias {menu} Q) +--- {menu:8} Menu escape key, followed by: +--- Menu keys: +--- {menu:7} Send the menu character itself to remote +--- {exit:7} Send the exit character itself to remote +--- {info:7} Show info +--- {upload:7} Upload file (prompt will be shown) +--- {repr:7} encoding +--- {filter:7} edit filters +--- Toggles: +--- {rts:7} RTS {dtr:7} DTR {brk:7} BREAK +--- {echo:7} echo {eol:7} EOL +--- +--- Port settings ({menu} followed by the following): +--- p change port +--- 7 8 set data bits +--- N E O S M change parity (None, Even, Odd, Space, Mark) +--- 1 2 3 set stop bits (1, 2, 1.5) +--- b change baud rate +--- x X disable/enable software flow control +--- r R disable/enable hardware flow control +""".format( + version=getattr(serial, "VERSION", "unknown version"), + exit=key_description(self.exit_character), + menu=key_description(self.menu_character), + rts=key_description("\x12"), + dtr=key_description("\x04"), + brk=key_description("\x02"), + echo=key_description("\x05"), + info=key_description("\x09"), + upload=key_description("\x15"), + repr=key_description("\x01"), + filter=key_description("\x06"), + eol=key_description("\x0c"), + ) + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +# default args can be used to override when calling main() from an other script +# e.g to create a miniterm-my-device.py +def main( + default_port=None, + default_baudrate=9600, + default_rts=None, + default_dtr=None, + serial_instance=None, +): + """Command line tool, entry point""" + + import argparse + + parser = argparse.ArgumentParser( + description="Miniterm - A simple terminal program for the serial port." + ) + + parser.add_argument( + "port", + nargs="?", + help='serial port name ("-" to show port list)', + default=default_port, + ) + + parser.add_argument( + "baudrate", + nargs="?", + type=int, + help="set baud rate, default: %(default)s", + default=default_baudrate, + ) + + group = parser.add_argument_group("port settings") + + group.add_argument( + "--parity", + choices=["N", "E", "O", "S", "M"], + type=lambda c: c.upper(), + help="set parity, one of {N E O S M}, default: N", + default="N", + ) + + group.add_argument( + "--rtscts", + action="store_true", + help="enable RTS/CTS flow control (default off)", + default=False, + ) + + group.add_argument( + "--xonxoff", + action="store_true", + help="enable software flow control (default off)", + default=False, + ) + + group.add_argument( + "--rts", + type=int, + help="set initial RTS line state (possible values: 0, 1)", + default=default_rts, + ) + + group.add_argument( + "--dtr", + type=int, + help="set initial DTR line state (possible values: 0, 1)", + default=default_dtr, + ) + + group.add_argument( + "--non-exclusive", + dest="exclusive", + action="store_false", + help="disable locking for native ports", + default=True, + ) + + group.add_argument( + "--ask", + action="store_true", + help="ask again for port when open fails", + default=False, + ) + + group = parser.add_argument_group("data handling") + + group.add_argument( + "-e", + "--echo", + action="store_true", + help="enable local echo (default off)", + default=False, + ) + + group.add_argument( + "--encoding", + dest="serial_port_encoding", + metavar="CODEC", + help="set the encoding for the serial port (e.g. hexlify, Latin1, UTF-8), default: %(default)s", + default="UTF-8", + ) + + group.add_argument( + "-f", + "--filter", + action="append", + metavar="NAME", + help="add text transformation", + default=[], + ) + + group.add_argument( + "--eol", + choices=["CR", "LF", "CRLF"], + type=lambda c: c.upper(), + help="end of line mode", + default="CRLF", + ) + + group.add_argument( + "--raw", + action="store_true", + help="Do no apply any encodings/transformations", + default=False, + ) + + group = parser.add_argument_group("hotkeys") + + group.add_argument( + "--exit-char", + type=int, + metavar="NUM", + help="Unicode of special character that is used to exit the application, default: %(default)s", + default=0x1D, + ) # GS/CTRL+] + + group.add_argument( + "--menu-char", + type=int, + metavar="NUM", + help="Unicode code of special character that is used to control miniterm (menu), default: %(default)s", + default=0x14, + ) # Menu: CTRL+T + + group = parser.add_argument_group("diagnostics") + + group.add_argument( + "-q", + "--quiet", + action="store_true", + help="suppress non-error messages", + default=False, + ) + + group.add_argument( + "--develop", + action="store_true", + help="show Python traceback on error", + default=False, + ) + + args = parser.parse_args() + + if args.menu_char == args.exit_char: + parser.error("--exit-char can not be the same as --menu-char") + + if args.filter: + if "help" in args.filter: + sys.stderr.write("Available filters:\n") + sys.stderr.write( + "\n".join( + "{:<10} = {.__doc__}".format(k, v) + for k, v in sorted(TRANSFORMATIONS.items()) + ) + ) + sys.stderr.write("\n") + sys.exit(1) + filters = args.filter + else: + filters = ["default"] + + while serial_instance is None: + # no port given on command line -> ask user now + if args.port is None or args.port == "-": + try: + args.port = ask_for_port() + except KeyboardInterrupt: + sys.stderr.write("\n") + parser.error("user aborted and port is not given") + else: + if not args.port: + parser.error("port is not given") + try: + serial_instance = serial.serial_for_url( + args.port, + args.baudrate, + parity=args.parity, + rtscts=args.rtscts, + xonxoff=args.xonxoff, + do_not_open=True, + ) + + if not hasattr(serial_instance, "cancel_read"): + # enable timeout for alive flag polling if cancel_read is not available + serial_instance.timeout = 1 + + if args.dtr is not None: + if not args.quiet: + sys.stderr.write( + "--- forcing DTR {}\n".format( + "active" if args.dtr else "inactive" + ) + ) + serial_instance.dtr = args.dtr + if args.rts is not None: + if not args.quiet: + sys.stderr.write( + "--- forcing RTS {}\n".format( + "active" if args.rts else "inactive" + ) + ) + serial_instance.rts = args.rts + + if isinstance(serial_instance, serial.Serial): + serial_instance.exclusive = args.exclusive + + serial_instance.open() + except serial.SerialException as e: + sys.stderr.write("could not open port {!r}: {}\n".format(args.port, e)) + if args.develop: + raise + if not args.ask: + sys.exit(1) + else: + args.port = "-" + else: + break + + miniterm = Miniterm( + serial_instance, echo=args.echo, eol=args.eol.lower(), filters=filters + ) + miniterm.exit_character = unichr(args.exit_char) + miniterm.menu_character = unichr(args.menu_char) + miniterm.raw = args.raw + miniterm.set_rx_encoding(args.serial_port_encoding) + miniterm.set_tx_encoding(args.serial_port_encoding) + + if not args.quiet: + sys.stderr.write( + "--- Miniterm on {p.name} {p.baudrate},{p.bytesize},{p.parity},{p.stopbits} ---\n".format( + p=miniterm.serial + ) + ) + sys.stderr.write( + "--- Quit: {} | Menu: {} | Help: {} followed by {} ---\n".format( + key_description(miniterm.exit_character), + key_description(miniterm.menu_character), + key_description(miniterm.menu_character), + key_description("\x08"), + ) + ) + sys.stderr.write( + "--- Specifically modified for managing HLPdataBMS4S in Venus OS ---\n".format( + p=miniterm.serial + ) + ) + sys.stderr.write( + "--- Quit: Ctrl-t q | Local echo: Ctrl-t Ctrl-e ---\n".format( + p=miniterm.serial + ) + ) + + miniterm.start() + try: + miniterm.join(True) + except KeyboardInterrupt: + pass + if not args.quiet: + sys.stderr.write("\n--- exit ---\n") + miniterm.join() + miniterm.close() + + +# - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - +if __name__ == "__main__": + main() From 314bb93f88adf994bd4d0b3bbdf696abf408c416 Mon Sep 17 00:00:00 2001 From: Manuel <mr-manuel@outlook.it> Date: Thu, 27 Apr 2023 15:00:59 +0200 Subject: [PATCH 06/17] Add support for Seplos BMS https://github.com/Louisvdw/dbus-serialbattery/pull/530 --- etc/dbus-serialbattery/battery.py | 3 + etc/dbus-serialbattery/dbus-serialbattery.py | 2 + etc/dbus-serialbattery/seplos.py | 301 +++++++++++++++++++ 3 files changed, 306 insertions(+) create mode 100644 etc/dbus-serialbattery/seplos.py diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index 1578a8c9..d3399436 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -14,6 +14,9 @@ class Protection(object): This class holds Warning and alarm states for different types of Checks They are of type integer, 2 represents an Alarm, 1 a Warning, 0 if everything is fine """ + ALARM = 2 + WARNING = 1 + OK = 0 def __init__(self): self.voltage_high: int = None diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index b1a034bd..991e2984 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -29,6 +29,7 @@ from lifepower import Lifepower from lltjbd import LltJbd from renogy import Renogy +from seplos import Seplos # from sinowealth import Sinowealth supported_bms_types = [ @@ -42,6 +43,7 @@ {"bms": LltJbd, "baud": 9600}, {"bms": Renogy, "baud": 9600, "address": b"\x30"}, {"bms": Renogy, "baud": 9600, "address": b"\xF7"}, + {"bms": Seplos, "baud": 19200}, # {"bms": Sinowealth}, ] expected_bms_types = [ diff --git a/etc/dbus-serialbattery/seplos.py b/etc/dbus-serialbattery/seplos.py new file mode 100644 index 00000000..a5319dcf --- /dev/null +++ b/etc/dbus-serialbattery/seplos.py @@ -0,0 +1,301 @@ +# -*- coding: utf-8 -*- +from battery import Protection, Battery, Cell +from utils import * + + +def int_from_hex_ascii(to_decode, signed=False): + return int.from_bytes( + bytes.fromhex(to_decode.decode("ascii")), byteorder="big", signed=signed + ) + + +class Seplos(Battery): + def __init__(self, port, baud, address=0x00): + super(Seplos, self).__init__(port, baud, address) + self.type = self.BATTERYTYPE + self.poll_interval = 5000 + + BATTERYTYPE = "Seplos" + + COMMAND_STATUS = 0x42 + COMMAND_ALARM = 0x44 + COMMAND_PROTOCOL_VERSION = 0x4F + COMMAND_VENDOR_INFO = 0x51 + + @staticmethod + def int_from_1byte_hex_ascii(data: bytes, offset: int, signed=False): + return int.from_bytes( + bytes.fromhex(data[offset : offset + 2].decode("ascii")), + byteorder="big", + signed=signed, + ) + + @staticmethod + def int_from_2byte_hex_ascii(data: bytes, offset: int, signed=False): + return int.from_bytes( + bytes.fromhex(data[offset : offset + 4].decode("ascii")), + byteorder="big", + signed=signed, + ) + + @staticmethod + def get_checksum(frame: bytes) -> int: + """implements the Seplos checksum algorithm, returns 4 bytes""" + checksum = 0 + for b in frame: + checksum += b + checksum %= 0xFFFF + checksum ^= 0xFFFF + checksum += 1 + return checksum + + @staticmethod + def get_info_length(info: bytes) -> int: + """implements the Seplos checksum for the info length""" + lenid = len(info) + if lenid == 0: + return 0 + + lchksum = (lenid & 0xF) + ((lenid >> 4) & 0xF) + ((lenid >> 8) & 0xF) + lchksum %= 16 + lchksum ^= 0xF + lchksum += 1 + + return (lchksum << 12) + lenid + + @staticmethod + def encode_cmd(address: int, cid2: int, info: bytes = b"") -> bytes: + """encodes a command sent to a battery (cid1=0x46)""" + cid1 = 0x46 + + info_length = Seplos.get_info_length(info) + + frame = "{:02X}{:02X}{:02X}{:02X}{:04X}".format( + 0x20, address, cid1, cid2, info_length + ).encode() + frame += info + + checksum = Seplos.get_checksum(frame) + encoded = b"~" + frame + "{:04X}".format(checksum).encode() + b"\r" + return encoded + + def test_connection(self): + # call a function that will connect to the battery, send a command and retrieve the result. + # The result or call should be unique to this BMS. Battery name or version, etc. + # Return True if success, False for failure + + try: + return self.read_status_data() + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + return False + + def get_settings(self): + # After successful connection get_settings will be call to set up the battery. + # Set the current limits, populate cell count, etc. + # Return True if success, False for failure + + # Uncomment if BMS does not supply capacity + # self.capacity = BATTERY_CAPACITY + self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT + self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT + self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count + + # init the cell array + for _ in range(self.cell_count): + self.cells.append(Cell(False)) + + return True + + def refresh_data(self): + # call all functions that will refresh the battery data. + # This will be called for every iteration (self.poll_interval) + # Return True if success, False for failure + result_status = self.read_status_data() + # sleep(0.5) + result_alarm = self.read_alarm_data() + + return result_status and result_alarm + + @staticmethod + def decode_alarm_byte(data_byte: int, alarm_bit: int, warn_bit: int): + if data_byte & (1 << alarm_bit) != 0: + return Protection.ALARM + if data_byte & (1 << warn_bit) != 0: + return Protection.WARNING + return Protection.OK + + def read_alarm_data(self): + data = self.read_serial_data_seplos( + self.encode_cmd(address=0x00, cid2=self.COMMAND_ALARM, info=b"01") + ) + # check if connection success + if data is False: + return False + + logger.debug("alarm info raw {}".format(data)) + return self.decode_alarm_data(bytes.fromhex(data.decode("ascii"))) + + def decode_alarm_data(self, data: bytes): + logger.debug("alarm info decoded {}".format(data)) + voltage_alarm_byte = data[30] + self.protection.voltage_cell_low = Seplos.decode_alarm_byte( + data_byte=voltage_alarm_byte, alarm_bit=3, warn_bit=2 + ) + # cell high voltage is actually unused because DBUS does not seem to support it, decoding anyway + # c.f. https://github.com/victronenergy/venus/wiki/dbus#battery + self.protection.voltage_cell_high = Seplos.decode_alarm_byte( + data_byte=voltage_alarm_byte, alarm_bit=1, warn_bit=0 + ) + self.protection.voltage_low = Seplos.decode_alarm_byte( + data_byte=voltage_alarm_byte, alarm_bit=7, warn_bit=6 + ) + self.protection.voltage_high = Seplos.decode_alarm_byte( + data_byte=voltage_alarm_byte, alarm_bit=5, warn_bit=4 + ) + + temperature_alarm_byte = data[31] + self.protection.temp_low_charge = Seplos.decode_alarm_byte( + data_byte=temperature_alarm_byte, alarm_bit=3, warn_bit=2 + ) + self.protection.temp_high_charge = Seplos.decode_alarm_byte( + data_byte=temperature_alarm_byte, alarm_bit=1, warn_bit=0 + ) + self.protection.temp_low_discharge = Seplos.decode_alarm_byte( + data_byte=temperature_alarm_byte, alarm_bit=7, warn_bit=6 + ) + self.protection.temp_high_discharge = Seplos.decode_alarm_byte( + data_byte=temperature_alarm_byte, alarm_bit=5, warn_bit=4 + ) + + current_alarm_byte = data[33] + self.protection.current_over = Seplos.decode_alarm_byte( + data_byte=current_alarm_byte, alarm_bit=1, warn_bit=0 + ) + self.protection.current_under = Seplos.decode_alarm_byte( + data_byte=current_alarm_byte, alarm_bit=3, warn_bit=2 + ) + + soc_alarm_byte = data[34] + self.protection.soc_low = Seplos.decode_alarm_byte( + data_byte=soc_alarm_byte, alarm_bit=3, warn_bit=2 + ) + + switch_byte = data[35] + self.discharge_fet = True if switch_byte & 0b01 != 0 else False + self.charge_fet = True if switch_byte & 0b10 != 0 else False + return True + + def read_status_data(self): + logger.debug("read status data") + data = self.read_serial_data_seplos( + self.encode_cmd(address=0x00, cid2=0x42, info=b"01") + ) + + # check if connection success + if data is False: + return False + + cell_count_offset = 4 + voltage_offset = 6 + temps_offset = 72 + self.cell_count = Seplos.int_from_1byte_hex_ascii( + data=data, offset=cell_count_offset + ) + if self.cell_count == len(self.cells): + for i in range(self.cell_count): + voltage = ( + Seplos.int_from_2byte_hex_ascii(data, voltage_offset + i * 4) / 1000 + ) + self.cells[i].voltage = voltage + logger.debug("Voltage cell[{}]={}V".format(i, voltage)) + for i in range(min(4, self.cell_count)): + temp = ( + Seplos.int_from_2byte_hex_ascii(data, temps_offset + i * 4) - 2731 + ) / 10 + self.cells[i].temp = temp + logger.debug("Temp cell[{}]={}°C".format(i, temp)) + + self.temp1 = ( + Seplos.int_from_2byte_hex_ascii(data, temps_offset + 4 * 4) - 2731 + ) / 10 + self.temp2 = ( + Seplos.int_from_2byte_hex_ascii(data, temps_offset + 5 * 4) - 2731 + ) / 10 + self.current = ( + Seplos.int_from_2byte_hex_ascii(data, offset=96, signed=True) / 100 + ) + self.voltage = Seplos.int_from_2byte_hex_ascii(data, offset=100) / 100 + self.capacity_remain = Seplos.int_from_2byte_hex_ascii(data, offset=104) / 100 + self.capacity = Seplos.int_from_2byte_hex_ascii(data, offset=110) / 100 + self.soc = Seplos.int_from_2byte_hex_ascii(data, offset=114) / 10 + self.cycles = Seplos.int_from_2byte_hex_ascii(data, offset=122) + self.hardware_version = "Seplos BMS {} cells".format(self.cell_count) + + logger.debug("Current = {}A , Voltage = {}V".format(self.current, self.voltage)) + logger.debug( + "Capacity = {}/{}Ah , SOC = {}%".format( + self.capacity_remain, self.capacity, self.soc + ) + ) + logger.debug("Cycles = {}".format(self.cycles)) + logger.debug( + "Environment temp = {}°C , Power temp = {}°C".format( + self.temp1, self.temp2 + ) + ) + logger.debug("HW:" + self.hardware_version) + + return True + + @staticmethod + def is_valid_frame(data: bytes) -> bool: + """checks if data contains a valid frame + * minimum length is 18 Byte + * checksum needs to be valid + * also checks for error code as return code in cid2 + * not checked: lchksum + """ + if len(data) < 18: + logger.warning("short read, data={}".format(data)) + return False + + chksum = Seplos.get_checksum(data[1:-5]) + if chksum != Seplos.int_from_2byte_hex_ascii(data, -5): + logger.warning("checksum error") + return False + + cid2 = data[7:9] + if cid2 != b"00": + logger.warning("command returned with error code {}".format(cid2)) + return False + + return True + + def read_serial_data_seplos(self, command): + logger.debug("read serial data seplos") + + with serial.Serial(self.port, baudrate=self.baud_rate, timeout=1) as ser: + ser.flushOutput() + ser.flushInput() + written = ser.write(command) + logger.debug( + "wrote {} bytes to serial port {}, command={}".format( + written, self.port, command + ) + ) + + data = ser.readline() + + if not Seplos.is_valid_frame(data): + return False + + length_pos = 10 + return_data = data[length_pos + 3 : -5] + info_length = Seplos.int_from_2byte_hex_ascii(b"0" + data[length_pos:], 0) + logger.debug( + "return info data of length {} : {}".format(info_length, return_data) + ) + + return return_data From aceeac97c19aeafc755f2a08b573977d0ab451b6 Mon Sep 17 00:00:00 2001 From: Manuel <mr-manuel@outlook.it> Date: Thu, 27 Apr 2023 15:01:28 +0200 Subject: [PATCH 07/17] change flake8 settings --- .flake8 | 26 ++++++++++---------- etc/dbus-serialbattery/dbus-serialbattery.py | 4 +-- 2 files changed, 15 insertions(+), 15 deletions(-) diff --git a/.flake8 b/.flake8 index 5373f0c2..5e3bb3fe 100644 --- a/.flake8 +++ b/.flake8 @@ -1,19 +1,19 @@ [flake8] max-line-length = 120 exclude = - ./etc/dbus-serialbattery/ant.py, - ./etc/dbus-serialbattery/battery_template.py, - ./etc/dbus-serialbattery/daly.py, - ./etc/dbus-serialbattery/dbus-serialbattery.py, - ./etc/dbus-serialbattery/dbushelper.py, - ./etc/dbus-serialbattery/ecs.py, - ./etc/dbus-serialbattery/lifepower.py, - ./etc/dbus-serialbattery/lltjbd.py, - ./etc/dbus-serialbattery/minimalmodbus.py, - ./etc/dbus-serialbattery/mnb.py, - ./etc/dbus-serialbattery/renogy.py, - ./etc/dbus-serialbattery/revov.py, - ./etc/dbus-serialbattery/sinowealth.py, + #./etc/dbus-serialbattery/ant.py, + #./etc/dbus-serialbattery/battery_template.py, + #./etc/dbus-serialbattery/daly.py, + #./etc/dbus-serialbattery/dbus-serialbattery.py, + #./etc/dbus-serialbattery/dbushelper.py, + #./etc/dbus-serialbattery/ecs.py, + #./etc/dbus-serialbattery/lifepower.py, + #./etc/dbus-serialbattery/lltjbd.py, + #./etc/dbus-serialbattery/minimalmodbus.py, + #./etc/dbus-serialbattery/mnb.py, + #./etc/dbus-serialbattery/renogy.py, + #./etc/dbus-serialbattery/revov.py, + #./etc/dbus-serialbattery/sinowealth.py, ./etc/dbus-serialbattery/test_max17853.py, ./etc/dbus-serialbattery/util_max17853.py, ./velib_python diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index 991e2984..f91f0bea 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -4,7 +4,7 @@ from time import sleep from dbus.mainloop.glib import DBusGMainLoop -from threading import Thread +# from threading import Thread ## removed with https://github.com/Louisvdw/dbus-serialbattery/pull/582 import sys if sys.version_info.major == 2: @@ -104,7 +104,7 @@ def get_port() -> str: This prevent problems when using the driver only with a serial connection """ if port == "Jkbms_Ble": - from jkbms_ble import Jkbms_Ble + from jkbms_ble import Jkbms_Ble # noqa: F401 (ignore flake "imported but unused" error) class_ = eval(port) testbms = class_("", 9600, sys.argv[2]) From 64a08f41d8c09200b2bda12b333b928980949a18 Mon Sep 17 00:00:00 2001 From: Manuel <mr-manuel@outlook.it> Date: Thu, 27 Apr 2023 15:10:31 +0200 Subject: [PATCH 08/17] fix black lint errors --- etc/dbus-serialbattery/battery.py | 1 + etc/dbus-serialbattery/daly.py | 19 ++++++++++++------- etc/dbus-serialbattery/dbus-serialbattery.py | 7 +++++-- etc/dbus-serialbattery/utils.py | 6 +++++- 4 files changed, 23 insertions(+), 10 deletions(-) diff --git a/etc/dbus-serialbattery/battery.py b/etc/dbus-serialbattery/battery.py index d3399436..e86c7e2e 100644 --- a/etc/dbus-serialbattery/battery.py +++ b/etc/dbus-serialbattery/battery.py @@ -14,6 +14,7 @@ class Protection(object): This class holds Warning and alarm states for different types of Checks They are of type integer, 2 represents an Alarm, 1 a Warning, 0 if everything is fine """ + ALARM = 2 WARNING = 1 OK = 0 diff --git a/etc/dbus-serialbattery/daly.py b/etc/dbus-serialbattery/daly.py index 492b0500..0abeb79e 100644 --- a/etc/dbus-serialbattery/daly.py +++ b/etc/dbus-serialbattery/daly.py @@ -247,9 +247,9 @@ def read_cells_volts(self, ser): buffer[2] = self.command_cell_volts[0] if (int(self.cell_count) % 3) == 0: - maxFrame = (int(self.cell_count / 3)) + maxFrame = int(self.cell_count / 3) else: - maxFrame = (int(self.cell_count / 3) + 1) + maxFrame = int(self.cell_count / 3) + 1 lenFixed = ( maxFrame * 13 ) # 0xA5, 0x01, 0x95, 0x08 + 1 byte frame + 6 byte data + 1byte reserved + chksum @@ -279,10 +279,15 @@ def read_cells_volts(self, ser): ): # we at least need 4 bytes to extract the identifiers b1, b2, b3, b4 = unpack_from(">BBBB", cells_volts_data, bufIdx) if b1 == 0xA5 and b2 == 0x01 and b3 == 0x95 and b4 == 0x08: - frame, frameCell[0], frameCell[1], frameCell[2], _, chk = unpack_from( - ">BhhhBB", cells_volts_data, bufIdx + 4 - ) - if sum(cells_volts_data[bufIdx:bufIdx+12]) & 0xFF != chk: + ( + frame, + frameCell[0], + frameCell[1], + frameCell[2], + _, + chk, + ) = unpack_from(">BhhhBB", cells_volts_data, bufIdx + 4) + if sum(cells_volts_data[bufIdx : bufIdx + 12]) & 0xFF != chk: logger.warning("bad cell voltages checksum") return False for idx in range(3): @@ -367,7 +372,7 @@ def read_serial_data_daly(self, ser, command): start, flag, command_ret, length = unpack_from("BBBB", data) checksum = sum(data[:-1]) & 0xFF - if start == 165 and length == 8 and len(data)>12 and checksum == data[12]: + if start == 165 and length == 8 and len(data) > 12 and checksum == data[12]: return data[4 : length + 4] else: logger.error(">>> ERROR: Incorrect Reply") diff --git a/etc/dbus-serialbattery/dbus-serialbattery.py b/etc/dbus-serialbattery/dbus-serialbattery.py index f91f0bea..1fdd7bf1 100644 --- a/etc/dbus-serialbattery/dbus-serialbattery.py +++ b/etc/dbus-serialbattery/dbus-serialbattery.py @@ -4,6 +4,7 @@ from time import sleep from dbus.mainloop.glib import DBusGMainLoop + # from threading import Thread ## removed with https://github.com/Louisvdw/dbus-serialbattery/pull/582 import sys @@ -30,6 +31,7 @@ from lltjbd import LltJbd from renogy import Renogy from seplos import Seplos + # from sinowealth import Sinowealth supported_bms_types = [ @@ -37,7 +39,7 @@ {"bms": Daly, "baud": 9600, "address": b"\x40"}, {"bms": Daly, "baud": 9600, "address": b"\x80"}, {"bms": Ecs, "baud": 19200}, - {'bms': HLPdataBMS4S, "baud": 9600}, + {"bms": HLPdataBMS4S, "baud": 9600}, {"bms": Jkbms, "baud": 115200}, {"bms": Lifepower, "baud": 9600}, {"bms": LltJbd, "baud": 9600}, @@ -104,7 +106,8 @@ def get_port() -> str: This prevent problems when using the driver only with a serial connection """ if port == "Jkbms_Ble": - from jkbms_ble import Jkbms_Ble # noqa: F401 (ignore flake "imported but unused" error) + # noqa: F401 --> ignore flake "imported but unused" error + from jkbms_ble import Jkbms_Ble # noqa: F401 class_ = eval(port) testbms = class_("", 9600, sys.argv[2]) diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 7cf6eac6..04ac14ea 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -442,7 +442,11 @@ def read_serialport_data( count = 0 data = bytearray(res) - packetlen = length_fixed if length_fixed is not None else length_pos + length_byte_size + length + length_check + packetlen = ( + length_fixed + if length_fixed is not None + else length_pos + length_byte_size + length + length_check + ) while len(data) < packetlen: res = ser.read(packetlen - len(data)) data.extend(res) From 31647db46a7f11464ca3564855499e8f30367a60 Mon Sep 17 00:00:00 2001 From: Manuel <mr-manuel@outlook.it> Date: Thu, 27 Apr 2023 17:17:40 +0200 Subject: [PATCH 09/17] removed wildcard imports --- etc/dbus-serialbattery/ant.py | 24 ++++++++------- etc/dbus-serialbattery/battery_template.py | 18 ++++++----- etc/dbus-serialbattery/daly.py | 34 +++++++++++--------- etc/dbus-serialbattery/lifepower.py | 24 ++++++++++----- etc/dbus-serialbattery/lltjbd.py | 36 +++++++++++++--------- etc/dbus-serialbattery/renogy.py | 24 ++++++++------- etc/dbus-serialbattery/sinowealth.py | 24 +++++++++------ etc/dbus-serialbattery/utils.py | 2 +- 8 files changed, 109 insertions(+), 77 deletions(-) diff --git a/etc/dbus-serialbattery/ant.py b/etc/dbus-serialbattery/ant.py index 07b58d11..fde6d612 100644 --- a/etc/dbus-serialbattery/ant.py +++ b/etc/dbus-serialbattery/ant.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- -from battery import Protection, Battery, Cell -from utils import * -from struct import * +from battery import Battery +from utils import read_serial_data, logger +import utils +from struct import unpack_from class Ant(Battery): @@ -25,8 +26,9 @@ def test_connection(self): result = False try: result = self.read_status_data() - except: - pass + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False return result @@ -34,8 +36,8 @@ def get_settings(self): # After successful connection get_settings will be call to set up the battery. # Set the current limits, populate cell count, etc # Return True if success, False for failure - self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT - self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT + self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT + self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT self.version = "ANT BMS V2.0" logger.info(self.hardware_version) return True @@ -59,8 +61,8 @@ def read_status_data(self): self.current = 0.0 if current == 0 else current / -10 self.cell_count = unpack_from(">b", status_data, 123)[0] - self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count - self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count + self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count cell_max_no, cell_max_voltage, cell_min_no, cell_min_voltage = unpack_from( ">bhbh", status_data, 115 @@ -95,9 +97,9 @@ def read_status_data(self): ) self.protection.voltage_cell_low = ( 2 - if self.cell_min_voltage < MIN_CELL_VOLTAGE - 0.1 + if self.cell_min_voltage < utils.MIN_CELL_VOLTAGE - 0.1 else 1 - if self.cell_min_voltage < MIN_CELL_VOLTAGE + if self.cell_min_voltage < utils.MIN_CELL_VOLTAGE else 0 ) self.protection.temp_high_charge = ( diff --git a/etc/dbus-serialbattery/battery_template.py b/etc/dbus-serialbattery/battery_template.py index e921efc0..10b851ee 100644 --- a/etc/dbus-serialbattery/battery_template.py +++ b/etc/dbus-serialbattery/battery_template.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- from battery import Protection, Battery, Cell -from utils import * -from struct import * +from utils import is_bit_set, read_serial_data, logger +import utils +from struct import unpack_from class BatteryTemplate(Battery): @@ -20,8 +21,9 @@ def test_connection(self): result = False try: result = self.read_status_data() - except: - pass + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False return result @@ -32,10 +34,10 @@ def get_settings(self): # Uncomment if BMS does not supply capacity # self.capacity = BATTERY_CAPACITY - self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT - self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT - self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count - self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count + self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT + self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT + self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count return True def refresh_data(self): diff --git a/etc/dbus-serialbattery/daly.py b/etc/dbus-serialbattery/daly.py index 0abeb79e..d91ae568 100644 --- a/etc/dbus-serialbattery/daly.py +++ b/etc/dbus-serialbattery/daly.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- -from battery import Protection, Battery, Cell -from utils import * -from struct import * +from battery import Battery, Cell +from utils import open_serial_port, read_serialport_data, logger +import utils +from struct import unpack_from class Daly(Battery): @@ -37,21 +38,26 @@ def __init__(self, port, baud, address): TEMP_ZERO_CONSTANT = 40 def test_connection(self): + # call a function that will connect to the battery, send a command and retrieve the result. + # The result or call should be unique to this BMS. Battery name or version, etc. + # Return True if success, False for failure result = False try: ser = open_serial_port(self.port, self.baud_rate) if ser is not None: result = self.read_status_data(ser) ser.close() - except: - pass + + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False return result def get_settings(self): - self.capacity = BATTERY_CAPACITY - self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT - self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT + self.capacity = utils.BATTERY_CAPACITY + self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT + self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT return True def refresh_data(self): @@ -95,8 +101,8 @@ def read_status_data(self, ser): self.cycles, ) = unpack_from(">bb??bhx", status_data) - self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count - self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count + self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count self.hardware_version = "DalyBMS " + str(self.cell_count) + " cells" logger.info(self.hardware_version) @@ -104,8 +110,8 @@ def read_status_data(self, ser): def read_soc_data(self, ser): # Ensure data received is valid - crntMinValid = -(MAX_BATTERY_DISCHARGE_CURRENT * 2.1) - crntMaxValid = MAX_BATTERY_CHARGE_CURRENT * 1.3 + crntMinValid = -(utils.MAX_BATTERY_DISCHARGE_CURRENT * 2.1) + crntMaxValid = utils.MAX_BATTERY_CHARGE_CURRENT * 1.3 triesValid = 2 while triesValid > 0: soc_data = self.read_serial_data_daly(ser, self.command_soc) @@ -117,7 +123,7 @@ def read_soc_data(self, ser): current = ( (current - self.CURRENT_ZERO_CONSTANT) / -10 - * INVERT_CURRENT_MEASUREMENT + * utils.INVERT_CURRENT_MEASUREMENT ) if crntMinValid < current < crntMaxValid: self.voltage = voltage / 10 @@ -262,7 +268,7 @@ def read_cells_volts(self, ser): return False frameCell = [0, 0, 0] - lowMin = MIN_CELL_VOLTAGE / 2 + lowMin = utils.MIN_CELL_VOLTAGE / 2 frame = 0 bufIdx = 0 diff --git a/etc/dbus-serialbattery/lifepower.py b/etc/dbus-serialbattery/lifepower.py index cc3058c0..84c93a37 100644 --- a/etc/dbus-serialbattery/lifepower.py +++ b/etc/dbus-serialbattery/lifepower.py @@ -1,8 +1,9 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function, unicode_literals -from battery import Protection, Battery, Cell -from utils import * -from struct import * +from battery import Battery, Cell +from utils import read_serial_data, logger +import utils +from struct import unpack_from import re @@ -24,14 +25,21 @@ def test_connection(self): # call a function that will connect to the battery, send a command and retrieve the result. # The result or call should be unique to this BMS. Battery name or version, etc. # Return True if success, False for failure - return self.read_status_data() + result = False + try: + result = self.read_status_data() + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False + + return result def get_settings(self): # After successful connection get_settings will be call to set up the battery. # Set the current limits, populate cell count, etc # Return True if success, False for failure - self.max_battery_current = MAX_BATTERY_CURRENT - self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT + self.max_battery_current = utils.MAX_BATTERY_CURRENT + self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT hardware_version = self.read_serial_data_eg4(self.command_hardware_version) if hardware_version: # I get some characters that I'm not able to figure out the encoding, probably chinese so I discard it @@ -89,8 +97,8 @@ def read_status_data(self): # Cells self.cell_count = len(groups[0]) - self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count - self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count + self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count self.cells = [Cell(True) for _ in range(0, self.cell_count)] for i, cell in enumerate(self.cells): diff --git a/etc/dbus-serialbattery/lltjbd.py b/etc/dbus-serialbattery/lltjbd.py index a8222fd6..29ac7068 100644 --- a/etc/dbus-serialbattery/lltjbd.py +++ b/etc/dbus-serialbattery/lltjbd.py @@ -1,7 +1,9 @@ # -*- coding: utf-8 -*- from battery import Protection, Battery, Cell -from utils import * -from struct import * +from utils import is_bit_set, read_serial_data, logger +import utils +from struct import unpack_from +import struct class LltJbdProtection(Protection): @@ -59,18 +61,22 @@ def __init__(self, port, baud, address): LENGTH_POS = 3 def test_connection(self): + # call a function that will connect to the battery, send a command and retrieve the result. + # The result or call should be unique to this BMS. Battery name or version, etc. + # Return True if success, False for failure result = False try: result = self.read_hardware_data() - except: - pass + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False return result def get_settings(self): self.read_gen_data() - self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT - self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT + self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT + self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT return True def refresh_data(self): @@ -79,7 +85,7 @@ def refresh_data(self): return result def to_protection_bits(self, byte_data): - tmp = bin(byte_data)[2:].rjust(13, zero_char) + tmp = bin(byte_data)[2:].rjust(13, utils.zero_char) self.protection.voltage_high = 2 if is_bit_set(tmp[10]) else 0 self.protection.voltage_low = 2 if is_bit_set(tmp[9]) else 0 @@ -92,7 +98,7 @@ def to_protection_bits(self, byte_data): # Software implementations for low soc self.protection.soc_low = ( - 2 if self.soc < SOC_LOW_ALARM else 1 if self.soc < SOC_LOW_WARNING else 0 + 2 if self.soc < utils.SOC_LOW_ALARM else 1 if self.soc < utils.SOC_LOW_WARNING else 0 ) # extra protection flags for LltJbd @@ -107,17 +113,17 @@ def to_cell_bits(self, byte_data, byte_data_high): for c in self.cells: self.cells.remove(c) # get up to the first 16 cells - tmp = bin(byte_data)[2:].rjust(min(self.cell_count, 16), zero_char) + tmp = bin(byte_data)[2:].rjust(min(self.cell_count, 16), utils.zero_char) for bit in reversed(tmp): self.cells.append(Cell(is_bit_set(bit))) # get any cells above 16 if self.cell_count > 16: - tmp = bin(byte_data_high)[2:].rjust(self.cell_count - 16, zero_char) + tmp = bin(byte_data_high)[2:].rjust(self.cell_count - 16, utils.zero_char) for bit in reversed(tmp): self.cells.append(Cell(is_bit_set(bit))) def to_fet_bits(self, byte_data): - tmp = bin(byte_data)[2:].rjust(2, zero_char) + tmp = bin(byte_data)[2:].rjust(2, utils.zero_char) self.charge_fet = is_bit_set(tmp[1]) self.discharge_fet = is_bit_set(tmp[0]) @@ -152,15 +158,15 @@ def read_gen_data(self): self.version = float(str(version >> 4 & 0x0F) + "." + str(version & 0x0F)) self.to_fet_bits(fet) self.to_protection_bits(protection) - self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count - self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count + self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count for t in range(self.temp_sensors): temp1 = unpack_from(">H", gen_data, 23 + (2 * t))[0] if t == 0: - self.to_temp("mos", kelvin_to_celsius(temp1 / 10)) + self.to_temp("mos", utils.kelvin_to_celsius(temp1 / 10)) else: - self.to_temp(t, kelvin_to_celsius(temp1 / 10)) + self.to_temp(t, utils.kelvin_to_celsius(temp1 / 10)) return True diff --git a/etc/dbus-serialbattery/renogy.py b/etc/dbus-serialbattery/renogy.py index fe493da6..ea513f23 100644 --- a/etc/dbus-serialbattery/renogy.py +++ b/etc/dbus-serialbattery/renogy.py @@ -1,6 +1,7 @@ # -*- coding: utf-8 -*- -from battery import Protection, Battery, Cell -from utils import * +from battery import Battery, Cell +from utils import read_serial_data, unpack, unpack_from, logger +import utils import struct @@ -16,7 +17,8 @@ def __init__(self, port, baud, address): LENGTH_CHECK = 4 LENGTH_POS = 2 - # command bytes [Address field][Function code (03 = Read register)][Register Address (2 bytes)][Data Length (2 bytes)][CRC (2 bytes little endian)] + # command bytes [Address field][Function code (03 = Read register)] + # [Register Address (2 bytes)][Data Length (2 bytes)][CRC (2 bytes little endian)] command_read = b"\x03" # Core data = voltage, temp, current, soc command_cell_count = b"\x13\x88\x00\x01" # Register 5000 @@ -44,9 +46,9 @@ def test_connection(self): result = False try: result = self.read_gen_data() - except: - logger.exception("Unexpected exception encountered") - pass + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False return result @@ -54,11 +56,11 @@ def get_settings(self): # After successful connection get_settings will be call to set up the battery. # Set the current limits, populate cell count, etc # Return True if success, False for failure - self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT - self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT + self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT + self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT - self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count - self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count + self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count return True def refresh_data(self): @@ -203,7 +205,7 @@ def read_serial_data_renogy(self, command): return False start, flag, length = unpack_from("BBB", data) - checksum = unpack_from(">H", data, length + 3) + # checksum = unpack_from(">H", data, length + 3) if flag == 3: return data[3 : length + 3] diff --git a/etc/dbus-serialbattery/sinowealth.py b/etc/dbus-serialbattery/sinowealth.py index f5021ff5..60b41895 100755 --- a/etc/dbus-serialbattery/sinowealth.py +++ b/etc/dbus-serialbattery/sinowealth.py @@ -1,7 +1,8 @@ # -*- coding: utf-8 -*- -from battery import Protection, Battery, Cell -from utils import * -from struct import * +from battery import Battery, Cell +from utils import kelvin_to_celsius, read_serial_data, logger +import utils +from struct import unpack_from class Sinowealth(Battery): @@ -33,25 +34,30 @@ def __init__(self, port, baud, address): LENGTH_POS = 0 def test_connection(self): + # call a function that will connect to the battery, send a command and retrieve the result. + # The result or call should be unique to this BMS. Battery name or version, etc. + # Return True if success, False for failure result = False try: result = self.read_status_data() result = result and self.read_remaining_capacity() result = result and self.read_pack_config_data() - except: - pass + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False + return result def get_settings(self): # hardcoded parameters, to be requested from the BMS in the future - self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT - self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT + self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT + self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT if self.cell_count is None: self.read_pack_config_data() - self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count - self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count + self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count self.hardware_version = "Daly/Sinowealth BMS " + str(self.cell_count) + " cells" logger.info(self.hardware_version) diff --git a/etc/dbus-serialbattery/utils.py b/etc/dbus-serialbattery/utils.py index 04ac14ea..d90bbfbb 100644 --- a/etc/dbus-serialbattery/utils.py +++ b/etc/dbus-serialbattery/utils.py @@ -36,7 +36,7 @@ def _get_list_from_config( # Constants - Need to dynamically get them in future DRIVER_VERSION = "1.0" -DRIVER_SUBVERSION = ".0-jkbms_ble (20230426)" +DRIVER_SUBVERSION = ".0-jkbms_ble (20230427)" zero_char = chr(48) degree_sign = "\N{DEGREE SIGN}" From 8d3f25e5cacf340385191f59d23fb740c2160f8f Mon Sep 17 00:00:00 2001 From: Manuel <mr-manuel@outlook.it> Date: Thu, 27 Apr 2023 17:33:09 +0200 Subject: [PATCH 10/17] fixed black lint errors --- etc/dbus-serialbattery/hlpdatabms4s.py | 23 ++++++++++--------- .../hlpdatabms4s_miniterm.py | 13 +++++------ 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/etc/dbus-serialbattery/hlpdatabms4s.py b/etc/dbus-serialbattery/hlpdatabms4s.py index 63995357..6fcbc6b0 100644 --- a/etc/dbus-serialbattery/hlpdatabms4s.py +++ b/etc/dbus-serialbattery/hlpdatabms4s.py @@ -1,7 +1,7 @@ # -*- coding: utf-8 -*- -from battery import Protection, Battery, Cell -from utils import * -from struct import * +from battery import Battery, Cell +from utils import logger +import utils import serial from time import sleep @@ -20,9 +20,10 @@ def test_connection(self): result = False try: result = self.read_test_data() - except Exception as e: - # logger.error(e, exc_info=True) - pass + except Exception as err: + logger.error(f"Unexpected {err=}, {type(err)=}") + result = False + return result def get_settings(self): @@ -33,7 +34,7 @@ def get_settings(self): try: result = self.read_settings_data() except Exception as e: - # logger.error(e, exc_info=True) + logger.error(e, exc_info=True) pass return result @@ -45,7 +46,7 @@ def refresh_data(self): try: result = self.read_status_data() except Exception as e: - # logger.error(e, exc_info=True) + logger.error(e, exc_info=True) pass return result @@ -71,8 +72,8 @@ def read_test_data(self): if ix > 0: self.hardware_version = s1[ix : len(s1) - 1] self.version = self.hardware_version - self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT - self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT + self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT + self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT self.poll_interval = 10000 self.control_discharge_current = 1000 self.control_charge_current = 1000 @@ -201,7 +202,7 @@ def read_serial_data2(command, port, baud, time, min_len): try: with serial.Serial(port, baudrate=baud, timeout=0.5) as ser: ret = read_serialport_data2(ser, command, time, min_len) - if not ret is False: + if False is not ret: return ret return False diff --git a/etc/dbus-serialbattery/hlpdatabms4s_miniterm.py b/etc/dbus-serialbattery/hlpdatabms4s_miniterm.py index 741ac75a..22df77fd 100644 --- a/etc/dbus-serialbattery/hlpdatabms4s_miniterm.py +++ b/etc/dbus-serialbattery/hlpdatabms4s_miniterm.py @@ -19,7 +19,6 @@ import serial from serial.tools.list_ports import comports from serial.tools import hexlify_codec -from time import sleep # pylint: disable=wrong-import-order,wrong-import-position @@ -549,10 +548,10 @@ def reader(self): # read all that is there or wait for one byte try: data2 = self.serial.read(self.serial.in_waiting or 1) - except: + except Exception: data2 = b"" if data2: - if self.remove_no_write == True: + if self.remove_no_write is True: self.remove_no_write = False self.no_write = False data = data2 @@ -567,7 +566,7 @@ def reader(self): text = transformation.rx(text) if b"m1\n" in data: self.no_write = True - if self.no_write == False: + if self.no_write is False: self.console.write(text) data = b"" @@ -606,7 +605,7 @@ def writer(self): for transformation in self.tx_transformations: echo_text = transformation.echo(echo_text) self.console.write(echo_text) - except: + except Exception: self.alive = False raise @@ -1163,12 +1162,12 @@ def main( ) ) sys.stderr.write( - "--- Specifically modified for managing HLPdataBMS4S in Venus OS ---\n".format( + "--- Specifically modified for managing HLPdataBMS4S in Venus OS ---\n".format( # noqa: F522 p=miniterm.serial ) ) sys.stderr.write( - "--- Quit: Ctrl-t q | Local echo: Ctrl-t Ctrl-e ---\n".format( + "--- Quit: Ctrl-t q | Local echo: Ctrl-t Ctrl-e ---\n".format( # noqa: F522 p=miniterm.serial ) ) From a4a3efbb19ea9984ce9870c146c843e8fe775a33 Mon Sep 17 00:00:00 2001 From: Manuel <mr-manuel@outlook.it> Date: Thu, 27 Apr 2023 17:33:30 +0200 Subject: [PATCH 11/17] change flake8 settings --- .flake8 | 10 +++++----- CHANGELOG.md | 2 ++ 2 files changed, 7 insertions(+), 5 deletions(-) diff --git a/.flake8 b/.flake8 index 5e3bb3fe..fcc7f0bf 100644 --- a/.flake8 +++ b/.flake8 @@ -2,17 +2,17 @@ max-line-length = 120 exclude = #./etc/dbus-serialbattery/ant.py, - #./etc/dbus-serialbattery/battery_template.py, + ./etc/dbus-serialbattery/battery_template.py, #./etc/dbus-serialbattery/daly.py, #./etc/dbus-serialbattery/dbus-serialbattery.py, - #./etc/dbus-serialbattery/dbushelper.py, + ./etc/dbus-serialbattery/dbushelper.py, #./etc/dbus-serialbattery/ecs.py, #./etc/dbus-serialbattery/lifepower.py, #./etc/dbus-serialbattery/lltjbd.py, - #./etc/dbus-serialbattery/minimalmodbus.py, - #./etc/dbus-serialbattery/mnb.py, + ./etc/dbus-serialbattery/minimalmodbus.py, + ./etc/dbus-serialbattery/mnb.py, #./etc/dbus-serialbattery/renogy.py, - #./etc/dbus-serialbattery/revov.py, + ./etc/dbus-serialbattery/revov.py, #./etc/dbus-serialbattery/sinowealth.py, ./etc/dbus-serialbattery/test_max17853.py, ./etc/dbus-serialbattery/util_max17853.py, diff --git a/CHANGELOG.md b/CHANGELOG.md index 12d2ee32..9aa162c6 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -27,6 +27,8 @@ * Added: Show charge/discharge limitation reason * Added: Show specific TimeToSoC points in GUI, if 0%, 10%, 20%, 80%, 90% and/or 100% are selected * Added: Show TimeToGo in GUI only, if enabled +* Added: Support for HLPdata BMS4S https://github.com/Louisvdw/dbus-serialbattery/pull/505 by @peterohman +* Added: Support for Seplos BMS https://github.com/Louisvdw/dbus-serialbattery/pull/530 by @wollew * Added: Temperature name for temperature sensor 1 & 2. This allows to see which sensor is low and high (e.g. battery and cable) * Changed: `reinstalllocal.sh` to recreate `/data/conf/serial-starter.d` if deleted by `disabledriver.sh` --> to check if the file `conf/serial-starter.d` could now be removed from the repository * Changed: Added QML to `restoregui.sh` From 4a26d0d5f4ce9f60058848eef0969cff67d6612f Mon Sep 17 00:00:00 2001 From: Manuel <mr-manuel@outlook.it> Date: Thu, 27 Apr 2023 17:39:58 +0200 Subject: [PATCH 12/17] remove wildcard import and fix black lint errors --- etc/dbus-serialbattery/lltjbd.py | 6 +++++- etc/dbus-serialbattery/seplos.py | 12 +++++++----- 2 files changed, 12 insertions(+), 6 deletions(-) diff --git a/etc/dbus-serialbattery/lltjbd.py b/etc/dbus-serialbattery/lltjbd.py index 29ac7068..c285d44a 100644 --- a/etc/dbus-serialbattery/lltjbd.py +++ b/etc/dbus-serialbattery/lltjbd.py @@ -98,7 +98,11 @@ def to_protection_bits(self, byte_data): # Software implementations for low soc self.protection.soc_low = ( - 2 if self.soc < utils.SOC_LOW_ALARM else 1 if self.soc < utils.SOC_LOW_WARNING else 0 + 2 + if self.soc < utils.SOC_LOW_ALARM + else 1 + if self.soc < utils.SOC_LOW_WARNING + else 0 ) # extra protection flags for LltJbd diff --git a/etc/dbus-serialbattery/seplos.py b/etc/dbus-serialbattery/seplos.py index a5319dcf..1adf12bf 100644 --- a/etc/dbus-serialbattery/seplos.py +++ b/etc/dbus-serialbattery/seplos.py @@ -1,6 +1,8 @@ # -*- coding: utf-8 -*- from battery import Protection, Battery, Cell -from utils import * +from utils import logger +import utils +import serial def int_from_hex_ascii(to_decode, signed=False): @@ -97,10 +99,10 @@ def get_settings(self): # Uncomment if BMS does not supply capacity # self.capacity = BATTERY_CAPACITY - self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT - self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT - self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count - self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count + self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT + self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT + self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count # init the cell array for _ in range(self.cell_count): From 564d4edc151500bdf00dac54f76ffd9822b51893 Mon Sep 17 00:00:00 2001 From: Manuel <mr-manuel@outlook.it> Date: Thu, 27 Apr 2023 17:47:43 +0200 Subject: [PATCH 13/17] removed wildcard import --- etc/dbus-serialbattery/ecs.py | 22 +++++++++++----------- 1 file changed, 11 insertions(+), 11 deletions(-) diff --git a/etc/dbus-serialbattery/ecs.py b/etc/dbus-serialbattery/ecs.py index d8bc7295..1777e6f0 100644 --- a/etc/dbus-serialbattery/ecs.py +++ b/etc/dbus-serialbattery/ecs.py @@ -1,8 +1,8 @@ # -*- coding: utf-8 -*- from __future__ import absolute_import, division, print_function, unicode_literals from battery import Protection, Battery, Cell -from utils import * -from struct import * +from utils import logger +import utils import minimalmodbus @@ -33,7 +33,7 @@ def test_connection(self): # Trying to find Green Meter ID try: - mbdev = minimalmodbus.Instrument(self.port, GREENMETER_ADDRESS) + mbdev = minimalmodbus.Instrument(self.port, utils.GREENMETER_ADDRESS) mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN tmpId = mbdev.read_register(0, 0) if tmpId in range(self.GREENMETER_ID_500A, self.GREENMETER_ID_125A + 1): @@ -52,7 +52,7 @@ def test_connection(self): def find_LiPro_cells(self): # test for LiPro cell devices - for cell_address in range(LIPRO_START_ADDRESS, LIPRO_END_ADDRESS + 1): + for cell_address in range(utils.LIPRO_START_ADDRESS, utils.LIPRO_END_ADDRESS + 1): try: mbdev = minimalmodbus.Instrument(self.port, cell_address) mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN @@ -74,11 +74,11 @@ def get_settings(self): # Return True if success, False for failure # Uncomment if BMS does not supply capacity - self.max_battery_charge_current = MAX_BATTERY_CHARGE_CURRENT - self.max_battery_discharge_current = MAX_BATTERY_DISCHARGE_CURRENT - self.cell_count = LIPRO_CELL_COUNT - self.max_battery_voltage = MAX_CELL_VOLTAGE * self.cell_count - self.min_battery_voltage = MIN_CELL_VOLTAGE * self.cell_count + self.max_battery_charge_current = utils.MAX_BATTERY_CHARGE_CURRENT + self.max_battery_discharge_current = utils.MAX_BATTERY_DISCHARGE_CURRENT + self.cell_count = utils.LIPRO_CELL_COUNT + self.max_battery_voltage = utils.MAX_CELL_VOLTAGE * self.cell_count + self.min_battery_voltage = utils.MIN_CELL_VOLTAGE * self.cell_count self.temp_sensors = 2 return self.read_status_data() @@ -94,7 +94,7 @@ def refresh_data(self): def read_status_data(self): try: - mbdev = minimalmodbus.Instrument(self.port, GREENMETER_ADDRESS) + mbdev = minimalmodbus.Instrument(self.port, utils.GREENMETER_ADDRESS) mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN self.max_battery_discharge_current = abs( @@ -120,7 +120,7 @@ def read_status_data(self): def read_soc_data(self): try: - mbdev = minimalmodbus.Instrument(self.port, GREENMETER_ADDRESS) + mbdev = minimalmodbus.Instrument(self.port, utils.GREENMETER_ADDRESS) mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN self.voltage = ( From 41cdadb837e83fc5b336f748f6cfd3fb03a7b813 Mon Sep 17 00:00:00 2001 From: Manuel <mr-manuel@outlook.it> Date: Thu, 27 Apr 2023 17:53:00 +0200 Subject: [PATCH 14/17] fixed black lint check --- etc/dbus-serialbattery/ecs.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/etc/dbus-serialbattery/ecs.py b/etc/dbus-serialbattery/ecs.py index 1777e6f0..41065b7e 100644 --- a/etc/dbus-serialbattery/ecs.py +++ b/etc/dbus-serialbattery/ecs.py @@ -52,7 +52,9 @@ def test_connection(self): def find_LiPro_cells(self): # test for LiPro cell devices - for cell_address in range(utils.LIPRO_START_ADDRESS, utils.LIPRO_END_ADDRESS + 1): + for cell_address in range( + utils.LIPRO_START_ADDRESS, utils.LIPRO_END_ADDRESS + 1 + ): try: mbdev = minimalmodbus.Instrument(self.port, cell_address) mbdev.serial.parity = minimalmodbus.serial.PARITY_EVEN From 440b113100f6b4ae051103f63a26ee5a53ec28e6 Mon Sep 17 00:00:00 2001 From: Manuel <mr-manuel@outlook.it> Date: Thu, 27 Apr 2023 18:23:26 +0200 Subject: [PATCH 15/17] config changes --- .flake8 | 7 ---- .gitignore | 5 ++- CHANGELOG.md | 104 ++++++++++++++++++++++++++------------------------- 3 files changed, 57 insertions(+), 59 deletions(-) diff --git a/.flake8 b/.flake8 index fcc7f0bf..85d1bd06 100644 --- a/.flake8 +++ b/.flake8 @@ -1,19 +1,12 @@ [flake8] max-line-length = 120 exclude = - #./etc/dbus-serialbattery/ant.py, ./etc/dbus-serialbattery/battery_template.py, - #./etc/dbus-serialbattery/daly.py, #./etc/dbus-serialbattery/dbus-serialbattery.py, ./etc/dbus-serialbattery/dbushelper.py, - #./etc/dbus-serialbattery/ecs.py, - #./etc/dbus-serialbattery/lifepower.py, - #./etc/dbus-serialbattery/lltjbd.py, ./etc/dbus-serialbattery/minimalmodbus.py, ./etc/dbus-serialbattery/mnb.py, - #./etc/dbus-serialbattery/renogy.py, ./etc/dbus-serialbattery/revov.py, - #./etc/dbus-serialbattery/sinowealth.py, ./etc/dbus-serialbattery/test_max17853.py, ./etc/dbus-serialbattery/util_max17853.py, ./velib_python diff --git a/.gitignore b/.gitignore index 287a9c8b..59bf8fe4 100644 --- a/.gitignore +++ b/.gitignore @@ -151,6 +151,9 @@ cython_debug/ # option (not recommended) you can uncomment the following to ignore the entire idea folder. #.idea/ +# VS Code +.vscode/ + # Custom for this repo venus-data.tar.gz BMS-trials @@ -161,4 +164,4 @@ BMS-trials etc/dbus-serialbattery/config.ini # Local Clone of velib_python -velib_python \ No newline at end of file +velib_python diff --git a/CHANGELOG.md b/CHANGELOG.md index 9aa162c6..ba0d4aa5 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -2,58 +2,60 @@ ## v1.0.0-jkbms_ble -* Added: Balancing status for JKBMS -* Added: Balancing switch status for JKBMS -* Added: Balancing switch status to the GUI -> SerialBattery -> IO -* Added: Charge Mode display -* Added: Choose how battery temperature is assembled (mean temp 1 & 2, only temp 1 or only temp 2) -* Added: Create empty `config.ini` for easier user usage -* Added: Cronjob to restart Bluetooth service every 12 hours -* Added: Driver uninstall script -* Added: Fix for Venus OS >= v3.00~14 showing unused items https://github.com/Louisvdw/dbus-serialbattery/issues/469 -* Added: HighInternalTemperature alarm (MOSFET) for JKBMS -* Added: Install needed components automatically after a Venus OS upgrade -* Added: JKBMS - MOS temperature https://github.com/Louisvdw/dbus-serialbattery/pull/440 -* Added: JKBMS BLE - Balancing switch status -* Added: JKBMS BLE - Capacity -* Added: JKBMS BLE - Cell imbalance alert -* Added: JKBMS BLE - Charging switch status -* Added: JKBMS BLE - Discharging switch status -* Added: JKBMS BLE - MOS temperature -* Added: JKBMS BLE - Show if balancing is active and which cells are balancing -* Added: Post install notes -* Added: Script to install directly from repository -* Added: Show charge mode (absorption, bulk, ...) in Parameters page -* Added: Show charge/discharge limitation reason -* Added: Show specific TimeToSoC points in GUI, if 0%, 10%, 20%, 80%, 90% and/or 100% are selected -* Added: Show TimeToGo in GUI only, if enabled +* Added: Balancing status for JKBMS by @mr-manuel +* Added: Balancing switch status for JKBMS by @mr-manuel +* Added: Balancing switch status to the GUI -> SerialBattery -> IO by @mr-manuel +* Added: Charge Mode display by @mr-manuel +* Added: Choose how battery temperature is assembled (mean temp 1 & 2, only temp 1 or only temp 2) by @mr-manuel +* Added: Config file by @ppuetsch +* Added: Create empty `config.ini` for easier user usage by @mr-manuel +* Added: Cronjob to restart Bluetooth service every 12 hours by @mr-manuel +* Added: Driver uninstall script by @mr-manuel +* Added: Fix for Venus OS >= v3.00~14 showing unused items https://github.com/Louisvdw/dbus-serialbattery/issues/469 by @mr-manuel +* Added: HighInternalTemperature alarm (MOSFET) for JKBMS by @mr-manuel +* Added: Install needed components automatically after a Venus OS upgrade by @mr-manuel +* Added: JKBMS - MOS temperature https://github.com/Louisvdw/dbus-serialbattery/pull/440 by @mr-manuel +* Added: JKBMS BLE - Balancing switch status by @mr-manuel +* Added: JKBMS BLE - Capacity by @mr-manuel +* Added: JKBMS BLE - Cell imbalance alert by @mr-manuel +* Added: JKBMS BLE - Charging switch status by @mr-manuel +* Added: JKBMS BLE - Discharging switch status by @mr-manuel +* Added: JKBMS BLE - MOS temperature by @mr-manuel +* Added: JKBMS BLE - Show if balancing is active and which cells are balancing by @mr-manuel +* Added: Post install notes by @mr-manuel +* Added: Script to install directly from repository by @mr-manuel +* Added: Show charge mode (absorption, bulk, ...) in Parameters page by @mr-manuel +* Added: Show charge/discharge limitation reason by @mr-manuel +* Added: Show specific TimeToSoC points in GUI, if 0%, 10%, 20%, 80%, 90% and/or 100% are selected by @mr-manuel +* Added: Show TimeToGo in GUI only, if enabled by @mr-manuel * Added: Support for HLPdata BMS4S https://github.com/Louisvdw/dbus-serialbattery/pull/505 by @peterohman * Added: Support for Seplos BMS https://github.com/Louisvdw/dbus-serialbattery/pull/530 by @wollew -* Added: Temperature name for temperature sensor 1 & 2. This allows to see which sensor is low and high (e.g. battery and cable) -* Changed: `reinstalllocal.sh` to recreate `/data/conf/serial-starter.d` if deleted by `disabledriver.sh` --> to check if the file `conf/serial-starter.d` could now be removed from the repository -* Changed: Added QML to `restoregui.sh` -* Changed: Bash output -* Changed: Default config file - * Added missing descriptions to make it much clearer to understand - * Changed name from `default_config.ini` to `config.default.ini` https://github.com/Louisvdw/dbus-serialbattery/pull/412#issuecomment-1434287942 - * Changed TimeToSoc default value `TIME_TO_SOC_VALUE_TYPE` from `Both seconds and time string "<seconds> [<days>d <hours>h <minutes>m <seconds>s]"` to `1 Seconds` - * Changed TimeToSoc description - * Changed value positions, added groups and much clearer descriptions -* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/239 -* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/311 -* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/351 +* Added: Temperature name for temperature sensor 1 & 2. This allows to see which sensor is low and high (e.g. battery and cable) by @mr-manuel +* Changed: `reinstalllocal.sh` to recreate `/data/conf/serial-starter.d` if deleted by `disabledriver.sh` --> to check if the file `conf/serial-starter.d` could now be removed from the repository by @mr-manuel +* Changed: Added QML to `restoregui.sh` by @mr-manuel +* Changed: Bash output by @mr-manuel +* Changed: Default config file by @mr-manuel + * Added missing descriptions to make it much clearer to understand by @mr-manuel + * Changed name from `default_config.ini` to `config.default.ini` https://github.com/Louisvdw/dbus-serialbattery/pull/412#issuecomment-1434287942 by @mr-manuel + * Changed TimeToSoc default value `TIME_TO_SOC_VALUE_TYPE` from `Both seconds and time string "<seconds> [<days>d <hours>h <minutes>m <seconds>s]"` to `1 Seconds` by @mr-manuel + * Changed TimeToSoc description by @mr-manuel + * Changed value positions, added groups and much clearer descriptions by @mr-manuel +* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/239 by @mr-manuel +* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/311 by @mr-manuel +* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/351 by @mr-manuel * Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/397 by @transistorgit -* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/421 -* Changed: Fixed black lint errors -* Changed: Fixed cell balancing background for cells 17-24 -* Changed: Fixed Time-To-Go is not working, if `TIME_TO_SOC_VALUE_TYPE` is set to other than `1` https://github.com/Louisvdw/dbus-serialbattery/pull/424#issuecomment-1440511018 +* Changed: Fix for https://github.com/Louisvdw/dbus-serialbattery/issues/421 by @mr-manuel +* Changed: Fixed black lint errors by @mr-manuel +* Changed: Fixed cell balancing background for cells 17-24 by @mr-manuel +* Changed: Fixed Time-To-Go is not working, if `TIME_TO_SOC_VALUE_TYPE` is set to other than `1` https://github.com/Louisvdw/dbus-serialbattery/pull/424#issuecomment-1440511018 by @mr-manuel * Changed: Improved JBD BMS soc calculation https://github.com/Louisvdw/dbus-serialbattery/pull/439 by @aaronreek -* Changed: Logging to get relevant data -* Changed: Moved ble part to `installble.sh` -* Changed: Optimized installation scripts -* Changed: Serial-Starter file is now created from `reinstalllocal.sh`. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/520 -* Changed: Separate Time-To-Go and Time-To-SoC activation -* Changed: Temperature alarm changed in order to not trigger all in the same condition for JKBMS -* Changed: Time-To-Soc repetition from cycles to seconds. Minimum value is every 5 seconds. This prevents CPU overload and ensures system stability. Renamed `TIME_TO_SOC_LOOP_CYCLES` to `TIME_TO_SOC_RECALCULATE_EVERY` -* Changed: Time-To-Soc string from `days, HR:MN:SC` to `<days>d <hours>h <minutes>m <seconds>s` (same as Time-To-Go) -* Changed: Uninstall also installed Bluetooth modules on uninstall. +* Changed: Logging to get relevant data by @mr-manuel +* Changed: Moved ble part to `installble.sh` by @mr-manuel +* Changed: Optimized installation scripts by @mr-manuel +* Changed: Removed wildcard imports from several BMS drivers and fixed black lint errors by @mr-manuel +* Changed: Serial-Starter file is now created from `reinstalllocal.sh`. Fixes also https://github.com/Louisvdw/dbus-serialbattery/issues/520 by @mr-manuel +* Changed: Separate Time-To-Go and Time-To-SoC activation by @mr-manuel +* Changed: Temperature alarm changed in order to not trigger all in the same condition for JKBMS by @mr-manuel +* Changed: Time-To-Soc repetition from cycles to seconds. Minimum value is every 5 seconds. This prevents CPU overload and ensures system stability. Renamed `TIME_TO_SOC_LOOP_CYCLES` to `TIME_TO_SOC_RECALCULATE_EVERY` by @mr-manuel +* Changed: Time-To-Soc string from `days, HR:MN:SC` to `<days>d <hours>h <minutes>m <seconds>s` (same as Time-To-Go) by @mr-manuel +* Changed: Uninstall also installed Bluetooth modules on uninstall. by @mr-manuel From bc91973ca9d4fb5af8ea446b36cb2f29dee397cd Mon Sep 17 00:00:00 2001 From: Manuel <mr-manuel@outlook.it> Date: Thu, 27 Apr 2023 18:53:07 +0200 Subject: [PATCH 16/17] remove old log message in handle_changed_setting() --- etc/dbus-serialbattery/dbushelper.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index 36b3c819..0a629778 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -94,10 +94,6 @@ def handle_changed_setting(self, setting, oldvalue, newvalue): self.battery.role, self.instance = self.get_role_instance() logger.info("Changed DeviceInstance = %d", self.instance) return - logger.info( - "Changed DeviceInstance = %d", float(self.settings["CellVoltageMin"]) - ) - # self._dbusservice['/History/ChargeCycles'] def setup_vedbus(self): # Set up dbus service and device instance From 83668b62b558af7b052d93d570588e42f531d8c4 Mon Sep 17 00:00:00 2001 From: Manuel <mr-manuel@outlook.it> Date: Thu, 27 Apr 2023 19:09:18 +0200 Subject: [PATCH 17/17] simplified condition for Time-To-Go/Soc --- etc/dbus-serialbattery/dbushelper.py | 14 +++----------- etc/dbus-serialbattery/mnb.py | 2 +- 2 files changed, 4 insertions(+), 12 deletions(-) diff --git a/etc/dbus-serialbattery/dbushelper.py b/etc/dbus-serialbattery/dbushelper.py index 0a629778..246a1aad 100644 --- a/etc/dbus-serialbattery/dbushelper.py +++ b/etc/dbus-serialbattery/dbushelper.py @@ -498,22 +498,14 @@ def publish_dbus(self): except: pass - # Update TimeToSoC + # Update TimeToGo and/or TimeToSoC try: if ( self.battery.capacity is not None and (TIME_TO_GO_ENABLE or len(TIME_TO_SOC_POINTS) > 0) and ( - ( - # update only once in same second - int(time()) != self.battery.time_to_soc_update - and - # update only every x seconds - int(time()) % TIME_TO_SOC_RECALCULATE_EVERY == 0 - ) - or - # update on first run - self.battery.time_to_soc_update == 0 + int(time()) - self.battery.time_to_soc_update + >= TIME_TO_SOC_RECALCULATE_EVERY ) ): self.battery.time_to_soc_update = int(time()) diff --git a/etc/dbus-serialbattery/mnb.py b/etc/dbus-serialbattery/mnb.py index 8e91bd5b..ce627a39 100644 --- a/etc/dbus-serialbattery/mnb.py +++ b/etc/dbus-serialbattery/mnb.py @@ -3,7 +3,7 @@ from struct import * # from test_max17853 import *#{these two lines are mutually} -from util_max17853 import * # {exclusive. use test for testing} +# from util_max17853 import * # {exclusive. use test for testing} class MNBProtection(Protection):