From a50deaae376b69552b5dcc0c81cc7e3f193b087f Mon Sep 17 00:00:00 2001 From: Patrick762 Date: Sun, 18 Feb 2024 16:37:37 +0100 Subject: [PATCH] added device type detection to config_flow --- .../base_devices/ProtocolV2Device.py | 2 +- .../bluetooth/device_recognizer.py | 59 +++++++++++++++++++ .../bluetti_bt_lib/utils/device_builder.py | 6 +- custom_components/bluetti_bt/config_flow.py | 25 ++++---- 4 files changed, 74 insertions(+), 18 deletions(-) create mode 100644 custom_components/bluetti_bt/bluetti_bt_lib/bluetooth/device_recognizer.py diff --git a/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/ProtocolV2Device.py b/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/ProtocolV2Device.py index 92f4808..b383c6c 100644 --- a/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/ProtocolV2Device.py +++ b/custom_components/bluetti_bt/bluetti_bt_lib/base_devices/ProtocolV2Device.py @@ -12,7 +12,7 @@ class ProtocolV2Device(BluettiDevice): self.struct = DeviceStruct() # Device info - self.struct.add_string_field("device_type", 110, 6) + self.struct.add_swap_string_field("device_type", 110, 6) self.struct.add_sn_field("serial_number", 116) # Power IO diff --git a/custom_components/bluetti_bt/bluetti_bt_lib/bluetooth/device_recognizer.py b/custom_components/bluetti_bt/bluetti_bt_lib/bluetooth/device_recognizer.py new file mode 100644 index 0000000..dc5b426 --- /dev/null +++ b/custom_components/bluetti_bt/bluetti_bt_lib/bluetooth/device_recognizer.py @@ -0,0 +1,59 @@ +"""Device recognizer.""" + +import asyncio +import logging +from typing import Any, Callable +from bleak import BleakClient + +from custom_components.bluetti_bt.bluetti_bt_lib.utils.commands import ( + ReadHoldingRegisters, +) + +from ..base_devices.ProtocolV2Device import ProtocolV2Device +from ..bluetooth.device_reader import DeviceReader + +_LOGGER = logging.getLogger(__name__) + + +async def recognize_device( + bleak_client: BleakClient, + future_builder_method: Callable[[], asyncio.Future[Any]], +) -> str: + + # Since we don't know the type we use the base device + bluetti_device = ProtocolV2Device("Unknown", "Unknown", "Unknown") + + # Create device builder + device_reader = DeviceReader(bleak_client, bluetti_device, future_builder_method) + + # Retry a few times to get data + for _ in range(1, 50): + # We only need 6 registers to get the device type + data = await device_reader.read_data( + [ + ReadHoldingRegisters(110, 6), + ] + ) + + if data is not None: + break + + await asyncio.sleep(0.1) + + if data is None: + # Should not happen + return "Unknown" + + field_data = data.get("device_type") + + if field_data is None: + # We have a problem + _LOGGER.error("No data in device type field_datas. Maybe it's a V1 device?") + return "Unknown" + + if not isinstance(field_data, str): + # We have a problem + _LOGGER.error("Invalid data in device type field_datas") + return "Unknown" + + return field_data diff --git a/custom_components/bluetti_bt/bluetti_bt_lib/utils/device_builder.py b/custom_components/bluetti_bt/bluetti_bt_lib/utils/device_builder.py index 177610d..1d3856d 100644 --- a/custom_components/bluetti_bt/bluetti_bt_lib/utils/device_builder.py +++ b/custom_components/bluetti_bt/bluetti_bt_lib/utils/device_builder.py @@ -41,6 +41,6 @@ def build_device(address: str, name: str): def get_type_by_bt_name(bt_name: str): match = DEVICE_NAME_RE.match(bt_name) - if len(match) > 0: - return match[1] - return "Unknown" + if match is None or len(match) < 2: + return "Unknown" + return match[1] diff --git a/custom_components/bluetti_bt/config_flow.py b/custom_components/bluetti_bt/config_flow.py index 6b2899e..0d20d13 100644 --- a/custom_components/bluetti_bt/config_flow.py +++ b/custom_components/bluetti_bt/config_flow.py @@ -5,6 +5,7 @@ from __future__ import annotations import re import logging from typing import Any +from bleak import BleakClient import voluptuous as vol @@ -13,11 +14,12 @@ from homeassistant.components.bluetooth import ( BluetoothServiceInfoBleak, async_discovered_service_info, ) -from homeassistant.const import CONF_ADDRESS, CONF_TYPE, CONF_NAME +from homeassistant.const import CONF_ADDRESS, CONF_NAME from homeassistant.data_entry_flow import FlowResult from homeassistant.helpers import selector from .bluetti_bt_lib.utils.device_builder import get_type_by_bt_name +from .bluetti_bt_lib.bluetooth.device_recognizer import recognize_device from .const import ( CONF_MAX_RETRIES, @@ -26,7 +28,6 @@ from .const import ( CONF_POLLING_TIMEOUT, CONF_USE_CONTROLS, DOMAIN, - SUPPORTED_MODELS, ) _LOGGER = logging.getLogger(__name__) @@ -46,6 +47,14 @@ class BluettiConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): """Handle bluetooth discovery.""" await self.async_set_unique_id(discovery_info.address) self._abort_if_unique_id_configured() + + # Get device type if needed + if isinstance(discovery_info.name, str) and discovery_info.name.startswith("PBOX"): + bleak_device = BleakClient(discovery_info.device) + device_type = await recognize_device(bleak_device, self.hass.loop.create_future) + _LOGGER.info("Device identified as %s", device_type) + discovery_info.name = discovery_info.name.replace("PBOX", device_type.strip()) + self._discovery_info = discovery_info self.context["title_placeholders"] = {"name": discovery_info.name} return await self.async_step_user() @@ -60,18 +69,12 @@ class BluettiConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): discovery_info = self._discovered_devices[address] await self.async_set_unique_id(address, raise_on_progress=False) self._abort_if_unique_id_configured() - dev_type = get_type_by_bt_name(discovery_info.name) name = re.sub("[^A-Z0-9]+", "", discovery_info.name) - if dev_type == "Unknown": - dev_type = user_input[CONF_TYPE] - name = name.replace("PBOX", dev_type) - return self.async_create_entry( title=name, data={ CONF_ADDRESS: discovery_info.address, - CONF_TYPE: dev_type, CONF_NAME: name, }, ) @@ -97,12 +100,6 @@ class BluettiConfigFlow(config_entries.ConfigFlow, domain=DOMAIN): for service_info in self._discovered_devices.values() } ), - vol.Optional(CONF_TYPE): vol.In( - { - model: model - for model in SUPPORTED_MODELS - } - ), } ) return self.async_show_form(