aiobmsble 0.2.0__py3-none-any.whl → 0.2.2__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 (37) hide show
  1. aiobmsble/__init__.py +5 -1
  2. aiobmsble/__main__.py +5 -1
  3. aiobmsble/basebms.py +10 -1
  4. aiobmsble/bms/__init__.py +5 -0
  5. aiobmsble/bms/abc_bms.py +168 -0
  6. aiobmsble/bms/ant_bms.py +200 -0
  7. aiobmsble/bms/braunpwr_bms.py +171 -0
  8. aiobmsble/bms/cbtpwr_bms.py +172 -0
  9. aiobmsble/bms/cbtpwr_vb_bms.py +188 -0
  10. aiobmsble/bms/daly_bms.py +168 -0
  11. aiobmsble/bms/dpwrcore_bms.py +211 -0
  12. aiobmsble/bms/dummy_bms.py +93 -0
  13. aiobmsble/bms/ecoworthy_bms.py +155 -0
  14. aiobmsble/bms/ective_bms.py +181 -0
  15. aiobmsble/bms/ej_bms.py +237 -0
  16. aiobmsble/bms/felicity_bms.py +143 -0
  17. aiobmsble/bms/jbd_bms.py +207 -0
  18. aiobmsble/bms/jikong_bms.py +305 -0
  19. aiobmsble/bms/neey_bms.py +218 -0
  20. aiobmsble/bms/ogt_bms.py +218 -0
  21. aiobmsble/bms/pro_bms.py +148 -0
  22. aiobmsble/bms/redodo_bms.py +131 -0
  23. aiobmsble/bms/renogy_bms.py +152 -0
  24. aiobmsble/bms/renogy_pro_bms.py +109 -0
  25. aiobmsble/bms/roypow_bms.py +190 -0
  26. aiobmsble/bms/seplos_bms.py +249 -0
  27. aiobmsble/bms/seplos_v2_bms.py +209 -0
  28. aiobmsble/bms/tdt_bms.py +203 -0
  29. aiobmsble/bms/tianpwr_bms.py +142 -0
  30. aiobmsble/utils.py +16 -6
  31. {aiobmsble-0.2.0.dist-info → aiobmsble-0.2.2.dist-info}/METADATA +3 -2
  32. aiobmsble-0.2.2.dist-info/RECORD +36 -0
  33. aiobmsble-0.2.0.dist-info/RECORD +0 -10
  34. {aiobmsble-0.2.0.dist-info → aiobmsble-0.2.2.dist-info}/WHEEL +0 -0
  35. {aiobmsble-0.2.0.dist-info → aiobmsble-0.2.2.dist-info}/entry_points.txt +0 -0
  36. {aiobmsble-0.2.0.dist-info → aiobmsble-0.2.2.dist-info}/licenses/LICENSE +0 -0
  37. {aiobmsble-0.2.0.dist-info → aiobmsble-0.2.2.dist-info}/top_level.txt +0 -0
