0%

配置

配置项(config entries)

配置项是Home Assistant持久存储的配置数据。配置条目是由用户通过UI创建的。UI流由组件定义的config flow handler提供支持。配置条目也可以有一个额外的options flow handler,也是由组件定义的。

生命周期

状态名称 描述
未加载(not loaded) 配置项尚未加载。这是在创建配置条目或重新启动Home Assistant时的初始状态。
已加载(loaded) 配置项已加载
setup error 试图设置配置项时发生错误
setup retry 配置条目的依赖项还没有准备好。Home Assistant将在将来自动重试加载此配置条目。每次尝试的间隔时间会自动增加。
migration error 配置条目必须迁移到新版本,但是迁移失败了
failed unload 试图卸载配置条目,但这要么不被支持,要么引发异常。

820c44e44b674ba4b895f885f9089673

设置一个配置项

在启动期间,Home Assistant首先调用normal component setup,然后为每个条目调用方法async_setup_entry(hass,entry)。如果在运行时创建了一个新的配置项,async_setup_entry(hass, Entry)`(示例)。

对于平台

如果组件包含平台,则需要将Config Entry转发到平台。这可以通过调用配置项管理器上的forward函数来实现(示例

1
2
3
4
5
6
# Use `hass.async_create_task` to avoid a circular dependency between the platform and the component
hass.async_create_task(
hass.config_entries.async_forward_entry_setup(
config_entry, "light"
)
)

对于一个支持配置项的平台,它需要添加一个setup条目方法(示例):

1
2
async def async_setup_entry(hass, config_entry, async_add_devices):
"""Set up entry."""

卸载配置项

组件可以选择支持卸载配置项。当卸载一个项时,组件需要清理所有实体,取消订阅任何事件监听器并关闭所有连接。要实现这一点,请向组件(示例)添加async_unload_entry(hass, entry)

对于您将配置项转发到的每个平台,您也需要转发卸载。

1
await self.hass.config_entries.async_forward_entry_unload(self.config_entry, "light")

如果你需要清理平台中实体使用的资源,让实体实现async_will_remove_from_hass方法。

删除配置项

如果一个组件需要在删除条目时清理代码,它可以定义一个删除方法:

1
2
async def async_remove_entry(hass, entry) -> None:
"""Handle removal of an entry."""

数据输入流(Data Entry Flow)

数据输入流是一个数据输入框架,是HA的一部分。数据输入是通过数据输入流完成的。一个流可以表示一个简单的登录表单或一个组件的多步骤设置向导。流管理器管理所有正在进行的流,并处理新流的创建。

“数据输入流”在HA中用于创建配置项。

流管理器

这是管理正在进行的流的类。当实例化一个时,你传入两个异步回调函数(即以下两个方法):

  1. 创建
1
2
async def async_create_flow(handler, context=context, data=data):
"""Create flow."""

管理器将配置流处理程序的实例化委托给这个异步回调。这允许管理器的父类定义自己的查找处理程序和为实例化准备处理程序的方法。例如,在配置条目管理器的情况下,它将确保已经设置了依赖项和需求。

  1. 完成
1
2
async def async_finish_flow(flow, result):
"""Finish flow."""

此异步回调在流完成或中止时调用。比如result['type'] in [RESULT_TYPE_CREATE_ENTRY, RESULT_TYPE_ABORT]。回调函数可以修改result并将其返回,如果结果类型(即result['type'])更改为RESULT_TYPE_FORM,流将继续运行,显示另一个表单。

如果结果类型是RESULT_TYPE_FORM,结果应该看起来像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
{
# The result type of the flow
"type": RESULT_TYPE_FORM,
# the id of the flow
"flow_id": "abcdfgh1234",
# handler name
"handler": "hue",
# name of the step, flow.async_step_[step_id] will be called when form submitted
"step_id": "init",
# a voluptuous schema to build and validate user input
"data_schema": vol.Schema(),
# an errors dict, None if no errors
"errors": errors,
# a detail information about the step
"description_placeholders": description_placeholders,
}

如果结果类型是RESULT_TYPE_CREATE_ENTRY,结果应该看起来像:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
{
# Data schema version of the entry
"version": 2,
# The result type of the flow
"type": RESULT_TYPE_CREATE_ENTRY,
# the id of the flow
"flow_id": "abcdfgh1234",
# handler name
"handler": "hue",
# title and data as created by the handler
"title": "Some title",
"result": {
"some": "data"
},
}

如果结果类型是RESULT_TYPE_ABORT,结果应该看起来像:

1
2
3
4
5
6
7
8
9
10
{
# The result type of the flow
"type": RESULT_TYPE_ABORT,
# the id of the flow
"flow_id": "abcdfgh1234",
# handler name
"handler": "hue",
# the abort reason
"reason": "already_configured",
}

流处理程序(FLow handler)

流处理程序将处理单个流。一个流包含一个或多个步骤(step)。当流被实例化时,将调用FlowHandler.init_step步骤。每一步都有三个不同的可能结果:“Show Form”、“Abort”和“Create Entry”。

至少,每个流处理程序必须定义一个版本号和一个步骤。这并不一定是init,因为async_create_flow可以根据当前工作流分配init_step,例如在配置中。context.source将用作init_step

最小配置流程:

1
2
3
4
5
6
7
8
9
10
11
12
13
from homeassistant import data_entry_flow


@config_entries.HANDLERS.register(DOMAIN)
class ExampleConfigFlow(data_entry_flow.FlowHandler):

# The schema version of the entries that it creates
# Home Assistant will call your migrate method if the version changes
# (this is not implemented yet)
VERSION = 1

async def async_step_user(self, user_input=None):
"""Handle user step."""

Show Form

这个结果类型将向用户显示一个要填写的表单。您可以定义当前步骤、数据模式(使用voluptuous)和一个错误字典(可选)。

1
2
3
4
5
6
7
8
9
10
11
12
class ExampleConfigFlow(data_entry_flow.FlowHandler):
async def async_step_user(self, user_input=None):
# Specify items in the order they are to be displayed in the UI
data_schema = {
vol.Required("username"): str,
vol.Required("password"): str,
}

if self.show_advanced_options:
data_schema["allow_groups"] = bool

return self.async_show_form(step_id="init", data_schema=vol.Schema(data_schema))

如果您想在表单中预先填充数据,您有两个选择。第一个是使用默认参数。这将预先填充字段,并且在用户让字段为空的情况下作为默认值。

1
2
3
data_schema = {
vol.Optional("field_name", default="default value"): str,
}

另一种方法是使用一个建议值——这也将预先填充表单字段,但如果用户愿意,将允许用户将其保留为空。

1
2
3
4
5
data_schema = {
vol.Optional(
"field_name", description={"suggested_value": "suggested value"}
): str,
}

您也可以混合和匹配-通过suggested_value预填充,并使用一个不同的值作为默认值,以防该字段是空的,但这可能会混淆用户,所以小心使用。

该步骤的标题和描述将通过翻译文件提供。在哪里定义它取决于数据输入流的上下文。

用户填写完表单后,将再次调用该步骤方法,并传入用户输入。只有当用户输入传入数据模式(data schema)时,才会调用您的步骤方法。当用户传入数据时,您必须对数据进行额外的验证。例如,您可以验证传入的用户名和密码是否有效。

如果出现错误,您可以返回一个带有错误的字典。错误字典中的每个键都指向包含错误的字段名。如果希望显示与特定字段无关的错误,请使用base。指定的错误需要引用翻译文件中的一个键。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
class ExampleConfigFlow(data_entry_flow.FlowHandler):
async def async_step_user(self, user_input=None):
errors = {}
if user_input is not None:
# Validate user input
valid = await is_valid(user_input)
if valid:
# See next section on create entry usage
return self.async_create_entry(...)

errors["base"] = "auth_error"

# Specify items in the order they are to be displayed in the UI
data_schema = {
vol.Required("username"): str,
vol.Required("password"): str,
}

return self.async_show_form(
step_id="init", data_schema=vol.Schema(data_schema), errors=errors
)
Multi-step flows

如果用户输入通过验证,您可以再次返回三个返回值之一。如果您希望将用户导航到下一步,则返回该步骤的返回值

1
2
3
4
5
6
7
8
9
10
11
12
13
class ExampleConfigFlow(data_entry_flow.FlowHandler):
async def async_step_init(self, user_input=None):
errors = {}
if user_input is not None:
# Validate user input
valid = await is_valid(user_input)
if valid:
# Store info to use in next step
self.init_info = user_input
# Return the form of the next step
return await self.async_step_account()

...

Create Entry

当结果为“Create Entry”时,将创建一个条目并传递给流管理器的父条目(the parent of the flow manager)。将向用户显示一条成功消息,流程就完成了。您可以通过传递标题和数据来创建条目。可以在UI中使用标题来告诉用户它是哪个条目。数据可以是任何数据类型,只要它是JSON可序列化的。

1
2
3
4
5
6
7
8
class ExampleConfigFlow(data_entry_flow.FlowHandler):
async def async_step_user(self, user_input=None):
return self.async_create_entry(
title="Title of the entry",
data={
"something_special": user_input["username"]
},
)

Abort

当流不能完成时,您需要中止它。这将完成流并通知用户流已经完成。流无法完成的原因可能是设备已配置或与Home Assistant不兼容。

1
2
3
class ExampleConfigFlow(data_entry_flow.FlowHandler):
async def async_step_user(self, user_input=None):
return self.async_abort(reason="not_supported")

External Step & External Step Done

用户可能需要通过在外部网站上执行操作来完成配置流程。例如,通过重定向到外部网页来设置集成。这通常用于使用OAuth2授权用户的集成。

这个例子是关于配置项的,但是也适用于其他使用数据输入流的部分。

流程如下:

1. 用户在HA界面开始配置流程。
2. 配置流程提示用户在外部网站上完成流程。
3. 用户打开外部网站。
4. 完成外部步骤后,用户的浏览器将被重定向到Home Assistant端点以交付响应。
5. 端点验证响应,在验证时,将外部步骤标记为已完成,并返回JavaScript代码来关闭窗口:`<script>window.close()</script>`。
6. 为了能够将外部步骤的结果路由到Home Assistant端点,您需要确保包含配置流ID。如果您的外部步骤是OAuth2流,您可以为此利用OAuth2状态。这个变量不由授权页解释,而是按原样传递给Home Assistant端点。
7. 窗口关闭,带有配置流的Home Assistant用户界面将再次对用户可见。
8. 当外部步骤被标记为“已完成”时,配置流自动进入下一个步骤。系统提示用户执行下一步操作。

包含外部步骤的配置流示例:

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
from homeassistant import config_entries


@config_entries.HANDLERS.register(DOMAIN)
class ExampleConfigFlow(data_entry_flow.FlowHandler):
VERSION = 1
data = None

async def async_step_user(self, user_input=None):
if not user_input:
return self.async_external_step(
step_id="user",
url=f"https://example.com/?config_flow_id={self.flow_id}",
)

self.data = user_input
return self.async_external_step_done(next_step_id="finish")

async def async_step_finish(self, user_input=None):
return self.async_create_entry(title=self.data["title"], data=self.data)

避免在返回一个async_mark_external_step_done之前基于外部步骤数据进行工作。相反,在标记外部步骤已完成时,执行您称为next_step_id的步骤中的工作。这将为用户提供更好的用户体验,在工作完成时在UI中显示一个旋转器。

如果您在授权回调中进行工作,用户将盯着一个空白屏幕,直到因为数据已经转发而突然关闭为止。如果您在标记外部步骤之前完成了工作,当后台工作正在完成时,用户仍然会看到带有“打开外部网站”按钮的表单。这也是不可取的。

将外部步骤标记为已完成的示例代码:

1
2
3
4
5
6
7
8
9
10
from homeassistant import data_entry_flow


async def handle_result(hass, flow_id, data):
result = await hass.config_entries.async_configure(flow_id, data)

if result["type"] == data_entry_flow.RESULT_TYPE_EXTERNAL_STEP_DONE:
return "success!"
else:
return "Invalid config flow specified"

Show Progress and Show Progress Done

我们可能需要用户等待一个需要几分钟时间的任务。

这个例子是关于配置项的,但是也适用于其他使用数据输入流的部分。

流程如下:

  1. 用户在HA界面启动配置流程。
  2. 配置流通过调用async_show_progress提示用户任务正在进行,需要一些时间才能完成。流应该传递一个任务特定的字符串作为progress_action参数来表示提示的翻译文本字符串。
  3. 该流程负责管理后台任务,并在任务完成或取消时继续执行该流程。通过调用FlowManager.async_configure来继续流程,例如:通过 hass.config_entries.flow.async_configure。创建一个这样做的新任务,以避免死锁。
  4. 当一个或多个任务完成时,流应该使用async_show_progress_done方法标记要完成的进度。
  5. 每次我们调用show progress或show progress done时,前端都会更新。
  6. 当进度被标记为完成时,配置流将自动前进到下一个步骤。系统提示用户执行下一步操作。

包含两个显示进度任务的配置流示例。

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
from homeassistant import config_entries

from .const import DOMAIN


class TestFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
task_one = None
task_two = None

async def _async_do_task(self, task):
await task # A task that take some time to complete.

# Continue the flow after show progress when the task is done.
# To avoid a potential deadlock we create a new task that continues the flow.
# The task must be completely done so the flow can await the task
# if needed and get the task result.
self.hass.async_create_task(
self.hass.config_entries.flow.async_configure(flow_id=self.flow_id)
)

async def async_step_user(self, user_input=None):
if not self.task_one or not self.task_two:
if not self.task_one:
task = asyncio.sleep(10)
self.task_one = self.hass.async_create_task(self._async_do_task(task))
progress_action = "task_one"
else:
task = asyncio.sleep(10)
self.task_two = self.hass.async_create_task(self._async_do_task(task))
progress_action = "task_two"
return self.async_show_progress(
step_id="user",
progress_action=progress_action,
)

return self.async_show_progress_done(next_step_id="finish")

async def async_step_finish(self, user_input=None):
if not user_input:
return self.async_show_form(step_id="finish")
return self.async_create_entry(title="Some title", data={})

Translations

数据输入流依赖于在转换来显示表单中文本。这取决于存储该数据的数据输入流管理器的父级在哪里存储。对于配置和选项流,这是string.jsonconfigoption之下;

有关string.json的更详细解释请见backend translation页面。

Initializing a config flow from an external source

您可能希望以编程方式初始化配置流。例如,如果我们在网络上发现一个需要用户交互才能完成设置的设备。要做到这一点,在初始化流时传递一个源参数和可选的用户输入:

1
2
3
await flow_mgr.async_init(
"hue", context={"source": data_entry_flow.SOURCE_DISCOVERY}, data=discovery_info
)

配置流处理程序不会从inti步骤开始。相反,它将被实例化为带有步骤名等于源。该步骤应遵循与普通步骤相同的返回值。

1
2
3
class ExampleConfigFlow(data_entry_flow.FlowHandler):
async def async_step_discovery(self, info):
"""Handle discovery info."""

The source of a config flow is available as self.source on FlowHandler