构建 Home Assistant 自定义组件(第三部分):配置流程
引言
在本部分中,我们将更新自定义组件,使其能够通过用户界面(UI)进行配置,这通过添加配置流程(Config Flow)来实现。我们仍使用之前的示例项目 github - custom - component。你可以在 feature/part3 分支上找到本文的差异。
更新 manifest.json
第一步是更新manifest.json。我们将config_flow键设置为true,这将让 Home Assistant 知道此组件可以通过配置 UI 添加。
{
"codeowners": ["@boralyl"],
- "config_flow": false,
+ "config_flow": true,
"dependencies": [],
"documentation": "https://github.com/boralyl/github-custom-component-tutorial",
"domain": "github_custom"
}
添加配置流程
接下来,我们将创建config_flow.py文件。在这个文件中,我们将扩展ConfigFlow类,并定义用户首次设置组件时在 UI 中应显示的不同步骤。
在撰写本文时,通过配置流程处理需要未知数量配置值列表的组件并非易事。为了尝试绕过此限制,我决定将配置流程设计为两个步骤。第一步要求用户提供 GitHub 访问令牌和可选的企业服务器 URL。提交该信息后,用户进入第二步,允许他们输入存储库和可选名称。为了允许用户添加其他存储库,我添加了一个复选框,如果选中,将重复第二步。用户可以根据需要多次执行此操作,直到添加了他们希望为其创建传感器的所有存储库。
用户步骤
当用户点击添加按钮并选择 GitHub Custom 集成时,将调用配置流程类的async_step_user方法。
初始化配置流程
让我们逐步了解此方法的功能。
async def async_step_user(self, user_input: Optional[Dict[str, Any]] = None):
"""Invoked when a user initiates a flow via the user interface."""
errors: Dict[str, str] = {}
if user_input is not None:
try:
await validate_auth(user_input[CONF_ACCESS_TOKEN], self.hass)
except ValueError:
errors["base"] = "auth"
if not errors:
# 输入有效,设置数据
self.data = user_input
self.data[CONF_REPOS] = []
# 返回下一步的表单
return await self.async_step_repo()
return self.async_show_form(
step_id="user", data_schema=AUTH_SCHEMA, errors=errors
)
首次调用此步骤时,user_input变量默认为None。当用户点击提交按钮时,该变量将填充包含他们输入数据的字典。Home Assistant 将根据你定义的数据模式为你进行一些基本验证。我添加了一些额外的验证,将使用提供的访问令牌来确保其有效性。如果失败,我们将基本错误设置为auth。此值与strings.json中的错误对象相对应,并将显示那里定义的描述。
如果没有错误,数据将存储在类的self.data属性中。除了存储输入数据外,我还为下一步要添加的存储库初始化了一个空列表。最后,我们调用下一步的方法async_step_repo,将用户推进到第二个表单,在那里他们可以输入他们想要监控的所有 GitHub 存储库。
存储库步骤
用户成功完成初始步骤后,将调用async_step_repo方法。此步骤负责显示一个表单以输入存储库信息。如果用户勾选了 “添加另一个存储库” 复选框,则我们保存输入的数据并在提交时重置表单。
存储库流程步骤
此方法中的逻辑与第一步非常相似。
async def async_step_repo(self, user_input: Optional[Dict[str, Any]] = None):
"""Second step in config flow to add a repo to watch."""
errors: Dict[str, str] = {}
if user_input is not None:
# 验证路径
try:
validate_path(user_input[CONF_PATH])
except ValueError:
errors["base"] = "invalid_path"
if not errors:
# 输入有效,设置数据
self.data[CONF_REPOS].append(
{
"path": user_input[CONF_PATH],
"name": user_input.get(CONF_NAME, user_input[CONF_PATH]),
}
)
# 如果用户勾选了该框,则再次显示此表单,以便他们可以添加另一个存储库
if user_input.get("add_another", False):
return await self.async_step_repo()
# 用户完成添加存储库,创建配置项
return self.async_create_entry(title="GitHub Custom", data=self.data)
return self.async_show_form(
step_id="repo", data_schema=REPO_SCHEMA, errors=errors
)
一个关键区别是,如果勾选了add_another复选框,我们将继续返回当前步骤。当用户完成时,最后一步是调用async_create_entry方法,该方法将创建我们的配置项并在 Home Assistant 中注册。
设置配置项
接下来我们需要从创建的配置项中设置传感器。在__init__.py文件中,我们定义一个async_setup_entry函数,该函数将任务转发到传感器平台。有关其工作原理的更多详细信息,我建议你查看有关该主题的优秀文档。
async def async_setup_entry(
hass: core.HomeAssistant, entry: config_entries.ConfigEntry
) -> bool:
"""Set up platform from a ConfigEntry."""
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][entry.entry_id] = entry.data
# 将设置转发到传感器平台
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(entry, "sensor")
)
return True
在上面的函数中,我们将配置项的数据存储在hass中,键为我们的DOMAIN。这将允许我们在用户多次设置集成时存储多个配置项。例如,他们可能有一个用于工作的企业服务器账户和一个常规的个人账户。他们可以为每种情况设置两个不同的项。
然后我们将设置转发到sensor平台。在sensor.py中,我们添加一个async_setup_entry函数,该函数将接受一个配置项实例并为组件创建传感器。你会注意到这个函数看起来几乎与它下面用于从configuration.yaml设置传感器的async_setup_platform函数相同。唯一的区别是我们从配置项实例中检索配置数据。
async def async_setup_entry(
hass: core.HomeAssistant,
config_entry: config_entries.ConfigEntry,
async_add_entities,
):
"""Setup sensors from a config entry created in the integrations UI."""
config = hass.data[DOMAIN][config_entry.entry_id]
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)
翻译
在解释错误如何定义时,我简要提到了strings.json。此文件包含配置流程中使用的字符串。我将strings.json复制到translations文件夹中,并将其重命名为en.json用于英语翻译。你可以根据需要添加任意数量的翻译文件,它们应使用两个字母的 ISO 639 - 2 语言代码命名。所有键应与strings.json相同,值应为翻译后的字符串。例如,这是我的另一个自定义组件的挪威语翻译文件:nb.json。
单元测试
我想简要介绍如何对配置流程进行单元测试。如果你安装并使用pytest - home - assistant - custom - component,你可以利用一些pytest夹具,使测试更加简单。
让我们看一个测试,以验证如果 GitHub 访问令牌无效,我们是否显示错误。
@patch("custom_components.github_custom.config_flow.validate_auth")
async def test_flow_user_init_invalid_auth_token(m_validate_auth, hass):
"""Test errors populated when auth token is invalid."""
m_validate_auth.side_effect = ValueError
_result = await hass.config_entries.flow.async_init(
config_flow.DOMAIN, context={"source": "user"}
)
result = await hass.config_entries.flow.async_configure(
_result["flow_id"], user_input={CONF_ACCESS_TOKEN: "bad"}
)
assert {"base": "auth"} == result["errors"]
在这个测试中,我们模拟validate_auth函数并使其引发ValueError。传递给我们测试的hass参数来自pytest - home - assistant - custom - component安装的pytest夹具。首先,我们通过指定我们的域和步骤(在这种情况下为user)来初始化流程。然后我们在流程中运行该步骤并传入用户输入。结果包含一个errors键,我们断言它与我们的期望匹配。
下一步
有了此代码,我们现在可以通过 UI 而不是configuration.yaml文件配置和添加存储库。在开发新的配置流程时,当你修改文件并重新启动 Home Assistant 后,请确保在浏览器中进行硬刷新。我注意到浏览器可能会缓存一些此信息,导致你看到过时的数据。
我们实现中的一个明显问题是,没有办法在不通过再次初始化流程创建新配置项的情况下删除或添加新存储库。虽然这样可行,但并不理想,因为每次都需要重新输入 GitHub 访问令牌。在下一篇文章中,我们将研究如何使用 OptionsFlowHandler 来绕过此限制。
2410

被折叠的 条评论
为什么被折叠?



