diff --git a/custom_components/hunterdouglas_powerview_ble/__init__.py b/custom_components/hunterdouglas_powerview_ble/__init__.py index f0da803..fef2f61 100644 --- a/custom_components/hunterdouglas_powerview_ble/__init__.py +++ b/custom_components/hunterdouglas_powerview_ble/__init__.py @@ -200,8 +200,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntryType) -> boo unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS) if unload_ok: - for coordinator in entry.runtime_data.values(): - coordinator._async_stop() # noqa: SLF001 entry.runtime_data.clear() LOGGER.debug("Unloaded config entry: %s, ok? %s!", entry.unique_id, str(unload_ok)) diff --git a/custom_components/hunterdouglas_powerview_ble/api.py b/custom_components/hunterdouglas_powerview_ble/api.py index 5368941..58a361f 100644 --- a/custom_components/hunterdouglas_powerview_ble/api.py +++ b/custom_components/hunterdouglas_powerview_ble/api.py @@ -219,27 +219,27 @@ class PowerViewBLE: raise @staticmethod - def dec_manufacturer_data(data: bytearray) -> list[tuple[str, float]]: + def dec_manufacturer_data(data: bytearray) -> dict[str, float | int | bool]: """Decode manufacturer data from BLE advertisement V2.""" if len(data) != 9: LOGGER.debug("not a V2 record!") - return [] + return {} pos: Final[int] = int.from_bytes(data[3:5], byteorder="little") pos2: Final[int] = (int(data[5]) << 4) + (int(data[4]) >> 4) - return [ - (ATTR_CURRENT_POSITION, ((pos >> 2) / 10)), - ("position2", pos2 >> 2), - ("position3", int(data[6])), - (ATTR_CURRENT_TILT_POSITION, int(data[7])), - ("home_id", int.from_bytes(data[0:2], byteorder="little")), - ("type_id", int(data[2])), - ("is_opening", bool(pos & 0x3 == 0x2)), - ("is_closing", bool(pos & 0x3 == 0x1)), - ("battery_charging", bool(pos & 0x3 == 0x3)), # observed - ("battery_level", POWER_LEVELS[(data[8] >> 6)]), # cannot hit 4 - ("resetMode", bool(data[8] & 0x1)), - ("resetClock", bool(data[8] & 0x2)), - ] + return { + ATTR_CURRENT_POSITION: (pos >> 2) / 10, + "position2": pos2 >> 2, + "position3": int(data[6]), + ATTR_CURRENT_TILT_POSITION: int(data[7]), + "home_id": int.from_bytes(data[0:2], byteorder="little"), + "type_id": int(data[2]), + "is_opening": bool(pos & 0x3 == 0x2), + "is_closing": bool(pos & 0x3 == 0x1), + "battery_charging": bool(pos & 0x3 == 0x3), # observed + "battery_level": POWER_LEVELS[(data[8] >> 6)], # cannot hit 4 + "resetMode": bool(data[8] & 0x1), + "resetClock": bool(data[8] & 0x2), + } # position cmd: uint16_t pos1, uint16_t pos2, uint16_t pos3, uint16_t tilt, uint8_t velocity async def set_position( diff --git a/custom_components/hunterdouglas_powerview_ble/button.py b/custom_components/hunterdouglas_powerview_ble/button.py index 6826126..fcf1a3d 100644 --- a/custom_components/hunterdouglas_powerview_ble/button.py +++ b/custom_components/hunterdouglas_powerview_ble/button.py @@ -12,7 +12,7 @@ from homeassistant.components.button import ( ) from homeassistant.const import EntityCategory from homeassistant.core import HomeAssistant -from homeassistant.helpers.device_registry import DeviceInfo, format_mac +from homeassistant.helpers.device_registry import format_mac from homeassistant.helpers.entity_platform import AddEntitiesCallback from . import ConfigEntryType, async_setup_shade_platform @@ -50,7 +50,6 @@ class PowerViewButton(PassiveBluetoothCoordinatorEntity[PVCoordinator], ButtonEn """Representation of a powerview shade.""" _attr_has_entity_name = True - _attr_device_class = ButtonDeviceClass.IDENTIFY def __init__( self, @@ -66,11 +65,6 @@ class PowerViewButton(PassiveBluetoothCoordinatorEntity[PVCoordinator], ButtonEn ) super().__init__(coordinator) - @property - def device_info(self) -> DeviceInfo: # type: ignore[reportIncompatibleVariableOverride] - """Return the device_info of the device.""" - return self._coord.device_info - async def async_press(self) -> None: """Handle the button press.""" LOGGER.debug("identify cover") diff --git a/custom_components/hunterdouglas_powerview_ble/coordinator.py b/custom_components/hunterdouglas_powerview_ble/coordinator.py index 0fb07d5..e6e7a67 100644 --- a/custom_components/hunterdouglas_powerview_ble/coordinator.py +++ b/custom_components/hunterdouglas_powerview_ble/coordinator.py @@ -28,7 +28,6 @@ class PVCoordinator(PassiveBluetoothDataUpdateCoordinator): ) -> None: """Initialize BMS data coordinator.""" assert ble_device.name is not None - self._mac = ble_device.address self._friendly_name = friendly_name or ble_device.name home_key_hex: str = data.get(CONF_HOME_KEY, "") home_key: bytes = ( @@ -99,7 +98,7 @@ class PVCoordinator(PassiveBluetoothDataUpdateCoordinator): @property def device_present(self) -> bool: """Check if a device is present.""" - return bluetooth.async_address_present(self.hass, self._mac, connectable=True) + return bluetooth.async_address_present(self.hass, self.address, connectable=True) def _async_stop(self) -> None: """Shutdown coordinator and any connection.""" @@ -117,14 +116,17 @@ class PVCoordinator(PassiveBluetoothDataUpdateCoordinator): LOGGER.debug("BLE event %s: %s", change, service_info.manufacturer_data) self.api.set_ble_device(service_info.device) - self.data = {ATTR_RSSI: service_info.rssi} + new_data: dict[str, int | float | bool] = {ATTR_RSSI: service_info.rssi} if change == bluetooth.BluetoothChange.ADVERTISEMENT: - self.data.update( + new_data.update( self.api.dec_manufacturer_data( bytearray(service_info.manufacturer_data.get(2073, b"")) ) ) - self.api.encrypted = bool(self.data.get("home_id")) + self.api.encrypted = bool(new_data.get("home_id")) + if new_data == self.data: + return + self.data = new_data LOGGER.debug("data sample %s", self.data) super()._async_handle_bluetooth_event(service_info, change) diff --git a/custom_components/hunterdouglas_powerview_ble/cover.py b/custom_components/hunterdouglas_powerview_ble/cover.py index 3c6f283..c47af27 100644 --- a/custom_components/hunterdouglas_powerview_ble/cover.py +++ b/custom_components/hunterdouglas_powerview_ble/cover.py @@ -249,7 +249,7 @@ class PowerViewCoverTilt(PowerViewCover): async def async_stop_cover_tilt(self, **kwargs: Any) -> None: """Stop the cover.""" - await self.async_stop_cover(kwargs=kwargs) + await self.async_stop_cover(**kwargs) async def async_open_cover_tilt(self, **kwargs: Any) -> None: """Open the cover tilt."""