Skip to content

Commit 8618fdf

Browse files
patman15krahabb
andauthored
Switch to aiobmsble library usage (#477)
* migrate to aiobmsble * disable mypy * fix device discovery for aiobmsble * add test for v2 config entry not yet correct * complete test coverage * Update aiobmsble to 0.2.2 * disable fuzz tests * update for new retry-connector * update requirements * Update fuzzing.yaml * Update manifest.json * Update README.md * Feature align aiobmsble (#488) * adapt naming - `AdvertisementPattern` to `MatcherPattern` - `reconnect` to `keep_alive` * remove dummy example * fix removal of 0 values in _temp_values() (#480) * Add Seplos `SP1??B*` pattern (#482) * Feature align aiobmsble (#481) * adapt naming - `AdvertisementPattern` to `MatcherPattern` - `reconnect` to `keep_alive` * remove dummy example * Add detection of LiTime 24V BMS type based on serial number (#484) * Fix operator precedence (#485) * fix removal of 0 values in _temp_values() * fix operator precedence * Support ANT BMS (legacy BLE protocol) (#468) * Support ANT BMS (legacy BLE protocol) * Refine frame parsing for ant_bms_legacy * Add basic testing for ant_bms_legacy * Rename plugin ant_bms_legacy to ant_legacy_bms to comply with component conventions * Fix ant_legacy_bms test coverage * Fix compliancy with bms_ble repository design * Add 'total_charge' value (#476) to results * Remove 'dead' code * Remove unused import * Fix temperature sensors count * Remove cell high/low voltage parsing and calc 'delta_voltage' from internal '_add_missing_values' * Remove local reference to self._data in member function body * Fix 'BaseBMS._temp_values' operator precedence * Fix imported symbol * Fix missing comma after manual merge * fix pylint issues * fix design_capacity / temp sensors * add typing * Update ant_legacy_bms.py * align constants * test to check design capacity provision * rename legacy bms * Update README.md --------- Co-authored-by: patman15 <14628713+patman15@users.noreply.github.com> * update to 1.21.0 --------- Co-authored-by: krahabb <13969600+krahabb@users.noreply.github.com> * fix ant_leg_bms inclusion * Update CONTRIBUTING.md * Update README.md --------- Co-authored-by: krahabb <13969600+krahabb@users.noreply.github.com>
1 parent a8f4bdd commit 8618fdf

80 files changed

Lines changed: 358 additions & 13617 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.github/workflows/fuzzing.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,10 @@ permissions:
33
contents: read
44

55
on:
6-
pull_request:
6+
# pull_request:
77
workflow_dispatch:
8-
schedule:
9-
- cron: '1 5 * * *'
8+
# schedule:
9+
# - cron: '1 5 * * *'
1010

1111
jobs:
1212
validate:

.pre-commit-config.yaml

Lines changed: 7 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,10 @@ repos:
33
rev: v0.11.0 # ruff version, needs to match installed version
44
hooks:
55
- id: ruff
6-
- repo: https://github.com/pre-commit/mirrors-mypy
7-
rev: v1.17.0 # Use the latest stable version of mypy
8-
hooks:
9-
- id: mypy
10-
args: ["--allow-subclassing-any"] # avoid HA dependency installation
11-
# args: [--ignore-missing-imports] # Optional: customize your mypy flags
6+
# - repo: https://github.com/pre-commit/mirrors-mypy
7+
# rev: v1.17.0 # Use the latest stable version of mypy
8+
# hooks:
9+
# - id: mypy
10+
# additional_dependencies: [homeassistant==2025.7.0] # takes long thus mypy disabled here
11+
# args: ["--allow-subclassing-any"] # avoid HA dependency installation
12+
# # args: [--ignore-missing-imports] # Optional: customize your mypy flags

CONTRIBUTING.md

Lines changed: 9 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,13 @@
33
## Issues
44
In case you have troubles, please [enable the debug protocol](https://www.home-assistant.io/docs/configuration/troubleshooting/#debug-logs-and-diagnostics) for the [integration](https://my.home-assistant.io/redirect/integration/?domain=bms_ble) and [open an issue](https://github.com/patman15/BMS_BLE-HA/issues) with a good description of what happened and attach the log **as a file**.
55

6-
## Architecture Guidelines
7-
- The integration shall not use persistent information. That means all necessary info shall be determined on connecting the device.
8-
- The BT pattern matcher shall be unique to allow auto-detecting devices.
9-
- Frame parsing shall check the validity of a frame according to the protocol type, e.g. CRC, length, allowed type
10-
- All plugin classes shall inherit from `BaseBMS` and use the functions from there before overriding or replacing.
11-
- If available the data shall be read from the device, the `BaseBMS._add_missing_values()` functionality is only to have consistent data over all BMS types.
6+
## Adding a new battery management system
127

13-
to be extended ...
8+
The handling of the BMS types is done by the external library [aiobmsble](https://github.com/patman15/aiobmsble). To add a new type, please see the [CONTRIBUTING](https://github.com/patman15/aiobmsble?tab=contributing-ov-file) guidelines of this repository.
9+
10+
### Any contributions you make will be under the Apache-2.0 License
11+
12+
In short, when you submit code changes, your submissions are understood to be under the same [Apache-2.0](LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern.
1413

1514
## Coding Style Guidelines
1615

@@ -19,22 +18,10 @@ In general I use guidelines very close to the ones that Home Assistant uses for
1918
- `ruff check .`
2019
- `mypy .`
2120

22-
## Adding a new battery management system
23-
24-
1. Fork the repository and create a branch with the name of the new BMS to add.
25-
2. Add a new file to the `plugins` folder called, e.g. `my_bms.py`
26-
3. Populate the file with class called `BMS` derived from `BaseBMS`(see basebms.py). A dummy implementation without the actual functionality to query the BMS can befound below in section _Dummy BMS Example_
27-
4. Make sure that the dictionary returned by `async_update()` has (all) keys listed in `SENSOR_TYPES` (see `sensor.py`), __except__ for `ATTR_LQ` and `ATTR_RSSI` which are automatically handled. To make it simple, just follow the `ATTR_*` import in the example code below.
28-
5. In `const.py` add the filename (without extention), e.g. `my_bms`, to the constant `BMS_TYPES`.
29-
6. Add an appropriate [bluetooth device matcher](https://developers.home-assistant.io/docs/creating_integration_manifest#bluetooth) to `manifest.json`. Note that this is required to match the implementation of `match_dict_list()` in the new BMS class.
30-
7. Test and commit the changes to the branch and create a pull request to the main repository.
31-
3221
> [!NOTE]
3322
> In order to keep maintainability of this integration, pull requests are required to pass standard Home Assistant checks for integrations, [coding style guidelines](#coding-style-guidelines), Python linting, and 100% [branch test coverage](https://coverage.readthedocs.io/en/latest/branch.html#branch).
3423
35-
### Any contributions you make will be under the Apache-2.0 License
36-
37-
In short, when you submit code changes, your submissions are understood to be under the same [Apache-2.0](LICENSE) that covers the project. Feel free to contact the maintainers if that's a concern.
24+
## Architecture Guidelines
25+
- The integration shall not use persistent information. That means all necessary info shall be determined on connecting the device.
3826

39-
### Dummy BMS Example
40-
Note: In order for the [example](custom_components/bms_ble/plugins/dummy_bms.py) to work, you need to set the UUIDs of the service, the characteristic providing notifications, and the characteristic for sending commands to. While the device must be in Bluetooth range, the actual communication does not matter. Always the fixed values in the code will be shown.
27+
to be extended ...

README.md

Lines changed: 1 addition & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ This integration allows to monitor Bluetooth Low Energy (BLE) battery management
3030
- Any number of batteries in parallel
3131
- Native Home Assistant integration (works with all [HA installation methods](https://www.home-assistant.io/installation/#advanced-installation-methods))
3232
- Readout of individual cell voltages to be able to judge battery health
33-
- 100% test coverage plus fuzz tests for BLE data
33+
- 100% test coverage
3434

3535
### Supported Devices
3636
- ABC/SOK BMS (show up as `ABC-`&#x2026;, `SOK-`&#x2026;)
@@ -69,9 +69,6 @@ This integration allows to monitor Bluetooth Low Energy (BLE) battery management
6969
- Wattcycle batteries
7070
- TianPower BMS (show up as `TP_`&#x2026;)
7171

72-
> [!TIP]
73-
> New device types can be easily added via the plugin architecture of this integration. See the [contribution guidelines](CONTRIBUTING.md) for details.
74-
7572
### Provided Information
7673
> [!CAUTION]
7774
> This integration (including Home Assistant) **shall not be used for safety relevant operations**! The correctness or availability of data cannot be guaranteed (see [warranty section of the license](LICENSE)),

custom_components/bms_ble/__init__.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -78,16 +78,18 @@ async def async_migrate_entry(
7878
hass: HomeAssistant, config_entry: BTBmsConfigEntry
7979
) -> bool:
8080
"""Migrate old entry."""
81+
_latest_version: Final[int] = 2
8182

82-
if config_entry.version > 1:
83+
if config_entry.version > _latest_version:
8384
# This means the user has downgraded from a future version
8485
LOGGER.debug("Cannot downgrade from version %s", config_entry.version)
8586
return False
8687

8788
LOGGER.debug("Migrating from version %s", config_entry.version)
8889

8990
if config_entry.version == 0:
90-
bms_type = config_entry.data["type"]
91+
bms_type: str = str(config_entry.data["type"])
92+
new: dict[str, str] = {}
9193
if bms_type == "OGTBms":
9294
new = {"type": "custom_components.bms_ble.plugins.ogt_bms"}
9395
elif bms_type == "DalyBms":
@@ -105,6 +107,13 @@ async def async_migrate_entry(
105107
config_entry, data=new, minor_version=0, version=1
106108
)
107109

110+
if config_entry.version == 1:
111+
bms_type = str(config_entry.data["type"])
112+
new = {"type": f"aiobmsble.bms.{bms_type.rsplit(".", 1)[-1]}"}
113+
hass.config_entries.async_update_entry(
114+
config_entry, data=new, minor_version=0, version=2
115+
)
116+
108117
LOGGER.debug(
109118
"Migration to version %s.%s successful",
110119
config_entry.version,

custom_components/bms_ble/binary_sensor.py

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,7 +2,9 @@
22

33
from collections.abc import Callable
44

5-
from custom_components.bms_ble.plugins.basebms import BMSmode, BMSsample
5+
from aiobmsble import BMSmode
6+
from aiobmsble.basebms import BMSsample
7+
68
from homeassistant.components.binary_sensor import (
79
BinarySensorDeviceClass,
810
BinarySensorEntity,

custom_components/bms_ble/config_flow.py

Lines changed: 16 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,12 @@
11
"""Config flow for BLE Battery Management System integration."""
22

33
from dataclasses import dataclass
4-
from types import ModuleType
54
from typing import Any, Final
65

6+
from aiobmsble.utils import bms_identify
77
import voluptuous as vol
88

9+
from custom_components.bms_ble.const import DOMAIN, LOGGER
910
from homeassistant import config_entries
1011
from homeassistant.components.bluetooth import (
1112
BluetoothServiceInfoBleak,
@@ -14,20 +15,17 @@
1415
from homeassistant.config_entries import ConfigFlowResult
1516
from homeassistant.const import CONF_ADDRESS, CONF_ID, CONF_MODEL, CONF_NAME
1617
from homeassistant.helpers.device_registry import format_mac
17-
from homeassistant.helpers.importlib import async_import_module
1818
from homeassistant.helpers.selector import (
1919
SelectOptionDict,
2020
SelectSelector,
2121
SelectSelectorConfig,
2222
)
2323

24-
from .const import BMS_TYPES, DOMAIN, LOGGER
25-
2624

2725
class ConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
2826
"""Handle a config flow for BT Battery Management System."""
2927

30-
VERSION = 1
28+
VERSION = 2
3129
MINOR_VERSION = 0
3230

3331
@dataclass
@@ -52,22 +50,15 @@ async def _async_device_supported(
5250
self, discovery_info: BluetoothServiceInfoBleak
5351
) -> str | None:
5452
"""Check if device is supported by an available BMS class."""
55-
for bms_type in BMS_TYPES:
56-
bms_plugin: ModuleType = await async_import_module(
57-
self.hass, f"custom_components.bms_ble.plugins.{bms_type}"
58-
)
59-
try:
60-
if bms_plugin.BMS.supported(discovery_info):
61-
LOGGER.debug(
62-
"Device %s (%s) detected as '%s'",
63-
discovery_info.name,
64-
format_mac(discovery_info.address),
65-
bms_plugin.BMS.device_id(),
66-
)
67-
return bms_plugin.__name__
68-
except AttributeError:
69-
LOGGER.error("Invalid BMS plugin %s", bms_type)
70-
return None
53+
if not (bms_class := bms_identify(discovery_info.advertisement)):
54+
return None
55+
LOGGER.debug(
56+
"Device %s (%s) detected as '%s'",
57+
discovery_info.name,
58+
format_mac(discovery_info.address),
59+
bms_class.device_id(),
60+
)
61+
return str(bms_class.get_bms_module())
7162

7263
async def async_step_bluetooth(
7364
self, discovery_info: BluetoothServiceInfoBleak
@@ -78,14 +69,11 @@ async def async_step_bluetooth(
7869
await self.async_set_unique_id(discovery_info.address)
7970
self._abort_if_unique_id_configured()
8071

81-
device_class: Final[str | None] = await self._async_device_supported(
82-
discovery_info
83-
)
84-
if device_class is None:
72+
if not (bms_module := await self._async_device_supported(discovery_info)):
8573
return self.async_abort(reason="not_supported")
8674

8775
self._disc_dev = ConfigFlow.DiscoveredDevice(
88-
discovery_info.name, discovery_info, device_class
76+
discovery_info.name, discovery_info, bms_module
8977
)
9078
self.context["title_placeholders"] = {
9179
CONF_NAME: self._disc_dev.name,
@@ -136,14 +124,11 @@ async def async_step_user(
136124
address = discovery_info.address
137125
if address in current_addresses or address in self._disc_devs:
138126
continue
139-
device_class: str | None = await self._async_device_supported(
140-
discovery_info
141-
)
142-
if not device_class:
127+
if not (bms_module := await self._async_device_supported(discovery_info)):
143128
continue
144129

145130
self._disc_devs[address] = ConfigFlow.DiscoveredDevice(
146-
discovery_info.name, discovery_info, device_class
131+
discovery_info.name, discovery_info, bms_module
147132
)
148133

149134
if not self._disc_devs:

custom_components/bms_ble/coordinator.py

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,8 @@
55
from time import monotonic
66
from typing import Final
77

8+
from aiobmsble import BMSsample
9+
from aiobmsble.basebms import BaseBMS
810
from bleak.backends.device import BLEDevice
911
from bleak.exc import BleakError
1012
from habluetooth import BluetoothServiceInfoBleak
@@ -17,7 +19,6 @@
1719
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
1820

1921
from .const import DOMAIN, LOGGER, UPDATE_INTERVAL
20-
from .plugins.basebms import BaseBMS, BMSsample
2122

2223

2324
class BTBmsCoordinator(DataUpdateCoordinator[BMSsample]):
@@ -40,7 +41,9 @@ def __init__(
4041
config_entry=config_entry,
4142
)
4243
self._device: Final[BaseBMS] = bms_device
43-
self._link_q: deque[bool] = deque([False], maxlen=100) # track BMS update issues
44+
self._link_q: deque[bool] = deque(
45+
[False], maxlen=100
46+
) # track BMS update issues
4447
self._mac: Final[str] = ble_device.address
4548
self._stale: bool = False # indicates no BMS response for significant time
4649

custom_components/bms_ble/manifest.json

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -336,9 +336,10 @@
336336
"iot_class": "local_polling",
337337
"issue_tracker": "https://github.com/patman15/BMS_BLE-HA/issues",
338338
"loggers": [
339-
"bleak_retry_connector"
339+
"bleak_retry_connector",
340+
"aiobmsble"
340341
],
341342
"quality_scale": "silver",
342-
"requirements": [],
343-
"version": "1.21.0"
343+
"requirements": ["aiobmsble==0.2.3"],
344+
"version": "2.0.0-beta.2"
344345
}

custom_components/bms_ble/plugins/__init__.py

Lines changed: 0 additions & 1 deletion
This file was deleted.

0 commit comments

Comments
 (0)