sok-ble 0.1.0__tar.gz → 0.1.2__tar.gz

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 (24) hide show
  1. {sok_ble-0.1.0 → sok_ble-0.1.2}/PKG-INFO +6 -2
  2. {sok_ble-0.1.0 → sok_ble-0.1.2}/README.md +4 -0
  3. {sok_ble-0.1.0 → sok_ble-0.1.2}/pyproject.toml +2 -2
  4. {sok_ble-0.1.0 → sok_ble-0.1.2/src}/sok_ble/sok_bluetooth_device.py +37 -12
  5. {sok_ble-0.1.0 → sok_ble-0.1.2/src}/sok_ble/sok_parser.py +25 -33
  6. {sok_ble-0.1.0 → sok_ble-0.1.2/src}/sok_ble.egg-info/PKG-INFO +6 -2
  7. sok_ble-0.1.2/src/sok_ble.egg-info/SOURCES.txt +20 -0
  8. sok_ble-0.1.2/src/sok_ble.egg-info/requires.txt +2 -0
  9. {sok_ble-0.1.0 → sok_ble-0.1.2}/tests/test_device_full.py +15 -17
  10. {sok_ble-0.1.0 → sok_ble-0.1.2}/tests/test_device_minimal.py +8 -6
  11. sok_ble-0.1.2/tests/test_integration_mock.py +68 -0
  12. {sok_ble-0.1.0 → sok_ble-0.1.2}/tests/test_parser_full.py +8 -7
  13. {sok_ble-0.1.0 → sok_ble-0.1.2}/tests/test_parser_info.py +2 -2
  14. sok_ble-0.1.0/sok_ble.egg-info/SOURCES.txt +0 -19
  15. sok_ble-0.1.0/sok_ble.egg-info/requires.txt +0 -2
  16. {sok_ble-0.1.0 → sok_ble-0.1.2}/setup.cfg +0 -0
  17. {sok_ble-0.1.0 → sok_ble-0.1.2/src}/sok_ble/__init__.py +0 -0
  18. {sok_ble-0.1.0 → sok_ble-0.1.2/src}/sok_ble/const.py +0 -0
  19. {sok_ble-0.1.0 → sok_ble-0.1.2/src}/sok_ble/exceptions.py +0 -0
  20. {sok_ble-0.1.0 → sok_ble-0.1.2/src}/sok_ble.egg-info/dependency_links.txt +0 -0
  21. {sok_ble-0.1.0 → sok_ble-0.1.2/src}/sok_ble.egg-info/top_level.txt +0 -0
  22. {sok_ble-0.1.0 → sok_ble-0.1.2}/tests/test_const.py +0 -0
  23. {sok_ble-0.1.0 → sok_ble-0.1.2}/tests/test_derived.py +0 -0
  24. {sok_ble-0.1.0 → sok_ble-0.1.2}/tests/test_exceptions.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sok-ble
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: SOK BLE battery interface library
5
5
  Author-email: Mitchell Carlson <mitchell.carlson.pro@gmail.com>
6
6
  License: Apache-2.0
@@ -13,7 +13,7 @@ Classifier: Operating System :: OS Independent
13
13
  Requires-Python: >=3.11
14
14
  Description-Content-Type: text/markdown
15
15
  Requires-Dist: bleak>=0.22.3
16
- Requires-Dist: bleak-retry-connector>=3.10.0
16
+ Requires-Dist: bleak-retry-connector>=3.9.0
17
17
 
18
18
  # SOK BLE
19
19
 
@@ -38,3 +38,7 @@ async def main() -> None:
38
38
 
39
39
  asyncio.run(main())
40
40
  ```
41
+
42
+ ## References
43
+ [@zuccaro's comment](https://github.com/Louisvdw/dbus-serialbattery/issues/350#issuecomment-1500658941)
44
+ [Bluetooth-Devices/inkbird-ble](https://github.com/Bluetooth-Devices/inkbird-ble)
@@ -21,3 +21,7 @@ async def main() -> None:
21
21
 
22
22
  asyncio.run(main())
23
23
  ```
