十 Home Assistant获取数据

获取数据

你的集成需要从API获取数据,以便能够将其提供给Home Assistant。这个API可以通过网络(本地或云端)、套接字、通过USB记忆棒暴露的串行端口等提供。

推送与轮询

API有多种不同的形式,但核心上可分为两类:推送和轮询。

  • 推送:我们订阅一个API,当有新数据可用时,API会通知我们。它将更改推送给我们。推送API很棒,因为它们消耗的资源较少。当发生更改时,我们可以收到更改通知,而不必重新获取所有数据并查找更改。由于实体可以被禁用,你应该确保你的实体在async_added_to_hass回调中进行订阅,并在移除时取消订阅。
  • 轮询:我们将按指定的时间间隔从API获取最新数据。然后,你的集成将把这些数据提供给它的实体,实体再将数据写入Home Assistant。

因为轮询非常常见,Home Assistant默认假设你的实体基于轮询。如果不是这种情况,从Entity.should_poll属性返回False。当你禁用轮询时,你的集成将负责调用其中一种方法,以指示Home Assistant是时候将实体状态写入Home Assistant了:
- 如果你在异步函数中执行且不需要调用实体更新方法,调用Entity.async_write_ha_state()。这是一个异步回调,它将在向事件循环让步的同时将状态写入状态机。
- Entity.schedule_update_ha_state(force_refresh=False) / Entity.async_schedule_update_ha_state(force_refresh=False)将安排实体更新。如果force_refresh设置为True,Home Assistant将在写入状态之前调用你的实体更新方法(update() / async_update())。

轮询API端点

我们将在这里解释几种不同的API类型以及将它们集成到Home Assistant中的最佳方法。请注意,一些集成将遇到以下几种类型的组合。

为所有实体协调进行单一API轮询获取数据

这个API将有一个单一的方法来获取Home Assistant中所有实体的数据。在这种情况下,我们将希望对这个端点进行单一的定期轮询,然后在有新数据可用时立即通知实体。
Home Assistant提供了一个DataUpdateCoordinator类来帮助你尽可能高效地管理这个过程。

“使用DataUpdateCoordinator的示例集成”。

from datetime import timedelta
import logging
import async_timeout
from homeassistant.components.light import LightEntity
from homeassistant.core import callback
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import (
    CoordinatorEntity,
    DataUpdateCoordinator,
    UpdateFailed,
)
from.const import DOMAIN

_LOGGER = logging.getLogger(__name__)

async def async_setup_entry(hass, entry, async_add_entities):
    """配置项示例。"""
    # 假设API对象在这里由__init__.py存储
    my_api = hass.data[DOMAIN][entry.entry_id]
    coordinator = MyCoordinator(hass, my_api)
    # 获取初始数据,以便在实体订阅时有数据可用
    #
    # 如果刷新失败,async_config_entry_first_refresh将
    # 抛出ConfigEntryNotReady异常,设置将稍后重试
    #
    # 如果你不想在失败时重试设置,使用
    # coordinator.async_refresh()代替
    #
    await coordinator.async_config_entry_first_refresh()
    async_add_entities(
        MyEntity(coordinator, idx) for idx, ent in enumerate(coordinator.data)
    )

class MyCoordinator(DataUpdateCoordinator):
    """我的自定义协调器。"""
    def __init__(self, hass, my_api):
        """初始化我的协调器。"""
        super().__init__(
            hass,
            _LOGGER,
            # 数据名称。用于日志记录目的。
            name="My sensor",
            # 轮询间隔。只有在有订阅者时才会轮询。
            update_interval=timedelta(seconds=30),
        )
        self.my_api = my_api

    async def _async_update_data(self):
        """从API端点获取数据。
        这是预处理数据到查找表的地方,
        以便实体可以快速查找它们的数据。
        """
        try:
            # 注意:asyncio.TimeoutError和aiohttp.ClientError已经
            # 由数据更新协调器处理。
            async with async_timeout.timeout(10):
                # 获取活动上下文变量,以限制从API获取的数据
                # 注意:如果没有必要或无法限制从API检索的数据,则不需要使用上下文。
                listening_idx = set(self.async_contexts())
                return await self.my_api.fetch_data(listening_idx)
        except ApiAuthError as err:
            # 抛出ConfigEntryAuthFailed将取消未来的更新
            # 并启动一个配置流程,源为SOURCE_REAUTH(async_step_reauth)
            raise ConfigEntryAuthFailed from err
        except ApiError as err:
            raise UpdateFailed(f"与API通信时出错:{err}")

