aiobmsble 0.2.3__py3-none-any.whl → 0.4__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (64) hide show
  1. aiobmsble/__init__.py +2 -0
  2. aiobmsble/__main__.py +4 -2
  3. aiobmsble/basebms.py +40 -15
  4. aiobmsble/bms/abc_bms.py +2 -2
  5. aiobmsble/bms/ant_bms.py +4 -3
  6. aiobmsble/bms/ant_leg_bms.py +177 -0
  7. aiobmsble/bms/braunpwr_bms.py +2 -2
  8. aiobmsble/bms/cbtpwr_bms.py +2 -2
  9. aiobmsble/bms/cbtpwr_vb_bms.py +2 -2
  10. aiobmsble/bms/daly_bms.py +2 -2
  11. aiobmsble/bms/dpwrcore_bms.py +2 -2
  12. aiobmsble/bms/dummy_bms.py +3 -3
  13. aiobmsble/bms/ecoworthy_bms.py +2 -2
  14. aiobmsble/bms/ective_bms.py +2 -2
  15. aiobmsble/bms/ej_bms.py +9 -3
  16. aiobmsble/bms/felicity_bms.py +2 -2
  17. aiobmsble/bms/jbd_bms.py +2 -2
  18. aiobmsble/bms/jikong_bms.py +2 -2
  19. aiobmsble/bms/neey_bms.py +2 -2
  20. aiobmsble/bms/ogt_bms.py +2 -2
  21. aiobmsble/bms/pro_bms.py +2 -2
  22. aiobmsble/bms/redodo_bms.py +5 -2
  23. aiobmsble/bms/renogy_bms.py +2 -2
  24. aiobmsble/bms/renogy_pro_bms.py +2 -2
  25. aiobmsble/bms/roypow_bms.py +2 -2
  26. aiobmsble/bms/seplos_bms.py +3 -3
  27. aiobmsble/bms/seplos_v2_bms.py +2 -2
  28. aiobmsble/bms/tdt_bms.py +2 -2
  29. aiobmsble/bms/tianpwr_bms.py +2 -2
  30. aiobmsble/test_data/__init__.py +95 -0
  31. aiobmsble/test_data/abc_bms.json +34 -0
  32. aiobmsble/test_data/ant_bms.json +18 -0
  33. aiobmsble/test_data/ant_leg_bms.json +19 -0
  34. aiobmsble/test_data/braunpwr_bms.json +34 -0
  35. aiobmsble/test_data/cbtpwr_bms.json +90 -0
  36. aiobmsble/test_data/cbtpwr_vb_bms.json +20 -0
  37. aiobmsble/test_data/daly_bms.json +100 -0
  38. aiobmsble/test_data/dpwrcore_bms.json +18 -0
  39. aiobmsble/test_data/ecoworthy_bms.json +100 -0
  40. aiobmsble/test_data/ective_bms.json +104 -0
  41. aiobmsble/test_data/ej_bms.json +107 -0
  42. aiobmsble/test_data/felicity_bms.json +24 -0
  43. aiobmsble/test_data/ignore.json +48 -0
  44. aiobmsble/test_data/jbd_bms.json +440 -0
  45. aiobmsble/test_data/jikong_bms.json +54 -0
  46. aiobmsble/test_data/neey_bms.json +65 -0
  47. aiobmsble/test_data/ogt_bms.json +16 -0
  48. aiobmsble/test_data/pro_bms.json +15 -0
  49. aiobmsble/test_data/redodo_bms.json +151 -0
  50. aiobmsble/test_data/renogy_bms.json +19 -0
  51. aiobmsble/test_data/renogy_pro_bms.json +16 -0
  52. aiobmsble/test_data/roypow_bms.json +54 -0
  53. aiobmsble/test_data/seplos_bms.json +96 -0
  54. aiobmsble/test_data/seplos_v2_bms.json +41 -0
  55. aiobmsble/test_data/tdt_bms.json +14 -0
  56. aiobmsble/test_data/tianpwr_bms.json +13 -0
  57. aiobmsble-0.4.dist-info/METADATA +163 -0
  58. aiobmsble-0.4.dist-info/RECORD +64 -0
  59. aiobmsble-0.2.3.dist-info/METADATA +0 -122
  60. aiobmsble-0.2.3.dist-info/RECORD +0 -36
  61. {aiobmsble-0.2.3.dist-info → aiobmsble-0.4.dist-info}/WHEEL +0 -0
  62. {aiobmsble-0.2.3.dist-info → aiobmsble-0.4.dist-info}/entry_points.txt +0 -0
  63. {aiobmsble-0.2.3.dist-info → aiobmsble-0.4.dist-info}/licenses/LICENSE +0 -0
  64. {aiobmsble-0.2.3.dist-info → aiobmsble-0.4.dist-info}/top_level.txt +0 -0
