aiobmsble 0.1.0__tar.gz → 0.2.1__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.
- {aiobmsble-0.1.0/aiobmsble.egg-info → aiobmsble-0.2.1}/PKG-INFO +23 -14
- {aiobmsble-0.1.0 → aiobmsble-0.2.1}/README.md +15 -10
- aiobmsble-0.2.1/aiobmsble/__init__.py +99 -0
- aiobmsble-0.2.1/aiobmsble/__main__.py +93 -0
- aiobmsble-0.2.1/aiobmsble/basebms.py +529 -0
- aiobmsble-0.2.1/aiobmsble/bms/__init__.py +1 -0
- aiobmsble-0.2.1/aiobmsble/bms/abc_bms.py +164 -0
- aiobmsble-0.2.1/aiobmsble/bms/ant_bms.py +196 -0
- aiobmsble-0.2.1/aiobmsble/bms/braunpwr_bms.py +167 -0
- aiobmsble-0.2.1/aiobmsble/bms/cbtpwr_bms.py +168 -0
- aiobmsble-0.2.1/aiobmsble/bms/cbtpwr_vb_bms.py +184 -0
- aiobmsble-0.2.1/aiobmsble/bms/daly_bms.py +164 -0
- aiobmsble-0.2.1/aiobmsble/bms/dpwrcore_bms.py +207 -0
- aiobmsble-0.2.1/aiobmsble/bms/dummy_bms.py +89 -0
- aiobmsble-0.2.1/aiobmsble/bms/ecoworthy_bms.py +151 -0
- aiobmsble-0.2.1/aiobmsble/bms/ective_bms.py +177 -0
- aiobmsble-0.2.1/aiobmsble/bms/ej_bms.py +233 -0
- aiobmsble-0.2.1/aiobmsble/bms/felicity_bms.py +139 -0
- aiobmsble-0.2.1/aiobmsble/bms/jbd_bms.py +203 -0
- aiobmsble-0.2.1/aiobmsble/bms/jikong_bms.py +301 -0
- aiobmsble-0.2.1/aiobmsble/bms/neey_bms.py +214 -0
- aiobmsble-0.2.1/aiobmsble/bms/ogt_bms.py +214 -0
- aiobmsble-0.2.1/aiobmsble/bms/pro_bms.py +144 -0
- aiobmsble-0.2.1/aiobmsble/bms/redodo_bms.py +127 -0
- aiobmsble-0.2.1/aiobmsble/bms/renogy_bms.py +149 -0
- aiobmsble-0.2.1/aiobmsble/bms/renogy_pro_bms.py +105 -0
- aiobmsble-0.2.1/aiobmsble/bms/roypow_bms.py +186 -0
- aiobmsble-0.2.1/aiobmsble/bms/seplos_bms.py +245 -0
- aiobmsble-0.2.1/aiobmsble/bms/seplos_v2_bms.py +205 -0
- aiobmsble-0.2.1/aiobmsble/bms/tdt_bms.py +199 -0
- aiobmsble-0.2.1/aiobmsble/bms/tianpwr_bms.py +138 -0
- aiobmsble-0.2.1/aiobmsble/utils.py +163 -0
- {aiobmsble-0.1.0 → aiobmsble-0.2.1/aiobmsble.egg-info}/PKG-INFO +23 -14
- aiobmsble-0.2.1/aiobmsble.egg-info/SOURCES.txt +46 -0
- {aiobmsble-0.1.0 → aiobmsble-0.2.1}/aiobmsble.egg-info/requires.txt +4 -2
- {aiobmsble-0.1.0 → aiobmsble-0.2.1}/pyproject.toml +14 -13
- aiobmsble-0.2.1/tests/test_basebms.py +571 -0
- aiobmsble-0.2.1/tests/test_examples.py +86 -0
- aiobmsble-0.2.1/tests/test_fuzzing.py +61 -0
- aiobmsble-0.2.1/tests/test_main.py +150 -0
- aiobmsble-0.2.1/tests/test_plugins.py +42 -0
- aiobmsble-0.2.1/tests/test_utils.py +191 -0
- aiobmsble-0.1.0/aiobmsble/__init__.py +0 -54
- aiobmsble-0.1.0/aiobmsble/__main__.py +0 -69
- aiobmsble-0.1.0/aiobmsble/basebms.py +0 -313
- aiobmsble-0.1.0/aiobmsble/utils.py +0 -73
- aiobmsble-0.1.0/aiobmsble.egg-info/SOURCES.txt +0 -15
- aiobmsble-0.1.0/tests/test_ogt_bms.py +0 -296
- {aiobmsble-0.1.0 → aiobmsble-0.2.1}/LICENSE +0 -0
- {aiobmsble-0.1.0 → aiobmsble-0.2.1}/MANIFEST.in +0 -0
- {aiobmsble-0.1.0 → aiobmsble-0.2.1}/aiobmsble.egg-info/dependency_links.txt +0 -0
- {aiobmsble-0.1.0 → aiobmsble-0.2.1}/aiobmsble.egg-info/entry_points.txt +0 -0
- {aiobmsble-0.1.0 → aiobmsble-0.2.1}/aiobmsble.egg-info/top_level.txt +0 -0
- {aiobmsble-0.1.0 → aiobmsble-0.2.1}/setup.cfg +0 -0
@@ -1,12 +1,14 @@
|
|
1
1
|
Metadata-Version: 2.4
|
2
2
|
Name: aiobmsble
|
3
|
-
Version: 0.1
|
3
|
+
Version: 0.2.1
|
4
4
|
Summary: Asynchronous Python library to query battery management systems via Bluetooth Low Energy.
|
5
5
|
Author: Patrick Loschmidt
|
6
6
|
Maintainer: Patrick Loschmidt
|
7
7
|
License-Expression: Apache-2.0
|
8
8
|
Project-URL: Homepage, https://github.com/patman15/aiobmsble/
|
9
|
-
Project-URL: Documentation, https://github.com/patman15/aiobmsble/
|
9
|
+
Project-URL: Documentation, https://github.com/patman15/aiobmsble/README
|
10
|
+
Project-URL: Source Code, https://github.com/patman15/aiobmsble/
|
11
|
+
Project-URL: Bug Reports, https://github.com/patman15/aiobmsble/issues
|
10
12
|
Keywords: BMS,BLE,battery,bluetooth
|
11
13
|
Classifier: Programming Language :: Python :: 3
|
12
14
|
Classifier: Operating System :: OS Independent
|
@@ -16,8 +18,8 @@ Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
16
18
|
Requires-Python: >=3.12
|
17
19
|
Description-Content-Type: text/markdown
|
18
20
|
License-File: LICENSE
|
19
|
-
Requires-Dist: bleak~=
|
20
|
-
Requires-Dist: bleak-retry-connector~=
|
21
|
+
Requires-Dist: bleak~=1.1.0
|
22
|
+
Requires-Dist: bleak-retry-connector~=4.0.2
|
21
23
|
Requires-Dist: asyncio
|
22
24
|
Requires-Dist: logging
|
23
25
|
Requires-Dist: statistics
|
@@ -26,6 +28,8 @@ Provides-Extra: dev
|
|
26
28
|
Requires-Dist: pytest; extra == "dev"
|
27
29
|
Requires-Dist: pytest-asyncio; extra == "dev"
|
28
30
|
Requires-Dist: pytest-cov; extra == "dev"
|
31
|
+
Requires-Dist: pytest-xdist; extra == "dev"
|
32
|
+
Requires-Dist: hypothesis; extra == "dev"
|
29
33
|
Requires-Dist: mypy; extra == "dev"
|
30
34
|
Requires-Dist: ruff; extra == "dev"
|
31
35
|
Dynamic: license-file
|
@@ -35,9 +39,8 @@ Dynamic: license-file
|
|
35
39
|
# Aiobmsble
|
36
40
|
Requires Python 3 and uses [asyncio](https://pypi.org/project/asyncio/) and [bleak](https://pypi.org/project/bleak/)
|
37
41
|
> [!IMPORTANT]
|
38
|
-
> At the moment the library is under development and
|
42
|
+
> At the moment the library is under development and there might be missing functionality compared to the [BMS_BLE-HA integration](https://github.com/patman15/BMS_BLE-HA/)!
|
39
43
|
> Please do not (yet) report missing BMS support or bugs here. Instead please raise an issue at the integration till the library reached at least development status *beta*.
|
40
|
-
> Plan is to support all BMSs that are listed [here](https://github.com/patman15/BMS_BLE-HA/edit/main/README.md#supported-devices).
|
41
44
|
|
42
45
|
## Asynchronous Library to Query Battery Management Systems via Bluetooth LE
|
43
46
|
This library is intended to query data from battery management systems that use Bluetooth LE. It is developed to support [BMS_BLE-HA integration](https://github.com/patman15/BMS_BLE-HA/) that was written to make BMS data available to Home Assistant. While the integration depends on Home Assistant, this library can be used stand-alone in any Python environment (with necessary dependencies installed).
|
@@ -55,6 +58,7 @@ This example can also be found as an [example](/examples/minimal.py) in the resp
|
|
55
58
|
"""Example of using the aiobmsble library to find a BLE device by name and print its senosr data."""
|
56
59
|
|
57
60
|
import asyncio
|
61
|
+
import logging
|
58
62
|
from typing import Final
|
59
63
|
|
60
64
|
from bleak import BleakScanner
|
@@ -62,30 +66,35 @@ from bleak.backends.device import BLEDevice
|
|
62
66
|
from bleak.exc import BleakError
|
63
67
|
|
64
68
|
from aiobmsble import BMSsample
|
65
|
-
from aiobmsble.bms.
|
69
|
+
from aiobmsble.bms.dummy_bms import BMS # use the right BMS class for your device
|
66
70
|
|
67
71
|
NAME: Final[str] = "BT Device Name" # Replace with the name of your BLE device
|
68
72
|
|
73
|
+
# Configure logging
|
74
|
+
logging.basicConfig(level=logging.INFO)
|
75
|
+
logger: logging.Logger = logging.getLogger(__name__)
|
76
|
+
|
69
77
|
|
70
78
|
async def main(dev_name) -> None:
|
71
|
-
"""
|
79
|
+
"""Find a BLE device by name and update its sensor data."""
|
72
80
|
|
73
81
|
device: BLEDevice | None = await BleakScanner.find_device_by_name(dev_name)
|
74
82
|
if device is None:
|
75
|
-
|
83
|
+
logger.error("Device '%s' not found.", dev_name)
|
76
84
|
return
|
77
85
|
|
78
|
-
|
86
|
+
logger.info("Found device: %s (%s)", device.name, device.address)
|
79
87
|
bms = BMS(ble_device=device, reconnect=True)
|
80
88
|
try:
|
81
|
-
|
89
|
+
logger.info("Updating BMS data...")
|
82
90
|
data: BMSsample = await bms.async_update()
|
83
|
-
|
91
|
+
logger.info("BMS data: %s", repr(data).replace(", ", ",\n\t"))
|
84
92
|
except BleakError as ex:
|
85
|
-
|
93
|
+
logger.error("Failed to update BMS: %s", type(ex).__name__)
|
86
94
|
|
87
95
|
|
88
|
-
|
96
|
+
if __name__ == "__main__":
|
97
|
+
asyncio.run(main(NAME)) # pragma: no cover
|
89
98
|
```
|
90
99
|
|
91
100
|
## Installation
|
@@ -3,9 +3,8 @@
|
|
3
3
|
# Aiobmsble
|
4
4
|
Requires Python 3 and uses [asyncio](https://pypi.org/project/asyncio/) and [bleak](https://pypi.org/project/bleak/)
|
5
5
|
> [!IMPORTANT]
|
6
|
-
> At the moment the library is under development and
|
6
|
+
> At the moment the library is under development and there might be missing functionality compared to the [BMS_BLE-HA integration](https://github.com/patman15/BMS_BLE-HA/)!
|
7
7
|
> Please do not (yet) report missing BMS support or bugs here. Instead please raise an issue at the integration till the library reached at least development status *beta*.
|
8
|
-
> Plan is to support all BMSs that are listed [here](https://github.com/patman15/BMS_BLE-HA/edit/main/README.md#supported-devices).
|
9
8
|
|
10
9
|
## Asynchronous Library to Query Battery Management Systems via Bluetooth LE
|
11
10
|
This library is intended to query data from battery management systems that use Bluetooth LE. It is developed to support [BMS_BLE-HA integration](https://github.com/patman15/BMS_BLE-HA/) that was written to make BMS data available to Home Assistant. While the integration depends on Home Assistant, this library can be used stand-alone in any Python environment (with necessary dependencies installed).
|
@@ -23,6 +22,7 @@ This example can also be found as an [example](/examples/minimal.py) in the resp
|
|
23
22
|
"""Example of using the aiobmsble library to find a BLE device by name and print its senosr data."""
|
24
23
|
|
25
24
|
import asyncio
|
25
|
+
import logging
|
26
26
|
from typing import Final
|
27
27
|
|
28
28
|
from bleak import BleakScanner
|
@@ -30,30 +30,35 @@ from bleak.backends.device import BLEDevice
|
|
30
30
|
from bleak.exc import BleakError
|
31
31
|
|
32
32
|
from aiobmsble import BMSsample
|
33
|
-
from aiobmsble.bms.
|
33
|
+
from aiobmsble.bms.dummy_bms import BMS # use the right BMS class for your device
|
34
34
|
|
35
35
|
NAME: Final[str] = "BT Device Name" # Replace with the name of your BLE device
|
36
36
|
|
37
|
+
# Configure logging
|
38
|
+
logging.basicConfig(level=logging.INFO)
|
39
|
+
logger: logging.Logger = logging.getLogger(__name__)
|
40
|
+
|
37
41
|
|
38
42
|
async def main(dev_name) -> None:
|
39
|
-
"""
|
43
|
+
"""Find a BLE device by name and update its sensor data."""
|
40
44
|
|
41
45
|
device: BLEDevice | None = await BleakScanner.find_device_by_name(dev_name)
|
42
46
|
if device is None:
|
43
|
-
|
47
|
+
logger.error("Device '%s' not found.", dev_name)
|
44
48
|
return
|
45
49
|
|
46
|
-
|
50
|
+
logger.info("Found device: %s (%s)", device.name, device.address)
|
47
51
|
bms = BMS(ble_device=device, reconnect=True)
|
48
52
|
try:
|
49
|
-
|
53
|
+
logger.info("Updating BMS data...")
|
50
54
|
data: BMSsample = await bms.async_update()
|
51
|
-
|
55
|
+
logger.info("BMS data: %s", repr(data).replace(", ", ",\n\t"))
|
52
56
|
except BleakError as ex:
|
53
|
-
|
57
|
+
logger.error("Failed to update BMS: %s", type(ex).__name__)
|
54
58
|
|
55
59
|
|
56
|
-
|
60
|
+
if __name__ == "__main__":
|
61
|
+
asyncio.run(main(NAME)) # pragma: no cover
|
57
62
|
```
|
58
63
|
|
59
64
|
## Installation
|
@@ -0,0 +1,99 @@
|
|
1
|
+
"""Package for battery management systems (BMS) via Bluetooth LE."""
|
2
|
+
|
3
|
+
from collections.abc import Callable
|
4
|
+
from enum import IntEnum
|
5
|
+
from typing import Any, Literal, NamedTuple, TypedDict
|
6
|
+
|
7
|
+
type BMSvalue = Literal[
|
8
|
+
"battery_charging",
|
9
|
+
"battery_mode",
|
10
|
+
"battery_level",
|
11
|
+
"current",
|
12
|
+
"power",
|
13
|
+
"temperature",
|
14
|
+
"voltage",
|
15
|
+
"cycles",
|
16
|
+
"cycle_capacity",
|
17
|
+
"cycle_charge",
|
18
|
+
"delta_voltage",
|
19
|
+
"problem",
|
20
|
+
"runtime",
|
21
|
+
"balance_current",
|
22
|
+
"cell_count",
|
23
|
+
"cell_voltages",
|
24
|
+
"design_capacity",
|
25
|
+
"pack_count",
|
26
|
+
"temp_sensors",
|
27
|
+
"temp_values",
|
28
|
+
"problem_code",
|
29
|
+
]
|
30
|
+
|
31
|
+
type BMSpackvalue = Literal[
|
32
|
+
"pack_voltages",
|
33
|
+
"pack_currents",
|
34
|
+
"pack_battery_levels",
|
35
|
+
"pack_cycles",
|
36
|
+
]
|
37
|
+
|
38
|
+
|
39
|
+
class BMSmode(IntEnum):
|
40
|
+
"""Enumeration of BMS modes."""
|
41
|
+
|
42
|
+
UNKNOWN = -1
|
43
|
+
BULK = 0x00
|
44
|
+
ABSORPTION = 0x01
|
45
|
+
FLOAT = 0x02
|
46
|
+
|
47
|
+
|
48
|
+
class BMSsample(TypedDict, total=False):
|
49
|
+
"""Dictionary representing a sample of battery management system (BMS) data."""
|
50
|
+
|
51
|
+
battery_charging: bool # True: battery charging
|
52
|
+
battery_mode: BMSmode # BMS charging mode
|
53
|
+
battery_level: int | float # [%]
|
54
|
+
current: float # [A] (positive: charging)
|
55
|
+
power: float # [W] (positive: charging)
|
56
|
+
temperature: int | float # [°C]
|
57
|
+
voltage: float # [V]
|
58
|
+
cycle_capacity: int | float # [Wh]
|
59
|
+
cycles: int # [#]
|
60
|
+
delta_voltage: float # [V]
|
61
|
+
problem: bool # True: problem detected
|
62
|
+
runtime: int # [s]
|
63
|
+
# detailed information
|
64
|
+
balance_current: float # [A]
|
65
|
+
cell_count: int # [#]
|
66
|
+
cell_voltages: list[float] # [V]
|
67
|
+
cycle_charge: int | float # [Ah]
|
68
|
+
design_capacity: int # [Ah]
|
69
|
+
pack_count: int # [#]
|
70
|
+
temp_sensors: int # [#]
|
71
|
+
temp_values: list[int | float] # [°C]
|
72
|
+
problem_code: int # BMS specific code, 0 no problem
|
73
|
+
# battery pack data
|
74
|
+
pack_voltages: list[float] # [V]
|
75
|
+
pack_currents: list[float] # [A]
|
76
|
+
pack_battery_levels: list[int | float] # [%]
|
77
|
+
pack_cycles: list[int] # [#]
|
78
|
+
|
79
|
+
|
80
|
+
class BMSdp(NamedTuple):
|
81
|
+
"""Representation of one BMS data point."""
|
82
|
+
|
83
|
+
key: BMSvalue # the key of the value to be parsed
|
84
|
+
pos: int # position within the message
|
85
|
+
size: int # size in bytes
|
86
|
+
signed: bool # signed value
|
87
|
+
fct: Callable[[int], Any] = lambda x: x # conversion function (default do nothing)
|
88
|
+
idx: int = -1 # array index containing the message to be parsed
|
89
|
+
|
90
|
+
|
91
|
+
class MatcherPattern(TypedDict, total=False):
|
92
|
+
"""Optional patterns that can match Bleak advertisement data."""
|
93
|
+
|
94
|
+
local_name: str # name pattern that supports Unix shell-style wildcards
|
95
|
+
service_uuid: str # 128-bit UUID that the device must advertise
|
96
|
+
service_data_uuid: str # service data for the service UUID
|
97
|
+
manufacturer_id: int # required manufacturer ID
|
98
|
+
manufacturer_data_start: list[int] # required starting bytes of manufacturer data
|
99
|
+
connectable: bool # True if active connections to the device are required
|
@@ -0,0 +1,93 @@
|
|
1
|
+
"""Example function for package usage."""
|
2
|
+
|
3
|
+
import argparse
|
4
|
+
import asyncio
|
5
|
+
import logging
|
6
|
+
from typing import Final
|
7
|
+
|
8
|
+
from bleak import BleakScanner
|
9
|
+
from bleak.backends.device import BLEDevice
|
10
|
+
from bleak.backends.scanner import AdvertisementData
|
11
|
+
from bleak.exc import BleakError
|
12
|
+
|
13
|
+
from aiobmsble import BMSsample
|
14
|
+
from aiobmsble.basebms import BaseBMS
|
15
|
+
from aiobmsble.utils import bms_identify
|
16
|
+
|
17
|
+
logging.basicConfig(
|
18
|
+
format="%(levelname)s: %(message)s",
|
19
|
+
level=logging.INFO,
|
20
|
+
)
|
21
|
+
logger: logging.Logger = logging.getLogger(__package__)
|
22
|
+
|
23
|
+
|
24
|
+
async def scan_devices() -> dict[str, tuple[BLEDevice, AdvertisementData]]:
|
25
|
+
"""Scan for BLE devices and return results."""
|
26
|
+
logger.info("starting scan...")
|
27
|
+
scan_result: dict[str, tuple[BLEDevice, AdvertisementData]] = (
|
28
|
+
await BleakScanner.discover(return_adv=True)
|
29
|
+
)
|
30
|
+
logger.info(scan_result)
|
31
|
+
logger.info("%i BT devices in range.", len(scan_result))
|
32
|
+
return scan_result
|
33
|
+
|
34
|
+
|
35
|
+
async def detect_bms() -> None:
|
36
|
+
"""Query a Bluetooth device based on the provided arguments."""
|
37
|
+
|
38
|
+
scan_result: dict[str, tuple[BLEDevice, AdvertisementData]] = await scan_devices()
|
39
|
+
for ble_dev, advertisement in scan_result.values():
|
40
|
+
logger.info(
|
41
|
+
"%s\nBT device '%s' (%s)\n\t%s",
|
42
|
+
"-" * 72,
|
43
|
+
ble_dev.name,
|
44
|
+
ble_dev.address,
|
45
|
+
repr(advertisement).replace(", ", ",\n\t"),
|
46
|
+
)
|
47
|
+
|
48
|
+
if bms_cls := bms_identify(advertisement):
|
49
|
+
logger.info("Found matching BMS type: %s", bms_cls.device_id())
|
50
|
+
bms: BaseBMS = bms_cls(ble_device=ble_dev, reconnect=True)
|
51
|
+
|
52
|
+
try:
|
53
|
+
logger.info("Updating BMS data...")
|
54
|
+
data: BMSsample = await bms.async_update()
|
55
|
+
logger.info("BMS data: %s", repr(data).replace(", '", ",\n\t'"))
|
56
|
+
except (BleakError, TimeoutError) as exc:
|
57
|
+
logger.error("Failed to update BMS: %s", type(exc).__name__)
|
58
|
+
|
59
|
+
logger.info("done.")
|
60
|
+
|
61
|
+
|
62
|
+
def setup_logging(args: argparse.Namespace) -> None:
|
63
|
+
"""Configure logging based on command line arguments."""
|
64
|
+
loglevel: Final[int] = logging.DEBUG if args.verbose else logging.INFO
|
65
|
+
|
66
|
+
if args.logfile:
|
67
|
+
file_handler = logging.FileHandler(args.logfile)
|
68
|
+
file_handler.setLevel(loglevel)
|
69
|
+
file_handler.setFormatter(
|
70
|
+
logging.Formatter("%(asctime)s - %(levelname)s: %(message)s")
|
71
|
+
)
|
72
|
+
logger.addHandler(file_handler)
|
73
|
+
|
74
|
+
logger.setLevel(loglevel)
|
75
|
+
|
76
|
+
|
77
|
+
def main() -> None:
|
78
|
+
"""Entry point for the script to run the BMS detection."""
|
79
|
+
parser = argparse.ArgumentParser(
|
80
|
+
description="Reference script for 'aiobmsble' to show all recognized BMS in range."
|
81
|
+
)
|
82
|
+
parser.add_argument("-l", "--logfile", type=str, help="Path to the log file")
|
83
|
+
parser.add_argument(
|
84
|
+
"-v", "--verbose", action="store_true", help="Enable debug logging"
|
85
|
+
)
|
86
|
+
|
87
|
+
setup_logging(parser.parse_args())
|
88
|
+
|
89
|
+
asyncio.run(detect_bms())
|
90
|
+
|
91
|
+
|
92
|
+
if __name__ == "__main__":
|
93
|
+
main() # pragma: no cover
|