十五 Home Assistant 蓝牙

蓝牙

集成开发者最佳实践

需要使用蓝牙适配器的集成应在其manifest.jsondependencies中添加bluetooth_adaptersmanifest.json中的该项确保在集成尝试使用它们之前,所有支持的远程适配器都已连接。

调用bluetooth.async_get_scanner API来获取一个BleakScanner实例,并将其传递给你的库。返回的扫描器避免了运行多个扫描器的开销,这一点很重要。此外,如果用户更改蓝牙适配器设置,包装后的扫描器将继续正常工作。

避免在连接之间重用BleakClient,因为这会使连接不太可靠。

使用至少十(10)秒的连接超时,因为BlueZ在首次连接到新的或更新的设备时必须解析服务。连接时瞬态连接错误很常见,并且连接并非总是在第一次尝试时就能成功。bleak-retry-connector PyPI包可以消除快速可靠地建立与设备连接的猜测工作。

可连接和不可连接蓝牙控制器

Home Assistant支持远程蓝牙控制器。一些控制器仅支持监听广告数据,不支持连接到设备。由于许多设备只需要接收广告,所以我们有可连接设备和不可连接设备的概念。如果设备不需要活动连接,在这种情况下,应将connectable参数设置为False,以选择接收来自不支持向外连接的控制器的数据。当connectable设置为False时,将提供来自可连接和不可连接控制器的数据。

connectable的默认值为True。如果集成有一些设备需要连接,而一些设备不需要,manifest.json应为设备适当地设置该标志。如果无法构建匹配器来区分相似设备,检查配置流发现中的BluetoothServiceInfoBleakconnectable属性,并拒绝需要向外连接的设备的流。

获取蓝牙数据

选择获取数据的方法

如果设备通知更新的主要方法是蓝牙广播,并且其主要功能是传感器、二进制传感器或触发事件:

如果设备通知更新的主要方法是蓝牙广播,但其主要功能不是传感器、二进制传感器或触发事件:

如果您的设备仅通过主动蓝牙连接进行通信,且不使用蓝牙广播:DataUpdateCoordinator

BluetoothProcessorCoordinator

ActiveBluetoothProcessorCoordinatorPassiveBluetoothProcessorCoordinator 显著减少了创建主要功能为传感器、二进制传感器或触发事件的集成所需的代码。通过将输入到处理器协调器的数据格式化为 PassiveBluetoothDataUpdate 对象,框架可以按需创建实体,并允许最小化传感器和 binary_sensor 平台实现。

这些框架要求来自库的数据格式化为 PassiveBluetoothDataUpdate,如下所示:

@dataclasses.dataclass(frozen=True)
class PassiveBluetoothEntityKey:
    """被动蓝牙实体的键。
    示例:
    key: temperature
    device_id: outdoor_sensor_1
    """
    key: str
    device_id: str | None

@dataclasses.dataclass(frozen=True)
class PassiveBluetoothDataUpdate(Generic[_T]):
    """通用蓝牙数据。"""
    devices: dict[str | None, DeviceInfo] = dataclasses.field(default_factory=dict)
    entity_descriptions: Mapping[
        PassiveBluetoothEntityKey, EntityDescription
    ] = dataclasses.field(default_factory=dict)
    entity_names: Mapping[PassiveBluetoothEntityKey, str | None] = dataclasses.field(
        default_factory=dict
    )
    entity_data: Mapping[PassiveBluetoothEntityKey, _T] = dataclasses.field(
        default_factory=dict
    )
PassiveBluetoothProcessorCoordinator

使用 PassiveBluetoothProcessorCoordinator 的集成 __init__.py 中的示例 async_setup_entry

import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.components.bluetooth import BluetoothScanningMode
from homeassistant.components.bluetooth.passive_update_processor import (
    PassiveBluetoothProcessorCoordinator,
)
from.const import DOMAIN
from homeassistant.const import Platform

PLATFORMS: list[Platform] = [Platform.SENSOR]

from your_library import DataParser

_LOGGER = logging.getLogger(__name__)

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """从配置项设置示例BLE设备。"""
    address = entry.unique_id
    data = DataParser()
    coordinator = hass.data.setdefault(DOMAIN, {})[
        entry.entry_id
    ] = PassiveBluetoothProcessorCoordinator(
        hass,
        _LOGGER,
        address=address,
        mode=BluetoothScanningMode.ACTIVE,
        update_method=data.update,
    )
    await hass.config_entries.async_forward_entry_setups(entry,PLATFORMS)
    entry.async_on_unload(
        # 仅在所有平台有机会订阅后启动
        coordinator.async_start()
    )
    return True

示例 sensor.py