aiobmsble/__init__.py CHANGED
@@ -19,6 +19,7 @@ type BMSvalue = Literal[
19
19
  "cycles",
20
20
  "cycle_capacity",
21
21
  "cycle_charge",
22
+ "total_charge",
22
23
  "delta_voltage",
23
24
  "problem",
24
25
  "runtime",
@@ -69,6 +70,7 @@ class BMSsample(TypedDict, total=False):
69
70
  cell_count: int # [#]
70
71
  cell_voltages: list[float] # [V]
71
72
  cycle_charge: int | float # [Ah]
73
+ total_charge: int # [Ah], overall discharged
72
74
  design_capacity: int # [Ah]
73
75
  pack_count: int # [#]
74
76
  temp_sensors: int # [#]
aiobmsble/__main__.py CHANGED
@@ -31,7 +31,7 @@ async def scan_devices() -> dict[str, tuple[BLEDevice, AdvertisementData]]:
31
31
  scan_result: dict[str, tuple[BLEDevice, AdvertisementData]] = (
32
32
  await BleakScanner.discover(return_adv=True)
33
33
  )
34
- logger.info(scan_result)
34
+ logger.debug(scan_result)
35
35
  logger.info("%i BT devices in range.", len(scan_result))
36
36
  return scan_result
37
37
 
@@ -51,7 +51,7 @@ async def detect_bms() -> None:
51
51
 
52
52
  if bms_cls := bms_identify(advertisement):
53
53
  logger.info("Found matching BMS type: %s", bms_cls.device_id())
54
- bms: BaseBMS = bms_cls(ble_device=ble_dev, reconnect=True)
54
+ bms: BaseBMS = bms_cls(ble_device=ble_dev)
55
55
 
56
56
  try:
57
57
  logger.info("Updating BMS data...")
@@ -59,6 +59,8 @@ async def detect_bms() -> None:
59
59
  logger.info("BMS data: %s", repr(data).replace(", '", ",\n\t'"))
60
60
  except (BleakError, TimeoutError) as exc:
61
61
  logger.error("Failed to update BMS: %s", type(exc).__name__)
62
+ finally:
63
+ await bms.disconnect()
62
64
 
63
65
  logger.info("done.")
64
66
 
aiobmsble/basebms.py CHANGED
@@ -9,7 +9,8 @@ import asyncio
9
9
  from collections.abc import Callable, MutableMapping
10
10
  import logging
11
11
  from statistics import fmean
12
- from typing import Any, Final, Literal
12
+ from types import TracebackType
13
+ from typing import Any, Final, Literal, Self
13
14
 
14
15
  from bleak import BleakClient
15
16
  from bleak.backends.characteristic import BleakGATTCharacteristic
@@ -44,26 +45,28 @@ class BaseBMS(ABC):
44
45
  def __init__(
45
46
  self,
46
47
  ble_device: BLEDevice,
47
- reconnect: bool = False,
48
+ keep_alive: bool = True,
48
49
  logger_name: str = "",
49
50
  ) -> None:
50
51
  """Intialize the BMS.
51
52
 
52
- notification_handler: the callback function used for notifications from 'uuid_rx()'
53
+ `_notification_handler`: the callback function used for notifications from `uuid_rx()`
53
54
  characteristic. Not defined as abstract in this base class, as it can be both,
54
55
  a normal or async function
55
56
 
56
57
  Args:
57
- logger_name (str): name of the logger for the BMS instance (usually file name)
58
58
  ble_device (BLEDevice): the Bleak device to connect to
59
- reconnect (bool): if true, the connection will be closed after each update
59
+ keep_alive (bool): if true, the connection will be kept active after each update.
60
+ Make sure to call `disconnect()` when done using the BMS class or better use
61
+ `async with` context manager (requires `keep_alive=True`).
62
+ logger_name (str): name of the logger for the BMS instance, default: module name
60
63
 
61
64
  """
62
65
  assert (
63
66
  getattr(self, "_notification_handler", None) is not None
64
67
  ), "BMS class must define _notification_handler method"
65
68
  self._ble_device: Final[BLEDevice] = ble_device
66
- self._reconnect: Final[bool] = reconnect
69
+ self._keep_alive: Final[bool] = keep_alive
67
70
  self.name: Final[str] = self._ble_device.name or "undefined"
68
71
  self._inv_wr_mode: bool | None = None # invert write mode (WNR <-> W)
69
72
  logger_name = logger_name or self.__class__.__module__
@@ -83,6 +86,22 @@ class BaseBMS(ABC):
83
86
  self._data: bytearray = bytearray()
84
87
  self._data_event: Final[asyncio.Event] = asyncio.Event()
85
88
 
89
+ async def __aenter__(self) -> Self:
90
+ """Asynchronous context manager to implement `async with` functionality."""
91
+ if not self._keep_alive:
92
+ raise ValueError("usage of context manager requires `keep_alive=True`.")
93
+ await self._connect()
94
+ return self
95
+
96
+ async def __aexit__(
97
+ self,
98
+ typ: type[BaseException] | None,
99
+ exc: BaseException | None,
100
+ tb: TracebackType | None,
101
+ ) -> None:
102
+ """Asynchronous context manager exit functionality."""
103
+ await self.disconnect()
104
+
86
105
  @classmethod
87
106
  def get_bms_module(cls) -> str:
88
107
  """Return BMS module name, e.g. aiobmsble.bms.dummy_bms."""
@@ -177,6 +196,10 @@ class BaseBMS(ABC):
177
196
  {"voltage", "cycle_charge"},
178
197
  lambda: round(data.get("voltage", 0) * data.get("cycle_charge", 0), 3),
179
198
  ),
