updated polling

This commit is contained in:
Patrick762 committed 2023-07-31 12:35:01 +02:00
1 parent 3769747392
commit fad9fba574
3 files changed
+78 -20

No files matched your search

+2 -2
View File
@@ -7,7 +7,7 @@ import logging
from homeassistant.components import bluetooth from homeassistant.components import bluetooth
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ADDRESS, Platform from homeassistant.const import CONF_ADDRESS, CONF_TYPE, Platform
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.exceptions import ConfigEntryNotReady from homeassistant.exceptions import ConfigEntryNotReady
@@ -42,7 +42,7 @@ def device_info(entry: ConfigEntry) -> DeviceInfo:
identifiers={(DOMAIN, entry.data.get(CONF_ADDRESS))}, identifiers={(DOMAIN, entry.data.get(CONF_ADDRESS))},
name=entry.title, name=entry.title,
manufacturer=MANUFACTURER, manufacturer=MANUFACTURER,
model=get_type_by_bt_name(entry.title), model=entry.data.get(CONF_TYPE),
) )
+5 -1
View File
@@ -11,9 +11,10 @@ from homeassistant.components.bluetooth import (
BluetoothServiceInfoBleak, BluetoothServiceInfoBleak,
async_discovered_service_info, async_discovered_service_info,
) )
from homeassistant.const import CONF_ADDRESS from homeassistant.const import CONF_ADDRESS, CONF_TYPE, CONF_NAME
from homeassistant.data_entry_flow import FlowResult from homeassistant.data_entry_flow import FlowResult
from . import get_type_by_bt_name
from .const import DOMAIN from .const import DOMAIN
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -47,10 +48,13 @@ class BluettiConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
discovery_info = self._discovered_devices[address] discovery_info = self._discovered_devices[address]
await self.async_set_unique_id(address, raise_on_progress=False) await self.async_set_unique_id(address, raise_on_progress=False)
self._abort_if_unique_id_configured() self._abort_if_unique_id_configured()
dev_type = get_type_by_bt_name(discovery_info.name)
return self.async_create_entry( return self.async_create_entry(
title=discovery_info.name, title=discovery_info.name,
data={ data={
CONF_ADDRESS: discovery_info.address, CONF_ADDRESS: discovery_info.address,
CONF_TYPE: dev_type,
CONF_NAME: discovery_info.name,
}, },
) )
+71 -17
View File
@@ -5,13 +5,14 @@ from __future__ import annotations
import asyncio import asyncio
from datetime import timedelta from datetime import timedelta
import logging import logging
from bleak import BleakClient from typing import cast
from bleak import BleakClient, BleakError
from homeassistant.components import bluetooth from homeassistant.components import bluetooth
from homeassistant.components.sensor import SensorEntity from homeassistant.components.sensor import SensorEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback from homeassistant.core import HomeAssistant, callback
from homeassistant.const import CONF_ADDRESS from homeassistant.const import CONF_ADDRESS, CONF_NAME
from homeassistant.helpers.entity_platform import AddEntitiesCallback from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.entity import DeviceInfo from homeassistant.helpers.entity import DeviceInfo
from homeassistant.helpers.update_coordinator import ( from homeassistant.helpers.update_coordinator import (
@@ -20,6 +21,12 @@ from homeassistant.helpers.update_coordinator import (
) )
from bluetti_mqtt.bluetooth.client import BluetoothClient from bluetti_mqtt.bluetooth.client import BluetoothClient
from bluetti_mqtt.bluetooth import (
BadConnectionError,
ModbusError,
ParseError,
build_device,
)
from . import device_info as dev_info, get_unique_id from . import device_info as dev_info, get_unique_id
@@ -31,12 +38,13 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Setup sensor entities.""" """Setup sensor entities."""
device_name = entry.data.get(CONF_NAME)
address = entry.data.get(CONF_ADDRESS) address = entry.data.get(CONF_ADDRESS)
if address is None: if address is None:
_LOGGER.error("Device has no address") _LOGGER.error("Device has no address")
# Create coordinator for polling # Create coordinator for polling
coordinator = PollingCoordinator(hass, address) coordinator = PollingCoordinator(hass, address, device_name)
await coordinator.async_config_entry_first_refresh() await coordinator.async_config_entry_first_refresh()
# Generate device info # Generate device info
@@ -48,7 +56,7 @@ async def async_setup_entry(
class PollingCoordinator(DataUpdateCoordinator): class PollingCoordinator(DataUpdateCoordinator):
"""Polling coordinator.""" """Polling coordinator."""
def __init__(self, hass: HomeAssistant, address): def __init__(self, hass: HomeAssistant, address, device_name: str):
"""Initialize coordinator.""" """Initialize coordinator."""
super().__init__( super().__init__(
hass, hass,
@@ -59,7 +67,9 @@ class PollingCoordinator(DataUpdateCoordinator):
self._address = address self._address = address
self.notify_future = None self.notify_future = None
self.command_queue = asyncio.Queue() self.command_queue = asyncio.Queue()
self.current_command = None
self.notify_response = bytearray() self.notify_response = bytearray()
self.bluetti_device = build_device(address, device_name)
async def _async_update_data(self): async def _async_update_data(self):
"""Fetch data from API endpoint. """Fetch data from API endpoint.
@@ -67,23 +77,27 @@ class PollingCoordinator(DataUpdateCoordinator):
This is the place to pre-process the data to lookup tables This is the place to pre-process the data to lookup tables
so entities can quickly look up their data. so entities can quickly look up their data.
""" """
self.logger.error("Polling data") self.logger.debug("Polling data")
device = bluetooth.async_ble_device_from_address(self.hass, self._address) device = bluetooth.async_ble_device_from_address(self.hass, self._address)
if device is None: if device is None:
self.logger.error("Device not available") self.logger.error("Device not available")
return return
self.logger.error("BT RSSI to device %s: %s", self._address, device.rssi) if self.bluetti_device is None:
self.logger.error("Device type not found")
return
# Proceed with bluetti_mqtt parts # Fill command_queue
for command in self.bluetti_device.polling_commands:
# TODO: Fill command_queue await self.command_queue.put(command)
# Polling
client = BleakClient(device) client = BleakClient(device)
try: try:
client.connect() await client.connect()
await client.start_notify( await client.start_notify(
BluetoothClient.NOTIFY_UUID, self._notification_handler BluetoothClient.NOTIFY_UUID, self._notification_handler
) )
@@ -91,7 +105,8 @@ class PollingCoordinator(DataUpdateCoordinator):
while not self.command_queue.empty(): while not self.command_queue.empty():
try: try:
# Prepare to make request # Prepare to make request
current_command, cmd_future = self.command_queue.get() current_command = await self.command_queue.get()
self.current_command = current_command
self.notify_future = self.hass.loop.create_future() self.notify_future = self.hass.loop.create_future()
self.notify_response = bytearray() self.notify_response = bytearray()
@@ -104,17 +119,56 @@ class PollingCoordinator(DataUpdateCoordinator):
res = await asyncio.wait_for( res = await asyncio.wait_for(
self.notify_future, timeout=BluetoothClient.RESPONSE_TIMEOUT self.notify_future, timeout=BluetoothClient.RESPONSE_TIMEOUT
) )
if cmd_future:
cmd_future.set_result(res)
except:
self.logger.error("Error polling data")
# Process data
response = cast(bytes, res)
body = current_command.parse_response(response)
parsed = self.bluetti_device.parse(command.starting_address, body)
# TODO: Use parsed data
self.logger.error("Processing parsed data")
self.command_queue.task_done()
except ParseError:
self.logger.debug("Got a parse exception...")
except ModbusError as err:
self.logger.debug(
"Got an invalid request error for %s: %s",
current_command,
err,
)
except (BadConnectionError, BleakError) as err:
self.logger.debug("Needed to disconnect due to error: %s", err)
except BleakError as err:
self.logger.error("Bleak error: %s", err)
finally: finally:
client.disconnect() await client.disconnect()
def _notification_handler(self, _sender: int, data: bytearray): def _notification_handler(self, _sender: int, data: bytearray):
"""Handle bt data.""" """Handle bt data."""
self.logger.error("Data received: ", data.decode())
# Ignore notifications we don't expect
if not self.notify_future or self.notify_future.done():
return
# If something went wrong, we might get weird data.
if data == b"AT+NAME?\r" or data == b"AT+ADV?\r":
err = BadConnectionError("Got AT+ notification")
self.notify_future.set_exception(err)
return
# Save data
self.notify_response.extend(data)
if len(self.notify_response) == self.current_command.response_size():
if self.current_command.is_valid_response(self.notify_response):
self.notify_future.set_result(self.notify_response)
else:
self.notify_future.set_exception(ParseError("Failed checksum"))
elif self.current_command.is_exception_response(self.notify_response):
# We got a MODBUS command exception
msg = f"MODBUS Exception {self.current_command}: {self.notify_response[2]}"
self.notify_future.set_exception(ModbusError(msg))
class Battery(CoordinatorEntity, SensorEntity): class Battery(CoordinatorEntity, SensorEntity):