Make sure we always connect to last known bluetooth device in fjäråskupan (#77088)

Make sure we always connect to last known device
This commit is contained in:
Joakim Plate 2022-08-21 09:47:04 +02:00 committed by GitHub
parent 9edb25887c
commit 2689eddbe8
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 42 additions and 42 deletions

View File

@ -1,7 +1,8 @@
"""The Fjäråskupan integration.""" """The Fjäråskupan integration."""
from __future__ import annotations from __future__ import annotations
from collections.abc import Callable from collections.abc import AsyncIterator, Callable
from contextlib import asynccontextmanager
from dataclasses import dataclass from dataclasses import dataclass
from datetime import timedelta from datetime import timedelta
import logging import logging
@ -14,6 +15,7 @@ from homeassistant.components.bluetooth import (
BluetoothScanningMode, BluetoothScanningMode,
BluetoothServiceInfoBleak, BluetoothServiceInfoBleak,
async_address_present, async_address_present,
async_ble_device_from_address,
async_rediscover_address, async_rediscover_address,
async_register_callback, async_register_callback,
) )
@ -84,9 +86,20 @@ class Coordinator(DataUpdateCoordinator[State]):
def detection_callback(self, service_info: BluetoothServiceInfoBleak) -> None: def detection_callback(self, service_info: BluetoothServiceInfoBleak) -> None:
"""Handle a new announcement of data.""" """Handle a new announcement of data."""
self.device.device = service_info.device
self.device.detection_callback(service_info.device, service_info.advertisement) self.device.detection_callback(service_info.device, service_info.advertisement)
self.async_set_updated_data(self.device.state) self.async_set_updated_data(self.device.state)
@asynccontextmanager
async def async_connect_and_update(self) -> AsyncIterator[Device]:
"""Provide an up to date device for use during connections."""
if ble_device := async_ble_device_from_address(self.hass, self.device.address):
self.device.device = ble_device
async with self.device:
yield self.device
self.async_set_updated_data(self.device.state)
@dataclass @dataclass
class EntryState: class EntryState:

View File