199
+ "cycles": (
200
+ {"design_capacity", "total_charge"},
201
+ lambda: data.get("total_charge", 0) // data.get("design_capacity", 0),
202
+ ),
180
203
  "power": (
181
204
  {"voltage", "current"},
182
205
  lambda: round(data.get("voltage", 0) * current, 3),
@@ -356,8 +379,8 @@ class BaseBMS(ABC):
356
379
  if reset:
357
380
  self._inv_wr_mode = None # reset write mode
358
381
  await self._client.disconnect()
359
- except BleakError:
360
- self._log.warning("disconnect failed!")
382
+ except (BleakError, TimeoutError) as exc:
383
+ self._log.error("disconnect failed! (%s)", type(exc).__name__)
361
384
 
362
385
  async def _wait_event(self) -> None:
363
386
  """Wait for data event and clear it."""
@@ -384,7 +407,7 @@ class BaseBMS(ABC):
384
407
  data: BMSsample = await self._async_update()
385
408
  self._add_missing_values(data, self._calc_values())
386
409
 
387
- if self._reconnect:
410
+ if not self._keep_alive:
388
411
  # disconnect after data update to force reconnect next time (slow!)
389
412
  await self.disconnect()
390
413
 
@@ -478,16 +501,18 @@ class BaseBMS(ABC):
478
501
 
479
502
  """
480
503
  return [
481
- value / divider if divider != 1 else value
504
+ (value - offset) / divider
482
505
  for idx in range(values)
483
506
  if (len(data) >= start + (idx + 1) * size)
484
507
  and (
485
- value := int.from_bytes(
486
- data[start + idx * size : start + (idx + 1) * size],
487
- byteorder=byteorder,
488
- signed=signed,
508
+ (
509
+ value := int.from_bytes(
510
+ data[start + idx * size : start + (idx + 1) * size],
511
+ byteorder=byteorder,
512
+ signed=signed,
513
+ )
489
514
  )
490
- - offset
515
+ or (offset == 0)
491
516
  )
492
517
  ]
493
518
 
aiobmsble/bms/abc_bms.py CHANGED
@@ -47,9 +47,9 @@ class BMS(BaseBMS):
47
47
  )
48
48
  _RESPS: Final[set[int]] = {field.idx for field in _FIELDS} | {0xF4} # cell voltages
49
49
 
50
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
50
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
51
51
  """Initialize BMS."""
52
- super().__init__(ble_device, reconnect)
52
+ super().__init__(ble_device, keep_alive)
53
53
  self._data_final: dict[int, bytearray] = {}
54
54
  self._exp_reply: set[int] = set()
55
55
 
aiobmsble/bms/ant_bms.py CHANGED
@@ -41,13 +41,14 @@ class BMS(BaseBMS):
41
41
  | ((x & 0xF) if (x & 0xF) not in (0x1, 0x4, 0xB, 0xC, 0xF) else 0),
42
42
  ),
43
43
  BMSdp("cycle_charge", 54, 4, False, lambda x: x / 1e6),
44
+ BMSdp("total_charge", 58, 4, False, lambda x: x // 1000),
44
45
  BMSdp("delta_voltage", 82, 2, False, lambda x: x / 1000),
45
46
  BMSdp("power", 62, 4, True, lambda x: x / 1),
46
47
  )
47
48
 
48
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
49
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
49
50
  """Initialize BMS."""
50
- super().__init__(ble_device, reconnect)
51
+ super().__init__(ble_device, keep_alive)
51
52
  self._data_final: bytearray = bytearray()
52
53
  self._valid_reply: int = BMS._CMD_STAT | 0x10 # valid reply mask
53
54
  self._exp_len: int = BMS._MIN_LEN
@@ -87,7 +88,7 @@ class BMS(BaseBMS):
87
88
  @staticmethod
88
89
  def _calc_values() -> frozenset[BMSvalue]:
89
90
  return frozenset(
90
- {"cycle_capacity", "temperature"}
91
+ {"cycle_capacity", "cycles", "temperature"}
91
92
  ) # calculate further values from BMS provided set ones
92
93
 
93
94
  async def _init_connection(
@@ -0,0 +1,177 @@
1
+ """Module to support ANT BMS."""
2
+
3
+ import contextlib
4
+ from enum import IntEnum
5
+ from typing import Final, override
6
+
7
+ from bleak.backends.characteristic import BleakGATTCharacteristic
8
+ from bleak.backends.device import BLEDevice
9
+ from bleak.uuids import normalize_uuid_str
10
+
11
+ from aiobmsble import BMSdp, BMSsample, BMSvalue, MatcherPattern
12
+ from aiobmsble.basebms import BaseBMS, crc_sum
13
+
14
+
15
+ class BMS(BaseBMS):
16
+ """ANT BMS (legacy) implementation."""
17
+
18
+ class CMD(IntEnum):
19
+ """Command codes for ANT BMS."""
20
+
21
+ GET = 0xDB
22
+ SET = 0xA5
23
+
24
+ class ADR(IntEnum):
25
+ """Address codes for ANT BMS."""
26
+
27
+ STATUS = 0x00
28
+
29
+ _RX_HEADER: Final[bytes] = b"\xaa\x55\xaa"
30
+ _RX_HEADER_RSP_STAT: Final[bytes] = b"\xaa\x55\xaa\xff"
31
+
32
+ _RSP_STAT: Final[int] = 0xFF
33
+ _RSP_STAT_LEN: Final[int] = 140
34
+
35
+ _FIELDS: Final[tuple[BMSdp, ...]] = (
36
+ BMSdp("voltage", 4, 2, False, lambda x: x / 10),
37
+ BMSdp("current", 70, 4, True, lambda x: x / -10),
38
+ BMSdp("battery_level", 74, 1, False),
39
+ BMSdp("design_capacity", 75, 4, False, lambda x: x // 1e6),
40
+ BMSdp("cycle_charge", 79, 4, False, lambda x: x / 1e6),
41
+ BMSdp("total_charge", 83, 4, False, lambda x: x // 1000),
42
+ BMSdp("runtime", 87, 4, False),
43
+ BMSdp("cell_count", 123, 1, False),
44
+ )
45
+
46
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
47
+ """Initialize BMS."""
48
+ super().__init__(ble_device, keep_alive)
49
+ self._data_final: bytearray
50
+
51
+ @staticmethod
52
+ @override
53
+ def matcher_dict_list() -> list[MatcherPattern]:
54
+ """Provide BluetoothMatcher definition."""
55
+ return [
56
+ {
57
+ "local_name": "ANT-BLE*",
58
+ "service_uuid": BMS.uuid_services()[0],
59
+ "manufacturer_id": 1623,
60
+ "connectable": True,
61
+ }
62
+ ]
63
+
64
+ @staticmethod
65
+ @override
66
+ def device_info() -> dict[str, str]:
67
+ """Return device information for the battery management system."""
68
+ return {"manufacturer": "ANT", "model": "Smart BMS"}
69
+
70
+ @staticmethod
71
+ @override
72
+ def uuid_services() -> list[str]:
73
+ """Return list of 128-bit UUIDs of services required by BMS."""
74
+ return [normalize_uuid_str("ffe0")] # change service UUID here!
75
+
76
+ @staticmethod
77
+ @override
78
+ def uuid_rx() -> str:
79
+ """Return 16-bit UUID of characteristic that provides notification/read property."""
80
+ return "ffe1"
81
+
82
+ @staticmethod
83
+ @override
84
+ def uuid_tx() -> str:
85
+ """Return 16-bit UUID of characteristic that provides write property."""
86
+ return "ffe1"
87
+
88
+ @staticmethod
89
+ @override
90
+ def _calc_values() -> frozenset[BMSvalue]:
91
+ return frozenset(
92
+ (
93
+ "battery_charging",
94
+ "cycle_capacity",
95
+ "cycles",
96
+ "delta_voltage",
97
+ "power",
98
+ "temperature",
99
+ )
100
+ ) # calculate further values from BMS provided set ones
101
+
102
+ def _notification_handler(
103
+ self, _sender: BleakGATTCharacteristic, data: bytearray
104
+ ) -> None:
105
+ """Handle the RX characteristics notify event (new data arrives)."""
106
+
107
+ self._log.debug("RX BLE data: %s", data)
108
+
109
+ if data.startswith(BMS._RX_HEADER_RSP_STAT):
110
+ self._data = bytearray()
111
+ elif not self._data:
112
+ self._log.debug("invalid start of frame")
113
+ return
114
+
115
+ self._data += data
116
+
117
+ _data_len: Final[int] = len(self._data)
118
+ if _data_len < BMS._RSP_STAT_LEN:
119
+ return
120
+
121
+ if _data_len > BMS._RSP_STAT_LEN:
122
+ self._log.debug("invalid length %d > %d", _data_len, BMS._RSP_STAT_LEN)
123
+ self._data.clear()
124
+ return
125
+
126
+ if (local_crc := crc_sum(self._data[4:-2], 2)) != (
127
+ remote_crc := int.from_bytes(self._data[-2:], byteorder="big", signed=False)
128
+ ):
129
+ self._log.debug("invalid checksum 0x%X != 0x%X", local_crc, remote_crc)
130
+ self._data.clear()
131
+ return
132
+
133
+ self._data_final = self._data.copy()
134
+ self._data.clear()
135
+ self._data_event.set()
136
+
137
+ @staticmethod
138
+ def _cmd(cmd: CMD, adr: ADR, value: int = 0x0000) -> bytes:
139
+ """Assemble a ANT BMS command."""
140
+ _frame = bytearray((cmd, cmd, adr))
141
+ _frame += value.to_bytes(2, "big")
142
+ _frame += crc_sum(_frame[2:], 1).to_bytes(1, "big")
143
+ return bytes(_frame)
144
+
145
+ @override
146
+ async def _async_update(self) -> BMSsample:
147
+ """Update battery status information."""
148
+ await self._await_reply(BMS._cmd(BMS.CMD.GET, BMS.ADR.STATUS))
149
+
150
+ _data: bytearray = self._data_final
151
+ result: BMSsample = BMS._decode_data(
152
+ BMS._FIELDS, _data, byteorder="big", offset=0
153
+ )
154
+
155
+ result["cell_voltages"] = BMS._cell_voltages(
156
+ _data,
157
+ cells=result["cell_count"],
158
+ start=6,
159
+ size=2,
160
+ byteorder="big",
161
+ divider=1000,
162
+ )
163
+
164
+ if not result["design_capacity"]:
165
+ # Workaround for some BMS always reporting 0 for design_capacity
166
+ result.pop("design_capacity")
167
+ with contextlib.suppress(ZeroDivisionError):
168
+ result["design_capacity"] = int(
169
+ round((result["cycle_charge"] / result["battery_level"]) * 100, -1)
170
+ ) # leads to `cycles` not available when level == 0
171
+
172
+ # ANT-BMS carries 6 slots for temp sensors but only 4 looks like being connected by default
173
+ result["temp_values"] = BMS._temp_values(
174
+ _data, values=4, start=91, size=2, byteorder="big", signed=True
175
+ )
176
+
177
+ return result
@@ -38,9 +38,9 @@ class BMS(BaseBMS):
38
38
  0xF5, # BMS boot version
39
39
  }
40
40
 
41
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
41
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
42
42
  """Intialize private BMS members."""
43
- super().__init__(ble_device, reconnect)
43
+ super().__init__(ble_device, keep_alive)
44
44
  self._data_final: dict[int, bytearray] = {}
45
45
  self._exp_reply: tuple[int] = (0x01,)
46
46
 
@@ -37,9 +37,9 @@ class BMS(BaseBMS):
37
37
  )
38
38
  _CMDS: Final[list[int]] = list({field.idx for field in _FIELDS})
39
39
 
40
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
40
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
41
41
  """Intialize private BMS members."""
42
- super().__init__(ble_device, reconnect)
42
+ super().__init__(ble_device, keep_alive)
43
43
 
44
44
  @staticmethod
45
45
  def matcher_dict_list() -> list[MatcherPattern]:
@@ -35,9 +35,9 @@ class BMS(BaseBMS):
35
35
  BMSdp("problem_code", 15, 6, False, lambda x: x & 0xFFF000FF000F),
36
36
  )
37
37
 
38
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
38
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
39
39
  """Initialize BMS."""
40
- super().__init__(ble_device, reconnect)
40
+ super().__init__(ble_device, keep_alive)
41
41
  self._exp_len: int = 0
42
42
 
43
43
  @staticmethod
aiobmsble/bms/daly_bms.py CHANGED
@@ -39,9 +39,9 @@ class BMS(BaseBMS):
39
39
  BMSdp("problem_code", 116, 8, False, lambda x: x % 2**64),
40
40
  )
41
41
 
42
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
42
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
43
43
  """Intialize private BMS members."""
44
- super().__init__(ble_device, reconnect)
44
+ super().__init__(ble_device, keep_alive)
45
45
 
46
46
  @staticmethod
47
47
  def matcher_dict_list() -> list[MatcherPattern]:
@@ -55,9 +55,9 @@ class BMS(BaseBMS):
55
55
  )
56
56
  _CMDS: Final[set[Cmd]] = {Cmd(field.idx) for field in _FIELDS}
57
57
 
58
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
58
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
59
59
  """Intialize private BMS members."""
60
- super().__init__(ble_device, reconnect)
60
+ super().__init__(ble_device, keep_alive)
61
61
  assert self._ble_device.name is not None # required for unlock
62
62
  self._data_final: dict[int, bytearray] = {}
63
63
 
@@ -19,9 +19,9 @@ class BMS(BaseBMS):
19
19
  # _TAIL: Final[bytes] = b"\xAA" # end of frame
20
20
  # _FRAME_LEN: Final[int] = 10 # length of frame, including SOF and checksum
21
21
 
22
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
22
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
23
23
  """Initialize BMS."""
24
- super().__init__(ble_device, reconnect)
24
+ super().__init__(ble_device, keep_alive)
25
25
 
26
26
  @staticmethod
27
27
  def matcher_dict_list() -> list[MatcherPattern]:
@@ -84,7 +84,7 @@ class BMS(BaseBMS):
84
84
  self._log.debug("replace with command to UUID %s", BMS.uuid_tx())
85
85
  # await self._await_reply(b"<some_command>")
86
86
 
87
- # # TODO: parse data from self._data here
87
+ # TODO: parse data from self._data here
88
88
 
89
89
  return {
90
90
  "voltage": 12,
@@ -42,9 +42,9 @@ class BMS(BaseBMS):
42
42
 
43
43
  _CMDS: Final[set[int]] = set({field.idx for field in _FIELDS_V1})
44
44
 
45
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
45
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
46
46
  """Initialize BMS."""
47
- super().__init__(ble_device, reconnect)
47
+ super().__init__(ble_device, keep_alive)
48
48
  self._mac_head: Final[tuple] = tuple(
49
49
  int(self._ble_device.address.replace(":", ""), 16).to_bytes(6) + head
50
50
  for head in BMS._HEAD
@@ -33,9 +33,9 @@ class BMS(BaseBMS):
33
33
  BMSdp("problem_code", 37, 2, False, lambda x: x),
34
34
  )
35
35
 
36
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
36
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
37
37
  """Initialize BMS."""
38
- super().__init__(ble_device, reconnect)
38
+ super().__init__(ble_device, keep_alive)
39
39
  self._data_final: bytearray = bytearray()
40
40
 
41
41
  @staticmethod
aiobmsble/bms/ej_bms.py CHANGED
@@ -45,9 +45,9 @@ class BMS(BaseBMS):
45
45
  ), # mask status bits
46
46
  )
47
47
 
48
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
48
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
49
49
  """Initialize BMS."""
50
- super().__init__(ble_device, reconnect)
50
+ super().__init__(ble_device, keep_alive)
51
51
  self._data_final: bytearray = bytearray()
52
52
 
53
53
  @staticmethod
@@ -65,7 +65,6 @@ class BMS(BaseBMS):
65
65
  "connectable": True,
66
66
  },
67
67
  {"local_name": "SV12V*", "manufacturer_id": 33384, "connectable": True},
68
- {"local_name": "LT-24*", "manufacturer_id": 22618, "connectable": True},
69
68
  ]
70
69
  + [ # LiTime
71
70
  MatcherPattern( # LiTime based on ser#
@@ -75,6 +74,13 @@ class BMS(BaseBMS):
75
74
  )
76
75
  for m_id in (33384, 22618)
77
76
  ]
77
+ + [ # LiTime based on ser#
78
+ {
79
+ "local_name": "LT-24???B-A00[0-2]*",
80
+ "manufacturer_id": 22618,
81
+ "connectable": True,
82
+ }
83
+ ]
78
84
  )
79
85
 
80
86
  @staticmethod
@@ -36,9 +36,9 @@ class BMS(BaseBMS):
36
36
  ("battery_level", "BatsocList", lambda x: x[0][0] / 100),
37
37
  ]
38
38
 
39
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
39
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
40
40
  """Initialize BMS."""
41
- super().__init__(ble_device, reconnect)
41
+ super().__init__(ble_device, keep_alive)
42
42
  self._data_final: dict = {}
43
43
 
44
44
  @staticmethod
aiobmsble/bms/jbd_bms.py CHANGED
@@ -32,9 +32,9 @@ class BMS(BaseBMS):
32
32
  BMSdp("problem_code", 20, 2, False, lambda x: x),
33
33
  ) # general protocol v4
34
34
 
35
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
35
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
36
36
  """Intialize private BMS members."""
37
- super().__init__(ble_device, reconnect)
37
+ super().__init__(ble_device, keep_alive)
38
38
  self._valid_reply: int = 0x00
39
39
  self._data_final: bytearray = bytearray()
40
40
 
@@ -35,9 +35,9 @@ class BMS(BaseBMS):
35
35
  BMSdp("problem_code", 166, 4, False, lambda x: x),
36
36
  )
37
37
 
38
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
38
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
39
39
  """Intialize private BMS members."""
40
- super().__init__(ble_device, reconnect)
40
+ super().__init__(ble_device, keep_alive)
41
41
  self._data_final: bytearray = bytearray()
42
42
  self._char_write_handle: int = -1
43
43
  self._bms_info: dict[str, str] = {}
aiobmsble/bms/neey_bms.py CHANGED
@@ -34,9 +34,9 @@ class BMS(BaseBMS):
34
34
  ("balance_current", 217, "<f", lambda x: round(x, 3)),
35
35
  ]
36
36
 
37
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
37
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
38
38
  """Intialize private BMS members."""
39
- super().__init__(ble_device, reconnect)
39
+ super().__init__(ble_device, keep_alive)
40
40
  self._data_final: bytearray = bytearray()
41
41
  self._bms_info: dict[str, str] = {}
42
42
  self._exp_len: int = BMS._MIN_FRAME
aiobmsble/bms/ogt_bms.py CHANGED
@@ -30,9 +30,9 @@ class BMS(BaseBMS):
30
30
  reg: int
31
31
  value: int
32
32
 
33
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
33
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
34
34
  """Intialize private BMS members."""
35
- super().__init__(ble_device, reconnect)
35
+ super().__init__(ble_device, keep_alive)
36
36
  self._type: str = (
37
37
  self.name[9]
38
38
  if len(self.name) >= 10 and set(self.name[10:]).issubset(digits)
aiobmsble/bms/pro_bms.py CHANGED
@@ -52,9 +52,9 @@ class BMS(BaseBMS):
52
52
  BMSdp("power", 32, 4, False, lambda x: x / 100),
53
53
  )
54
54
 
55
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
55
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
56
56
  """Initialize private BMS members."""
57
- super().__init__(ble_device, reconnect)
57
+ super().__init__(ble_device, keep_alive)
58
58
  self._valid_reply: int = BMS._RT_DATA
59
59
 
60
60
  @staticmethod
@@ -29,9 +29,9 @@ class BMS(BaseBMS):
29
29
  BMSdp("problem_code", 76, 4, False, lambda x: x),
30
30
  )
31
31
 
32
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
32
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
33
33
  """Initialize BMS."""
34
- super().__init__(ble_device, reconnect)
34
+ super().__init__(ble_device, keep_alive)
35
35
 
36
36
  @staticmethod
37
37
  def matcher_dict_list() -> list[MatcherPattern]:
@@ -56,6 +56,9 @@ class BMS(BaseBMS):
56
56
  "L-24*",
57
57
  "L-51*",
58
58
  "LT-12???BG-A0[7-9]*", # LiTime based on ser#
59
+ "LT-24???B-A00[3-9]*",
60
+ "LT-24???B-A0[1-9]*",
61
+ "LT-24???B-A[1-9]*",
59
62
  "LT-51*",
60
63
  )
61
64
  ]
@@ -28,9 +28,9 @@ class BMS(BaseBMS):
28
28
  BMSdp("cycles", 15, 2, False, lambda x: x),
29
29
  )
30
30
 
31
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
31
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
32
32
  """Initialize BMS."""
33
- super().__init__(ble_device, reconnect)
33
+ super().__init__(ble_device, keep_alive)
34
34
 
35
35
  @staticmethod
36
36
  def matcher_dict_list() -> list[MatcherPattern]:
@@ -24,9 +24,9 @@ class BMS(RenogyBMS):
24
24
  BMSdp("cycles", 15, 2, False, lambda x: x),
25
25
  )
26
26
 
27
- def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
27
+ def __init__(self, ble_device: BLEDevice, keep_alive: bool = True) -> None:
28
28
  """Intialize private BMS members."""
29
- super().__init__(ble_device, reconnect)
29
+ super().__init__(ble_device, keep_alive)
30
30
  self._char_write_handle: int = -1
31
31
 
32
32
  @staticmethod