from homeassistant import config_entries
from homeassistant.components.bluetooth.passive_update_processor import (
    PassiveBluetoothDataProcessor,
    PassiveBluetoothDataUpdate,
    PassiveBluetoothEntityKey,
    PassiveBluetoothProcessorCoordinator,
    PassiveBluetoothProcessorEntity,
)
from homeassistant.components.sensor import SensorEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from.const import DOMAIN

def sensor_update_to_bluetooth_data_update(parsed_data):
    """将传感器更新转换为蓝牙数据更新。"""
    # 此函数必须将您库的update_method中的parsed_data
    # 转换为`PassiveBluetoothDataUpdate`
    # 请参阅上述结构
    return PassiveBluetoothDataUpdate(
        devices={},
        entity_descriptions={},
        entity_data={},
        entity_names={},
    )

async def async_setup_entry(
    hass: HomeAssistant,
    entry: config_entries.ConfigEntry,
    async_add_entities: AddEntitiesCallback,
) -> None:
    """设置示例BLE传感器。"""
    coordinator: PassiveBluetoothProcessorCoordinator = hass.data[DOMAIN][
        entry.entry_id
    ]
    processor = PassiveBluetoothDataProcessor(sensor_update_to_bluetooth_data_update)
    entry.async_on_unload(
        processor.async_add_entities_listener(
            ExampleBluetoothSensorEntity, async_add_entities
        )
    )
    entry.async_on_unload(coordinator.async_register_processor(processor))

class ExampleBluetoothSensorEntity(PassiveBluetoothProcessorEntity, SensorEntity):
    """示例BLE传感器的表示。"""
    @property
    def native_value(self) -> float | int | str | None:
        """返回原生值。"""
        return self.processor.entity_data.get(self.entity_key)
ActiveBluetoothProcessorCoordinator

ActiveBluetoothProcessorCoordinator 的功能与 PassiveBluetoothProcessorCoordinator 几乎相同,但还会根据 needs_poll_methodpoll_method 函数(在设备的蓝牙广播更改时调用)建立主动连接以轮询数据。sensor.py 的实现与 PassiveBluetoothProcessorCoordinator 相同。

使用 ActiveBluetoothProcessorCoordinator 的集成 __init__.py 中的示例 async_setup_entry

from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import CoreState, HomeAssistant
from homeassistant.components.bluetooth import BluetoothScanningMode
from homeassistant.components.bluetooth import (
    BluetoothScanningMode,
    BluetoothServiceInfoBleak,
    async_ble_device_from_address,
)
from homeassistant.const import Platform
from homeassistant.components.bluetooth.active_update_processor import (
    ActiveBluetoothProcessorCoordinator,
)

PLATFORMS: list[Platform] = [Platform.SENSOR]

from your_library import DataParser

_LOGGER = logging.getLogger(__name__)

async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
    """从配置项设置示例BLE设备。"""
    address = entry.unique_id
    assert address is not None
    data = DataParser()

    def _needs_poll(
        service_info: BluetoothServiceInfoBleak, last_poll: float | None
    ) -> bool:
        return (
            hass.state == CoreState.running
            and data.poll_needed(service_info, last_poll)
            and bool(
                async_ble_device_from_address(
                    hass, service_info.device.address, connectable=True
                )
            )
        )

    async def _async_poll(service_info: BluetoothServiceInfoBleak):
        if service_info.connectable:
            connectable_device = service_info.device
        elif device := async_ble_device_from_address(
            hass, service_info.device.address, True
        ):
            connectable_device = device
        else:
            # 我们没有可连接到设备的蓝牙控制器来轮询它
            raise RuntimeError(
                f"No connectable device found for {service_info.device.address}"
            )
        return await data.async_poll(connectable_device)

    coordinator = hass.data.setdefault(DOMAIN, {})[
        entry.entry_id
    ] = ActiveBluetoothProcessorCoordinator(
        hass,
        _LOGGER,
        address=address,
        mode=BluetoothScanningMode.PASSIVE,
        update_method=data.update,
        needs_poll_method=_needs_poll,
        poll_method=_async_poll,
        # 我们将接收来自不可连接设备的广播
        # 因为如果需要轮询,我们将用可连接设备替换BLEDevice
        connectable=False,
    )
    await hass.config_entries.async_forward_entry_setups(entry,PLATFORMS)
    entry.async_on_unload(
        # 仅在所有平台有机会订阅后启动
        coordinator.async_start()
    )
    return True
BluetoothCoordinator

ActiveBluetoothCoordinatorPassiveBluetoothCoordinator 协调器的功能类似于 DataUpdateCoordinators,但它们由传入的广播数据驱动,而不是轮询。

PassiveBluetoothCoordinator

以下是 PassiveBluetoothDataUpdateCoordinator 的示例。传入的数据通过 _async_handle_bluetooth_event 接收,并由集成的库进行处理。