@ -8,7 +8,6 @@ from fjaraskupan import (
COMMAND_AFTERCOOKINGTIMERMANUAL, COMMAND_AFTERCOOKINGTIMERMANUAL,
COMMAND_AFTERCOOKINGTIMEROFF, COMMAND_AFTERCOOKINGTIMEROFF,
COMMAND_STOP_FAN, COMMAND_STOP_FAN,
Device,
State, State,
) )
@ -58,7 +57,7 @@ async def async_setup_entry(
"""Set up sensors dynamically through discovery.""" """Set up sensors dynamically through discovery."""
def _constructor(coordinator: Coordinator): def _constructor(coordinator: Coordinator):
return [Fan(coordinator, coordinator.device, coordinator.device_info)] return [Fan(coordinator, coordinator.device_info)]
async_setup_entry_platform(hass, config_entry, async_add_entities, _constructor) async_setup_entry_platform(hass, config_entry, async_add_entities, _constructor)
@ -72,14 +71,12 @@ class Fan(CoordinatorEntity[Coordinator], FanEntity):
def __init__( def __init__(
self, self,
coordinator: Coordinator, coordinator: Coordinator,
device: Device,
device_info: DeviceInfo, device_info: DeviceInfo,
) -> None: ) -> None:
"""Init fan entity.""" """Init fan entity."""
super().__init__(coordinator) super().__init__(coordinator)
self._device = device
self._default_on_speed = 25 self._default_on_speed = 25
self._attr_unique_id = device.address self._attr_unique_id = coordinator.device.address
self._attr_device_info = device_info self._attr_device_info = device_info
self._percentage = 0 self._percentage = 0
self._preset_mode = PRESET_MODE_NORMAL self._preset_mode = PRESET_MODE_NORMAL
@ -90,8 +87,8 @@ class Fan(CoordinatorEntity[Coordinator], FanEntity):
new_speed = percentage_to_ordered_list_item( new_speed = percentage_to_ordered_list_item(
ORDERED_NAMED_FAN_SPEEDS, percentage ORDERED_NAMED_FAN_SPEEDS, percentage
) )
await self._device.send_fan_speed(int(new_speed)) async with self.coordinator.async_connect_and_update() as device:
self.coordinator.async_set_updated_data(self._device.state) await device.send_fan_speed(int(new_speed))
async def async_turn_on( async def async_turn_on(
self, self,
@ -111,34 +108,32 @@ class Fan(CoordinatorEntity[Coordinator], FanEntity):
ORDERED_NAMED_FAN_SPEEDS, percentage ORDERED_NAMED_FAN_SPEEDS, percentage
) )
async with self._device: async with self.coordinator.async_connect_and_update() as device:
if preset_mode != self._preset_mode: if preset_mode != self._preset_mode:
if command := PRESET_TO_COMMAND.get(preset_mode): if command := PRESET_TO_COMMAND.get(preset_mode):
await self._device.send_command(command) await device.send_command(command)
else: else:
raise UnsupportedPreset(f"The preset {preset_mode} is unsupported") raise UnsupportedPreset(f"The preset {preset_mode} is unsupported")
if preset_mode == PRESET_MODE_NORMAL: if preset_mode == PRESET_MODE_NORMAL:
await self._device.send_fan_speed(int(new_speed)) await device.send_fan_speed(int(new_speed))
elif preset_mode == PRESET_MODE_AFTER_COOKING_MANUAL: elif preset_mode == PRESET_MODE_AFTER_COOKING_MANUAL:
await self._device.send_after_cooking(int(new_speed)) await device.send_after_cooking(int(new_speed))
elif preset_mode == PRESET_MODE_AFTER_COOKING_AUTO: elif preset_mode == PRESET_MODE_AFTER_COOKING_AUTO:
await self._device.send_after_cooking(0) await device.send_after_cooking(0)
self.coordinator.async_set_updated_data(self._device.state)
async def async_set_preset_mode(self, preset_mode: str) -> None: async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode.""" """Set new preset mode."""
if command := PRESET_TO_COMMAND.get(preset_mode): if command := PRESET_TO_COMMAND.get(preset_mode):
await self._device.send_command(command) async with self.coordinator.async_connect_and_update() as device:
self.coordinator.async_set_updated_data(self._device.state) await device.send_command(command)
else: else:
raise UnsupportedPreset(f"The preset {preset_mode} is unsupported") raise UnsupportedPreset(f"The preset {preset_mode} is unsupported")
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity off.""" """Turn the entity off."""
await self._device.send_command(COMMAND_STOP_FAN) async with self.coordinator.async_connect_and_update() as device:
self.coordinator.async_set_updated_data(self._device.state) await device.send_command(COMMAND_STOP_FAN)
@property @property
def speed_count(self) -> int: def speed_count(self) -> int:

View File

@ -3,7 +3,7 @@ from __future__ import annotations
from typing import Any from typing import Any
from fjaraskupan import COMMAND_LIGHT_ON_OFF, Device from fjaraskupan import COMMAND_LIGHT_ON_OFF
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
@ -23,7 +23,7 @@ async def async_setup_entry(
"""Set up tuya sensors dynamically through tuya discovery.""" """Set up tuya sensors dynamically through tuya discovery."""
def _constructor(coordinator: Coordinator) -> list[Entity]: def _constructor(coordinator: Coordinator) -> list[Entity]:
return [Light(coordinator, coordinator.device, coordinator.device_info)] return [Light(coordinator, coordinator.device_info)]
async_setup_entry_platform(hass, config_entry, async_add_entities, _constructor) async_setup_entry_platform(hass, config_entry, async_add_entities, _constructor)
@ -36,31 +36,29 @@ class Light(CoordinatorEntity[Coordinator], LightEntity):
def __init__( def __init__(
self, self,
coordinator: Coordinator, coordinator: Coordinator,
device: Device,
device_info: DeviceInfo, device_info: DeviceInfo,
) -> None: ) -> None:
"""Init light entity.""" """Init light entity."""
super().__init__(coordinator) super().__init__(coordinator)
self._device = device
self._attr_color_mode = ColorMode.BRIGHTNESS self._attr_color_mode = ColorMode.BRIGHTNESS
self._attr_supported_color_modes = {ColorMode.BRIGHTNESS} self._attr_supported_color_modes = {ColorMode.BRIGHTNESS}
self._attr_unique_id = device.address self._attr_unique_id = coordinator.device.address
self._attr_device_info = device_info self._attr_device_info = device_info
async def async_turn_on(self, **kwargs: Any) -> None: async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the light on.""" """Turn the light on."""
if ATTR_BRIGHTNESS in kwargs: async with self.coordinator.async_connect_and_update() as device:
await self._device.send_dim(int(kwargs[ATTR_BRIGHTNESS] * (100.0 / 255.0))) if ATTR_BRIGHTNESS in kwargs:
else: await device.send_dim(int(kwargs[ATTR_BRIGHTNESS] * (100.0 / 255.0)))
if not self.is_on: else:
await self._device.send_command(COMMAND_LIGHT_ON_OFF) if not self.is_on:
self.coordinator.async_set_updated_data(self._device.state) await device.send_command(COMMAND_LIGHT_ON_OFF)
async def async_turn_off(self, **kwargs: Any) -> None: async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity off.""" """Turn the entity off."""
if self.is_on: if self.is_on:
await self._device.send_command(COMMAND_LIGHT_ON_OFF) async with self.coordinator.async_connect_and_update() as device:
self.coordinator.async_set_updated_data(self._device.state) await device.send_command(COMMAND_LIGHT_ON_OFF)
@property @property
def is_on(self) -> bool: def is_on(self) -> bool:

View File

@ -1,8 +1,6 @@
"""Support for sensors.""" """Support for sensors."""
from __future__ import annotations from __future__ import annotations
from fjaraskupan import Device
from homeassistant.components.number import NumberEntity from homeassistant.components.number import NumberEntity
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import TIME_MINUTES from homeassistant.const import TIME_MINUTES
@ -23,9 +21,7 @@ async def async_setup_entry(
def _constructor(coordinator: Coordinator) -> list[Entity]: def _constructor(coordinator: Coordinator) -> list[Entity]:
return [ return [
PeriodicVentingTime( PeriodicVentingTime(coordinator, coordinator.device_info),
coordinator, coordinator.device, coordinator.device_info
),
] ]
async_setup_entry_platform(hass, config_entry, async_add_entities, _constructor) async_setup_entry_platform(hass, config_entry, async_add_entities, _constructor)
@ -45,13 +41,11 @@ class PeriodicVentingTime(CoordinatorEntity[Coordinator], NumberEntity):
def __init__( def __init__(
self, self,
coordinator: Coordinator, coordinator: Coordinator,
device: Device,
device_info: DeviceInfo, device_info: DeviceInfo,
) -> None: ) -> None:
"""Init number entities.""" """Init number entities."""
super().__init__(coordinator) super().__init__(coordinator)
self._device = device self._attr_unique_id = f"{coordinator.device.address}-periodic-venting"
self._attr_unique_id = f"{device.address}-periodic-venting"
self._attr_device_info = device_info self._attr_device_info = device_info
self._attr_name = "Periodic venting" self._attr_name = "Periodic venting"
@ -64,5 +58,5 @@ class PeriodicVentingTime(CoordinatorEntity[Coordinator], NumberEntity):
async def async_set_native_value(self, value: float) -> None: async def async_set_native_value(self, value: float) -> None:
"""Set new value.""" """Set new value."""
await self._device.send_periodic_venting(int(value)) async with self.coordinator.async_connect_and_update() as device:
self.coordinator.async_set_updated_data(self._device.state) await device.send_periodic_venting(int(value))