@@ -0,0 +1,172 @@
1
+ """Module to support CBT Power Smart BMS.
2
+
3
+ Project: aiobmsble, https://pypi.org/p/aiobmsble/
4
+ License: Apache-2.0, http://www.apache.org/licenses/
5
+ """
6
+
7
+ from typing import Final
8
+
9
+ from bleak.backends.characteristic import BleakGATTCharacteristic
10
+ from bleak.backends.device import BLEDevice
11
+ from bleak.uuids import normalize_uuid_str
12
+
13
+ from aiobmsble import BMSdp, BMSsample, BMSvalue, MatcherPattern
14
+ from aiobmsble.basebms import BaseBMS, crc_sum
15
+
16
+
17
+ class BMS(BaseBMS):
18
+ """CBT Power Smart BMS class implementation."""
19
+
20
+ HEAD: Final[bytes] = bytes([0xAA, 0x55])
21
+ TAIL_RX: Final[bytes] = bytes([0x0D, 0x0A])
22
+ TAIL_TX: Final[bytes] = bytes([0x0A, 0x0D])
23
+ MIN_FRAME: Final[int] = len(HEAD) + len(TAIL_RX) + 3 # CMD, LEN, CRC, 1 Byte each
24
+ CRC_POS: Final[int] = -len(TAIL_RX) - 1
25
+ LEN_POS: Final[int] = 3
26
+ CMD_POS: Final[int] = 2
27
+ CELL_VOLTAGE_CMDS: Final[list[int]] = [0x5, 0x6, 0x7, 0x8]
28
+ _FIELDS: Final[tuple[BMSdp, ...]] = (
29
+ BMSdp("voltage", 4, 4, False, lambda x: x / 1000, 0x0B),
30
+ BMSdp("current", 8, 4, True, lambda x: x / 1000, 0x0B),
31
+ BMSdp("temperature", 4, 2, True, lambda x: x, 0x09),
32
+ BMSdp("battery_level", 4, 1, False, lambda x: x, 0x0A),
33
+ BMSdp("design_capacity", 4, 2, False, lambda x: x, 0x15),
34
+ BMSdp("cycles", 6, 2, False, lambda x: x, 0x15),
35
+ BMSdp("runtime", 14, 2, False, lambda x: x * BMS._HRS_TO_SECS / 100, 0x0C),
36
+ BMSdp("problem_code", 4, 4, False, lambda x: x, 0x21),
37
+ )
38
+ _CMDS: Final[list[int]] = list({field.idx for field in _FIELDS})
39
+
40
+ def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
41
+ """Intialize private BMS members."""
42
+ super().__init__(ble_device, reconnect)
43
+
44
+ @staticmethod
45
+ def matcher_dict_list() -> list[MatcherPattern]:
46
+ """Provide BluetoothMatcher definition."""
47
+ return [
48
+ {"service_uuid": BMS.uuid_services()[0], "connectable": True},
49
+ { # Creabest
50
+ "service_uuid": normalize_uuid_str("fff0"),
51
+ "manufacturer_id": 0,
52
+ "connectable": True,
53
+ },
54
+ {
55
+ "service_uuid": normalize_uuid_str("03c1"),
56
+ "manufacturer_id": 0x5352,
57
+ "connectable": True,
58
+ },
59
+ ]
60
+
61
+ @staticmethod
62
+ def device_info() -> dict[str, str]:
63
+ """Return device information for the battery management system."""
64
+ return {"manufacturer": "CBT Power", "model": "Smart BMS"}
65
+
66
+ @staticmethod
67
+ def uuid_services() -> list[str]:
68
+ """Return list of services required by BMS."""
69
+ return [normalize_uuid_str("ffe5"), normalize_uuid_str("ffe0")]
70
+
71
+ @staticmethod
72
+ def uuid_rx() -> str:
73
+ """Return characteristic that provides notification/read property."""
74
+ return "ffe4"
75
+
76
+ @staticmethod
77
+ def uuid_tx() -> str:
78
+ """Return characteristic that provides write property."""
79
+ return "ffe9"
80
+
81
+ @staticmethod
82
+ def _calc_values() -> frozenset[BMSvalue]:
83
+ return frozenset(
84
+ {
85
+ "power",
86
+ "battery_charging",
87
+ "delta_voltage",
88
+ "cycle_capacity",
89
+ "temperature",
90
+ }
91
+ )
92
+
93
+ def _notification_handler(
94
+ self, _sender: BleakGATTCharacteristic, data: bytearray
95
+ ) -> None:
96
+ """Retrieve BMS data update."""
97
+ self._log.debug("RX BLE data: %s", data)
98
+
99
+ # verify that data is long enough
100
+ if len(data) < BMS.MIN_FRAME or len(data) != BMS.MIN_FRAME + data[BMS.LEN_POS]:
101
+ self._log.debug("incorrect frame length (%i): %s", len(data), data)
102
+ return
103
+
104
+ if not data.startswith(BMS.HEAD) or not data.endswith(BMS.TAIL_RX):
105
+ self._log.debug("incorrect frame start/end: %s", data)
106
+ return
107
+
108
+ if (crc := crc_sum(data[len(BMS.HEAD) : len(data) + BMS.CRC_POS])) != data[
109
+ BMS.CRC_POS
110
+ ]:
111
+ self._log.debug(
112
+ "invalid checksum 0x%X != 0x%X",
113
+ data[len(data) + BMS.CRC_POS],
114
+ crc,
115
+ )
116
+ return
117
+
118
+ self._data = data
119
+ self._data_event.set()
120
+
121
+ @staticmethod
122
+ def _cmd(cmd: bytes, value: list[int] | None = None) -> bytes:
123
+ """Assemble a CBT Power BMS command."""
124
+ value = [] if value is None else value
125
+ assert len(value) <= 255
126
+ frame = bytearray([*BMS.HEAD, cmd[0], len(value), *value])
127
+ frame.append(crc_sum(frame[len(BMS.HEAD) :]))
128
+ frame.extend(BMS.TAIL_TX)
129
+ return bytes(frame)
130
+
131
+ async def _async_update(self) -> BMSsample:
132
+ """Update battery status information."""
133
+ resp_cache: dict[int, bytearray] = {} # avoid multiple queries
134
+ for cmd in BMS._CMDS:
135
+ self._log.debug("request command 0x%X.", cmd)
136
+ try:
137
+ await self._await_reply(BMS._cmd(cmd.to_bytes(1)))
138
+ except TimeoutError:
139
+ continue
140
+ if cmd != self._data[BMS.CMD_POS]:
141
+ self._log.debug(
142
+ "incorrect response 0x%X to command 0x%X",
143
+ self._data[BMS.CMD_POS],
144
+ cmd,
145
+ )
146
+ resp_cache[self._data[BMS.CMD_POS]] = self._data.copy()
147
+
148
+ voltages: list[float] = []
149
+ for cmd in BMS.CELL_VOLTAGE_CMDS:
150
+ try:
151
+ await self._await_reply(BMS._cmd(cmd.to_bytes(1)))
152
+ except TimeoutError:
153
+ break
154
+ cells: list[float] = BMS._cell_voltages(
155
+ self._data, cells=5, start=4, byteorder="little"
156
+ )
157
+ voltages.extend(cells)
158
+ if len(voltages) % 5 or len(cells) == 0:
159
+ break
160
+
161
+ data: BMSsample = BMS._decode_data(BMS._FIELDS, resp_cache, byteorder="little")
162
+
163
+ # get cycle charge from design capacity and SoC
164
+ if data.get("design_capacity") and data.get("battery_level"):
165
+ data["cycle_charge"] = (
166
+ data.get("design_capacity", 0) * data.get("battery_level", 0) / 100
167
+ )
168
+ # remove runtime if not discharging
169
+ if data.get("current", 0) >= 0:
170
+ data.pop("runtime", None)
171
+
172
+ return data | {"cell_voltages": voltages}
@@ -0,0 +1,188 @@
1
+ """Module to support CBT Power VB series BMS.
2
+
3
+ Project: aiobmsble, https://pypi.org/p/aiobmsble/
4
+ License: Apache-2.0, http://www.apache.org/licenses/
5
+ """
6
+
7
+ from string import hexdigits
8
+ from typing import Final
9
+
10
+ from bleak.backends.characteristic import BleakGATTCharacteristic
11
+ from bleak.backends.device import BLEDevice
12
+ from bleak.uuids import normalize_uuid_str
13
+
14
+ from aiobmsble import BMSdp, BMSsample, BMSvalue, MatcherPattern
15
+ from aiobmsble.basebms import BaseBMS, lrc_modbus
16
+
17
+
18
+ class BMS(BaseBMS):
19
+ """CBT Power VB series battery class implementation."""
20
+
21
+ _HEAD: Final[bytes] = b"\x7e"
22
+ _TAIL: Final[bytes] = b"\x0d"
23
+ _CMD_VER: Final[int] = 0x11 # TX protocol version
24
+ _RSP_VER: Final[int] = 0x22 # RX protocol version
25
+ _LEN_POS: Final[int] = 9
26
+ _MIN_LEN: Final[int] = _LEN_POS + 3 + len(_HEAD) + len(_TAIL) + 4
27
+ _MAX_LEN: Final[int] = 255
28
+ _CELL_POS: Final[int] = 6
29
+
30
+ _FIELDS: Final[tuple[BMSdp, ...]] = (
31
+ BMSdp("voltage", 2, 2, False, lambda x: x / 10),
32
+ BMSdp("current", 0, 2, True, lambda x: x / 10),
33
+ BMSdp("battery_level", 4, 2, False, lambda x: min(x, 100)),
34
+ BMSdp("cycles", 7, 2, False),
35
+ BMSdp("problem_code", 15, 6, False, lambda x: x & 0xFFF000FF000F),
36
+ )
37
+
38
+ def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
39
+ """Initialize BMS."""
40
+ super().__init__(ble_device, reconnect)
41
+ self._exp_len: int = 0
42
+
43
+ @staticmethod
44
+ def matcher_dict_list() -> list[MatcherPattern]:
45
+ """Provide BluetoothMatcher definition."""
46
+ return [
47
+ { # Creabest
48
+ "service_uuid": normalize_uuid_str("fff0"),
49
+ "manufacturer_id": 16963,
50
+ "connectable": True,
51
+ },
52
+ ]
53
+
54
+ @staticmethod
55
+ def device_info() -> dict[str, str]:
56
+ """Return device information for the battery management system."""
57
+ return {"manufacturer": "Creabest", "model": "VB series"}
58
+
59
+ @staticmethod
60
+ def uuid_services() -> list[str]:
61
+ """Return list of 128-bit UUIDs of services required by BMS."""
62
+ return [
63
+ normalize_uuid_str("ffe0"),
64
+ normalize_uuid_str("ffe5"),
65
+ ]
66
+
67
+ @staticmethod
68
+ def uuid_rx() -> str:
69
+ """Return 16-bit UUID of characteristic that provides notification/read property."""
70
+ return "ffe4"
71
+
72
+ @staticmethod
73
+ def uuid_tx() -> str:
74
+ """Return 16-bit UUID of characteristic that provides write property."""
75
+ return "ffe9"
76
+
77
+ @staticmethod
78
+ def _calc_values() -> frozenset[BMSvalue]:
79
+ return frozenset(
80
+ {
81
+ "battery_charging",
82
+ "delta_voltage",
83
+ "temperature",
84
+ "power",
85
+ "runtime",
86
+ "cycle_capacity",
87
+ "cycle_charge",
88
+ }
89
+ ) # calculate further values from BMS provided set ones
90
+
91
+ def _notification_handler(
92
+ self, _sender: BleakGATTCharacteristic, data: bytearray
93
+ ) -> None:
94
+ """Handle the RX characteristics notify event (new data arrives)."""
95
+
96
+ if len(data) > BMS._LEN_POS + 4 and data.startswith(BMS._HEAD):
97
+ self._data = bytearray()
98
+ try:
99
+ length: Final[int] = int(data[BMS._LEN_POS : BMS._LEN_POS + 4], 16)
100
+ self._exp_len = length & 0xFFF
101
+ if BMS.lencs(length) != length >> 12:
102
+ self._exp_len = 0
103
+ self._log.debug("incorrect length checksum.")
104
+ except ValueError:
105
+ self._exp_len = 0
106
+
107
+ self._data += data
108
+ self._log.debug(
109
+ "RX BLE data (%s): %s", "start" if data == self._data else "cnt.", data
110
+ )
111
+
112
+ if len(self._data) < self._exp_len + BMS._MIN_LEN:
113
+ return
114
+
115
+ if not self._data.endswith(BMS._TAIL):
116
+ self._log.debug("incorrect EOF: %s", data)
117
+ self._data.clear()
118
+ return
119
+
120
+ if not all(chr(c) in hexdigits for c in self._data[1:-1]):
121
+ self._log.debug("incorrect frame encoding.")
122
+ self._data.clear()
123
+ return
124
+
125
+ if (ver := bytes.fromhex(self._data[1:3].decode())) != BMS._RSP_VER.to_bytes():
126
+ self._log.debug("unknown response frame version: 0x%X", int.from_bytes(ver))
127
+ self._data.clear()
128
+ return
129
+
130
+ if (crc := lrc_modbus(self._data[1:-5])) != int(self._data[-5:-1], 16):
131
+ self._log.debug(
132
+ "invalid checksum 0x%X != 0x%X", crc, int(self._data[-5:-1], 16)
133
+ )
134
+ self._data.clear()
135
+ return
136
+
137
+ self._data = bytearray(
138
+ bytes.fromhex(self._data.strip(BMS._HEAD + BMS._TAIL).decode())
139
+ )
140
+ self._data_event.set()
141
+
142
+ @staticmethod
143
+ def lencs(length: int) -> int:
144
+ """Calculate the length checksum."""
145
+ return (sum((length >> (i * 4)) & 0xF for i in range(3)) ^ 0xF) + 1 & 0xF
146
+
147
+ @staticmethod
148
+ def _cmd(cmd: int, dev_id: int = 1, data: bytes = b"") -> bytes:
149
+ """Assemble a Seplos VB series command."""
150
+ assert len(data) <= 0xFFF
151
+ cdat: Final[bytes] = data + int.to_bytes(dev_id)
152
+ frame = bytearray([BMS._CMD_VER, dev_id, 0x46, cmd])
153
+ frame.extend(
154
+ int.to_bytes(len(cdat) * 2 + (BMS.lencs(len(cdat) * 2) << 12), 2, "big")
155
+ )
156
+ frame.extend(cdat)
157
+ frame.extend(
158
+ int.to_bytes(lrc_modbus(bytearray(frame.hex().upper().encode())), 2, "big")
159
+ )
160
+ return BMS._HEAD + frame.hex().upper().encode() + BMS._TAIL
161
+
162
+ async def _async_update(self) -> BMSsample:
163
+ """Update battery status information."""
164
+
165
+ await self._await_reply(BMS._cmd(0x42))
166
+ result: BMSsample = {"cell_count": self._data[BMS._CELL_POS]}
167
+ temp_pos: Final[int] = BMS._CELL_POS + result.get("cell_count", 0) * 2 + 1
168
+ result["temp_sensors"] = self._data[temp_pos]
169
+ result["cell_voltages"] = BMS._cell_voltages(
170
+ self._data, cells=result.get("cell_count", 0), start=BMS._CELL_POS + 1
171
+ )
172
+ result["temp_values"] = BMS._temp_values(
173
+ self._data,
174
+ values=result.get("temp_sensors", 0),
175
+ start=temp_pos + 1,
176
+ divider=10,
177
+ )
178
+
179
+ result |= BMS._decode_data(
180
+ BMS._FIELDS, self._data, offset=temp_pos + 2 * result["temp_sensors"] + 1
181
+ )
182
+
183
+ await self._await_reply(BMS._cmd(0x81, 1, b"\x01\x00"), max_size=20)
184
+ result["design_capacity"] = (
185
+ int.from_bytes(self._data[6:8], byteorder="big", signed=False) // 10
186
+ )
187
+
188
+ return result
@@ -0,0 +1,168 @@
1
+ """Module to support Daly Smart BMS.
2
+
3
+ Project: aiobmsble, https://pypi.org/p/aiobmsble/
4
+ License: Apache-2.0, http://www.apache.org/licenses/
5
+ """
6
+
7
+ from typing import Final
8
+
9
+ from bleak.backends.characteristic import BleakGATTCharacteristic
10
+ from bleak.backends.device import BLEDevice
11
+ from bleak.uuids import normalize_uuid_str
12
+
13
+ from aiobmsble import BMSdp, BMSsample, BMSvalue, MatcherPattern
14
+ from aiobmsble.basebms import BaseBMS, crc_modbus
15
+
16
+
17
+ class BMS(BaseBMS):
18
+ """Daly Smart BMS class implementation."""
19
+
20
+ HEAD_READ: Final[bytes] = b"\xd2\x03"
21
+ CMD_INFO: Final[bytes] = b"\x00\x00\x00\x3e\xd7\xb9"
22
+ MOS_INFO: Final[bytes] = b"\x00\x3e\x00\x09\xf7\xa3"
23
+ HEAD_LEN: Final[int] = 3
24
+ CRC_LEN: Final[int] = 2
25
+ MAX_CELLS: Final[int] = 32
26
+ MAX_TEMP: Final[int] = 8
27
+ INFO_LEN: Final[int] = 84 + HEAD_LEN + CRC_LEN + MAX_CELLS + MAX_TEMP
28
+ MOS_TEMP_POS: Final[int] = HEAD_LEN + 8
29
+ MOS_NOT_AVAILABLE: Final[tuple[str]] = ("DL-FB4C2E0",)
30
+ _FIELDS: Final[tuple[BMSdp, ...]] = (
31
+ BMSdp("voltage", 80, 2, False, lambda x: x / 10),
32
+ BMSdp("current", 82, 2, False, lambda x: (x - 30000) / 10),
33
+ BMSdp("battery_level", 84, 2, False, lambda x: x / 10),
34
+ BMSdp("cycle_charge", 96, 2, False, lambda x: x / 10),
35
+ BMSdp("cell_count", 98, 2, False, lambda x: min(x, BMS.MAX_CELLS)),
36
+ BMSdp("temp_sensors", 100, 2, False, lambda x: min(x, BMS.MAX_TEMP)),
37
+ BMSdp("cycles", 102, 2, False, lambda x: x),
38
+ BMSdp("delta_voltage", 112, 2, False, lambda x: x / 1000),
39
+ BMSdp("problem_code", 116, 8, False, lambda x: x % 2**64),
40
+ )
41
+
42
+ def __init__(self, ble_device: BLEDevice, reconnect: bool = False) -> None:
43
+ """Intialize private BMS members."""
44
+ super().__init__(ble_device, reconnect)
45
+
46
+ @staticmethod
47
+ def matcher_dict_list() -> list[MatcherPattern]:
48
+ """Provide BluetoothMatcher definition."""
49
+ return [
50
+ MatcherPattern(
51
+ local_name="DL-*",
52
+ service_uuid=BMS.uuid_services()[0],
53
+ connectable=True,
54
+ )
55
+ ] + [
56
+ MatcherPattern(
57
+ manufacturer_id=m_id,
58
+ connectable=True,
59
+ )
60
+ for m_id in (0x102, 0x104, 0x0302, 0x0303)
61
+ ]
62
+
63
+ @staticmethod
64
+ def device_info() -> dict[str, str]:
65
+ """Return device information for the battery management system."""
66
+ return {"manufacturer": "Daly", "model": "Smart BMS"}
67
+
68
+ @staticmethod
69
+ def uuid_services() -> list[str]:
70
+ """Return list of 128-bit UUIDs of services required by BMS."""
71
+ return [normalize_uuid_str("fff0")]
72
+
73
+ @staticmethod
74
+ def uuid_rx() -> str:
75
+ """Return 16-bit UUID of characteristic that provides notification/read property."""
76
+ return "fff1"
77
+
78
+ @staticmethod
79
+ def uuid_tx() -> str:
80
+ """Return 16-bit UUID of characteristic that provides write property."""
81
+ return "fff2"
82
+
83
+ @staticmethod
84
+ def _calc_values() -> frozenset[BMSvalue]:
85
+ return frozenset(
86
+ {
87
+ "cycle_capacity",
88
+ "power",
89
+ "battery_charging",
90
+ "runtime",
91
+ "temperature",
92
+ }
93
+ )
94
+
95
+ def _notification_handler(
96
+ self, _sender: BleakGATTCharacteristic, data: bytearray
97
+ ) -> None:
98
+ self._log.debug("RX BLE data: %s", data)
99
+
100
+ if (
101
+ len(data) < BMS.HEAD_LEN
102
+ or data[0:2] != BMS.HEAD_READ
103
+ or data[2] + 1 != len(data) - len(BMS.HEAD_READ) - BMS.CRC_LEN
104
+ ):
105
+ self._log.debug("response data is invalid")
106
+ return
107
+
108
+ if (crc := crc_modbus(data[:-2])) != int.from_bytes(
109
+ data[-2:], byteorder="little"
110
+ ):
111
+ self._log.debug(
112
+ "invalid checksum 0x%X != 0x%X",
113
+ int.from_bytes(data[-2:], byteorder="little"),
114
+ crc,
115
+ )
116
+ self._data.clear()
117
+ return
118
+
119
+ self._data = data
120
+ self._data_event.set()
121
+
122
+ async def _async_update(self) -> BMSsample:
123
+ """Update battery status information."""
124
+ result: BMSsample = {}
125
+ if ( # do not query devices that do not support MOS temperature, e.g. Bulltron
126
+ not self.name or not self.name.startswith(BMS.MOS_NOT_AVAILABLE)
127
+ ):
128
+ try:
129
+ # request MOS temperature (possible outcome: response, empty response, no response)
130
+ await self._await_reply(BMS.HEAD_READ + BMS.MOS_INFO)
131
+
132
+ if sum(self._data[BMS.MOS_TEMP_POS :][:2]):
133
+ self._log.debug("MOS info: %s", self._data)
134
+ result["temp_values"] = [
135
+ int.from_bytes(
136
+ self._data[BMS.MOS_TEMP_POS :][:2],
137
+ byteorder="big",
138
+ signed=True,
139
+ )
140
+ - 40
141
+ ]
142
+ except TimeoutError:
143
+ self._log.debug("no MOS temperature available.")
144
+
145
+ await self._await_reply(BMS.HEAD_READ + BMS.CMD_INFO)
146
+
147
+ if len(self._data) != BMS.INFO_LEN:
148
+ self._log.debug("incorrect frame length: %i", len(self._data))
149
+ return {}
150
+
151
+ result |= BMS._decode_data(BMS._FIELDS, self._data, offset=BMS.HEAD_LEN)
152
+
153
+ # add temperature sensors
154
+ result.setdefault("temp_values", []).extend(
155
+ BMS._temp_values(
156
+ self._data,
157
+ values=result.get("temp_sensors", 0),
158
+ start=64 + BMS.HEAD_LEN,
159
+ offset=40,
160
+ )
161
+ )
162
+
163
+ # get cell voltages
164
+ result["cell_voltages"] = BMS._cell_voltages(
165
+ self._data, cells=result.get("cell_count", 0), start=BMS.HEAD_LEN
166
+ )
167
+
168
+ return result