class MyEntity(CoordinatorEntity, LightEntity):
    """使用CoordinatorEntity的实体。
    CoordinatorEntity类提供:
    should_poll
    async_update
    async_added_to_hass
    available
    """
    def __init__(self, coordinator, idx):
        """将协调器传递给CoordinatorEntity。"""
        super().__init__(coordinator, context=idx)
        self.idx = idx

    @callback
    def _handle_coordinator_update(self) -> None:
        """处理来自协调器的更新数据。"""
        self._attr_is_on = self.coordinator.data[self.idx]["state"]
        self.async_write_ha_state()

    async def async_turn_on(self, **kwargs):
        """打开灯。
        示例方法,展示如何请求数据更新。
        """
        # 执行打开操作。
        #...
        # 更新数据
        await self.coordinator.async_request_refresh()
为每个单独实体单独轮询

一些API将为每个设备提供一个端点。有时可能无法将API中的设备映射到单个实体。如果你从单个API设备端点创建多个实体,请参阅上一节。
如果你可以将一个设备端点精确映射到单个实体,你可以在update() / async_update()方法中获取这个实体的数据。确保轮询设置为True,Home Assistant将定期调用这个方法。
如果你的实体在首次写入Home Assistant之前需要获取数据,将update_before_add=True传递给add_entities方法:add_entities([MyEntity()], update_before_add=True)
你可以通过在你的平台中定义SCAN_INTERVAL常量来控制集成的轮询间隔。注意不要将其设置得太低。这将占用Home Assistant中的资源,可能会使承载API的设备不堪重负,或者可能导致你被云API阻止。允许的最小值是5秒。

from datetime import timedelta
SCAN_INTERVAL = timedelta(seconds=5)

推送API端点

如果你有一个推送数据的API端点,如果你愿意,仍然可以使用数据更新协调器。通过不向构造函数传递轮询参数update_methodupdate_interval来实现。
当新数据到达时,使用coordinator.async_set_updated_data(data)将数据传递给实体。如果在轮询的协调器上使用此方法,它将重置到下次轮询数据的时间。

请求并行性

注意:这是一个高级主题。
Home Assistant有内置逻辑来确保集成不会过度请求API并消耗Home Assistant中的所有可用资源。这个逻辑是围绕限制并行请求的数量构建的。这个逻辑在服务调用和实体更新期间自动使用。
Home Assistant通过为每个集成维护一个信号量来控制并行更新(对update()的调用)的数量。例如,如果信号量允许1个并行连接,如果有一个正在进行中,更新和服务调用将等待。如果值为0,集成本身负责在必要时限制并行请求的数量。
平台的并行请求默认值是根据添加到Home Assistant的第一个实体决定的。如果实体定义了async_update方法,则为0,否则为1(这是一个遗留决策)。
平台可以通过在其平台中定义PARALLEL_UPDATES常量(例如rflink/light.py)来覆盖默认值。

总结

此页面主要介绍了在Home Assistant集成中获取数据的相关内容,包括推送与轮询两种方式获取数据的特点及使用场景,详细阐述了轮询API端点中为所有实体协调进行单一API轮询和为每个单独实体单独轮询的方法及示例代码,提及推送API端点如何使用数据更新协调器处理新数据,最后介绍了Home Assistant控制集成请求并行性的逻辑及相关参数设置。

评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

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

抵扣说明:

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

余额充值