构建 Home Assistant 自定义组件(第一部分):项目结构与基础
项目结构
引言
本系列博客文章将是一个创建 Home Assistant 自定义组件的教程。我们将从一个基础组件开始,并在每篇文章中对其进行扩展。在教程结束时,你将拥有一个功能完备的组件,在集成质量量表上至少应获得银牌分数。
对于这个项目,我们将使用 GitHub API 为自定义组件提供数据。虽然已有一个 GitHub 集成,但我们将创建自己的集成,并尝试通过添加单元测试、通过 UI 进行配置以及希望添加更多功能来改进现有集成。
每篇文章将是 GitHub 存储库中的一个不同分支,因此你可以在自己的编辑器中跟进,或者通过查看相应分支来浏览每篇文章的代码。此部分添加的更改可在 feature/part1 分支上查看。
我建议查看官方开发者文档,以便更好地理解 Home Assistant 架构中的所有概念。
项目结构
要开始,我们需要为自定义组件生成基本文件。幸运的是,使用我的 cookiecutter 项目模板很容易做到这一点。
让我们安装 cookiecutter 并通过回答一些提示来创建项目。
```powershell
$ pip install cookiecutter
$ cookiecutter https://github.com/boralyl/cookiecutter-homeassistant-component
domain [my_component]: github_custom
name [My Component]: Github Custom
docs_url [https://github.com/user/my_component/]: https://github.com/boralyl/github-custom-component-tutorial
owner [@user]: @boralyl
Select config_flow:
1 - yes
2 - no
Choose from 1, 2 [1]: 2
Select iot_class:
1 - Assumed State
2 - Cloud Polling
3 - Cloud Push
4 - Local Polling
5 - Local Push
Choose from 1, 2, 3, 4, 5 [1]: 2
注意:开始时我们将跳过使用配置流程(Config Flow)。我们将在本教程的后续文章中添加此功能。
项目树
目前我们将忽略根目录和 tests 目录中的文件。让我们专注于custom_components目录以及其中的github_custom目录。manifest.json包含有关我们组件的一些基本信息,Home Assistant 在设置组件时将使用这些信息。const.py只包含我们的常量,在这种情况下就是组件的 DOMAIN。init.py包含async_setup方法,Home Assistant 将调用此方法来设置我们的组件。
此时,自定义组件是有效的,如果你将其放置在 Home Assistant 的config目录中的custom_components目录内,它将正确加载,但实际上它不会创建任何实体。
实现组件
现在是时候开始编写我们的组件了。为此基本上有 4 个部分。
将我们的需求添加到manifest.json中。如果我们需要添加外部 Python 依赖项,则需要在此处添加。
添加我们的平台配置模式。这将定义用户在其configuration.yaml中添加此集成时期望的值。
向 Home Assistant 注册我们所有的传感器。这将在我们的async_setup_platform函数中完成。
创建一个新实体,该实体表示我们要收集的有关每个 GitHub 存储库的状态和数据。此实体还应实现async_update方法,该方法从 GitHub 更新数据。
这 4 个部分的最终实现可以在此差异中看到。在该差异中需要注意的一点是,我从__init__.py中删除了async_setup函数。因为我们的集成使用平台,所以我们可以删除该代码。平台允许你有多个集成实例,而不仅仅是一个。如果你同时监视公共存储库和可能具有自己 GitHub Enterprise 服务器 URL 的一些私有存储库,这将非常有用。
向 manifest.json 添加需求
我们将使用 gidgethub 库与 GitHub API 进行交互。它开箱即支持异步通信,并且使用简单直接。
{
"documentation": "https://github.com/boralyl/github-custom-component-tutorial",
"domain": "github_custom",
"name": "Github Custom",
- "requirements": []
+ "requirements": ["gidgethub[aiohttp]==4.1.1"]
}
我们只需将需求添加到requirements数组中,并指定固定版本号。关于此特定库需要注意的一点是,异步需求是可选的。为了确保安装那些依赖项,我们需要在需求中指定aiohttp额外内容。
平台配置模式
对于我们的平台配置模式,我们将遵循官方 GitHub 集成的模式。一个基本示例如下所示:
yaml
复制
Example configuration.yaml entry
sensor:
- platform: github_custom
access_token:!secret github_access_token
repositories:
- path: "home-assistant/core"
name: "Home Assistant Core"
- path: "boralyl/steam-wishlist"
- platform: github_custom
url: https://my.enterprisegithubserver.com
access_token:!secret github_access_token
repositories:
- path: "company/some-repo"
我们在这里不做任何更改,因此模式将与官方集成相同:
REPO_SCHEMA = vol.Schema(
{vol.Required(CONF_PATH): cv.string, vol.Optional(CONF_NAME): cv.string}
)
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_ACCESS_TOKEN): cv.string,
vol.Required(CONF_REPOS): vol.All(cv.ensure_list, [REPO_SCHEMA]),
vol.Optional(CONF_URL): cv.url,
}
)
上面的内容非常直接。我们需要一个访问令牌和一个存储库列表。每个存储库必须有一个path键,并且可以可选地有一个name键。我们还可选地允许一个url键,可用于 GitHub Enterprise 服务器 URL。
上述代码本质上扩展了 Home Assistant 平台模式,以将我们的域github_custom添加到其中,并带有上述模式。它将为我们处理验证并在适当的时候显示错误。
注册传感器
下一步是注册我们所有的传感器。对于平台配置中指定的每个存储库,我们将有一个传感器。按照惯例,Home Assistant 将在你的sensor.py文件中查找setup_platform或async_setup_platform函数。如果你的数据将使用使用 asyncio 异步获取数据的库进行更新,那么你应该声明async_setup_platform函数,否则创建setup_platform函数。由于我们要使用的库 gidgethub 支持异步,我们将使用async_setup_platform函数。
async def async_setup_platform(
hass: HomeAssistantType,
config: ConfigType,
async_add_entities: Callable,
discovery_info: Optional[DiscoveryInfoType] = None,
) -> None:
"""Set up the sensor platform."""
session = async_get_clientsession(hass)
github = GitHubAPI(session, "requester", oauth_token=config[CONF_ACCESS_TOKEN])
sensors = [GitHubRepoSensor(github, repo) for repo in config[CONF_REPOS]]
async_add_entities(sensors, update_before_add=True)
在此函数中,我们首先检索一个 aiohttp 客户端会话。这个辅助函数负责为我们检索和关闭会话(少了一件需要考虑的事情)。我们初始化我们的 GitHub API 客户端,并为configuration.yaml中指定的每个存储库创建一个GitHubRepoSensor。
async_add_entities函数将处理向 Home Assistant 添加和注册这些传感器。第二个参数也值得注意。将其设置为True将告诉 Home Assistant 在集成完成设置时应进行数据更新。如果未指定(或将其设置为False),它将等到SCAN_INTERVAL才从 GitHub 获取数据。由于该常量设置为 10 分钟,这意味着在 Home Assistant 重新启动后的前 10 分钟内,我们的传感器将没有数据(或者它将从重新启动前的上一次更新中恢复数据)。
GitHubRepoSensor 实体和 async_update
我们组件的最后一部分是定义我们的实体并指定更新方法。为简洁起见,我不会包含完整的类,但你可以在 GitHub 上查看它。
主要要点是它扩展了homeassistant.helpers.entity.Entity,这个类为你实现了大多数必需的逻辑。我们定义自己的state属性,该属性返回传感器的状态。对于此传感器,我们将使用提交哈希的前 7 个字符。我们还定义了一个device_state_attributes属性,该属性返回与状态相关的属性,可由自动化和 lovelace UI 访问。
我们的自定义传感器类还必须指定update或async_update方法,用于从 GitHub 获取数据以填充我们的状态和设备状态属性。同样,由于我们的组件使用支持异步的库,我们包含一个async_update方法。它从 GitHub 获取我们需要的所有数据。Home Assistant 将根据我们定义的间隔调用此方法。惯例是查找名为SCAN_INTERVAL的常量。如果你在文件中定义它,其值应该是一个datetime.timedelta实例。我们将为我们的组件使用 10 分钟的更新间隔。
SCAN_INTERVAL = timedelta(minutes=10)
总结
所以现在我们有了一个功能完备的异步自定义组件,这是对官方集成的改进。
在下一篇文章中,我们将简要讨论如何添加单元测试以帮助改进你的自定义组件并在其他人之前捕获错误。我们还将使用 GitHub Actions 为你的自定义组件添加持续集成。