24
+
25
+ ## References
26
+ [@zuccaro's comment](https://github.com/Louisvdw/dbus-serialbattery/issues/350#issuecomment-1500658941)
27
+ [Bluetooth-Devices/inkbird-ble](https://github.com/Bluetooth-Devices/inkbird-ble)
@@ -1,12 +1,12 @@
1
1
  [project]
2
2
  name = "sok-ble"
3
- version = "0.1.0"
3
+ version = "0.1.2"
4
4
  description = "SOK BLE battery interface library"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.11"
7
7
  dependencies = [
8
8
  "bleak>=0.22.3",
9
- "bleak-retry-connector>=3.10.0",
9
+ "bleak-retry-connector>=3.9.0",
10
10
  ]
11
11
  authors = [
12
12
  {name = "Mitchell Carlson", email = "mitchell.carlson.pro@gmail.com"}
@@ -4,6 +4,8 @@ from __future__ import annotations
4
4
 
5
5
  from contextlib import asynccontextmanager
6
6
  from typing import AsyncIterator, Optional
7
+ import asyncio
8
+ import struct
7
9
  import logging
8
10
  import statistics
9
11
 
@@ -65,32 +67,55 @@ class SokBluetoothDevice:
65
67
  await client.disconnect()
66
68
  logger.debug("Disconnected from %s", self._ble_device.address)
67
69
 
70
+ async def _send_command(
71
+ self, client: BleakClientWithServiceCache, cmd: int, expected: int
72
+ ) -> bytes:
73
+ """Send a command and return the response bytes with the given header."""
74
+
75
+ # If the client supports notifications (real BLE client), prefer that
76
+ start_notify = getattr(client, "start_notify", None)
77
+ if start_notify is None:
78
+ await client.write_gatt_char(UUID_TX, _sok_command(cmd))
79
+ data = bytes(await client.read_gatt_char(UUID_RX))
80
+ return data
81
+
82
+ queue: asyncio.Queue[bytes] = asyncio.Queue()
83
+
84
+ def handler(_: int, data: bytearray) -> None:
85
+ queue.put_nowait(bytes(data))
86
+
87
+ await client.start_notify(UUID_RX, handler)
88
+ try:
89
+ await client.write_gatt_char(UUID_TX, _sok_command(cmd))
90
+ while True:
91
+ data = await asyncio.wait_for(queue.get(), 5.0)
92
+ if struct.unpack_from(">H", data)[0] == expected:
93
+ return data
94
+ finally:
95
+ await client.stop_notify(UUID_RX)
96
+
68
97
  async def async_update(self) -> None:
69
98
  """Poll the device for all telemetry and update attributes."""
70
99
  responses: dict[int, bytes] = {}
71
100
  async with self._connect() as client:
72
101
  logger.debug("Send C1")
73
- await client.write_gatt_char(UUID_TX, _sok_command(0xC1))
74
- data = bytes(await client.read_gatt_char(UUID_RX))
75
- logger.debug("Recv 0xCCF0: %s", data.hex())
102
+ data = await self._send_command(client, 0xC1, 0xCCF0)
103
+ logger.debug("Recv 0x%04X: %s", struct.unpack_from(">H", data)[0], data.hex())
76
104
  responses[0xCCF0] = data
77
105
 
78
106
  logger.debug("Send C1")
79
- await client.write_gatt_char(UUID_TX, _sok_command(0xC1))
80
- data = bytes(await client.read_gatt_char(UUID_RX))
81
- logger.debug("Recv 0xCCF2: %s", data.hex())
107
+ data = await self._send_command(client, 0xC1, 0xCCF2)
108
+ logger.debug("Recv 0x%04X: %s", struct.unpack_from(">H", data)[0], data.hex())
82
109
  responses[0xCCF2] = data
83
110
 
84
111
  logger.debug("Send C2")
85
- await client.write_gatt_char(UUID_TX, _sok_command(0xC2))
86
- data = bytes(await client.read_gatt_char(UUID_RX))
87
- logger.debug("Recv 0xCCF3: %s", data.hex())
112
+ data = await self._send_command(client, 0xC2, 0xCCF3)
113
+ logger.debug("Recv 0x%04X: %s", struct.unpack_from(">H", data)[0], data.hex())
88
114
  responses[0xCCF3] = data
89
115
 
90
116
  logger.debug("Send C2")
91
- await client.write_gatt_char(UUID_TX, _sok_command(0xC2))
92
- data = bytes(await client.read_gatt_char(UUID_RX))
93
- logger.debug("Recv 0xCCF4: %s", data.hex())
117
+ data = await self._send_command(client, 0xC2, 0xCCF4)
118
+ logger.debug("Recv 0x%04X: %s", struct.unpack_from(">H", data)[0], data.hex())
94
119
  responses[0xCCF4] = data
95
120
 
96
121
  parsed = SokParser.parse_all(responses)
@@ -4,6 +4,7 @@ from __future__ import annotations
4
4
 
5
5
  import logging
6
6
  import struct
7
+ import statistics
7
8
  from typing import Dict, Sequence
8
9
 
9
10
  from sok_ble.exceptions import InvalidResponseError
@@ -43,27 +44,19 @@ class SokParser:
43
44
 
44
45
  @staticmethod
45
46
  def parse_info(buf: bytes) -> Dict[str, float | int]:
46
- """Parse the information frame for voltage, current and SOC."""
47
+ """Parse the information frame for current, SOC and cycles."""
47
48
  logger.debug("parse_info input: %s", buf.hex())
48
- if len(buf) < 18:
49
+ if len(buf) < 20:
49
50
  raise InvalidResponseError("Info buffer too short")
50
51
 
51
- cells = [
52
- get_le_ushort(buf, 0),
53
- get_le_ushort(buf, 2),
54
- get_le_ushort(buf, 4),
55
- get_le_ushort(buf, 6),
56
- ]
57
- voltage = (sum(cells) / len(cells) * 4) / 1000
58
-
59
- current = get_le_int3(buf, 8) / 10
60
-
61
- soc = struct.unpack_from('<H', buf, 16)[0]
52
+ current = get_le_int3(buf, 5) / 1000
53
+ num_cycles = get_le_ushort(buf, 14)
54
+ soc = get_le_ushort(buf, 16)
62
55
 
63
56
  result = {
64
- "voltage": voltage,
65
57
  "current": current,
66
58
  "soc": soc,
59
+ "num_cycles": num_cycles,
67
60
  }
68
61
  logger.debug("parse_info result: %s", result)
69
62
  return result
@@ -72,27 +65,23 @@ class SokParser:
72
65
  def parse_temps(buf: bytes) -> float:
73
66
  """Parse the temperature from the temperature frame."""
74
67
  logger.debug("parse_temps input: %s", buf.hex())
75
- if len(buf) < 7:
68
+ if len(buf) < 20:
76
69
  raise InvalidResponseError("Temp buffer too short")
77
70
 
78
- temperature = get_le_short(buf, 5) / 10
71
+ temperature = get_le_short(buf, 5)
79
72
  logger.debug("parse_temps result: %s", temperature)
80
73
  return temperature
81
74
 
82
75
  @staticmethod
83
76
  def parse_capacity_cycles(buf: bytes) -> Dict[str, float | int]:
84
- """Parse rated capacity and cycle count."""
77
+ """Parse rated capacity."""
85
78
  logger.debug("parse_capacity_cycles input: %s", buf.hex())
86
- if len(buf) < 6:
79
+ if len(buf) < 20:
87
80
  raise InvalidResponseError("Capacity buffer too short")
88
81
 
89
- capacity = get_le_ushort(buf, 0) / 100
90
- num_cycles = get_le_ushort(buf, 4)
82
+ capacity = get_be_uint3(buf, 5) / 128
91
83
 
92
- result = {
93
- "capacity": capacity,
94
- "num_cycles": num_cycles,
95
- }
84
+ result = {"capacity": capacity}
96
85
  logger.debug("parse_capacity_cycles result: %s", result)
97
86
  return result
98
87
 
@@ -100,15 +89,13 @@ class SokParser:
100
89
  def parse_cells(buf: bytes) -> list[float]:
101
90
  """Parse individual cell voltages."""
102
91
  logger.debug("parse_cells input: %s", buf.hex())
103
- if len(buf) < 8:
92
+ if len(buf) < 20:
104
93
  raise InvalidResponseError("Cells buffer too short")
105
94
 
106
- cells = [
107
- get_le_ushort(buf, 0) / 1000,
108
- get_le_ushort(buf, 2) / 1000,
109
- get_le_ushort(buf, 4) / 1000,
110
- get_le_ushort(buf, 6) / 1000,
111
- ]
95
+ cells = [0.0, 0.0, 0.0, 0.0]
96
+ for x in range(4):
97
+ cell_idx = buf[2 + x * 4]
98
+ cells[cell_idx - 1] = get_le_ushort(buf, 3 + x * 4) / 1000
112
99
  logger.debug("parse_cells result: %s", cells)
113
100
  return cells
114
101
 
@@ -125,10 +112,15 @@ class SokParser:
125
112
  capacity_info = cls.parse_capacity_cycles(responses[0xCCF3])
126
113
  cells = cls.parse_cells(responses[0xCCF4])
127
114
 
115
+ voltage = statistics.mean(cells) * 4
116
+
128
117
  result = {
129
- **info,
118
+ "voltage": voltage,
119
+ "current": info["current"],
120
+ "soc": info["soc"],
130
121
  "temperature": temperature,
131
- **capacity_info,
122
+ "capacity": capacity_info["capacity"],
123
+ "num_cycles": info["num_cycles"],
132
124
  "cell_voltages": cells,
133
125
  }
134
126
  logger.debug("parse_all result: %s", result)
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: sok-ble
3
- Version: 0.1.0
3
+ Version: 0.1.2
4
4
  Summary: SOK BLE battery interface library
5
5
  Author-email: Mitchell Carlson <mitchell.carlson.pro@gmail.com>
6
6
  License: Apache-2.0
@@ -13,7 +13,7 @@ Classifier: Operating System :: OS Independent
13
13
  Requires-Python: >=3.11
14
14
  Description-Content-Type: text/markdown
15
15
  Requires-Dist: bleak>=0.22.3
16
- Requires-Dist: bleak-retry-connector>=3.10.0
16
+ Requires-Dist: bleak-retry-connector>=3.9.0
17
17
 
18
18
  # SOK BLE
19
19
 
@@ -38,3 +38,7 @@ async def main() -> None:
38
38
 
39
39
  asyncio.run(main())
40
40
  ```
41
+
42
+ ## References
43
+ [@zuccaro's comment](https://github.com/Louisvdw/dbus-serialbattery/issues/350#issuecomment-1500658941)
44
+ [Bluetooth-Devices/inkbird-ble](https://github.com/Bluetooth-Devices/inkbird-ble)
@@ -0,0 +1,20 @@
1
+ README.md
2
+ pyproject.toml
3
+ src/sok_ble/__init__.py
4
+ src/sok_ble/const.py
5
+ src/sok_ble/exceptions.py
6
+ src/sok_ble/sok_bluetooth_device.py
7
+ src/sok_ble/sok_parser.py
8
+ src/sok_ble.egg-info/PKG-INFO
9
+ src/sok_ble.egg-info/SOURCES.txt
10
+ src/sok_ble.egg-info/dependency_links.txt
11
+ src/sok_ble.egg-info/requires.txt
12
+ src/sok_ble.egg-info/top_level.txt
13
+ tests/test_const.py
14
+ tests/test_derived.py
15
+ tests/test_device_full.py
16
+ tests/test_device_minimal.py
17
+ tests/test_exceptions.py
18
+ tests/test_integration_mock.py
19
+ tests/test_parser_full.py
20
+ tests/test_parser_info.py
@@ -0,0 +1,2 @@
1
+ bleak>=0.22.3
2
+ bleak-retry-connector>=3.9.0
@@ -24,12 +24,10 @@ class DummyClient:
24
24
  @pytest.mark.asyncio
25
25
  async def test_full_update(monkeypatch):
26
26
  responses = [
27
- bytes.fromhex(
28
- "E4 0C E9 0C EE 0C F3 0C 64 00 00 00 00 00 00 00 41 00"
29
- ),
30
- bytes.fromhex("00 00 00 00 00 FA 00"),
31
- bytes.fromhex("10 27 00 00 32 00 00 00"),
32
- bytes.fromhex("E4 0C E9 0C EE 0C F3 0C"),
27
+ bytes.fromhex("ccf0000000102700000000000000320041000000"),
28
+ bytes.fromhex("ccf2000000140000000000000000000000000000"),
29
+ bytes.fromhex("ccf3000000003200000000000000000000000000"),
30
+ bytes.fromhex("ccf401c50c0002c60c0003bf0c0004c00c000000"),
33
31
  ]
34
32
 
35
33
  dummy = DummyClient(responses)
@@ -42,18 +40,18 @@ async def test_full_update(monkeypatch):
42
40
 
43
41
  await dev.async_update()
44
42
 
45
- assert dev.voltage == 13.23
43
+ assert dev.voltage == pytest.approx(13.066)
46
44
  assert dev.current == 10.0
47
45
  assert dev.soc == 65
48
- assert dev.temperature == 25.0
46
+ assert dev.temperature == 20.0
49
47
  assert dev.capacity == 100.0
50
48
  assert dev.num_cycles == 50
51
- assert dev.cell_voltages == [3.3, 3.305, 3.31, 3.315]
52
- assert dev.power == pytest.approx(132.3)
53
- assert dev.cell_voltage_max == 3.315
54
- assert dev.cell_voltage_min == 3.3
55
- assert dev.cell_voltage_avg == pytest.approx(3.3075)
56
- assert dev.cell_voltage_median == pytest.approx(3.3075)
57
- assert dev.cell_voltage_delta == pytest.approx(0.015)
58
- assert dev.cell_index_max == 3
59
- assert dev.cell_index_min == 0
49
+ assert dev.cell_voltages == [3.269, 3.27, 3.263, 3.264]
50
+ assert dev.power == pytest.approx(130.66)
51
+ assert dev.cell_voltage_max == 3.27
52
+ assert dev.cell_voltage_min == 3.263
53
+ assert dev.cell_voltage_avg == pytest.approx(3.2665)
54
+ assert dev.cell_voltage_median == pytest.approx(3.2665)
55
+ assert dev.cell_voltage_delta == pytest.approx(0.007)
56
+ assert dev.cell_index_max == 1
57
+ assert dev.cell_index_min == 2
@@ -7,7 +7,12 @@ from sok_ble import sok_bluetooth_device as device_mod
7
7
 
8
8
  class DummyClient:
9
9
  def __init__(self, *args, **kwargs):
10
- pass
10
+ self._responses = [
11
+ bytes.fromhex("ccf0000000102700000000000000320041000000"),
12
+ bytes.fromhex("ccf2000000140000000000000000000000000000"),
13
+ bytes.fromhex("ccf3000000003200000000000000000000000000"),
14
+ bytes.fromhex("ccf401c50c0002c60c0003bf0c0004c00c000000"),
15
+ ]
11
16
 
12
17
  async def connect(self):
13
18
  return True
@@ -19,10 +24,7 @@ class DummyClient:
19
24
  return True
20
25
 
21
26
  async def read_gatt_char(self, *args, **kwargs):
22
- # Response bytes correspond to voltage=13.23V, current=10A, soc=65
23
- return bytes.fromhex(
24
- "E4 0C E9 0C EE 0C F3 0C 64 00 00 00 00 00 00 00 41 00"
25
- )
27
+ return self._responses.pop(0)
26
28
 
27
29
 
28
30
  @pytest.mark.asyncio
@@ -36,6 +38,6 @@ async def test_minimal_update(monkeypatch):
36
38
 
37
39
  await dev.async_update()
38
40
 
39
- assert dev.voltage == 13.23
41
+ assert dev.voltage == pytest.approx(13.066)
40
42
  assert dev.current == 10.0
41
43
  assert dev.soc == 65
@@ -0,0 +1,68 @@
1
+ import pytest
2
+ from bleak.backends.device import BLEDevice
3
+ from contextlib import asynccontextmanager
4
+
5
+ from sok_ble import sok_bluetooth_device as device_mod
6
+
7
+
8
+ class DummyClient:
9
+ def __init__(self, responses):
10
+ self._responses = list(responses)
11
+ self.writes = []
12
+
13
+ async def connect(self):
14
+ return True
15
+
16
+ async def disconnect(self):
17
+ return True
18
+
19
+ async def write_gatt_char(self, uuid, data):
20
+ self.writes.append((uuid, bytes(data)))
21
+ return True
22
+
23
+ async def read_gatt_char(self, uuid):
24
+ return self._responses.pop(0)
25
+
26
+
27
+ @pytest.mark.asyncio
28
+ async def test_async_update_full_flow(monkeypatch):
29
+ responses = [
30
+ bytes.fromhex("ccf0000000102700000000000000320041000000"),
31
+ bytes.fromhex("ccf2000000140000000000000000000000000000"),
32
+ bytes.fromhex("ccf3000000003200000000000000000000000000"),
33
+ bytes.fromhex("ccf401c50c0002c60c0003bf0c0004c00c000000"),
34
+ ]
35
+
36
+ @asynccontextmanager
37
+ async def fake_connect(self):
38
+ dummy = DummyClient(responses)
39
+ await dummy.connect()
40
+ try:
41
+ yield dummy
42
+ finally:
43
+ await dummy.disconnect()
44
+
45
+ monkeypatch.setattr(device_mod.SokBluetoothDevice, "_connect", fake_connect)
46
+
47
+ dev = device_mod.SokBluetoothDevice(
48
+ BLEDevice("00:11:22:33:44:55", "Test", None, -60)
49
+ )
50
+
51
+ await dev.async_update()
52
+
53
+ assert dev.voltage == pytest.approx(13.066)
54
+ assert dev.current == 10.0
55
+ assert dev.soc == 65
56
+ assert dev.temperature == 20.0
57
+ assert dev.capacity == 100.0
58
+ assert dev.num_cycles == 50
59
+ assert dev.cell_voltages == [3.269, 3.27, 3.263, 3.264]
60
+ assert dev.power == pytest.approx(130.66)
61
+ assert dev.cell_voltage_max == 3.27
62
+ assert dev.cell_voltage_min == 3.263
63
+ assert dev.cell_voltage_avg == pytest.approx(3.2665)
64
+ assert dev.cell_voltage_median == pytest.approx(3.2665)
65
+ assert dev.cell_voltage_delta == pytest.approx(0.007)
66
+ assert dev.cell_index_max == 1
67
+ assert dev.cell_index_min == 2
68
+ assert dev.num_samples == 1
@@ -1,18 +1,19 @@
1
+ import pytest
1
2
  from sok_ble.sok_parser import SokParser
2
3
 
3
4
 
4
5
  def test_parse_all():
5
6
  info_buf = bytes.fromhex(
6
- "E4 0C E9 0C EE 0C F3 0C 64 00 00 00 00 00 00 00 41 00"
7
+ "ccf0000000102700000000000000320041000000"
7
8
  )
8
9
  temp_buf = bytes.fromhex(
9
- "00 00 00 00 00 FA 00"
10
+ "ccf2000000140000000000000000000000000000"
10
11
  )
11
12
  cap_buf = bytes.fromhex(
12
- "10 27 00 00 32 00 00 00"
13
+ "ccf3000000003200000000000000000000000000"
13
14
  )
14
15
  cell_buf = bytes.fromhex(
15
- "E4 0C E9 0C EE 0C F3 0C"
16
+ "ccf401c50c0002c60c0003bf0c0004c00c000000"
16
17
  )
17
18
 
18
19
  responses = {
@@ -24,12 +25,12 @@ def test_parse_all():
24
25
 
25
26
  result = SokParser.parse_all(responses)
26
27
  assert result == {
27
- "voltage": 13.23,
28
+ "voltage": pytest.approx(13.066, rel=1e-3),
28
29
  "current": 10.0,
29
30
  "soc": 65,
30
- "temperature": 25.0,
31
+ "temperature": 20.0,
31
32
  "capacity": 100.0,
32
33
  "num_cycles": 50,
33
- "cell_voltages": [3.3, 3.305, 3.31, 3.315],
34
+ "cell_voltages": [3.269, 3.27, 3.263, 3.264],
34
35
  }
35
36
 
@@ -3,13 +3,13 @@ from sok_ble.sok_parser import SokParser
3
3
 
4
4
  def test_parse_info_basic():
5
5
  hex_data = bytes.fromhex(
6
- "E4 0C E9 0C EE 0C F3 0C 64 00 00 00 00 00 00 00 41 00"
6
+ "ccf0000000102700000000000000320041000000"
7
7
  )
8
8
  result = SokParser.parse_info(hex_data)
9
9
  assert result == {
10
- "voltage": 13.23,
11
10
  "current": 10.0,
12
11
  "soc": 65,
12
+ "num_cycles": 50,
13
13
  }
14
14
 
15
15
 
@@ -1,19 +0,0 @@
1
- README.md
2
- pyproject.toml
3
- sok_ble/__init__.py
4
- sok_ble/const.py
5
- sok_ble/exceptions.py
6
- sok_ble/sok_bluetooth_device.py
7
- sok_ble/sok_parser.py
8
- sok_ble.egg-info/PKG-INFO
9
- sok_ble.egg-info/SOURCES.txt
10
- sok_ble.egg-info/dependency_links.txt
11
- sok_ble.egg-info/requires.txt
12
- sok_ble.egg-info/top_level.txt
13
- tests/test_const.py
14
- tests/test_derived.py
15
- tests/test_device_full.py
16
- tests/test_device_minimal.py
17
- tests/test_exceptions.py
18
- tests/test_parser_full.py
19
- tests/test_parser_info.py
@@ -1,2 +0,0 @@
1
- bleak>=0.22.3
2
- bleak-retry-connector>=3.10.0
File without changes
File without changes
File without changes
File without changes
File without changes