import logging
from typing import TYPE_CHECKING
from homeassistant.components import bluetooth
from homeassistant.components.bluetooth.active_update_coordinator import (
    PassiveBluetoothDataUpdateCoordinator,
)
from homeassistant.core import CoreState, HomeAssistant, callback

if TYPE_CHECKING:
    from bleak.backends.device import BLEDevice

class ExamplePassiveBluetoothDataUpdateCoordinator(
    PassiveBluetoothDataUpdateCoordinator[None]
):
    """管理获取示例数据的类。"""
    def __init__(
        self,
        hass: HomeAssistant,
        logger: logging.Logger,
        ble_device: BLEDevice,
        device: YourLibDevice,
    ) -> None:
        """初始化示例数据协调器。"""
        super().__init__(
            hass=hass,
            logger=logger,
            address=ble_device.address,
            mode=bluetooth.BluetoothScanningMode.ACTIVE,
            connectable=False,
        )
        self.device = device

    @callback
    def _async_handle_unavailable(
        self, service_info: bluetooth.BluetoothServiceInfoBleak
    ) -> None:
        """处理设备不可用的情况。"""

    @callback
    def _async_handle_bluetooth_event(
        self,
        service_info: bluetooth.BluetoothServiceInfoBleak,
        change: bluetooth.BluetoothChange,
    ) -> None:
        """处理蓝牙事件。"""
        # 您的设备应处理传入的广播数据
ActiveBluetoothCoordinator

以下是 ActiveBluetoothDataUpdateCoordinator 的示例。传入的数据通过 _async_handle_bluetooth_event 接收,并由集成的库进行处理。

传递给 needs_poll_method 的方法在每次蓝牙广播更改时被调用,以确定是否应调用传递给 poll_method 的方法来建立与设备的主动连接以获取更多数据。

import logging
from typing import TYPE_CHECKING
from homeassistant.components import bluetooth
from homeassistant.components.bluetooth.active_update_coordinator import (
    ActiveBluetoothDataUpdateCoordinator,
)
from homeassistant.core import CoreState, HomeAssistant, callback

if TYPE_CHECKING:
    from bleak.backends.device import BLEDevice

class ExampleActiveBluetoothDataUpdateCoordinator(
    ActiveBluetoothDataUpdateCoordinator[None]
):
    """管理获取示例数据的类。"""
    def __init__(
        self,
        hass: HomeAssistant,
        logger: logging.getLogger(__name__),
        ble_device: BLEDevice,
        device: YourLibDevice,
    ) -> None:
        """初始化示例数据协调器。"""
        super().__init__(
            hass=hass,
            logger=logger,
            address=ble_device.address,
            needs_poll_method=self._needs_poll,
            poll_method=self._async_update,
            mode=bluetooth.BluetoothScanningMode.ACTIVE,
            connectable=True,
        )
        self.device = device

    @callback
    def _needs_poll(
        self,
        service_info: bluetooth.BluetoothServiceInfoBleak,
        seconds_since_last_poll: float | None,
    ) -> bool:
        # 仅在hass正在运行、需要轮询且我们实际上有办法连接到设备时轮询
        return (
            self.hass.state == CoreState.running
            and self.device.poll_needed(seconds_since_last_poll)
            and bool(
                bluetooth.async_ble_device_from_address(
                    self.hass, service_info.device.address, connectable=True
                )
            )
        )

    async def _async_update(
        self, service_info: bluetooth.BluetoothServiceInfoBleak
    ) -> None:
        """轮询设备。"""

    @callback
    def _async_handle_unavailable(
        self, service_info: bluetooth.BluetoothServiceInfoBleak
    ) -> None:
        """处理设备不可用的情况。"""

    @callback
    def _async_handle_bluetooth_event(
        self,
        service_info: bluetooth.BluetoothServiceInfoBleak,
        change: bluetooth.BluetoothChange,
    ) -> None:
        """处理蓝牙事件。"""
        # 您的设备应处理传入的广播数据

总结

该文档主要介绍了在Home Assistant中获取蓝牙数据的不同方法及相关协调器的使用。首先根据设备通知更新方式和主要功能选择合适的协调器,如 PassiveBluetoothProcessorCoordinatorActiveBluetoothProcessorCoordinatorPassiveBluetoothCoordinatorActiveBluetoothCoordinatorDataUpdateCoordinator。然后详细说明了 BluetoothProcessorCoordinator(包括 PassiveActive 两种类型)和 BluetoothCoordinator(包括 PassiveActive 两种类型)的功能、使用示例及相关数据格式要求。这些协调器有助于减少创建蓝牙相关集成所需的代码量,并能有效地处理蓝牙设备的数据更新和连接管理等操作,使开发者能更方便地在Home Assistant中集成蓝牙设备并获取其数据。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值