/custom_components/mqtt_custom/`
* 打开 `mqtt_custom/manifest.json`,将 `domain` 的值从 `mqtt` 改为 `mqtt_custom`
* 打开 `mqtt_custom/__init__.py`,将 `DOMAIN` 的值从 `mqtt` 改为 `mqtt_custom`
* 在配置中所有引用 `mqtt` 的地方,改为引用 `mqtt_custom`。例如,使用 `mqtt_custom:` 来指定 host,在指定 platform 时使用 `platform: mqtt_custom`。
---
url: /developers/blog/2019-05-22-internet-of-things-and-the-modern-web.md
---
import DiscussionBox from '../static/js/discourse\_discussion.jsx'
[Home Assistant](https://www.home-assistant.io) 是全球最大的开源家庭自动化平台。它[支持](https://www.home-assistant.io/components/)超过 1000 种设备和服务,并被广泛使用:家庭、[船只](https://hasspodcast.io/ha048/) 和 [博物馆](https://twitter.com/sjvanterpool/status/1124035433212649475)。
今天我想谈谈我们的 UI、我们要解决的问题以及我们如何解决问题。在深入探讨原因、内容和方式之前,让我们先看看我们的用户界面。这是我们的演示的嵌入式版本,完全可交互。
嵌入式演示已隐藏,因为屏幕太小。在新屏幕中打开演示。
# 使用场景
在构建物联网平台时,有许多使用场景需要用户界面。请注意,为了这篇博文,我们专注于尺寸相当于手机或更大的设备上的图形用户界面。
**安全。** 当用户不在家时,想要快速查看房屋情况。他们想快速检查车库门是否关闭,或者收到后院意外运动或水槽下方漏水的通知需要查看。这些用户会希望看到包含最近安全相关事件的 logbook、当前摄像头流以及门、窗、水位和运动传感器的当前 state。
**控制一切。** 用户希望有一个单一的地方来控制所有设备。他们的目标是快速概览房屋的各个部分,并控制 lights、switches、thermostats 和音乐。
**控制单个房间。** 用户正在为每个房间中的设备构建界面。这些设备需要暴露单个房间的所有控制,以及可选的关于整个房屋的额外信息和控制房屋其他部分的选项。
**监控。** 用户希望构建自己的 dashboards,以可视化 Home Assistant 和其他数据库(如 InfluxDB)中的所有当前和历史数据。他们想跟踪能源消耗、互联网速度或当前通勤时间。
**管理和自动化。** 用户希望拥有包含管理 Home Assistant 控件的 dashboards。其中包含 automations、scripts 和各种输入控件,用于配置 automations 和 scripts 的参数。
如果你正在使用 Home Assistant,那么你的 UI 很可能是上述几个使用场景的混合体。今天的博文将讨论我们使用了哪些技术来构建一个 UI,让用户能够构建适合所有这些使用场景的 UI。
# 需求
去年当我们开始重新架构界面的一部分时,我们根据使用场景、构建前一个界面的经验以及遇到的问题,提出了一份需求清单。
* **它需要零运行时 magic。** 每个用户想要的东西都不同,因此如果在运行时做决定,人们会要求配置选项来影响这个决定。我们的 UI 需要基于配置做出所有决定,只有在不提供配置时才进行智能干预。我们仍然可以向用户建议配置,但一旦用户编辑了它,他们就拥有了它,并且是唯一更新它的人。
* **它需要可自定义。** 用户希望能够更改一切。我们允许用户指定配置选项来更改样式、名称或图标等,这非常重要。
* **它需要 mobile first。** Home Assistant 不附带单独的 mobile 应用。我们的 UI 将在移动设备、笔记本电脑和介于两者之间的设备上使用。需要仅用一部手机就能安装、使用和配置 Home Assistant。
* **它需要适应新兴市场。** Home Assistant 希望让智能家居对所有人触手可及。因为我们是免费和开源的,所以全球各地广泛的人群都可以使用我们。大多数人使用的是低端手机,并且网络不稳定。我们的用户界面仍然需要可用,并且能够在许多不同的手机上运行。
* **它需要可扩展。** 由于要涵盖许多使用场景,因此 UI 的可扩展性很重要。可扩展性允许开发者进行实验,并允许我们支持所有内容,而不必在 Home Assistant core 中满足所有边缘情况/独特功能,因为一旦放入,就必须永远维护。
* **它需要易于开发。** 在工具中迷失方向很容易。尤其是在尝试维护对所有不同设备的支持的同时,还要提供简单的 theming 和配置。应该让任何人都可以轻松地从现在用户转变为开发者,而无需花费 2 天时间准备开发环境。
# 现代 Web 是我们的平台之选
相当长一份需求清单。Home Assistant 是一个开源项目,得益于用户的慷慨支持,有 2 名全职开发者,加上遍布全球的志愿者开发者大军。在这样的配置下,我们必须做出决策来实现我们的目标。而只有一个平台完全符合我们的要求,一个允许我们提供 theming、默认配置选项,还允许用户导入外部组件来渲染页面部分,并允许我们以简单的方式组织代码以便用户从任何设备轻松访问它的平台:现代 Web。
请注意我说的是现代。近年来,浏览器、Web 框架和工具在提供解决我们挑战的方法方面取得了长足的进步。我们看到了 CSS properties、custom elements 的引入,以及所有主要 Web 工具开箱即用地支持 code splitting。
所有这些现代 Web 优势都广泛可用:桌面浏览器已变得 evergreen,意味着它们无需用户干预即可自动更新。Android 用户可以通过 Play Store 获取浏览器更新,而 Apple 倾向于为其手机提供 5 年的更新,每次更新都包含更新的浏览器。
# 实现
我们所做的事情大多基于或受他人启发。虽然我没有一份列出所有启发我们选择或方法的人的完整名单,但它确实受到了 [Alex Russel](https://twitter.com/slightlylate)、[Rich Harris](https://twitter.com/Rich_Harris)、[Justin Fagnani](https://twitter.com/justinfagnani)、[Malte Ubl](https://twitter.com/cramforce) 的推文和演讲,以及 Polymer 团队的信条 "Do less and be lazy" 的启发。这些人帮助我们塑造了如何构建优秀 UI 的心态。
如果你好奇一个开源项目是如何拥有我们做的这么多酷炫功能的,其实正是因为我们是一个开源项目。对于参与其中的志愿者来说,这是乐趣所在。我们热爱跟进最新的前端发展并实现它们,而无需担心有人抱怨我们的时间应该花在别的地方。
* **[TypeScript](https://www.typescriptlang.org/) 是我们的首选语言。** Home Assistant 支持许多不同类型的设备,包含大量 API,并在前端支持许多不同的 widgets。指望人们记住这一切是不可能的,通过利用 TypeScript,没有人需要记住。TypeScript 是 JavaScript 的带类型版本,提供编译时类型检查和出色的自动补全。当我们发布时,TypeScript 会被转换为 JavaScript,因此浏览器中不需要特殊的运行时。
* **[Lit Element](https://lit-element.polymer-project.org/) 是我们的首选 Web 框架,为我们的用户界面提供动力。** Lit Element 是一个允许声明式模板的 Web 框架,gzip 后仅 6KB,速度极快,并具有出色的 TypeScript 支持。Lit Element 不需要任何构建工具,因此它也成为了我们开发者创建 UI 扩展的首选框架。
* **使用 [Custom Elements](https://developer.mozilla.org/en-US/docs/Web/Web_Components/Using_custom_elements) 作为可扩展性的首选技术。** Web 不断变化,我们希望吸引广泛的开发者。所以我们不想选择一个框架并强制用户使用它。因此,我们选择了浏览器内置的兼容层:custom elements。Custom elements 是一个浏览器规范,允许开发者指定自己的 HTML 标签,这些标签添加到 DOM 后,会与你的 JavaScript 代码交互以更新页面。
Custom elements 非常灵活。可以在实际 JavaScript 加载之前将标签添加到页面中。一旦加载,它将接管并渲染正确的 UI。这使得无需预先加载所有内容即可快速构建页面成为可能。浏览器还允许我们监听 custom element 何时被定义,从而随着内容的加载重建 UI 的部分。
* **使用 Shadow DOM 隔离我们的 components。** 虽然我们想让用户在我们的页面上运行代码,但我们不希望他们不小心弄乱我们的内置 components,甚至更糟——破坏整个页面。为了保护我们的 components,我们使用 Shadow DOM。这使我们能够封装 components 的 DOM 和样式。
* **使用 CSS properties 实现 theming。** 用户想要自定义内置 components,还想要自定义他们导入的第三方 components。无论开发者使用什么 Web 框架来构建他们的 widgets,这都必须有效。我们使用 CSS properties 解决了这个问题。CSS properties 是用户可以在整个 UI 上、每个 tab 上,有时是每个 card 上设置以自定义外观的 CSS 值。
* **依赖静态 DOM。** 当页面首次加载时,我们将根据用户的配置构建页面。之后,我们不会再触碰它。相反,当 state 改变时,我们会将 state 传递给所有相关 components,lit-html 将高效地更改需要更新的部分。
* **交付现代 JavaScript。** 我们构建两个前端版本。一个版本面向现代浏览器,作为 module 导入,可以利用 lambdas、classes 和 async/await 等现代功能。现代版本生成的代码更少,浏览器中的 JavaScript 引擎可以更好地理解代码意图,从而执行更快。
我们以前使用浏览器 user agent 检测来决定交付哪个版本,但最近通过利用 `type=module`、`nomodule` 以及一个特定的检查,以确保 Safari 10.1 不会同时加载两者,转而采用前端功能检测。
* **使用 Service Workers 激进地预缓存。** Home Assistant 附带一个 service worker。Service worker 是一个在网站后台运行的独立 JavaScript 脚本,我们将其设置为提前下载显示我们 UI 所需的所有代码。这意味着一旦 service worker 启动并运行,对服务器的唯一请求就是数据请求!我们在你登录的同时就已经注册了 service worker,这样当你完成登录时,前端就已经下载好了。我们使用 [Workbox](https://developers.google.com/web/tools/workbox/) 在构建时生成我们的 service worker。
* **解耦获取数据的代码和渲染 UI 的代码。** 我们在页面上加载的第一样东西是一个 5KB 的 JS 文件,它将启动与服务器的连接并开始请求必要的数据。与此同时,浏览器正在解析渲染初始页面所需的其他 JavaScript。我们使用 `link rel=preload` 来确保该 JavaScript 文件在页面加载后立即成为第一个被下载的内容。
* **简单的 state management。** 为了能够将 state 与任何 component(无论是内部还是外部)共享,我们决定自己实现 state management。它是一个简单的对象,包含房屋当前的 state,并沿着我们的 component 树向下传递。每当 state 改变时,我们复制一份并更新更改的部分,然后再次将其传递给所有 components。Components 随后重新渲染并显示最新的 state。所有 state 更新都由 Home Assistant JS Websocket 管理,并且全部在服务器上生成。它们通过 web sockets 推送到前端,使所有打开的 UI 保持同步,因为它们都能在第一时间收到最新更改。
[Home Assistant JS Websocket](https://github.com/home-assistant/home-assistant-js-websocket/) 是我们 4KB 的 WebSocket + Auth 库,可以添加到任何网站中,将其与 Home Assistant 实例集成。关于一个使用 50 行 JavaScript 构建的独立 Home Assistant UI,请参见[这个 glitch](https://hass-auth-demo.glitch.me/)。
* **使用 modules 和 unpkg 轻松进行扩展开发。** 开发者无需运行任何工具即可开发自己的 card。你只需要创建一个 JavaScript 文件,在配置的 resource 部分引用它,就可以开始使用了([文档中的示例](/developers/frontend/custom-ui/custom-card.md#example-using-js-modules))。我们默认将资源作为 type=module 导入,这样开发者可以在开发过程中直接从 http://unpkg.com 导入他们的依赖。
* **使 bundle 中的 bloat 易于发现。** 如果我们使发现不必要打包的内容变得更容易,开发者就更有可能发现它并花时间去除 bloat。为此,我们在仓库中包含脚本,允许任何人快速使用 [webpack-bundle-analyzer](https://github.com/webpack-contrib/webpack-bundle-analyzer) 运行 bundle 分析,并查看哪些包增加了我们的 bundle 大小。这些分析看起来[是这样的](https://s3.amazonaws.com/home-assistant-demos/bundle-analysis/report-20190522.html)。
* **拆分翻译的 code。** 我们使用 [lokalise.com](https://lokalise.com/) 管理我们的 translations。我们代码库中的所有翻译字符串会自动上传到 lokalise.co,志愿者在那里翻译可用的字符串。我们构建了构建工具,根据 UI 中不同的 panel 拆分我们的 translations。这使我们能够仅按需动态加载所需的 translations。
* **去中心化路由。** 应用的顶层只知道如何路由 url 的第一部分。它知道要加载哪些代码以及实例化哪个 web component。它会将 url 的其余部分传递给 panel,进一步的路由可以从那里完成。这将避免初始 bundle 随着更多部分被拆分而不断增长。我们还为 web components 创建了一个简单易用的 router,支持 code splitting 和加载屏幕,使我们的开发者很容易做正确的事。
* **避免在解析 source 或初始化 classes 时执行操作。** 这属于 "Do less and be lazy" 类别。尽快达到初始渲染很重要。因此,避免任何不必要的操作非常重要。Lit Element 通过提供在 update 完成后运行的生命周期的 hooks 来帮助我们。没有 "组件更新前运行" 的生命周期。
在 Polymer 中,这个信条的一个很好的例子是 debounce。它的 debounce 不会生成一个防抖函数,而是你只需在想要调用函数时调用它。然后它可能会调用函数,并返回一段 state,你需要在下次调用时传回。这样你就不需要在需要之前生成防抖函数。
# 挑战 / 未来改进
我们做了很多酷炫的事情,但工作永远不会结束。以下是一些我们面临的挑战和可能探索的未来改进。在编写这份列表时,我有一个有趣的观察:我们的一些问题并非由于糟糕的设计,而是因为超出了设计的范围。当 UI 较小时,这些设计是正确的,但现在我们成长了,需要适应。
* **处理重复的 custom element 名称。** Custom element 只能定义一次。这意味着如果一个 component 导入了不同的版本,这将会出问题。Home Assistant 拥有自己的专用操作系统 [Hass.io](https://www.home-assistant.io/hassio/),它随自身发布周期附带自己的 control panel。我们无法对版本兼容性做任何保证,在我们弄坏用户 UI 太多次之后,我们坐下来解决了它:我们在 iframe 中运行 Hass.io control panel,以便 custom elements 与父页面隔离。
我们还为此功能提供了开发者扩展点。通过 [custom panel integration](https://www.home-assistant.io/components/panel_custom/),他们可以构建自己的 panel,可选地支持 iframe 隔离。我们为 iframe 隔离发现的另一个使用场景是 React。这个 Web 框架很流行,但无法在 ShadowDOM 中工作。通过在 iframe 中隔离它,我们也解决了这个问题([示例代码](https://github.com/home-assistant/custom-panel-starter-kit-react))。
* **不再尝试将所有内容放入 Webpack。** 当项目从 HTML imports 迁移到 JS imports 时,我们切换到 WebPack 作为我们的 bundler。我们有点过于兴奋,在 WebPack 中做了所有事情,包括生成 HTML。由于我们有前端两个构建版本,实际上我们有两个 WebPack 构建。通过将所有内容放入 WebPack,我们无法创建能够引用现代和 ES5 版本构建的哈希文件名的文件。我们的目标是将 WebPack 的职责最小化到转换 TypeScript、打包文件和压缩。通过减少职责,将更容易尝试其他 bundler。我很期待尝试 [Rollup](https://rollupjs.org/) + [Closure Compiler plugin](https://github.com/ampproject/rollup-plugin-closure-compiler)。
* **在现代 JavaScript 构建中使用 [dynamic imports](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Statements/import#Dynamic_imports)。** WebPack 只有捆绑构建的单一输出模式,它在运行时使用自己的依赖注入系统。如果我们能切换到 Rollup,我们应该能够利用 dynamic imports,这是浏览器内置的。
* **从主 bundle 中移除 Polymer。** UI 的第一版本是使用 Polymer Web 框架和 [Paper elements](https://www.webcomponents.org/collection/polymerelements/paper-elements)(一组使用 Polymer 构建的 material design components)构建的。我们仍然有一些依赖 Polymer 的核心 UI 代码,这意味着它仍然是初始 UI bundle 的一部分。这意味着目前 Polymer 和 Lit Element 都是我们初始 bundle 的一部分,使该 bundle 不必要地臃肿。
* **翻译所有字符串。** 我们仍然交付尚未翻译的文本。Home Assistant 被各种人群在家庭中使用。让用户使用他们能理解的语言使用 UI 很重要。此外,我们最近在 RTL 支持方面取得了很大进展,大部分已覆盖,但仍有可以改进的地方。
* **去中心化一切。** 我从 Malte Ubl 关于[设计大型 JavaScript 应用](https://medium.com/@cramforce/designing-very-large-javascript-applications-6e013a3291a3)的文章中获得的关键要点之一是避免集中配置。集中配置会随着应用的成长而不断增长,如果它是主 bundle 的一部分,你将交付不必要的内容。这目前在我们的代码中体现在两个地方:
* 我们交付一个在发布时构建的 iconset,包含整个 UI 中引用的所有图标。随着 UI 的增长,这个 iconset 也在增长。它现在包含大量初始加载不再需要的图标,但仍然被加载。一个可探索的选项是用支持包含 SVG 图标的 lit-html 模板的组件替换我们当前的图标渲染组件。然后我们可以通过正常的 JS import 开始加载图标。这将允许我们的 JS bundler 仅包含 bundle 中代码引用的图标。我们应该确保可以标记这些文件为允许重复,否则 JS bundler 会生成大量只包含单个图标的小 bundle。
* 我们目前按语言和 UI panel 分别拆分翻译文件,然后单独指纹验证每个文件。这个 manifest 文件包含在我们的初始构建中,随着我们的 app 在 panel 数量上的增长以及支持更多语言,我们的 manifest 文件现在成了初始 UI bundle 的一大块。我们的第一步应该是为每种语言生成单个 hash,使我们只能包含每种语言的一行。
* **分析当前的 code splitting 算法。** 目前 WebPack 会确保每个 JavaScript 文件只加载一次(按照规范)。然而,这意味着我们最终以某些文件完全不被打包结束,因为有太多 code splitted 的代码片段引用该文件,导致它成为自己的 chunk。目前我们前端构建包含 126 个 code splitted chunks,其中 85 个小于 5KB!我们需要运行 traces 来查看这是否是个问题,或者 service worker 隐藏了大部分。
* **停止使用 local storage。** 我们使用 local storage 存储 auth tokens、语言偏好以及是否显示侧边栏。由于 local storage 是同步访问的,浏览器会在等待从磁盘读取 local storage 时停止执行页面上的任何操作。浏览器厂商正在开发像 [kv-storage](https://github.com/WICG/kv-storage) 这样的替代方案,它是异步的,可能会成为一个可行的替代方案。
* **将 Paper elements 替换为 [Material Web Components](https://github.com/material-components/material-components-web-components)。** Paper elements 不再维护,Material Web Components 将是继承者,但仍在开发中。MWC components 底层使用 Lit Element,并依赖 material.io 的 Material design VanillaJS 实现。这意味着它们将比当前的 Paper elements 轻得多,并且实际上会得到维护。
## 评论
---
url: /developers/blog/2019-07-03-climate-cleanup.md
---
import DiscussionBox from '../static/js/discourse\_discussion.jsx'
*本文介绍 Home Assistant climate integration 即将发生的变更。这些变更目前正在实施中,不再接受讨论。如果你希望看到变更,请考虑提出[架构议题](https://github.com/home-assistant/architecture/issues)。*
从 Home Assistant 0.96 开始,我们将交付 climate integrations 的大型架构清理。这项努力由 Pascal Vizeli 领导。
多年来,climate integration 从一个简单的恒温器位置,发展为容纳空调、热泵和通风系统。在此期间,出现了失误,导致设计混乱且难以使用。清理工作正在进行中,我们将在 Home Assistant 0.96 中交付。
我们犯的最大错误是将 operating mode 和 operation mode 混淆了。Operation mode 是你希望恒温器做的事,例如将房屋加热到 19 °C。Operating mode 是恒温器\_当前\_在做什么。是因为房屋太冷而在加热?还是已经达到了目标温度,当前处于 idle?
此外,我们发现许多 integrations 通过大量选项过度重载了 operation mode,如 "eco"、"comfort"、"boost" 或 "sleep"。在仔细分析了各种气候系统后,我们得出结论,这些替代 operation modes 是现有 operation modes(heat、cool、heat-cool)的变体,但可能有不同的目标温度范围,或者尝试以更慢的速度加热/冷却以节省能源。
为了尽可能支持多种恒温器,我们决定允许 climate devices 指定它们正在运行的新 "preset" mode。它涵盖 hold mode、away mode,或恒温器包含的任何自定义 operation mode。
你可能想知道,为什么要使用 preset 而不是允许重载 operation mode?乍一看,一个 integration 列出可能的选项和当前选择的选项似乎是可行的,很容易构建 UI。然而,这不是 Home Assistant 被使用的唯一方式。我们还作为单一地点为外部系统(如 Google Assistant 或 Amazon Alexa)控制任何恒温器。它们有一套有限的期望的 operation modes。
变更要点:
* `operation_mode` 已重命名为 `hvac_mode`,以强调该 mode 的用途。
* 我们将 HVAC mode `auto` 拆分为 `auto` 和 `heat_cool`。如果是 `heat_cool`,用户设定了设备必须使用加热和冷却来保持在内的温度范围。Auto mode 现在仅限于运行在 schedule 或 AI 上的设备。
* Climate entity 的 state 现在总是等于 HVAC mode。
* `hvac_action` 引入用于知道当前 action(heating、cooling、idle)的 integrations。
* `set_away_mode` 和 `set_hold_mode` 已合并为 `set_preset_mode`。因此,不再是 `turn_away_mode_on()`,现在我们将调用 `set_preset_mode("away")`。
* `is_on` 属性已移除。如果设备能够关闭,它应包含 hvac mode `HVAC_MODE_OFF`。
* 属性名已对齐,所有以 "\_list" 结尾的现在命名为 "\_modes"。
如果你是拥有 climate platform 的 integration 的维护者,或维护拥有 climate platform 的 custom component,请确保你[跟进该 pull request](https://github.com/home-assistant/core/pull/23899),并在 beta 期间测试你的 integration,确保它按预期工作。
## 评论
---
url: /developers/blog/2019-07-19-building-all-the-things.md
---
import DiscussionBox from '../static/js/discourse\_discussion.jsx'
*Home Assistant 如何使用 Azure Pipelines 自动化一切。*
在 Home Assistant,我们热爱自动化!不仅是在我们的家中,也在开发工作中。我们每三周发布一次版本,包含来自 80 多个不同人员的代码。所有这些都由两名全职人员管理(感谢 [Nabu Casa](https://www.nabucasa.com)!)。
我们将 Home Assistant 作为 Python 包分发,并作为我们名为 Hass.io 的一体化系统的一部分。Hass.io 有自己的操作系统、一个 supervisor Docker 容器和 Home Assistant docker 容器。除此之外,Hass.io 还支持 add-ons,我们有一套官方 add-ons 也进行维护。对于 Hass.io 所做的每一件事,我们都要做五次,每次针对我们支持的一种架构:amd64、i386、armv6、armv7、aarch64。
为了保持快速推进,我们花了很多时间自动化构建任务。在试用了 [TravisCI](https://travis-ci.org/) 和 [CircleCI](https://circleci.com/) 之后,我们最终选择了 [Azure Pipelines](https://azure.microsoft.com/en-us/services/devops/pipelines/)。
我们最初从 Travis 开始,但意识到可以通过利用包含 4 个 worker、测试分布和构建 artifact 缓存的 CircleCI 开源计划来显著加速构建。我们尝试从 CircleCI 获取更多 worker,但根据他们的新定价计划添加 worker 太贵了。
[Pascal Vizeli](https://www.github.com/pvizeli) 在以前的工作中有 Azure 经验,他能够在几天内迁移并构建所有基于 YAML 的 pipelines。他得到了 [Kees Schollaart](https://twitter.com/keesschollaart) 的支持,后者是 Azure 的 DevRel,但在我们的社区中因创建 [Home Assistant Companion extension for VS Code](https://marketplace.visualstudio.com/items?itemName=keesschollaart.vscode-home-assistant) 而闻名。每当我们卡住时,他都给予了很多帮助。
Azure 的开源计划附带 10 个免费 agents,在我们联系 Microsoft 后,他们额外提供了 20 个 agents 来帮助我们自动化一切!
有兴趣看看我们用这些 Azure 能力做了什么吗?
## 持续集成
对 Home Assistant 的所有更改都经过我们的 CI pipeline。当有 pull request 提交到 Home Assistant 仓库时,它将触发按 [`azure-pipelines-ci.yml`](https://github.com/home-assistant/home-assistant/blob/dev/azure-pipelines-ci.yml) 配置的 pipeline。
Home Assistant 的贡献会经过我们的测试套件、代码风格和类型检查,以确保贡献不会破坏东西。我们将测试套件拆分为 3 个不同的 stages。通过使用 stages,我们可以运行快速的验证 stage,如果不正确就快速失败。
这一策略使我们在已知基础 linting 无法通过时,不会浪费任何 worker 时间运行完整的测试套件。
下面是我们 Home Assistant CI pipeline 的截图。由于所有工作分布在多个 worker 上,总运行时间是第一列中最长的任务,加上第二列中最长的任务。

为了在 Azure 上改善我们的 CI 时间,我们使用了 [pipeline artifact caching](https://marketplace.visualstudio.com/items?itemName=1ESLighthouseEng.PipelineArtifactCaching) 的预览版本。这允许我们构建一次依赖,将其作为包存储在测试 runner 附近,并在 jobs 和 pipeline runs 之间重用。
另一个我们进行的优化是使用自己的 base images 作为测试 runner。这些镜像已经安装了正确的 Python 和其他基础工具。我们将它们添加到 [resources](https://github.com/home-assistant/core/blob/de3d28d9d5bd5dd69cf9f84d021d683da2c322d6/azure-pipelines-ci.yml#L12-L18),然后在 [strategy](https://github.com/home-assistant/core/blob/de3d28d9d5bd5dd69cf9f84d021d683da2c322d6/azure-pipelines-ci.yml#L72-L80) 部分引用它们。
CircleCI 有一个不错的功能,它可以通过跟踪每个测试的持续时间将测试分布在多个 worker 上。这将 PyTest 套件的持续时间减少到约 10 分钟。但这使用了更多 agents,我们会很快耗尽 agents,产生大量积压。后续 stages 只有在 "Overview" stage 完成后才会排队,导致它们排在其他贡献后面。
使用 Azure,我们获得更多 runner,它们更强大、更快,但没有 parallelization。这意味着单个构建将花费更长时间,但随着许多构建涌入,单个构建时间大致保持不变,因为我们没有遇到积压。

[测试结果](https://dev.azure.com/home-assistant/Home%20Assistant/_build/results?buildId=3976&view=ms.vss-test-web.build-test-results-tab)的截图。
## 构建 HassOS
HassOS 是我们自己的嵌入式 Linux 发行版。它专为在 Docker 容器中运行 Home Assistant 所需的最基本功能而定制。BuildRoot 驱动构建,我们针对五种不同的处理器架构。
Azure Pipelines agents 使用 amd64 CPU 架构。可以使用称为 cross-compiling 的方法为不同架构编译。我们尝试了,但意识到它比使用与我们目标相同的 native CPU 进行编译要慢得多。
我们使用 Azure Pipelines 上称为 [self-hosted agents](https://docs.microsoft.com/en-us/azure/devops/pipelines/agents/agents?view=azure-devops#install) 的功能,在具有正确架构的自有硬件上托管 build agents。Azure Pipelines 管理 pipeline 定义和编排,但执行在我们的自有硬件上飞速运行。

我们的 build cabinet。
## 构建 Python Wheels
Home Assistant 作为独立的 Python 包分发,并捆绑在 Docker 容器中。我们的 Docker 容器包含不同 integrations 的所有 Python 包预装。因此,创建 Docker 容器需要很长时间,因为我们必须为每种架构安装和构建每个 Python 包。Home Assistant + 所有 integrations 依赖于超过 1000 个不同的 Python 包。
为了加速这个过程,我们利用 Python Wheels。Wheels 是一种二进制格式,包含特定操作系统和 CPU 架构的预构建 Python 包。我们有一个 Azure pipeline 帮助在所有支持的 CPU 架构上为所有不同包构建 wheels。总计 5000 个 wheels!
有一种特殊的 wheels 格式,可以通过单次构建针对多个平台。我们无法使用它,因为它与 musl libc 不兼容,而 Alpine 使用 musl。
该 pipeline 在贡献被接受并合并到开发中且更改了任何 requirements 时自动触发([pipeline 定义](https://github.com/home-assistant/core/blob/de3d28d9d5bd5dd69cf9f84d021d683da2c322d6/azure-pipelines-wheels.yml#L3-L10))。我们还每天运行它以确保捕获所有更改。
由于使用 wheel 安装要快得多,我们还允许 custom integrations [注册自己](https://github.com/home-assistant/custom-components-wheels)。我们将每天扫描每个 custom integration 一次,以确保我们有它们使用的 Python 包的 wheels。
通过利用 wheels,我们将 Home Assistant containers 的构建时间从 1 小时减少到 10 分钟(来自 amd64 构建的测量)。
*(旁注。Python 是以 Monty Python 命名的,包索引则因他们著名的一个段子被叫做 "the Cheese Shop"。因此 Wheel 格式是以一块轮状奶酪命名的,而不是汽车的轮子。5000 块轮状奶酪是很多的奶酪。)*
## 构建 Home Assistant 发布版本
Home Assistant 每三周发布一次稳定版本。在这个稳定版本之前,我们通常有大约 6 个 beta 版本。稳定版本发布后,我们通常有 2 到 3 个 patch 版本。
每个版本都需要构建为独立的 Python 包,以及针对五种支持的 CPU 架构之一的 Docker 容器。
该 pipeline 在[新 release 打 tag](https://github.com/home-assistant/core/blob/dev/azure-pipelines-release.yml#L3-L7)时触发。我们首先验证 tag 名是否匹配代码中的版本,并且 release 是由我们白名单中的 release 管理员之一创建的。然后我们将自动构建所有 releases 并发布。
## 构建 Hass.io add-ons
Hass.io 的功能之一是 add-ons。Add-ons 允许开发者重用 Home Assistant 在 Hass.io 中运行所使用的相同基础设施,但用于你想在网络上运行的任何其他程序。这些 add-ons 可以与 Home Assistant 紧密集成。例如,可以从 [Community Add-ons](https://github.com/hassio-addons/repository) 一键安装 AdGuard,并使其数据可以在 Home Assistant 中访问。
Hass.io 包含几个核心 add-ons。这些 add-ons 的每个版本也需要为所有五种架构构建。我们将所有 add-ons 保存在一个仓库中,但每个 add-on 有自己的 pipeline 定义,仅在该 add-on 更改时触发([示例](https://github.com/home-assistant/hassio-addons/blob/master/mosquitto/azure-pipelines.yml))。
## 结论
构建广泛的自动化系统确实帮助 Home Assistant 以最少的资源进行了扩展。我们对从 Azure 及其员工那里得到的所有帮助感到非常高兴。干杯!
## 链接
* [Home Assistant on Azure Pipelines](https://dev.azure.com/home-assistant/Home%20Assistant/_build)
* [Hass.io on Azure Pipelines](https://dev.azure.com/home-assistant/Hass.io/_build)
## 评论
---
url: /developers/blog/2019-07-31-black.md
---
今天我们正式采用了 Python 代码格式化工具 [Black](https://black.readthedocs.io)。所有代码都已用 Black 格式化,所有未来的贡献都将被检查以确保已使用 Black 格式化。
作为采用 Black 的一部分,flake8 和 pylint 中的所有 style 检查已被禁用。
为了确保在提交更改时所有文件都使用 Black 格式,我们使用 [`pre-commit`](https://pre-commit.com/)。该 hook 会自动为新开发环境安装(作为 `script/setup` 的一部分)。
如果你有现有的开发者安装,请在你的 virtual environment 中运行:
```
pip install pre-commit
pre-commit install
```
`pre-commit` 不会更改你的文件,而只是确认更改的文件已由 Black 格式化。我们建议你设置编辑器,在保存时自动使用 Black 格式化文件。各种编辑器的说明可以在[这里](https://black.readthedocs.io/en/stable/integrations/editors.html)找到。
---
url: /developers/blog/2019-09-27-hacktoberfest.md
---
import DiscussionBox from '../static/js/discourse\_discussion.jsx'
今年我们再次参与 [Hacktoberfest](https://hacktoberfest.digitalocean.com)。Hacktoberfest 是一项庆祝开源的全球活动。如果你在 10 月提交 4 个 pull request,你将获得一件免费的 Hacktoberfest t-shirt。
Home Assistant 目前正在向我们神奇的 1.0 版本冲刺。Home Assistant 1.0 注重用户友好性。作为其中的一部分,我们最近引入了 device automations,并扩展了 scenes 在 Home Assistant 中的工作方式。
Home Assistant 支持广泛的产品,因此并非我们构建的每个功能都支持每种产品类型。在本次 Hacktoberfest 中,我们准备了 issues,帮助 integrations 使用最新的 Home Assistant 功能。
为了更容易入门,我们引入了一个新的 scaffolding 脚本。该脚本能够创建向现有 entity integrations 添加新功能所需的所有样板代码,包括 tests!你只需专注于与特定 integration 配合工作的代码。每个链接的 issue 中都有使用说明。
如果你想查看所有可用 issues,而不是下面的精选列表,[请点击这里](https://github.com/issues?page=1\&q=is%3Aopen+is%3Aissue+org%3Ahome-assistant+archived%3Afalse+label%3AHacktoberfest\&utf8=%E2%9C%93)(需要登录 GitHub)。
**9 月 30 日更新:** 添加了文档部分,更新了 Almond 部分的链接,添加了指向所有 Home Assistant Hacktoberfest issues 的链接。
**10 月 1 日更新:** 添加了 Frontend 部分。
## 场景
Scenes 允许用户定义灯光应该看起来的样子(即,它应该是开启的,颜色应该是蓝色的)。当 scene 被激活时,由 Home Assistant 决定调用哪些 services 来实现。
Scenes 是强大的工具,用户易于理解。因此,改进我们的 scene 支持很重要。我们准备了以下 issues:
* [添加 `scene.create` service 用于即时创建 scenes。](https://github.com/home-assistant/core/issues/27023)
* \~~[添加 `scene.apply` service 用于应用定义为 service data 的 scene。](https://github.com/home-assistant/core/issues/26813)~~
* \~~[在 script 语法中添加 activate scene](https://github.com/home-assistant/core/issues/27026)~~
* [为 entity integrations 添加 reproduce state 支持。](https://github.com/home-assistant/core/issues?utf8=%E2%9C%93\&q=label%3AHacktoberfest+reproduce_state+is%3Aopen+)(多个 issues)
## 设备自动化
Device Automations 是一种面向设备的方式,让用户创建 automations。在定义 automation 的 trigger、condition 或 action 部分时,用户将从选择一个设备开始,查看该设备的可能选项列表。简单!
Device automations 依赖 integrations 为每个设备定义可能的选项。由于这是一项新技术,我们仍在寻求帮助,使所有 entity integrations(light、switch 等)支持它。我们准备了以下 issues:
* [为 entity integrations 添加 device trigger 支持](https://github.com/home-assistant/core/issues?utf8=%E2%9C%93\&q=label%3AHacktoberfest+device_trigger+is%3Aopen+)(多个 issues)
* [为 entity integrations 添加 device condition 支持](https://github.com/home-assistant/core/issues?utf8=%E2%9C%93\&q=label%3AHacktoberfest+device_condition+is%3Aopen+)(多个 issues)
* [为 entity integrations 添加 device action 支持](https://github.com/home-assistant/core/issues?utf8=%E2%9C%93\&q=label%3AHacktoberfest+device_action+is%3Aopen+)(多个 issues)
## Home Assistant 文档
猜猜什么永远做不完?文档。
* [为 integrations 填充缺失的文档](https://github.com/home-assistant/home-assistant.io/issues?q=is%3Aissue+is%3Aopen+label%3AHacktoberfest)
* [为 hass.io add-ons 填充缺失的文档](https://github.com/home-assistant/hassio-addons/issues?q=is%3Aissue+is%3Aopen+label%3AHacktoberfest)
## 前端
我们还需要在 Home Assistant 的 Frontend 上做些工作。
因此我们也准备了一些 issues 帮你入门:
* [使所有文本可翻译](https://github.com/home-assistant/frontend/issues?utf8=%E2%9C%93\&q=label%3AHacktoberfest+label%3Alocalization+is%3Aopen+)(多个 issues)
* [帮助使 Home Assistant 更易访问](https://github.com/home-assistant/frontend/issues?q=is%3Aopen+label%3AHacktoberfest+label%3Aaccessibility)(多个 issues)
* [以及大量其他小 UX issues](https://github.com/home-assistant/frontend/labels/Hacktoberfest?page=2\&q=is%3Aopen+label%3AHacktoberfest)
## 奖励:Almond - 虚拟助手
[Almond](https://almond.stanford.edu/) 是斯坦福大学的一个开放、保护隐私的虚拟助手。我们一直在[合作](https://github.com/stanford-oval/thingpedia-common-devices/pull/80)使其与 Home Assistant 配合工作。目前它仅限于控制 Home Assistant 中的 lights。
在本次 Hacktoberfest 中,让我们看看能否扩展支持类型的数量!要开始,请查看以下链接:
* [关于如何贡献 Home Assistant 特定类型的文档](https://almond.stanford.edu/doc/home-assistant-integration.md)
* [关于如何运行 tests 的文档](https://almond.stanford.edu/doc/thingpedia-testing.md)
* 在[家中](https://github.com/stanford-oval/almond-server)、[Android 版本](https://play.google.com/store/apps/details?id=edu.stanford.thingengine.engine\&hl=en_US)或 [Gnome 版本](https://flathub.org/apps/details/edu.stanford.Almond) 运行 Almond
## 评论
---
url: /developers/blog/2019-10-05-simple-mode.md
---
import DiscussionBox from '../static/js/discourse\_discussion.jsx'
:::info
本博文引用了 simple 和 advanced mode。后来已重命名为 standard mode 和 advanced mode。
:::
Home Assistant UI 有两种不同的模式。一种是 simple mode,另一种是 advanced mode。Simple mode 最近已在 [Home Assistant 0.96](https://www.home-assistant.io/blog/2019/07/17/release-96/#advanced-mode) 中引入。在本文中,我想概述我对 Home Assistant 1.0 中 simple mode 的愿景。
**在 simple mode 中,我们专注于核心 Home Assistant 体验。我们正在努力回答这个问题:我们希望任何人都能做什么。我们能否使其足够直观,让人们(无论老少)都能安装 Home Assistant 并使用它。**
## 核心概念
Simple mode 是非高级用户的简化界面。这是默认 UI。Simple mode 提供 Home Assistant 的直观子集:易于理解且易于使用。
在 simple mode 中,我们将按照用户对房屋的理解来组织配置:
* Inputs:devices 和 persons
* Outputs:scenes、scripts、automations

还有其他 inputs,但它们用于支持 devices 和 persons:
* Integrations 允许你与设备交互
* Users 允许 persons 登录并访问 Home Assistant
* Areas 允许将 devices 分组
* Zones 在地图上标记位置,以跟踪携带 GPS 发射设备的人员
作为 simple mode 中的用户,你将不会被暴露于 events、entity IDs、YAML 或 services。
## 集成要求
Integrations 需要能够为 simple mode 中的用户提供 inputs。这意味着在 integration 被纳入 simple mode 之前,必须满足一些要求。
* Integration 的所有 entities 都需要有唯一的 ID
* Integration 的所有 entities 都需要提供 device info
Integrations 应通过 discovery 或 account linking 进行设置。在极少数情况下,我们可以允许通过 IP 地址配置 integration。
## 扩展发现
在 onboarding 期间以及用户访问 integrations 页面时,我们将触发 extended discovery。默认情况下,Home Assistant 使用 SSDP 和 zeroconf discovery 协议扫描网络。在 extended discovery 中,我们将为流行的 integrations(如 Plex 或 Unifi)运行自定义 discovery 协议。
## 无 YAML
Simple mode 将完全通过 UI 控制。不应暴露需要用户打开文本编辑器的功能。例如,需要用户在 configuration.yaml 中添加 client\_id/secret 的 config flow 不应属于 simple mode。
## 无 add-ons 的 Hass.io
在 simple mode 中,Hass.io 将是用户管理硬件设置和更新的地方。
Simple mode 的初始版本不会显示安装 Hass.io add-ons 的功能。这是为了控制 Home Assistant 1.0 的范围。未来我们将重新审视这一点,并决定哪些 add-ons 适合我们的 simple mode。
## 场景
用户将使用 scenes 而不是 services,从 scripts 和 automations 控制设备。通过 scenes,你可以对设备的当前 state 进行快照,并在将来某个时刻恢复。易于解释,也易于使用。我们管理 scenes 的 UI 将围绕 devices 和 areas 构建。
## 自动化与脚本
Automations 和 scripts 将使用当前编辑器,但 triggers、conditions 和 actions 的类型将限制为与我们 inputs 匹配的那些。
* Trigger 类型将限制为 time、sun、zone 和 device triggers。
* Condition 类型将限制为 time、zone、sun 和 device conditions。
* Action 类型将限制为 activate scene 和 device actions。
## 我是 power user,为什么要在乎?
通过集中精力在 simple mode 上,我们正在使 Home Assistant 对所有用户(无论是初学者还是 power users)都更容易使用。
一个最近受 simple mode 驱动但对每个人都有益的功能的绝佳例子是 device automations。使用 device automations,配置 Zigbee 遥控器变得轻而易举。你可以直接从列表中选择应该触发你的 automation 的按钮。与此相比,以前的方式是:你需要在 event dev tool 中监听 `zha_event`,然后按下按钮以学习事件数据,用于 event trigger 的匹配。
另一个将使所有人受益的正在进行的工作是,我们在 Hacktoberfest 中重点关注将 scenes 变为[一等公民](https://github.com/home-assistant/core/issues/25681)。
## 评论
---
url: /developers/blog/2020-02-04-new-zwave.md
---
import DiscussionBox from '../static/js/discourse\_discussion.jsx'
在 State of the Union 上,我们[介绍了我们正在开发新的 Z-Wave integration](https://youtu.be/tc17q1Zn0Xs?t=4482)。它基于 Open Z-Wave 项目的一个新项目 [OZWDaemon](https://github.com/OpenZWave/qt-openzwave),我们将通过 MQTT 与它通信。
这种新方法使我们能够直接与 Open Z-Wave 的核心集成,而无需依赖通过其他语言的 bindings。这使得我们能够轻松保持与最新 Open Z-Wave 版本的同步。
得益于 [@cgarwood] 和 [@marcelveldt] 的努力,integration 取得了很大进展,你可以从今天开始作为 [custom component](https://github.com/cgarwood/homeassistant-zwave_mqtt) 测试它。目前还处于早期阶段,并非所有设备类型都已支持。目前它支持 lights、switches、sensors 和 binary sensors。该 custom component 由新的 [`python-openzwave-mqtt`](https://github.com/cgarwood/python-openzwave-mqtt) 包提供动力。
[请试用。](https://github.com/cgarwood/homeassistant-zwave_mqtt#requirements) 如果你发现任何 bugs,请[创建 issue](https://github.com/cgarwood/homeassistant-zwave_mqtt/issues/new)。如果你使用 Home Assistant 0.105,请使用 topic `openzwave/#` 上的新 `mqtt.dump` service 对 Open Z-Wave 实例进行 dump,并将其附加到你的 issue 中。Dump 将被写入 config 文件夹中的 `mqtt_dump.txt`。
一旦 integration 足够成熟,它将成为 Home Assistant Core 的一部分。
[@cgarwood]: https://github.com/cgarwood
[@marcelveldt]: https://github.com/marcelveldt
## 评论
---
url: /developers/blog/2020-02-18-106-custom-card-changes.md
---
我们在 Home Assistant Core 0.106 中进行了一些可能会影响 custom Lovelace cards 的变更,如果你是 custom card 开发者,请阅读以下内容。
### 冻结配置
我们过去会给每张 card 一个配置的副本,因为有些 cards 会修改 Lovelace 传递给它们的配置。在 0.105 中我们停止了这种做法,因为为每张 card 创建深拷贝对性能不好。
这导致了一些问题,因为 cards 仍然在修改配置。在 0.106 中我们 freeze 了配置。这意味着 custom card 不能修改配置。如果它仍然尝试修改,将会抛出异常或静默失败,具体取决于是否在 strict mode 下运行。
请检查你的 custom card 在 0.106 中是否仍然正常工作,并进行调整以避免修改配置。如果你需要,可以自己创建配置的副本。
### 辅助函数
:::info
我们决定将此变更推迟到 0.107。
:::
所做的另一个变更是,我们不再默认加载所有 element types。我们在需要时加载它们。这也将有助于性能,但可能会破坏你的 custom card。
我们引入了一组 helpers,你可以使用它们来创建 Lovelace element,这些是 Home Assistant 内部使用的相同函数,并始终与用户使用的版本保持最新。
你可以按如下方式使用它们:
```js
const helpers = await loadCardHelpers();
const element = helpers.createRowElement(config);
element.hass = this.hass;
```
更多信息请参见 https://github.com/home-assistant/frontend/pull/4853
---
url: /developers/blog/2020-04-10-translation-handling.md
---
我们已经将 Home Assistant Core 仓库中的翻译脚本迁移到单个命名空间下。现在都可以通过 `python3 -m script.translations` 调用。
| 旧命令 | 新命令 |
| ------------------------------ | ----------------------------------------- |
| `script/translations_develop` | `python3 -m script.translations develop` |
| `script/translations_upload` | `python3 -m script.translations upload` |
| `script/translations_download` | `python3 -m script.translations download` |
| `script/translations_clean` | `python3 -m script.translations clean` |
这将帮助我们为 [Translations 2.0 effort](https://github.com/home-assistant/architecture/blob/master/adr/0009-translations-2.0.md) 做准备,它将清理 translations 并使其更好地扩展。
---
url: /developers/blog/2020-04-12-s6-overlay.md
---
Home Assistant 使用许多不同的 Docker 容器用于各种目的。不仅是可作为 Docker 容器提供的 Home Assistant Core,我们的 Supervisor 和所有 add-ons 也都在利用 Docker。
在许多情况下,我们需要在容器中运行多个进程,并且都需要被管理。我们过去使用简单的 Bash 脚本来完成这件事,但很快意识到我们需要一个真正的进程管理器来处理这件事。
我们决定使用 [S6 Overlay init system][s6-overlay],它基于出色的 [S6][s6] 工具集,提供进程监督和管
理、日志记录以及系统初始化。
S6 Overlay 已添加到我们的 [Docker base images][base-imags] 中,每个 Home Assistant 发布的 Docker 镜像都使用它。
所有容器都已更新,更改由 Home Assistant Supervisor 自动处理;对于 Home Assistant 用户来说,没有明显的
影响。
对于在 Docker 上使用 Home Assistant Core 容器的用户,这可能会影响你运行或启动容器的方式。如果你以覆写
Docker entry point 或 command 的方式运行 Home Assistant Core 容器,你需要调整这些设置。例如,一些容器
管理系统(如 Portainer 和 Synology)会自动为你覆写这些,因此你会受到影响。
在这些情况下:
* Entry point 已更改为:`/init`
* Command (CMD) 已更改为:` ` *(空/未设置)*
如果你覆写 command endpoint 以启动 Home Assistant,entry point 中的 init system 仍将在后台处于活动状态,
并第二次启动 Home Assistant。这可能导致意外行为。
[base-imags]: https://github.com/home-assistant/docker-base
[s6-overlay]: https://github.com/just-containers/s6-overlay
[s6]: http://skarnet.org/software/s6/
---
url: /developers/blog/2020-04-16-hassfest.md
---
Hassfest 是我们在 Home Assistant 中使用的内部工具,用于确保所有 integrations 拥有有效数据。我们现在已使 Hassfest 能够验证任何 integration,包括 custom integrations。为了让你轻松开始使用它,[@ludeeus](https://www.github.com/ludeeus) 创建了一个 GitHub Action,让你不到一分钟就能开始使用。
要安装它,请按照以下步骤操作:
1. 在 GitHub 上打开你的 custom component 仓库
2. 点击 "Create new file"
3. 对于文件名,粘贴 `.github/workflows/hassfest.yaml`
4. 粘贴以下内容:
```yaml
name: Validate with hassfest
on:
push:
pull_request:
schedule:
- cron: "0 0 * * *"
jobs:
validate:
runs-on: "ubuntu-latest"
steps:
- uses: "actions/checkout@v3"
- uses: home-assistant/actions/hassfest@master
```
GitHub 现在将使用 hassfest lint 所有传入的 PR 和 commits,并且每天晚上运行一次以检查最新的 requirements。
Hassfest action 将跟踪 beta release channel。这样你就可以在 integration 与较新版本的 Home Assistant 不兼容时收到通知。
---
url: /developers/blog/2020-04-23-frontend-translations.md
---
如果你是 custom Lovelace card 的作者,并且使用了 translations,请注意,state translation keys 已更改。
在 0.109 之前,state translations 位于 `state..` 或 `state...`(针对 binary sensors)。从 0.109 版本开始,这些 translations 现在是 backend 的一部分,因此它们具有 backend translations 的 key 格式。我们将 state 格式标准化为始终包含 device class。Device class `_` 保留为没有 device class 的 entities 的 fallback。
| 旧 | 新 |
| --------------------------------------- | ------------------------------------------------- |
| `state..` | `component..state._.` |
| `state...` | `component..state..` |
在未来的版本中,我们计划将 state attribute translations 也迁移到 backend。发生时我们将在此博客上发布。
---
url: /developers/blog/2020-05-08-instance-url-helper.md
---
如果你是 integration 开发者,并遇到了获取用户的 Home Assistant instance URL 的问题,你可能知道,这过去并不总是容易的。
主要问题是 Home Assistant instance 通常安装在家庭中。这意味着内部地址和外部地址可能不同,甚至这些地址本身也可能有变体(例如,如果用户拥有 Home Assistant Cloud 订阅)。
如果 integration 对 URL 有特定要求,情况会变得更糟;例如,它必须可从外部访问并且需要 SSL。
从 Home Assistant Core 0.110 开始,引入了一个新的 instance URL helper 来简化这一点。我们从以下流程图开始解决此问题:
[](/img/en/blog/2020-05-instance-url-helper/flowchart.png)
因此,之前可用的 `base_url` 现在被两个新的用户 core 配置设置替换:internal URL 和 external URL。
从开发的角度来看,使用 `hass.config.api.base_url` 现在已被弃用,改用新的 `get_url` helper 方法。
有关使用和实现此新 URL helper 方法的更多信息,请参阅我们的[文档](/developers/instance_url.md)。
---
url: /developers/blog/2020-05-08-logos-custom-integrations.md
---
最近,Home Assistant 开始支持为 integrations 显示 images 和 icons,以在前端中显示。它们看起来很棒,并为 Home Assistant 的 UI 增添了一些色彩。
最近我们收到了很多问题,关于 custom integrations(也称为 custom components)如何添加它们的 images。从今天起,这是可能的!

创建了 custom integration?想让你的 integration 的 logo 和 icon 显示在 Home Assistant 前端中?如果是这样,请访问我们的 [GitHub brands 仓库](https://github.com/home-assistant/brands) 添加你的!
*PS:你知道你也可以将你的 custom integration 添加到我们的 [Python wheels 仓库](https://github.com/home-assistant/wheels-custom-integrations) 吗?这将使你的 custom integration 在 Home Assistant 中的安装变得超快!*
---
url: /developers/blog/2020-05-09-custom-iconsets.md
---
如果你是 custom icon set 的维护者,你可能需要更新它。
在 Home Assistant core 版本 0.110 中,我们将更改 icons 的加载方式。我们不再一次性加载所有 `mdi` icons,它们也不会成为 DOM elements。
这将为我们节省近 5000 个 DOM elements,并将减少加载时间。
这也意味着我们不再使用或加载 ``,如果你的 icon set 依赖于这个 element,你将不得不更改你的 icon set。
我们引入了一个新 API,你可以通过一个 async 函数注册你的 custom icon set,我们将使用 icon name 作为参数调用它。
我们期望返回一个包含所请求 icon 的 object 的 promise。你的 icon set 可以决定加载和缓存的策略。
API 的格式为:
```ts
window.customIconsets: {
[iconset_name: string]: (icon_name: string) => Promise< { path: string; viewBox?: string } >
};
```
`path` 是 `svg` 的 path。这是 `` 元素的 `d` 属性中的字符串。
`viewBox` 是可选的,默认为 `0 0 24 24`。
以下是 icon `custom:icon` 的一个非常简单示例:
```js
async function getIcon(name) {
return {
path: "M13,14H11V10H13M13,18H11V16H13M1,21H23L12,2L1,21Z",
};
}
window.customIconsets = window.customIconsets || {};
window.customIconsets["custom"] = getIcon;
```
当设置了 icon `custom:icon` 时,Home Assistant 将调用函数 `getIcon("icon")`。
---
url: /developers/blog/2020-05-14-entity-class-names.md
---
你是否在为自己的 entity integrations 实现 entities 时,曾经疑惑过为什么必须扩展 `BinarySensorDevice` 而不是 `BinarySensorEntity`?不用再疑惑了,因为我们在 Home Assistant 0.110 中通过重命名所有名称中错误包含 Device 的类解决了这个问题。旧类仍然存在,但使用时将记录警告。
Home Assistant 中的所有 integrations 都已升级。Custom component 作者需要自己进行迁移。你可以使用以下代码片段在保持向后兼容的同时进行迁移:
```python
try:
from homeassistant.components.binary_sensor import BinarySensorEntity
except ImportError:
from homeassistant.components.binary_sensor import BinarySensorDevice as BinarySensorEntity
```
以下类已被重命名:
| 旧类名 | 新类名 |
| -------------------- | -------------------- |
| `BinarySensorDevice` | `BinarySensorEntity` |
| `MediaPlayerDevice` | `MediaPlayerEntity` |
| `LockDevice` | `LockEntity` |
| `ClimateDevice` | `ClimateEntity` |
| `CoverDevice` | `CoverEntity` |
| `VacuumDevice` | `VacuumEntity` |
| `RemoteDevice` | `RemoteEntity` |
| `Light` | `LightEntity` |
| `SwitchDevice` | `SwitchEntity` |
| `WaterHeaterDevice` | `WaterHeaterEntity` |
---
url: /developers/blog/2020-06-01-getCardSize.md
---
自从我们在 Lovelace 中引入 lazy loading cards 以来,获取 lazy loaded card 的 card size 一直很难。
我们过去会在 element 加载之前发送一个 error element,它会带有一个 `getCardSize` 函数。但那个尺寸是错误的。
当 element 被定义时,我们会触发并重建事件,以便重新创建正确的 card。
在 0.110 中我们停止了这种做法,我们会返回正确的 element,但 element constructor 还没有加载,因此它没有 `getCardSize`。
当 constructor 加载时,element 将被升级并设置 config。从那一刻起,我们可以调用 `getCardSize`。
在这个版本中,我们更改了 `getCardSize` 的逻辑,使其会等待这一点。这意味着一些 cards(如 stacks)将返回一个 promise,因为它们必须等待其 children 被定义。
如果你是 custom card 开发者,并且你的 custom card 使用 `getCardSize` 获取其他 cards 的尺寸,你必须调整它以处理这些 promises。
我们获取 card size 的函数(你可以复制),现在看起来像这样:
```ts
export const computeCardSize = (
card: LovelaceCard | LovelaceHeaderFooter
): number | Promise => {
if (typeof card.getCardSize === "function") {
return card.getCardSize();
}
if (customElements.get(card.localName)) {
return 1;
}
return customElements
.whenDefined(card.localName)
.then(() => computeCardSize(card));
};
```
我们首先进行和以前相同的检查,如果 element 有 `getCardSize` 函数,我们将返回该值,这应该是一个 `number` 或解析为 `number` 的 `Promise`。
如果该函数不存在,我们将检查 element 的 constructor 是否已注册,如果已注册,这意味着该 element 没有 `getCardSize`,我们将像以前一样返回 `1`。
如果 element 尚未注册,我们将等待它被注册,然后再次调用相同函数获取现在已定义的 card 的尺寸。
---
url: /developers/blog/2020-07-13-alpine-python.md
---
我们的大多数 Containers 使用 Alpine。它是容器的完美发行版,因为它体积小(基于 BusyBox)、支持许多 CPU 架构,并且包系统精简。Alpine 使用 musl 作为其 C 库,而不是更常用的 glibc。
Alpine 与 musl 相比其同类产品相对年轻(分别有 15 年和 9 年的历史),但发展速度显著。由于进展如此之快,基于已不再真实的情况,对两者存在许多误解。本文的目标是解决其中几个问题,以及我们如何解决它们。
本博文无意成为 musl vs. glibc 的争吵。每种用例都不同,有自己的权衡。例如,我们在操作系统中使用 glibc。
对于测试,我使用了 [Docker Python library](https://github.com/docker-library/python) 的镜像,结果发布到我们的 [base images](https://github.com/home-assistant/docker-base)。我使用 pyperformance 进行实验室测试,并使用 Home Assistant 内部 benchmark 工具进行更真实的对比。测试环境运行在与 Docker host 相同的容器内。
## C/POSIX 标准库
我经常读到:Python 在使用 musl 作为默认 C 库时速度较慢。这个事实并不 100% 正确。如果 Python 运行时使用相同的 GCC 和 `-O3` 编译,glibc 版本在实验室基准测试中稍快,但在实际世界中,差异微不足道。Alpine 使用 `-Os` 编译,而大多数其他发行版使用 `-O2` 编译。这导致了 Python 运行时解释器之间常被提及的差异。但使用相同的编译器优化时,基于 musl 的 Python 运行时没有负面副作用。
但有一个变革者,使得基于 musl 的运行时相比基于 glibc 的运行时更实用。它是内存分配器 [jemalloc](http://jemalloc.net/),一个注重减少碎片和可扩展并发支持的通用 malloc 实现。我在一些关于 Rust 的博文上发现了一个有趣的效果。一些开发者发现 musl 在使用 jemalloc 时比 glibc 快得多,而 glibc 在使用 jemalloc 时反而变慢。可以肯定的是,glibc 和 jemalloc 的好处不在于速度(因为它们优化的是内存管理),但 musl 同时获得两者的好处。虽然纯 musl 和 glibc 之间的差异可以忽略不计,但 musl + jemalloc 与 glibc 之间的差异是实质性的(禁用 GCC 内置内存分配器优化时)。是的,现在的 jemalloc 与 musl 兼容(曾经有一段时间不兼容)。
## 编译器
如何编译 Python 也很重要。Fedora 或 Redhat 曾有关于禁用 `semantic-interposition` 以获得高性能提升的声明。我在 GCC 9.3.0 上无法重现这一点,但也未看到任何不良副作用。我建议禁用语义(如内置分配器优化)并在构建时链接 jemalloc。我也建议使用 `-O3` 优化。我们从未在目标平台上看到这些激进优化引发任何问题。需要说明的是,与发行版的 Python 运行时解释器不同,我们不需要在所有地方运行。因此,我们可以不加任何覆盖地使用 `--enable-optimizations` 并添加更多 flags。我可以肯定地说,PGO/LTO/O3 使 Python 更快,并且在我们的目标 CPU 上有效。
## Python 包
Alpine 使用 musl 确实没有 manylinux 兼容性。如果你不缓存构建,安装需要 C extensions 的包时,需要编译它们。这个过程需要时间,就像使用 Qemu 为不同 CPU 架构 cross-build 一样。你无法从 PyPi 获取预编译的二进制文件。这对我们不是问题,因为 PyPI 上提供的二进制文件大多不为我们的目标系统优化。
为了解决 Python 包的安装时间问题,我们创建了自己的 [wheel index](https://wheels.home-assistant.io/) 和 [backend](https://github.com/home-assistant/wheels),用于编译所有需要的 wheels,并通过 CI agents 保持最新。我们为每种 CPU 架构预构建了超过 1k 个包,Docker 文件的构建时间已不再重要。
## Alpine Linux
Alpine 是容器的优秀基础系统,使我们能够为用户提供最佳体验。非常感谢 Alpine Linux、musl 和 jemalloc,使这一切成为可能。
下表显示了 Alpine Linux 的 Python 运行时与我们优化(GCC 9.3.0/musl)的结果对比。所有测试均使用 Python 3.8.3 进行。
| Benchmark | Alpine | Optimized |
| ----------------------- | -------- | ----------------------------- |
| 2to3 | 924 ms | 699 ms: 1.32x faster (-24%) |
| chameleon | 37.9 ms | 25.6 ms: 1.48x faster (-33%) |
| chaos | 393 ms | 273 ms: 1.44x faster (-31%) |
| crypto\_pyaes | 373 ms | 245 ms: 1.52x faster (-34%) |
| deltablue | 22.8 ms | 16.4 ms: 1.39x faster (-28%) |
| django\_template | 184 ms | 145 ms: 1.27x faster (-21%) |
| dulwich\_log | 157 ms | 122 ms: 1.29x faster (-22%) |
| fannkuch | 1.81 sec | 1.32 sec: 1.38x faster (-27%) |
| float | 363 ms | 263 ms: 1.38x faster (-28%) |
| genshi\_text | 113 ms | 83.9 ms: 1.34x faster (-26%) |
| genshi\_xml | 226 ms | 171 ms: 1.32x faster (-24%) |
| go | 816 ms | 598 ms: 1.36x faster (-27%) |
| hexiom | 36.8 ms | 24.2 ms: 1.52x faster (-34%) |
| json\_dumps | 34.8 ms | 25.6 ms: 1.36x faster (-26%) |
| json\_loads | 61.2 us | 47.4 us: 1.29x faster (-23%) |
| logging\_format | 30.0 us | 23.5 us: 1.28x faster (-22%) |
| logging\_silent | 673 ns | 486 ns: 1.39x faster (-28%) |
| logging\_simple | 27.2 us | 21.3 us: 1.27x faster (-22%) |
| mako | 54.5 ms | 35.6 ms: 1.53x faster (-35%) |
| meteor\_contest | 344 ms | 219 ms: 1.57x faster (-36%) |
| nbody | 526 ms | 305 ms: 1.73x faster (-42%) |
| nqueens | 368 ms | 246 ms: 1.49x faster (-33%) |
| pathlib | 64.4 ms | 45.2 ms: 1.42x faster (-30%) |
| pickle | 20.3 us | 17.1 us: 1.19x faster (-16%) |
| pickle\_dict | 40.2 us | 33.6 us: 1.20x faster (-16%) |
| pickle\_list | 6.77 us | 5.88 us: 1.15x faster (-13%) |
| pickle\_pure\_python | 1.85 ms | 1.27 ms: 1.45x faster (-31%) |
| pidigits | 274 ms | 222 ms: 1.24x faster (-19%) |
| pyflate | 2.53 sec | 1.74 sec: 1.45x faster (-31%) |
| python\_startup | 14.9 ms | 12.1 ms: 1.23x faster (-19%) |
| python\_startup\_no\_site | 9.84 ms | 8.24 ms: 1.19x faster (-16%) |
| raytrace | 1.61 sec | 1.23 sec: 1.30x faster (-23%) |
| regex\_compile | 547 ms | 398 ms: 1.38x faster (-27%) |
| regex\_dna | 445 ms | 484 ms: 1.09x slower (+9%) |
| regex\_effbot | 10.3 ms | 9.96 ms: 1.03x faster (-3%) |
| regex\_v8 | 81.8 ms | 71.6 ms: 1.14x faster (-12%) |
| richards | 265 ms | 182 ms: 1.46x faster (-31%) |
| scimark\_fft | 1.31 sec | 851 ms: 1.54x faster (-35%) |
| scimark\_lu | 616 ms | 384 ms: 1.61x faster (-38%) |
| scimark\_monte\_carlo | 390 ms | 248 ms: 1.57x faster (-36%) |
| scimark\_sor | 838 ms | 571 ms: 1.47x faster (-32%) |
| scimark\_sparse\_mat\_mult | 19.0 ms | 13.2 ms: 1.43x faster (-30%) |
| spectral\_norm | 567 ms | 388 ms: 1.46x faster (-32%) |
| sqlalchemy\_declarative | 364 ms | 286 ms: 1.27x faster (-21%) |
| sqlalchemy\_imperative | 60.3 ms | 46.8 ms: 1.29x faster (-22%) |
| sqlite\_synth | 6.88 us | 5.09 us: 1.35x faster (-26%) |
| sympy\_expand | 1.39 sec | 1.05 sec: 1.32x faster (-24%) |
| sympy\_integrate | 67.3 ms | 49.5 ms: 1.36x faster (-26%) |
| sympy\_sum | 505 ms | 389 ms: 1.30x faster (-23%) |
| sympy\_str | 945 ms | 656 ms: 1.44x faster (-31%) |
| telco | 17.9 ms | 12.5 ms: 1.44x faster (-31%) |
| tornado\_http | 347 ms | 273 ms: 1.27x faster (-21%) |
| unpack\_sequence | 232 ns | 212 ns: 1.09x faster (-9%) |
| unpickle | 41.6 us | 30.7 us: 1.36x faster (-26%) |
| unpickle\_list | 10.5 us | 9.24 us: 1.14x faster (-12%) |
| unpickle\_pure\_python | 1.28 ms | 945 us: 1.36x faster (-26%) |
| xml\_etree\_parse | 335 ms | 292 ms: 1.15x faster (-13%) |
| xml\_etree\_iterparse | 281 ms | 226 ms: 1.24x faster (-20%) |
| xml\_etree\_generate | 330 ms | 219 ms: 1.51x faster (-34%) |
| xml\_etree\_process | 263 ms | 181 ms: 1.45x faster (-31%) |
---
url: /developers/blog/2020-09-30-customViewChanges.md
---
Custom Element 开发者现在可以创建用户可以加载和使用的 Custom View Layouts!
在 0.116 中,我们将更改在 Lovelace 中创建 Views 的方式。过去,我们有两种 views:default view 和 panel view。在讨论为 Lovelace 添加 Drag and Drop 时,我们决定可以做得更好,并开始允许自定义 view types。
Custom 开发者现在将能够创建一个接收以下 properties 的 view:
```ts
interface LovelaceViewElement {
hass?: HomeAssistant;
lovelace?: Lovelace;
index?: number;
cards?: Array;
badges?: LovelaceBadge[];
setConfig(config: LovelaceViewConfig): void;
}
```
Cards 和 Badges 将由 core code 创建和维护,并传递给 custom view。Custom views 旨于加载 cards 和 badges,并以自定义布局显示它们。
以下是示例:(注意:此示例没有包含所有 properties,但包含显示示例所必需的内容)
```js
class MyNewView extends LitElement {
setConfig(_config) {}
static get properties() {
return {
cards: {type: Array, attribute: false}
};
}
render() {
if(!this.cards) {
return html``;
}
return html`${this.cards.map((card) => html`${card}
`)}`;
}
}
```
你可以像在 Custom Element Registry 中定义 Custom Card 一样定义这个 element:
```js
customElements.define("my-new-view", MyNewView);
```
你可以在我们的 default view:`Masonry View` 中找到示例,位置在此:[frontend/src/panels/lovelace/views/hui-masonry-view.ts](https://github.com/home-assistant/frontend/blob/master/src/panels/lovelace/views/hui-masonry-view.ts)
下载并安装你的新 Custom View 的用户,可以通过编辑其 view 的 YAML 配置来使用它:
```yaml
- title: Home View
type: custom:my-new-view
badges: [...]
cards: [...]
```
Custom 开发者可以为每个 card 添加一个 `layout` property,用于存储 key、位置信息、宽度、高度等:
```yaml
- type: weather-card
layout:
key: 1234
width: 54px
entity: weather.my_weather
```
### 破坏性变更
对于像这样使用以下代码的 Custom Card 开发者:
```js
const LitElement = Object.getPrototypeOf(customElements.get("hui-view"));
```
你将不再能够使用 `hui-view` element 来获取 LitElement,因为它已被更改为 `updatingElement`。相反,你可以使用:
```js
const LitElement = Object.getPrototypeOf(customElements.get("hui-masonry-view"));
```
但请注意!HA 不支持这种做法。未来,这可能无法再用来导入 LitElement。
---
url: /developers/blog/2020-10-02-lazyMoreInfo.md
---
在 0.115 中,我们将 more info controls 转换为 lazy-loaded。这样做是因为 more info dialog 总是在页面加载的早期阶段加载。每个 domain 都可以有自己的 controls,而且它们可能很重。我们不需要在页面加载时拥有所有这些 elements,因此我们决定仅在需要时加载它们。
我们收到 Lovelace custom card 开发者的反馈,称这破坏了他们的 cards,因为并非所有 elements 都可用了。虽然我们不支持在 custom cards 中使用内部 elements(原因见下文),但我们决定在 0.115 中撤销此变更。
在 0.116 中,我们将重新添加 more info dialog controls 的 lazy loading。为了帮助 Lovelace custom card 开发者,我们使我们用于 lazy-load more info controls 的函数可供 custom card 开发者使用。
这意味着如果你的 card 需要某个 domain 的 controls,你可以加载它。
:::info
请注意,我们以任何方式都不支持此功能。我们不保证未来更新中会保留相同的 elements,依赖我们的 elements 时将发生 breaking changes。
:::
一个加载 `light` domain 的 more info controls 的示例:
```js
const helpers = await loadCardHelpers();
helpers.importMoreInfoControl("light");
```
## 我们为什么要这样做?
我们总是尝试使前端尽可能快。不仅是在拥有光纤网络的速度极快的台式机上,还是在只有 3G 的低端手机上。
我们尝试优化的一件事是加载代码。加载的代码越少,加载速度越快,我们就可以越早开始渲染页面。如果我们不加载所有内容,也会使用更少的内存。
然而存在权衡,如果我们总是只想加载需要的部分,就必须能够单独加载每个 element。这将意味着大量小型网络请求,由于网络请求的开销,最终比加载稍多但请求次数更少要慢。
因此,这总是在 chunk 大小和所需 chunk 数量之间寻找合适的平衡。我们的代码拆分为 chunks 是通过 bundler 完成的。目前,我们使用 Webpack 来完成这一点。
一些 custom Lovelace 开发者问我们,为什么不能将所有内部使用的 elements 暴露给 custom cards。这仅仅是因为你需要的 element 可能尚未加载,因为它之前并不需要。
使这变得格外困难的是,我们使用 custom elements。Custom elements 只能定义一次,如果你尝试再次定义它,将会报错。这意味着我们无法同时拥有较大的 chunks 和单独的 elements,因为第二次加载时会报错。
如果我们希望实现这一点,要么必须在首次加载时加载每个 component(这将使首次加载非常慢),要么必须将每个 component 拆分为自己的 chunk(这将导致大量小型网络请求,从而降低体验)。
此外,我们内部使用的 elements 本就不打算由外部开发者使用,其 API 未记录也不受支持,随时可能破坏。我们可以在任何 release 中决定替换某个 element,因为有更好的 element 或新的使用场景。或者更改 API,就像我们最近对 `chart` element 所做的那样。如果我们必须支持所有内部使用的 elements,就无法保持目前的速度。我们开发的是一个应用,elements 只是我们的构建模块。
## 那外部 elements 呢?
我们在前端中使用外部 custom elements,如 Google 的 [Material Web Components](https://github.com/material-components/material-components-web-components)。虽然我们很希望你使用相同的 elements 以提供统一的体验,但我们不能建议这样做。
就像我们自己的 elements 一样,外部 elements 也是我们 code splitted chunks 的一部分,并且很可能被 lazy-loaded。这意味着它们不会一直可用,可能会在稍后加载。
这意味着如果 custom card 在需要时因为 `mwc` element 不可用而加载并定义了它,而我们之后在 lazy-loaded 时尝试这样做,Home Assistant 前端就会遇到错误。
遗憾的是,目前对此没有技术解决方案。有一些解决方案,如来自 open-wc 的 [scoped elements](https://open-wc.org/docs/development/scoped-elements/),但它们在大多数情况下不起作用,因为导入的 element 要么自行注册,要么定义了无法被 scoping 的 sub-elements。
关于 [Scoped Custom Element Definitions](https://github.com/w3c/webcomponents/issues/716) 的提案讨论,可能会解决这个问题,但在提案被接受(如果能被接受的话)之前,可能需要很长时间才能在所有我们支持的浏览器中可用。
## 有解决方案吗?
有没有解决所有这些问题的方案?使 custom cards 能够提供相同的统一用户体验,而无需担心每次 release 都有 breaking changes 的风险?
我们看到的最佳解决方案是一套由 custom card 社区创建的 elements。这套 elements 将拥有自己的 namespace,不会与 Home Assistant 使用的 elements 的 namespace 冲突。所有 custom cards 都可以使用这些 elements,而无需担心 breaking changes 的风险。
---
url: /developers/blog/2020-10-29-supervisor-update.md
---
:::note
本文描述的 builder action(`home-assistant/builder@master`)是遗留工作流。它已被专门的 composite actions 取代。请参见 [builder migration 博文](/developers/blog/2026-04-02-builder-migration.md) 了解当前推荐的方法。
:::
## GitHub Action
你现在可以将我们的 [builder][marketplace] 作为 [GitHub action][github_action] 使用!:tada:
这已经在我们的 [hassio-addons 仓库][addons] 中使用,你可以[在这里][builder-action]查看我们如何实现它的示例。
它可以用来确保 add-on 在仓库做出更改后仍能构建,并将镜像作为 release workflow 的一部分发布。如何使用该 action 已在 [builder 仓库][builder-action] 中记录。
以下是使用它的示例:
```yaml
jobs:
build:
name: Test build
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v2
- name: Test build
uses: home-assistant/builder@master
with:
args: |
--test \
--all \
--target /data
```
此示例将在 add-on 的所有支持架构上运行测试构建。
:::tip
你的仓库被映射到 action 中的 `/data`,因此如果你的 add-on 文件在子目录中,你需要提供 `--target /data/{directoryname}` 作为 builder action 的参数。
:::
## 文档
我们的 [API 文档][api_docs] 已移至 developer documentation 站点。在此过程中,它还进行了样式更新,以便更易于导航。一些 endpoints 仍然缺少内容。如果你还没有达到 [Hacktoberfest] 的配额,也许你想为我们的 API 描述贡献更多细节?
## API 变更
* 使用 `/homeassistant/*` endpoints 已弃用,将于今年晚些时候移除。你需要改用 `/core/*`。
* 使用 `http://hassio/` 已弃用,将于今年晚些时候移除。你需要改用 `http://supervisor/`。
* 使用 `HASSIO_TOKEN` 已弃用,将于今年晚些时候移除。你需要改用 `SUPERVISOR_TOKEN`。
* 使用 `POST` 调用 `/supervisor/snapshots//remove` 删除 snapshots 已弃用,将于今年晚些时候移除。你需要改用 `DELETE` 方法调用 `/supervisor/snapshots/`。
* 使用 `X-Hassio-Key` 头作为认证方法已弃用,将于今年晚些时候移除。你需要改用带有 `Bearer` token 的 authorization 头。
[API 文档][api_docs] 已更新以反映这些变更。
## Add-on 选项
`/data/options.json` 文件的权限从 `644` 更改为 `600`。如果你的 add-on 以非 root 身份运行并读取此文件,现在将出现权限问题。
你可以在 add-on 中采取以下几个步骤来继续使用这些信息:
* 如果你在 add-on 中使用 [S6-overlay],可以使用 [`/etc/fix-attrs.d`][S6-overlay-permissions] 确保运行 add-on 的用户有权访问该文件。
* 你可以将 add-on 更改为以 `root`(默认)身份运行。
## 发布版本
到目前为止,Supervisor、我们的 plugins 和 add-ons 一直使用 build number 和 [Semantic Versioning (SemVer)][semver] 的混合作为版本系统。我们决定为这些仓库替换它,改为采用 [Calendar Versioning (CalVer)][calver] 作为我们的版本系统。
我们正在将 Supervisor 从基于 release 的开发迁移到 continuous development。这与我们现有的基于 channel 的更新策略(stable、beta 和 dev)完美契合。我们现在利用自动化的 pipelines 将新的 Supervisor 版本构建并发布到正确的 channel。采用这种结构后不再需要双分支设置,因此我们的 `dev` 和 `master` 分支现在已被新的 `main` 分支替换。Supervisor 的 plugins(DNS、Multicast、Observer、CLI)也将遵循这一 continuous development 原则。
我们做出这一更改是为了通过自动测试系统提供更高的软件质量。现在每个 commit 都会触发一个新的 dev release,由我们的测试实例进行测试。问题会立即报告给 sentry。这使我们有时间在创建 release 之前测试所有更改。创建 release 时,更改将在 beta channel 中可用。一旦宣布为 stable,我们就可以将 release 推广到 stable channel。
我们使用 [builder action][marketplace] 配合 [GitHub actions][github_action] 来构建和发布 Supervisor、我们的 plugins 和 Docker 容器的 base images。如果你有兴趣了解我们如何做到这一点,可以查看[此处的 Supervisor builder action][builder_action],以及[此处的 action helpers][action_helpers]。
[action_helpers]: https://github.com/home-assistant/actions/tree/master/helpers
[addons]: https://github.com/home-assistant/hassio-addons
[api_docs]: /docs/api/supervisor/endpoints
[builder_action]: https://github.com/home-assistant/supervisor/blob/main/.github/workflows/builder.yml
[builder-action]: https://github.com/home-assistant/builder#github-action
[calver]: https://calver.org/
[github_action]: https://github.com/features/actions
[Hacktoberfest]: https://hacktoberfest.digitalocean.com/
[marketplace]: https://github.com/marketplace/actions/home-assistant-builder
[S6-overlay-permissions]: https://github.com/just-containers/s6-overlay#fixing-ownership--permissions
[S6-overlay]: https://github.com/just-containers/s6-overlay
[semver]: https://semver.org/
---
url: /developers/blog/2020-11-09-system-health-and-templates.md
---
在 Home Assistant 0.118 中,将有可能会影响你的 custom integration 的两项变更。
## 移除已弃用的 `helpers.template.extract_entities`
之前已弃用的来自 Template helper 的 `extract_entities` 方法已被移除([PR 42601](https://github.com/home-assistant/core/pull/42601))。不要提取 entities 然后手动监听 state 变化,请改用 Event helper 中的新 `async_track_template_result`。它将动态确保每个接触到的 entity 都被正确跟踪。
```python
from homeassistant.helpers.event import async_track_template_result, TrackTemplate
template = "{{ light.kitchen.state == 'on' }}"
async_track_template_result(
hass,
[TrackTemplate(template, None)],
lambda event, updates: print(event, updates),
)
```
## 改进的 System Health
从 Home Assistant 0.118 开始,我们正在弃用为 integration 提供 system health 信息的旧方式。相反,请在你的 integration 中创建一个 `system_health.py` 文件([PR 42785](https://github.com/home-assistant/core/pull/42785))。
从这个版本开始,你还可以包含需要较长时间才能解析的健康检查([PR 42831](https://github.com/home-assistant/core/pull/42831)),例如检查服务是否在线。结果将在准备好时传递给前端。
```python
"""Provide info to system health."""
from homeassistant.components import system_health
from homeassistant.core import HomeAssistant, callback
from .const import DOMAIN
@callback
def async_register(
hass: HomeAssistant, register: system_health.RegisterSystemHealth
) -> None:
"""Register system health callbacks."""
register.async_register_info(system_health_info)
async def system_health_info(hass):
"""Get info for the info page."""
client = hass.data[DOMAIN]
return {
"server_version": client.server_version,
"can_reach_server": system_health.async_check_can_reach_url(
hass, client.server_url
)
}
```
---
url: /developers/blog/2021-01-29-custom-integration-changes.md
---
大家新年好!2021 年终于到来了 🎉
你可能已经知道,最近我们意识到几个流行的 custom integrations 中存在安全问题。你可以在此处阅读更多:
* https://www.home-assistant.io/blog/2021/01/14/security-bulletin/
* https://www.home-assistant.io/blog/2021/01/22/security-disclosure/
* https://www.home-assistant.io/blog/2021/01/23/security-disclosure2/
鉴于这些事件。从刚刚发布的 Home Assistant 2021.2.0 beta 开始,我们正在更改两件会影响 custom integrations 的事情。
## 已弃用的 utilities
位于 `homeassistant.utils` 包中的 `sanitize_filename` 和 `sanitize_path` helper 已被弃用,并等待移除。这将在计划于今年 4 月第一周发布的 Home Assistant 2021.4.0 中发生。
我们添加了 `raise_if_invalid_filename` 和 `raise_if_invalid_path` 作为替代品。它们位于同一个 `homeassistant.utils` 包中。这些新函数将抛出 `ValueError`,而不是依赖开发者比较函数输出与输入是否不同。这将防止滥用。
## 版本
第二个变更非常酷!版本!
[`manifest.json` 文件][manifest] 现在增加了对 `version` key 的支持。Version 应该是包含 major、minor 和 patch 版本的字符串。例如,`"1.0.0"`。
这个 version 将帮助用户向你传达他们遇到问题的 version。并且如果你发现 custom integration 中存在安全问题,Home Assistant 将能够阻止使用不安全的版本。
**从 Home Assistant 2021.6 版本开始,`version` key 是必需的**
## Hassfest 已更新
`hassfest` 是我们在 Home Assistant 中用于验证所有 integrations 的内部工具。今年 4 月,我们将其作为一个 GitHub Action 提供,帮助你发现 custom integration 中的问题。该 action 可用于任何托管在 GitHub 上的 custom integration。如果你还没有将它添加到仓库中,现在正是时候![在此处阅读更多][hassfest]。
如果你正在使用 `hassfest` GitHub action,现在当你缺少 [`manifest.json` 文件][manifest] 中的 `version` key 时,它运行时将开始显示警告。当 `version` key 完全成为 custom integrations 的必需项时,此警告将成为错误。
## 提供文件
使用户能够访问资源是 custom integrations 的常见用例,无论是 images、panels,还是用户可以在 Lovelace 中使用的增强功能。从路径提供静态文件的唯一方式应使用 `hass.http.async_register_static_paths`。请使用此方法,避免使用自己的方法,因为这可能导致严重的 bug 或安全问题。
```python
from pathlib import Path
from homeassisant.components.http import StaticPathConfig
should_cache = False
files_path = Path(__file__).parent / "static"
files2_path = Path(__file__).parent / "static2"
await hass.http.async_register_static_paths([
StaticPathConfig("/api/my_integration/static", str(files_path), should_cache),
StaticPathConfig("/api/my_integration/static2", str(files2_path), should_cache)
])
```
关于 custom integrations 的更新就这些。继续做很棒的事情!下次见 👋
[AwesomeVersion]: https://github.com/ludeeus/awesomeversion
[CalVer]: https://calver.org/
[SemVer]: https://semver.org/
[hassfest]: /blog/2020/04/16/hassfest
[manifest]: /docs/creating_integration_manifest
---
url: /developers/blog/2021-04-09-astral-2.2.md
---
我们最近合并了一个 [pull request](https://github.com/home-assistant/core/pull/48573),将 Home Assistant Core 中使用的 astral 库版本升级到 2.2。这将随 Home Assistant 2021.5 发布。这是 Astral 的一个 major 版本 bump,包含一些 breaking changes,这导致我们更新了依赖 astral 的内置 helpers 和 integrations。这导致我们的 sun helpers 出现了一些 breaking changes。
维护直接使用 sun helpers 或 astral 库的 integrations 的 custom integration 作者,应审查 breaking changes,并在需要时更新他们的 custom integrations。
Sun helper 已更改 `get_astral_location` 和 `get_location_astral_event_next` 的签名,以包含一个 `elevation` 参数。此外,`get_astral_location` 的返回值已更改为包含 elevation 的 tuple。
```py
@callback
@bind_hass
def get_astral_location(
hass: HomeAssistant,
) -> tuple[astral.location.Location, astral.Elevation]:
"""Get an astral location for the current Home Assistant configuration."""
@callback
def get_location_astral_event_next(
location: astral.Location,
location: astral.location.Location,
elevation: astral.Elevation,
event: str,
utc_point_in_time: datetime.datetime | None = None,
offset: datetime.timedelta | None = None,
) -> datetime.datetime:
"""Calculate the next specified solar event."""
```
详情请查看 [`astral` 的 changelog](https://github.com/sffjunkie/astral/blob/master/ChangeLog.md#22---2020-05-20)。
---
url: /developers/blog/2021-05-07-switch-pytz-to-python-dateutil.md
---
三年前,Paul Ganssle 写了一篇[对比文章](https://blog.ganssle.io/articles/2018/03/pytz-fastest-footgun.html),比较了 `pytz` 和 `python-dateutil` 之间的时区处理方式。在这篇文章中,他展示了 `pytz` 很容易以几乎正确但难以发现错误的方式来使用:
```python
import pytz
from datetime import datetime, timedelta
NYC = pytz.timezone('America/New_York')
dt = datetime(2018, 2, 14, 12, tzinfo=NYC)
print(dt)
# 2018-02-14 12:00:00-04:56
```
*[(链接到文章解释为什么是 -4:56 的部分)](https://blog.ganssle.io/articles/2018/03/pytz-fastest-footgun.html#pytz-s-time-zone-model)*
在 Home Assistant 2021.6 中,我们将切换到 `python-dateutil`。如果你的 custom integration 依赖于非官方接口 `my_time_zone.localize(my_dt)`,你需要进行升级。请改用 Python 的官方方法 `my_dt.astimezone(my_time_zone)`。
属性 `hass.config.time_zone` 也将变为字符串,而不是时区对象。
感谢 [@bdraco] 帮助重启这项努力并推动此变更[完成](https://github.com/home-assistant/core/pull/49643)。我们实际上在迁移过程中发现了一些 bugs!也感谢 Paul Ganssle 维护 `python-dateutil` 以及出色的文章。
[@bdraco]: https://github.com/bdraco
## 5 月 10 日更新
哇,时间过得真快!Paul,`python-dateutil` 的作者,也是启发我们的博文作者,提醒我们 Python 3.9 包含了升级后的时区处理,我们应该改用它。在 Nick 和 Paul 的帮助下,`python-dateutil` 已被移除,改为使用 `zoneinfo`([PR](https://github.com/home-assistant/core/pull/50387))。
---
url: /developers/blog/2021-05-19-lit-2.0.md
---
我们将前端升级到使用 Lit 2.0,这是 `LitElement`(3.0)和 `lit-html`(2.0)的 major bump,今后将一起在 [Lit](https://lit.dev) 名称下发展。
这次升级带来了许多出色的[改进](https://lit.dev/blog/2021-04-21-lit-2.0-meet-lit-all-over-again/),但也包含一些 breaking changes。
如果你开发了 custom card 或 view,并从我们的 components 中使用了 `LitElement` 和 `lit-html`,你的 component 将在下一个版本(2021.6)中使用 Lit 2.0。如果你不确定是否从我们的 components 中使用了 `LitElement`,你的代码看起来可能像这样:
```ts
const LitElement = Object.getPrototypeOf(customElements.get("ha-panel-lovelace"));
const html = LitElement.prototype.html;
const css = LitElement.prototype.css;
```
这不是推荐的做法,我们建议你打包 Lit 到你的 component 中,或从 `unpkg.com` 或其他来源导入,如这个[示例](https://developers.home-assistant.io/docs/frontend/custom-ui/custom-card#example-using-js-modules)。这样你的 card 就不依赖于 Home Assistant 随附的 Lit 版本。
其中一项变更是,`shadowRoot` 的创建不再在 constructor 中进行,而是在第一次 update 之前进行。这意味着如果你直接与 DOM 交互(如使用 query selector),你不能假设 `shadowRoot` 总是可用的。
所有变更请查看 Lit 文档中的[升级指南](https://lit.dev/docs/releases/upgrade/)。
我们预计大多数 cards 都能无问题地与 Lit 2.0 配合工作,但请 custom card 开发者确保兼容性。你可以使用当前 dev 版本的 Home Assistant 或 nightly 版本的 Home Assistant 来完成这一点,两者目前都使用 Lit 2.0。
---
url: /developers/blog/2021-05-25-sensor_attributes.md
---
Sensor entity 模型已更新,增加了两个新 properties:`state_class` 和 `last_reset`。引入这两个新 properties 的驱动力是启用自动生成长期统计。
### state\_class
诸如 `DEVICE_CLASS_TEMPERATURE` 等 sensor device classes 用于表示差异巨大的数据类型,例如:
* 定期更新的温度测量
* 历史或统计数据,例如每日平均温度
* 未来数据,例如明天的预测
区分代表测量的 sensors 和不代表测量的 sensors 是必要的,以便自动合理地选择要包含在长期统计中的 sensors。
[`state_class`](https://developers.home-assistant.io/docs/core/entity/sensor#properties) property 对 state 的类型进行分类:state 可能是来自温度传感器或电能表的\_当前时间测量值\_,*历史值*(如过去 24 小时的平均温度或上个月使用的能量),或\_预测值\_(如天气预报或下一次垃圾收集时间表)。如果 `state_class="measurement"`,state 表示当前值,而不是历史聚合或未来预测。否则,`state_class=None`。有一个[架构讨论](https://github.com/home-assistant/architecture/discussions/557),包含一些额外的背景信息。
请注意,上面提到的\_当前时间测量值\_并不意味着 state 必须以特定频率更新,也不意味着 sensor 不允许进行间接测量(如集成功率来计算能量)。换句话说,如果 sensor 表示最新观察值或时间序列中的最新数据点,它就符合 `state_class="measurement"`。
### last\_reset
累积型 sensor(如电表、燃气表、水表等)初始化的时间。如果初始化时间未知且表永远不会重置,设置为 UNIX epoch 0:`homeassistant.util.dt.utc_from_timestamp(0)`。请注意,`last_reset` property 返回的 `datetime.datetime` 在更新 entity 的 state attributes 时将被转换为 ISO 8601 格式的字符串。更改 `last_reset` 时,`state` 必须是有效数字。
---
url: /developers/blog/2021-08-12-sensor_temperature_conversion.md
---
温度单位转换正从 `Entity` base class 移至 `SensorEntity` base class。仅当 sensor 的 `device_class` 属性设置为 `DEVICE_CLASS_TEMPERATURE` 时,才会进行单位转换。如果 `device_class` 未设置或未设置为 `DEVICE_CLASS_TEMPERATURE`,温度转换将在过渡期间进行,并记录警告。
为此,sensor entity 模型已更新,增加了两个新 properties:`native_value` 和 `native_unit_of_measurement`。这使我们在未来可以添加额外的单位转换,而无需依赖 integrations 自行处理。
Sensor 实现不应再实现 `state()` property 函数或设置 `_attr_state` 属性。Sensor 实现也不应实现 `unit_of_measurement` property 函数,设置 `_attr_unit_of_measurement` 属性,或设置 `EntityDescription` 中的 `unit_of_measurement` 成员。
### native\_value
sensor 报告的值。
写入 state machine 的实际 state 可能会因单位转换而被 `SensorEntity` 修改。
### native\_unit\_of\_measurement
sensor 的测量单位(如果有)。
写入 state machine 的 `unit_of_measurement` 可能会因单位转换而被 `SensorEntity` 修改。
---
url: /developers/blog/2021-08-16-state_class_total.md
---
新增了一个新的 state class:`total_increasing`。此外,`last_reset` 属性已从 `SensorEntity` 中移除。推动这些变更的驱动力是使其更容易与设备(如公用事业表)集成。
### 状态类别
有 2 个定义的 state classes:
* `measurement`,state 表示当前时间的测量值,例如温度、电功率、股票投资组合的价值等。对于受支持的 sensors,每小时更新每小时 min、max 和平均 sensor 读数的统计,或自首次添加以来 sensor 值的累计增长或下降统计。
* `total_increasing`,单调递增的总计值,例如消耗的燃气、水或能量量。支持时,每小时更新自首次添加以来 sensor 值的累计增长。
#### `STATE_CLASS_TOTAL_INCREASING`
对于 state\_class 为 `STATE_CLASS_TOTAL_INCREASING` 的 sensors,递减的值被解释为新表计周期的开始或表的更换。重要的是,integration 必须确保在从存在测量噪声的 sensor 计算值时,值不会错误地递减。此 state class 适用于燃气表、电表、水表等。
sensor 首次添加到 Home Assistant 时的 state 用作初始零点。检测到新表计周期时,零点将被设置为 0。请参考下表了解这对统计的影响。
`STATE_CLASS_TOTAL_INCREASING` 带有新表计周期的示例:
| t | state | sum |
| :--------------------- | -----: | ---: |
| 2021-08-01T13:00:00 | 1000 | 0 |
| 2021-08-01T14:00:00 | 1010 | 10 |
| 2021-08-01T15:00:00 | 0 | 10 |
| 2021-08-01T16:00:00 | 5 | 15 |
`STATE_CLASS_TOTAL_INCREASING` 在新表计周期开始时初始 state 不为 0,但使用 0 作为零点的示例:
| t | state | sum |
| :--------------------- | -----: | ---: |
| 2021-08-01T13:00:00 | 1000 | 0 |
| 2021-08-01T14:00:00 | 1010 | 10 |
| 2021-08-01T15:00:00 | 5 | 15 |
| 2021-08-01T16:00:00 | 10 | 20 |
此 state class 以前由 state class `measurement` 与 `last_reset` 值组合表示。这种方法已被弃用,将被解释为具有自动 last reset 的 `total_increasing` state class。
---
url: /developers/blog/2021-08-24-supervisor_update.md
---
距离我们上次发布关于 Supervisor 的变更已经有一段时间了。以下是过去一年的一些亮点以及未来的计划。这些信息主要针对 add-on 开发者,但这里也有一些适合所有人的内容。如果你还没有看到,我们在[主站发布了一篇博客][main_blog],建议你阅读。
## Snapshot 到 Backup
首先,正如[主站上的博客][main_blog]中提到的,我们已经开始从"snapshot"这一名称(自 Supervisor 诞生之初就一直使用)向更容易被识别的"backup"过渡。
这些变更现已在 Supervisor 的 `dev` 频道上线,你可以开始测试和调整你的工具/add-ons,确保在用户使用时它们仍然能正常工作。
### API 变更
随着从"snapshot"到"backup"的过渡,Supervisor API 中增加了一个新的基础部分 [`/backups`][supervisor_api_backups],它的运作方式与 [`/snapshots`][supervisor_api_snapshots] 相同,拥有旧部分的所有相同 endpoints,但有两个关键区别:
* 如果你访问 `/backups`,返回的数据现在将是 `{"backups": []}` 而不是 `{"snapshots": []}`
* 要删除 snapshot,你现在必须使用 `DELETE` HTTP 方法配合 `/backups` endpoint,此前同时支持 `POST` 和 `DELETE`。
旧的 [`/snapshots`][supervisor_api_snapshots] endpoints 现已弃用,计划于今年第四季度移除。
### Backup 结构变更
为了保持一致性,我们还更改了 backup tar 中 meta 文件的名称,从 `snapshot.json` 改为 `backup.json`。如果你的工具使用了该文件,你应该查找这两种文件,这样你的工具就可以同时适用于现有和新备份。
## 流式 ingress
一些 add-ons 需要接收用户传来的大量数据,例如上传。此前,运行在 ingress 后面的 add-ons 每个请求的限制为 16 MB,这仍然是默认值。如果你需要接收更大的负载,可以通过在 add-on 配置中将 `ingress_stream` 设置为 `True` 来启用此功能。这样做时,请求将从客户端流式传输到你的 add-on,且请求没有大小限制,几乎没有任何开销。
请注意,并非所有 webservers 默认都能处理此功能,因此你可能需要对其进行调整。
## 弃用的 API endpoints
在过去几年中,我们重组了部分 API endpoints,但同时也保留了旧 endpoints 的可用性。如果你的工具/add-ons 使用了任何弃用的 endpoints,你应该立即迁移到新 endpoints。所有弃用的 endpoints 计划于今年第四季度移除。
以下是弃用的 endpoints 及其替代项列表:
弃用的 endpoints | 替代为
\-- | --
`/homeassistant/*` | `/core/*`
`/snapshots/*` | `/backups/*`
此外,以下内容也已弃用,并计划于今年第四季度移除。
* 环境变量 `HASSIO_TOKEN` 已被 `SUPERVISOR_TOKEN` 替代。
* 使用 `X-Hassio-Key` header 已被[使用带 Bearer token 的 `Authorization`][api_example]替代。
* 使用 `http://hassio/` 与 Supervisor 通信已被 `http://supervisor/` 替代。
## Supervised 安装
目前维护[supervised installation][supervised_installation]的体验并不理想。大多数用户用来安装的[脚本][supervised_script]落后于 Supervisor 对主机的要求。由于没有真正的升级路径,使用它的用户需要手动调整安装。
最近我们创建了 [OS Agent][os_agent],如[主站上的博客][main_blog]中所述。这改善了主机操作系统与 Supervisor 之间的通信,并引入了更多功能。为了利用这些功能,当前 supervised 安装的用户必须手动安装 OS Agent。
作为替代方案,可以将 supervised installation 打包并分发为 deb 包,以便在主机上使用 `apt` 安装和升级。为了使这可行,我们正在寻找一个人(或一群人)来创建和维护这种类型的部署,使 supervised installation 方法与我们的 OS 保持同等水平,更重要的是让用户在主机上进行必要的更新更容易。
如果你对这些变更有疑问,欢迎在[我们的 Discord 服务器][discord]上的 `#devs_supervisor` 频道中咨询。
下次再见 👋
[discord]: https://discord.gg/c5DvZ4e
[main_blog]: https://www.home-assistant.io/blog/2021/08/24/supervisor-update/
[supervisor_api_backups]: /docs/api/supervisor/endpoints#backup
[supervisor_api_snapshots]: /docs/api/supervisor/endpoints#backup
[supervised_installation]: https://github.com/home-assistant/architecture/blob/master/adr/0014-home-assistant-supervised.md
[supervised_script]: https://github.com/home-assistant/supervised-installer
[os_agent]: https://github.com/home-assistant/os-agent
[api_example]: /docs/api/supervisor/examples#get-network-information-with-curl
---
url: /developers/blog/2021-09-20-state_class_total.md
---
注意:本文于 2021-10-21 进行了编辑,移除了对 total accumulated increases 和 decreases 的引用,这些内容已被移除且从未包含在 Home Assistant 2021.10 版本中。
新增了一个 state class `total`,并且 `last_reset` 属性已重新添加到 `SensorEntity` 中,不再被弃用。此次变更的目的是支持那些使用 `total_increasing`(在 Home Assistant 2021.9 中引入)过于受限无法覆盖的情况。注意,为 state class `measurement` 的 sensor 设置 `last_reset` 仍然被弃用。
### 状态类别
共有 3 个已定义的 state class:
* `measurement`,state 表示当前时间的测量值,例如温度、电功率等。对于受支持的 sensor,会定期更新最小值、最大值和平均值统计。
* `total`,state 表示一个既可增加又可减少的总量,例如净能量电表。当受支持时,会定期更新 sensor 自首次添加以来值的累积增长或下降。
* `total_increasing`,单调递增的总量,例如消耗的气体、水或能量的数量。当受支持时,会定期更新 sensor 自首次添加以来值的累积增长。
#### 状态类别 `total`
对于 state class 为 `total` 的 sensor,`last_reset` 属性可选择性地设置,以获取对 meter cycle 的手动控制。
Sensor 首次添加到 Home Assistant 时的 state 被用作初始零点。当 `last_reset` 发生变化时,零点将被设置为 0。
如果未设置 last\_reset,则在计算 `sum` 统计时使用 sensor 首次添加时的值作为零点。
不设置 last\_reset 的 state class `total` 示例:
| t | state | sum |
| :--------------------- | -----: | -----: |
| 2021-08-01T13:00:00 | 1000 | 0 |
| 2021-08-01T14:00:00 | 1010 | 10 |
| 2021-08-01T15:00:00 | 0 | -1000 |
| 2021-08-01T16:00:00 | 5 | -995 |
设置 last\_reset 的 state class `total` 示例:
| t | state | last\_reset | sum |
| :--------------------- | -----: | ------------------- | -----: |
| 2021-08-01T13:00:00 | 1000 | 2021-08-01T13:00:00 | 0 |
| 2021-08-01T14:00:00 | 1010 | 2021-08-01T13:00:00 | 10 |
| 2021-08-01T15:00:00 | 1005 | 2021-08-01T13:00:00 | 5 |
| 2021-08-01T16:00:00 | 0 | 2021-09-01T16:00:00 | 5 |
| 2021-08-01T17:00:00 | 5 | 2021-09-01T16:00:00 | 10 |
新 meter cycle 开始时初始 state 不是 0,但使用 0 作为零点的 state class `total` 示例:
| t | state | last\_reset | sum |
| :--------------------- | -----: | ------------------- | -----: |
| 2021-08-01T13:00:00 | 1000 | 2021-08-01T13:00:00 | 0 |
| 2021-08-01T14:00:00 | 1010 | 2021-08-01T13:00:00 | 10 |
| 2021-08-01T15:00:00 | 1005 | 2021-08-01T13:00:00 | 5 |
| 2021-08-01T16:00:00 | 5 | 2021-09-01T16:00:00 | 10 |
| 2021-08-01T17:00:00 | 10 | 2021-09-01T16:00:00 | 15 |
#### 状态类别 `total_increasing`
对于 state\_class 为 `total_increasing` 的 sensor,减少的值被解释为新 meter cycle 的开始或 meter 的更换。集成必须确保当从含有 measurement noise 的 sensor 计算值时,值不会因误操作而减少。此 state class 适用于 gas meter、electricity meter、water meter 等。
Sensor 首次添加到 Home Assistant 时的 state 被用作初始零点。当检测到新的 meter cycle 时,零点将被设置为 0。
请参阅下方表格了解这对统计的影响。
具有新 meter cycle 的 state class `total_increasing` 示例:
| t | state | sum |
| :--------------------- | -----: | ---: |
| 2021-08-01T13:00:00 | 1000 | 0 |
| 2021-08-01T14:00:00 | 1010 | 10 |
| 2021-08-01T15:00:00 | 0 | 10 |
| 2021-08-01T16:00:00 | 5 | 15 |
新 meter cycle 开始时初始 state 不是 0,但使用 0 作为零点的 state class `total_increasing` 示例:
| t | state | sum |
| :--------------------- | -----: | ---: |
| 2021-08-01T13:00:00 | 1000 | 0 |
| 2021-08-01T14:00:00 | 1010 | 10 |
| 2021-08-01T15:00:00 | 5 | 15 |
| 2021-08-01T16:00:00 | 10 | 20 |
---
url: /developers/blog/2021-10-26-config-entity.md
---
Home Assistant Core 2021.11 引入了两个新概念,使设备管理更加容易:
device configuration URL 和 entity categories。
### 设备配置 URL
现在可以提供一个 configuration\_url 作为 device registry 信息的一部分。
configuration\_url 在 device card 中使用,允许用户访问设备
以进行在 Home Assistant 中不可用的配置或诊断。
注意,链接到的 URL 不由 Home Assistant 代理,因此在远程连接到 Home Assistant 时通常无法使用。
[](/img/en/blog/2021-10-26-config-entity/configuration_url.png)
### 实体类别
实体现在有一个可选属性 `entity_category`,用于分类非 primary
实体。对于允许更改设备配置的实体,设置为 `config`,
例如,一个可以开关设备背景照明的 switching entity。对于暴露设备的某些配置参数
或诊断信息但不允许更改它的实体,设置为 `diagnostic`,例如,显示
RSSI 或 MAC 地址(含允许值)的 sensor。
设置了 `entity_category` 的实体:
* 不包含在针对整个设备或 area 的 service call 中。
* 默认不暴露给 Google Assistant 或 Alexa。
* 显示在设备配置页面的独立 card 上。
* 不出现在自动生成的 Lovelace Dashboards 上。
[](/img/en/blog/2021-10-26-config-entity/entity_categories.png)
---
url: /developers/blog/2022-02-18-paper-elements.md
---
在 Home Assistant Core 2022.3 版本中,我们正在 Frontend Repository 中移除所有 `paper-dropdown-menu` 和 `paper-input` elements。这意味着任何之前在卡片或卡片编辑器中使用这些 elements 的 custom card 将无法正确渲染,需要更新其代码库。
使用 Home Assistant Frontend 中 elements 的 Custom Cards 从未被支持。
## 为什么这么做?
我们正在将代码库从已弃用的 `paper` elements 转换为 Google 的新 [Material Web Components](https://github.com/material-components/material-components-web-components)。这是为了使我们的前端保持与最新组件和功能同步。
## Custom cards 应该怎么做?
我们的建议是使用 [Scoped Custom Element Registry](https://github.com/lit/lit/tree/main/packages/labs/scoped-registry-mixin) 在你的 custom card 中打包 Material Web Components。从 Home Assistant 2022.3 版本开始,Home Assistant Frontend 加载了为此所需的[polyfill](https://github.com/webcomponents/polyfills/tree/master/packages/scoped-custom-element-registry)。
这将允许任何 custom card 利用 Lovelace Editors 中使用的相同 MWC elements。你可以在 [Boilerplate Card](https://github.com/custom-cards/boilerplate-card) 中看到这种用法的示例。其他 HA elements(如图标选择器 ``)不支持以同样的方式使用。
更多信息请参见[这篇旧博客文章](https://developers.home-assistant.io/blog/2020/10/02/lazyMoreInfo#what-about-external-elements)。
---
url: /developers/blog/2022-02-28-opt-in-remove-device.md
---
在 Home Assistant Core 2022.3 版本中,[Erik Montnemery](https://github.com/emontnemery) 添加了[支持](https://github.com/home-assistant/core/pull/66188),允许任何集成选择(opt in)在 Home Assistant 界面的设备页面上显示"删除设备"按钮。
请参阅我们的[开发者文档](/developers/device_registry_index.md#removing-devices)了解如何在集成中实现此功能。
到目前为止,已有五个集成选择加入了此功能:
* [Google Cast](https://github.com/home-assistant/core/pull/66808)
* [MQTT](https://github.com/home-assistant/core/pull/66766)
* [MySensors](https://github.com/home-assistant/core/pull/67128)
* [RFXCOM RFXtrx](https://github.com/home-assistant/core/pull/58252)
* [Tasmota](https://github.com/home-assistant/core/pull/66811)
## 为什么需要这个功能?
在此功能添加之前,建议集成自动清理 config entry 中的过时设备。但并非所有集成都能自动确定设备是否应被移除,因此在某些情况下仍需要一种让用户自行选择的手动选项。
为满足此需求,一些集成实现了一个集成特定的 websocket command 并附带前端支持,以允许手动移除设备。有了这个新功能,集成不再需要添加任何 websocket 或前端代码,只需实现后端回调函数即可支持手动设备移除。
Happy days!
---
url: /developers/blog/2022-03-20-update-entity.md
---
Home Assistant 2022.4 将提供一个全新的 entity platform:
`update` entity。
`update` entity 可由集成提供,用于向 Home Assistant 用户指示设备或服务有可用的更新。它允许你提供关于更新的附加信息,例如可用最新版本、发布说明摘要以及在线完整发布公告的链接。
此外,可以实现 `install` 方法,使用户能够直接从 Home Assistant 中安装更新。
向你的集成添加 `update` platform 相对简单,因为它只需要几个属性和(如果集成支持的话)一个 `install` 方法。大多数其他细节由 Home Assistant 自动处理。
在 Home Assistant Core [2021.12](https://www.home-assistant.io/blog/2021/12/11/release-202112/#brand-new-configuration-panel) 中,我们将由 Supervisor 提供的更新移到了 Configuration 面板。通过使用这个新 platform,我们将能够在不久的将来通过新 entities 提供的信息对其进行扩展。
请参阅我们的[开发者文档](/developers/core/entity/update.md)了解如何在集成中实现此功能。
如果你的集成此前提供了一个带有 `update` device class 的 `binary_sensor` 或 `button` entity,请考虑用 `update` entity 替换它们。
---
url: /developers/blog/2022-03-30-2022.4-new-dev-features.md
---
2022.4 中有一些面向开发者的新功能:
* [Integration Type]:集成现在可以标记自己为 helper。
* [Backup platform]:集成现在可以通过实现新的 backup platform 在备份期间暂停工作。
* entity registry 中的 hidden\_by:实体现在可以通过 entity registry 隐藏。当向 voice assistants 等外部 UI 暴露实体时,这些实体应默认为不包含。
* \[新的和更新的 selectors]:我们添加了一堆新的 selectors,可用于 blueprints、services.yaml 和 config flows。
* \[backend flows 中的 selectors]:现在可以在 config flows 中使用 `selector` helper。它的配置和渲染方式与 services.yaml/blueprints 中的 selectors 完全相同。
* [ha-form context]:Context 是一种允许 selectors 根据其他字段的值进行动态配置的新方式。目前仅适用于 attribute selector。
* \[config flow fields 的 helper text]:Config flow fields 现在可以有一个 description。
* [Schema Config Flow helper]:基于 config schema 创建 config flows 的新 helper。[示例实现](https://github.com/home-assistant/core/blob/dev/homeassistant/components/switch_as_x/config_flow.py)
* [Show Menu step]:数据录入流程中的这个新步骤会向用户显示一个菜单。
* [Fan speed]:我们引入了一个新格式,此过渡现已完成。
* [Device State Attributes]:此属性的向后兼容支持在自 Home Assistant 2021.4 弃用后已被移除。请改用 `extra_state_attributes`。
* \[不带 width/height 的 Cameras]:已移除不带 width/height 的 cameras 的兼容层。
* [Calendar offset]:`calculate_offset` 和 `is_offset_reached` 的 Calendar 方法签名已更改,为 Calendar 的 typing 改进做准备,并可能进一步简化 offsets。
[ha-form context]: https://github.com/home-assistant/frontend/pull/12062
[Integration Type]: /docs/creating_integration_manifest
[Backup platform]: /docs/core/platform/backup
[New and updated selectors]: https://next.home-assistant.io/docs/blueprint/selectors/
[Selectors in backend flows]: /docs/data_entry_flow_index#show-form
[config flow helper text for fields]: /docs/data_entry_flow_index#show-form
[Schema Config Flow helper]: https://github.com/home-assistant/core/blob/dev/homeassistant/helpers/schema_config_entry_flow.py
[Show Menu step]: /docs/data_entry_flow_index#show-menu
[Fan speed]: https://github.com/home-assistant/core/pull/67743
[Device State Attributes]: https://github.com/home-assistant/core/pull/67837
[Cameras without width/height]: https://github.com/home-assistant/core/pull/68039
[Calendar offset]: https://github.com/home-assistant/core/pull/68724
---
url: /developers/blog/2022-04-02-support-constants-deprecation.md
---
从 Home Assistant Core 2022.5 开始,所有 `SUPPORT_*` 常量已被弃用,
每个 entity platform 都提供了一个 `EntityFeature` enum 来替代它们。
这适用于以下 platform:
* **Alarm Control Panel**
已弃用的 supported feature 常量:
* `SUPPORT_ALARM_ARM_AWAY`
* `SUPPORT_ALARM_ARM_CUSTOM_BYPASS`
* `SUPPORT_ALARM_ARM_HOME`
* `SUPPORT_ALARM_ARM_NIGHT`
* `SUPPORT_ALARM_ARM_VACATION`
* `SUPPORT_ALARM_TRIGGER`
请改用新的 [`AlarmControlPanelEntityFeature`](/developers/core/entity/alarm-control-panel.md#supported-features) enum。
* **Camera**
已弃用的 supported feature 常量:
* `SUPPORT_ON_OFF`
* `SUPPORT_STREAM`
请改用新的 [`CameraEntityFeature`](/developers/core/entity/camera.md#supported-features) enum。
* **Cover**
已弃用的 supported feature 常量:
* `SUPPORT_OPEN`
* `SUPPORT_CLOSE`
* `SUPPORT_SET_POSITION`
* `SUPPORT_STOP`
* `SUPPORT_OPEN_TILT`
* `SUPPORT_CLOSE_TILT`
* `SUPPORT_STOP_TILT`
* `SUPPORT_SET_TILT_POSITION`
请改用新的 [`CoverEntityFeature`](/developers/core/entity/cover.md#supported-features) enum。
* **Climate**
已弃用的 supported feature 常量:
* `SUPPORT_TARGET_TEMPERATURE`
* `SUPPORT_TARGET_TEMPERATURE_RANGE`
* `SUPPORT_TARGET_HUMIDITY`
* `SUPPORT_FAN_MODE`
* `SUPPORT_PRESET_MODE`
* `SUPPORT_SWING_MODE`
* `SUPPORT_AUX_HEAT`
请改用新的 [`ClimateEntityFeature`](/developers/core/entity/climate.md#supported-features) enum。
* **Humidifier**
已弃用的 supported feature 常量:
* `SUPPORT_MODES`
请改用新的 [`HumidifierEntityFeature`](/developers/core/entity/humidifier.md#supported-features) enum。
* **Fan**
已弃用的 supported feature 常量:
* `SUPPORT_SET_SPEED`
* `SUPPORT_OSCILLATE`
* `SUPPORT_DIRECTION`
* `SUPPORT_PRESET_MODE`
请改用新的 [`FanEntityFeature`](/developers/core/entity/fan.md#supported-features) enum。
* **Light**
已弃用的 supported feature 常量:
* `SUPPORT_EFFECT`
* `SUPPORT_FLASH`
* `SUPPORT_TRANSITION`
请改用新的 [`LightEntityFeature`](/developers/core/entity/light.md#supported-features) enum。
注意,以下 light 常量此前已被弃用,
因此 `LightEntityFeature` 没有为它们提供替代项。
* `SUPPORT_BRIGHTNESS`
* `SUPPORT_COLOR_TEMP`
* `SUPPORT_COLOR`
* `SUPPORT_WHITE_VALUE`
这些情况应迁移到[新的 color modes](/developers/core/entity/light.md#color-modes)。
* **Lock**
已弃用的 supported feature 常量:
* `SUPPORT_OPEN`
请改用新的 [`LockEntityFeature`](/developers/core/entity/lock.md#supported-features) enum。
* **Media Player**
已弃用的 supported feature 常量:
* `SUPPORT_PAUSE`
* `SUPPORT_SEEK`
* `SUPPORT_VOLUME_SET`
* `SUPPORT_VOLUME_MUTE`
* `SUPPORT_PREVIOUS_TRACK`
* `SUPPORT_NEXT_TRACK`
* `SUPPORT_TURN_ON`
* `SUPPORT_TURN_OFF`
* `SUPPORT_PLAY_MEDIA`
* `SUPPORT_VOLUME_STEP`
* `SUPPORT_SELECT_SOURCE`
* `SUPPORT_STOP`
* `SUPPORT_CLEAR_PLAYLIST`
* `SUPPORT_PLAY`
* `SUPPORT_SHUFFLE_SET`
* `SUPPORT_SELECT_SOUND_MODE`
* `SUPPORT_BROWSE_MEDIA`
* `SUPPORT_REPEAT_SET`
* `SUPPORT_GROUPING`
请改用新的 [`MediaPlayerEntityFeature`](/developers/core/entity/media-player.md#supported-features) enum。
* **Remote**
已弃用的 supported feature 常量:
* `SUPPORT_LEARN_COMMAND`
* `SUPPORT_DELETE_COMMAND`
* `SUPPORT_ACTIVITY`
请改用新的 [`RemoteEntityFeature`](/developers/core/entity/remote.md#supported-features) enum。
* **Siren**
已弃用的 supported feature 常量:
* `SUPPORT_DURATION`
* `SUPPORT_TONES`
* `SUPPORT_TURN_OFF`
* `SUPPORT_TURN_ON`
* `SUPPORT_VOLUME_SET`
请改用新的 [`SirenEntityFeature`](/developers/core/entity/siren.md#supported-features) enum。
* **Vacuum**
已弃用的 supported feature 常量:
* `SUPPORT_TURN_ON`
* `SUPPORT_TURN_OFF`
* `SUPPORT_PAUSE`
* `SUPPORT_STOP`
* `SUPPORT_RETURN_HOME`
* `SUPPORT_FAN_SPEED`
* `SUPPORT_BATTERY`
* `SUPPORT_STATUS`
* `SUPPORT_SEND_COMMAND`
* `SUPPORT_LOCATE`
* `SUPPORT_CLEAN_SPOT`
* `SUPPORT_MAP`
* `SUPPORT_STATE`
* `SUPPORT_START`
请改用新的 [`VacuumEntityFeature`](/developers/core/entity/vacuum.md#supported-features) enum。
* **Water Heater**
已弃用的 supported feature 常量:
* `SUPPORT_TARGET_TEMPERATURE`
* `SUPPORT_OPERATION_MODE`
* `SUPPORT_AWAY_MODE`
请改用新的 [`WaterHeaterEntityFeature`](/developers/core/entity/water-heater.md#supported-features) enum。
---
url: /developers/blog/2022-04-16-local-oauth2.md
---
从 Home Assistant Core 2022.5 开始,注册了 `config_entry_oauth2_flow.LocalOAuth2Implementation` 的集成必须对 `auth` component 有[manifest dependency](https://developers.home-assistant.io/docs/creating_integration_manifest?_highlight=manifest#dependencies)。
这是由改善 OAuth [Application Credentials](https://github.com/home-assistant/architecture/discussions/692#discussioncomment-2121633) 处理的更广泛努力所驱动的,该努力需要更改 OAuth callback 的 HTTP endpoint 注册方式。
---
url: /developers/blog/2022-04-18-calendar-data-model.md
---
从 Home Assistant Core 2022.5 开始,`CalendarDeviceEntity` 已被弃用,并由 `CalendarEntity` 取代。新 entity 拥有更明确的数据模型和简化的 websocket API。`CalendarDeviceEntity` 将在未来的 Home Assistant 版本中移除,custom components 需要迁移到新的 API。详情请参见新的 [Calendar Entity](https://developers.home-assistant.io/docs/core/entity/calendar) 开发者文档。
---
url: /developers/blog/2022-04-20-saying-goodbye-to-event_time_changed.md
---
从 Home Assistant Core 0.113 开始,我们开始迁移到使用 asyncio event loop 来调度事件。
在 2022.5 版本中,我们很高兴地宣布此过程现已完成。传统的 `time_changed` 事件(也称为 `EVENT_TIME_CHANGED`)不再每秒触发一次。监听所有事件的集成不再需要过滤掉 `EVENT_TIME_CHANGED`。在单独线程中运行的集成可能会看到轻微的性能提升,因为它们不再每秒被唤醒。
在之前的实现中,消费者会订阅 `time_changed` 事件,并在每次触发时检查时间,看是否匹配。这种模式导致大量回调,其中大部分回调会拒绝事件并继续接收回调,直到达到预期时间。
如果你的 custom integration 仍然依赖监听 `time_changed` 事件,则需要迁移到使用内置的 event helpers 之一,在大多数情况下,它们可以直接替换(只需一行)。更多信息请参阅[集成文档中的事件监听说明](https://developers.home-assistant.io/docs/integration_listen_events#tracking-time-changes)。
---
url: /developers/blog/2022-04-26-tts-say-media-source.md
---
从 Home Assistant 2022.5 开始,TTS say service 将把 media content ID 设置为 media source URI。这些需要在 media player 内部通过 media source integration 来解析。
此前,TTS integration 会将 media content ID 设置为指向 Home Assistant 的 URL,media players 无需额外操作。
Media players 需要支持 media source integration,详见[此处文档](/developers/core/entity/media-player.md#browse-media)。
---
url: /developers/blog/2022-05-03-constants-deprecations.md
---
对于 Home Assistant Core 2022.5,我们弃用了许多常量并用 enums 替换它们。
所有 `SUPPORT_*` 常量已被弃用,并在[之前发布的博文](/developers/blog/2022-04-02-support-constants-deprecation.md)中进行了总结。
此外,以下常量已被弃用:
* **Alarm Control Panel**
已弃用的常量:
* `FORMAT_TEXT`
* `FORMAT_NUMBER`
请改用新的 [`CodeFormat`](/developers/core/entity/alarm-control-panel.md#code-formats) enum。
* **Camera**
已弃用的常量:
* `STREAM_TYPE_HLS`
* `STREAM_TYPE_WEB_RTC`
请改用新的 [`StreamType`](/developers/core/entity/camera.md#properties) enum。
* **Climate**
已弃用的常量:
* `CURRENT_HVAC_COOL`
* `CURRENT_HVAC_DRY`
* `CURRENT_HVAC_FAN`
* `CURRENT_HVAC_HEAT`
* `CURRENT_HVAC_IDLE`
* `CURRENT_HVAC_OFF`
* `HVAC_MODE_AUTO`
* `HVAC_MODE_COOL`
* `HVAC_MODE_DRY`
* `HVAC_MODE_FAN_ONLY`
* `HVAC_MODE_HEAT_COOL`
* `HVAC_MODE_HEAT`
* `HVAC_MODE_OFF`
请改用新的 [`HVACAction`](/developers/core/entity/climate.md#hvac-action) 和 [`HVACMode`](/developers/core/entity/climate.md#hvac-modes) enums。
* **Light**
已弃用的常量:
* `COLOR_MODE_UNKNOWN`
* `COLOR_MODE_ONOFF`
* `COLOR_MODE_BRIGHTNESS`
* `COLOR_MODE_COLOR_TEMP`
* `COLOR_MODE_HS`
* `COLOR_MODE_XY`
* `COLOR_MODE_RGB`
* `COLOR_MODE_RGBW`
* `COLOR_MODE_RGBWW`
* `COLOR_MODE_WHITE`
请改用新的 [`ColorMode`](/developers/core/entity/light.md#color-modes) enum。
---
url: /developers/blog/2022-05-03-serviceinfo-model-improvements.md
---
从 Home Assistant Core 2022.6 开始,通过此前已弃用的 dictionary 方法访问 discovery 信息的能力已被移除。
这适用于 `DhcpServiceInfo`、`MqttServiceInfo`、`SsdpServiceInfo`、`UsbServiceInfo` 和 `ZeroconfServiceInfo` 实例。
Custom integrations 需要迁移到使用新的 dataclass properties。
---
url: /developers/blog/2022-05-12-s6-overlay-base-images.md
---
我们开始更新 base images 以使用新的 s6-Overlay v3。[迁移文章](https://github.com/just-containers/s6-overlay/blob/master/MOVING-TO-V3.md)也解释了围绕此变更的新可能性。这篇博客文章说明了使用我们的新 base images 所需的最小变更。
我们已经更新了我们的[示例 add-on](https://github.com/home-assistant/addons-example)以包含新的行为。
## 最低要求
如果你的 add-on 的 `config.yaml` 中还没有,请添加 `init: false`。在 V3 中,S6 强制要求正确使用其 init。如果使用 Docker 默认系统 init,启动 add-on 时会看到以下错误:
```
s6-overlay-suexec: fatal: can only run as pid 1
```
确保你的 git repository rootfs 文件夹中的所有可执行/脚本文件已设置执行权限。你可以使用以下命令更新权限:
```sh
$ git update-index --chmod=+x rootfs/etc/service.d/my-service/run
```
如果你在 S6-overlay services 中使用 `finish` 脚本(例如,在出错时停止容器),你需要将 `s6-svscanctl -t /var/run/s6/services` 替换为 `/run/s6/basedir/bin/halt`。
## AppArmor
你需要调整你的 [AppArmor profile](/developers/apps/presentation.md#apparmor) 使其与新 s6-Overlay 配合工作。我们更新了文档中的默认 profile。需要进行以下更改:
```txt
# S6-Overlay
/init ix,
/bin/** ix,
/usr/bin/** ix,
/run/{s6,s6-rc*,service}/** ix,
/package/** ix,
/command/** ix,
/etc/services.d/** rwix,
/etc/cont-init.d/** rwix,
/etc/cont-finish.d/** rwix,
/run/{,**} rwk,
/dev/tty rw,
```
## `host_pid` 选项
未启用 protection mode 的 addons 可以在配置中设置 `host_pid: true`。如[文档](https://developers.home-assistant.io/docs/apps/configuration#optional-configuration-options)中所述:
> 允许容器在 host PID namespace 上运行。仅适用于未受保护的 add-ons。
这存在问题,因为 S6 期望自己是 PID 1(这在其[标语](https://github.com/just-containers/s6-overlay#s6-overlay-)中明确说明),而在使用 host PID namespace 时这是不可能的。
在 V2 中,S6 实际上并没有检查自己是否作为 PID 1 运行。这就是为什么过去在此模式下它"能工作"的原因(尽管它需要一些[变通方法](https://github.com/hassio-addons/addon-glances/blob/8575d7903ef4c0a7c49e9ab32e0536bd2eb12dd6/glances/rootfs/bin/s6-nuke)来防止 s6 以此方式运行时破坏系统)。在 V3 中,S6 检查自己是否确实是 PID 1,否则拒绝启动。
要解决此问题,不要在你的 addon 中使用 s6 overlay,因为它不是为此用例设计的。你可以通过用 no-op 脚本覆盖 `/init` 然后使用正常的 docker init 系统来继续使用 addon base images。或者你可以切换到其他 base image(如 stock alpine 或 debian)并添加所需的内容。
---
url: /developers/blog/2022-05-24-entity_matches_only_removed_logbook_card.md
---
在 2022.6 之前,`entity_matches_only` 标志阻止 logbook 提供 context data,以换取在查询特定实体时的性能提升。采用新的 logbook 设计后,为特定实体选择 context data 不再是一个密集过程。无需立即采取行动,因为该标志将被忽略,你可以在方便时从任何活动代码路径中移除该标志。
---
url: /developers/blog/2022-05-31-media-player-updates.md
---
从 Home Assistant 2022.6 开始,media player integration 对 `media_player.play_media` service 进行了两项更新。`enqueue` 属性在 [PR #72406](https://github.com/home-assistant/core/pull/72406) 中已更改为 string,并在 [PR #72566](https://github.com/home-assistant/core/pull/72566) 中添加了新的 `announce` boolean 属性。
更多信息请参见更新的 [media player play\_media 文档](/developers/core/entity/media-player.md#play-media)。
---
url: /developers/blog/2022-06-02-update_coordinator-context.md
---
从 Home Assistant 2022.7 开始,update coordinator 支持跟踪每个监听 entity 的 context。这可用于基于启用的 entities 限制对 API 的请求。
对于依赖 update coordinators 并检查内部变量 `self._listeners` 和/或重载方法 `async_remove_listener()` 以检测监听器何时耗尽的 custom components,这可能会是一个 breaking change。改用 `async_update_listeners()` 触发对所有监听器的更新,并重载 `_unschedule_refresh()` 以检测监听器耗尽的情况。
更多信息请参见更新的[集成数据获取文档](/developers/integration_fetching_data.md#polling-api-endpoints)。
---
url: /developers/blog/2022-06-07-dataflow-constants-deprecation.md
---
从 Home Assistant Core 2022.7 开始,所有用于 data entry flow result types 的 `RESULT_TYPE_*` 常量已被弃用:
* `RESULT_TYPE_FORM`
* `RESULT_TYPE_CREATE_ENTRY`
* `RESULT_TYPE_ABORT`
* `RESULT_TYPE_EXTERNAL_STEP`
* `RESULT_TYPE_EXTERNAL_STEP_DONE`
* `RESULT_TYPE_SHOW_PROGRESS`
* `RESULT_TYPE_SHOW_PROGRESS_DONE`
* `RESULT_TYPE_MENU`
请改用新的 `FlowResultType` enum。
---
url: /developers/blog/2022-06-13-unsafe_reloads_during_entry_setup.md
---
在 2022.7 之前,可以在 config entry 仍在设置过程中触发其重新加载。在 config entry 设置期间重新加载通常会导致意外的失败模式,需要重启 Home Assistant 才能使 config entry 恢复到正常状态。现在在设置期间尝试重新加载会抛出 `OperationNotAllowed` 异常。
---
url: /developers/blog/2022-06-14-number_entity_refactoring.md
---
`NumberEntity` 现在支持温度单位转换,采用的模式与 `SensorEntity` 支持的单位转换类似。
对于 device class 设置为 `temperature` 的 number entities,温度转换将根据用户配置的温度单位自动进行。
为此,custom component 集成应更新为覆写属性 `native_max_value`、`native_min_value`、`native_step`、`native_unit_of_measurement`、`native_value`,而不是 `max_value`、`min_value`、`step`、`unit_of_measurement`、`value`,并覆写方法 `async_set_native_value` 和 `set_native_value`,而不是 `async_set_value` 和 `set_value`。
同样的重命名也已应用于 `_attr_*` 属性和 `NumberEntityDescription` 的成员。
在 Home Assistant Core 2023.1 中,覆写 `async_set_value`、`max_value`、`min_value`、`set_value`、`step`、`unit_of_measurement`、`value`,设置 `_attr_max_value`、`_attr_min_value`、`_attr_unit_of_measurement`、`_attr_step`、`_attr_value` 以及在 `NumberEntityDescription` 实例上设置 `max_value`、`min_value`、`unit_of_measurement`、`step` 将不再受支持。
---
url: /developers/blog/2022-06-27-weather_entity_refactoring.md
---
`WeatherEntity` 现在支持温度单位转换,采用的模式与 `NumberEntity` 和 `SensorEntity` 支持的单位转换类似。
降水、气压、温度、能见度和风速会根据用户配置的单位系统自动转换。此外,用户可以为特定的 weather entities 覆盖单位。
为此,custom component 集成应更新为覆写属性 `native_precipitation_unit`、`native_pressure`、`native_pressure_unit`、`native_temperature`、`native_temperature_unit`、`native_visibility`、`native_visibility_unit`、`native_wind_speed` 和 `native_wind_speed_unit`,而不是 `precipitation_unit`、`pressure`、`pressure_unit`、`temperature`、`temperature_unit`、`visibility`、`visibility_unit`、`wind_speed` 和 `wind_speed_unit`。
相同的重命名也已应用于相应的 `_attr_*` 属性以及 `Forecast` typed dict 的成员。
在 Home Assistant Core 2023.1 中,覆写 `precipitation_unit`、`pressure`、`pressure_unit`、`temperature`、`temperature_unit`、`visibility`、`visibility_unit`、`wind_speed`、`wind_speed_unit`,设置 `_attr_precipitation_unit`、`_attr_pressure`、`_attr_pressure_unit`、`_attr_temperature`、`_attr_temperature_unit`、`_attr_visibility`、`_attr_visibility_unit`、`_attr_wind_speed`、`_attr_wind_speed_unit` 以及在 `Forecast` 实例上设置 `precipitation`、`pressure`、`temperature`、`templow`、`wind_speed` 将不再受支持。
---
url: /developers/blog/2022-07-08-config_entry_forwards.md
---
在 2022.8 之前,如果 config entry 加载的 platforms 中有一个尚未加载,那么 `await` config entry platforms forwards 就会导致死锁。
集成需要重构,用等待 `hass.config_entries.async_forward_entry_setups` 替换对 `hass.config_entries.async_setup_platforms` 的调用,以确保 Home Assistant 不会在 entities 和 platforms 仍在设置时意外重新加载集成。
`hass.config_entries.async_setup_platforms` 计划在 2022.12 中移除。
---
url: /developers/blog/2022-07-08-generic-store.md
---
从 Home Assistant Core 2022.8 开始,Store(来自 `homeassistant/helpers/storage.py`)被定义为 Generic `Store(Generic[_T])`。建议在 Store 定义中指定要存储的数据类型。它应该是 JSON 可序列化的(dict 或 list),例如:
* 使用 dict 的标准定义:`self._store = Store[dict[str, int]](hass, STORAGE_VERSION, STORAGE_KEY)`
* 使用 TypedDict:`self._store = Store[EnergyPreferences](hass, STORAGE_VERSION, STORAGE_KEY)`
* 访问现有 Store:`store: Store[dict[str, Any]] = hass.data[DOMAIN][DATA_STORE]`
* 继承 Store:`class MyCustomStorage(Store[list[int]]):`
有关 generics 的更多信息,请参阅 [PEP 483](https://peps.python.org/pep-0483/#generic-types)。
---
url: /developers/blog/2022-07-10-entity_naming.md
---
我们正在改进和标准化实体命名。这将允许我们在未来的 UI 中以正确的上下文展示实体,同时从代码库中移除一些容易出错的实体名称魔法处理。
简要说明如下:
* 设备保持现有的名称,例如 "Dishwasher"。
* 实体将拥有自己的名称(不含 device、area)。或者,它们可以选择将名称设置为 `None`(在这种情况下它们继承设备名称)。
* 设备、Area 和实体名称都以大写字母开头,其余单词为小写(除非是品牌名称/专有名词/缩写)。
* 每个已迁移以遵循这些规则的实体应将 [`has_entity_name`](https://developers.home-assistant.io/docs/core/entity#entity-naming) 属性设置为 `True`。
在迁移期间,我们使用 `has_entity_name` 属性来创建"向后兼容"的 friendly names。未来,我们可以为未设置此属性的实体显示 deprecation warnings,之后完全移除它。
前端也将为此进行调整。它将能够以多种方式展示实体/设备,选择最适合上下文的方式。
### 示例
以下示例说明了设备和实体应如何按照新建议命名(类型:实体名称 / state 对象的 `friendly_name` / `entity_id`)。
开发者只需设置设备名称和实体名称,`friendly_name` 和 `entity_id` 会自动生成。
* 设备: Dishwasher
* 开关: `None` / Dishwasher / `switch.dishwasher`
* 传感器: Power usage / Dishwasher Power usage / `sensor.dishwasher_power_usage`
* 设备: Laundry machine
* 开关: `None` / Laundry machine / `switch.laundry_machine`
* 传感器: Power usage / Laundry machine Power usage / `sensor.laundry_machine_power_usage`
## 背景
Home Assistant 将用户的家庭建模为三个层次:
* Area(例如 Living Room)
* Device(例如 Switch)
* Entity(例如 Power usage)
Home Assistant 中的 entities 是设备提供的数据点,可以表示特定的控制(设备的开关、设备的灯光)。
由于 Home Assistant 在其存在的头几年中只有 entities,因此 Home Assistant 中的许多功能都是围绕 entities 构建的,最主要的就是 UI。
假设你有 2 个名为 Dishwasher 和 Laundry Machine 的 Shelly 开关,它们都报告 power usage。两个设备都有一个 switch entity 和一个 power sensor。之前的设备和实体看起来是这样的(类型:实体名称 / state 对象 `friendly_name` / `entity_id`):
* 设备: Dishwasher
* 开关: Dishwasher Switch / Dishwasher Switch / `switch.dishwasher_switch`
* 传感器: Dishwasher Power usage / Dishwasher Power usage / `sensor.dishwasher_power_usage`
* 设备: Laundry machine
* 开关: Laundry machine Switch / Laundry machine Switch / `switch.laundry_machine_switch`
* 传感器: Laundry machine Power usage / Laundry machine Power usage / `sensor.laundry_machine_power_usage`
### 为什么这是个问题?
设备名称没有单一的事实来源,因为实体名称中包含了设备名称。
因为我们将设备名称包含在实体名称中"解决"了 UI 和 `entity_id` 的问题,我们现在将这个解决方案应用到了所有使用实体的地方,不得不围绕这个解决方案进行变通处理。
这种命名方案使得将 UI 从长实体列表迁移到 area->device->entity 的分层视图变得不必要地困难。
---
url: /developers/blog/2022-07-29-device-tracker_source-type-deprecation.md
---
对于 Home Assistant Core 2022.9,我们弃用了 `device_tracker` 的 `SOURCE_TYPE_*` 常量。
请改用新的 [`SourceType`](/developers/core/entity/device-tracker.md) enum。
已弃用的常量:
* `SOURCE_TYPE_GPS`
* `SOURCE_TYPE_ROUTER`
* `SOURCE_TYPE_BLUETOOTH`
* `SOURCE_TYPE_BLUETOOTH_LE`
---
url: /developers/blog/2022-08-08-bluetooth-api-changes.md
---
对于 Home Assistant Core 2022.9,我们修改了 `PassiveBluetoothProcessorCoordinator` 和 `PassiveBluetoothDataProcessor` bluetooth API,使 `PassiveBluetoothProcessorCoordinator` 负责解析。然后 coordinator 将解析后的数据推送给 `PassiveBluetoothDataProcessor` 实例。
PassiveBluetoothProcessorCoordinator 现在接收一个必填的 update\_method 回调,该回调接收 bluetooth advertisements(形式为 BluetoothServiceInfoBleak),并返回应移交给任何订阅的 PassiveBluetoothDataProcessor 的数据:
```python
def my_parser(service_info: BluetoothServiceInfoBleak) -> MyDataClass:
...
return MyDataClass(
a=some_parsed_data,
b=some_other_parsed_data,
)
coordinator = PassiveBluetoothProcessorCoordinator(
hass,
_LOGGER,
address=address,
mode=BluetoothScanningMode.PASSIVE,
update_method=my_parser,
)
```
PassiveBluetoothDataProcessor 仍然接收一个 update\_method,但它现在接收的不是 BluetoothServiceInfoBleak,而是来自 PassiveBluetoothProcessorCoordinator 的 update\_method 返回的数据。它仍然应该像以前一样返回一个 `PassiveBluetoothDataUpdate`:
```python
def sensor_update_to_bluetooth_data_update(
sensor_update: MyDataClass,
) -> PassiveBluetoothDataUpdate:
"""Convert a sensor update to a bluetooth data update."""
...
return PassiveBluetoothDataUpdate( ... )
processor = PassiveBluetoothDataProcessor(sensor_update_to_bluetooth_data_update)
```
所有内置集成都已完成转换,因此可以查看它们以获取更多示例。
此变更将有助于那些在加载 platform 之前就需要开始解析数据的集成(例如,要加载的 platform 列表取决于 advertisements 中的数据),或者单一 advertisement 驱动多个 platforms 的情况(你不必解析广播两次)。
---
url: /developers/blog/2022-08-15-automation-action-type-deprecation.md
---
对于 Home Assistant Core 2022.9,我们弃用了来自 `homeassistant/components/automation/__init__.py` 的 `AutomationActionType`、`AutomationTriggerInfo` 和 `AutomationTriggerData`。
它们被来自 `homeassistant/helpers/trigger.py` 的 `TriggerActionType`、`TriggerInfo` 和 `TriggerData` 替代。
| 旧 | 新 |
| --- | --- |
| `AutomationActionType` | `TriggerActionType` |
| `AutomationTriggerInfo` | `TriggerInfo` |
| `AutomationTriggerData` | `TriggerData` |
此外,我们建议将 `async_attach_trigger` 函数的 `automation_info` 参数名更新为 `trigger_info`。
---
url: /developers/blog/2022-08-18-light_white_value_removed.md
---
`LightEntity` 不再支持 `white_value`,它已被[color mode](/developers/core/entity/light.md#color-modes) `white` 替代。
### 背景
Light 的 `white_value` 在 Home Assistant Core 2021.4 中通过
[PR #47720](https://github.com/home-assistant/core/pull/47720)被弃用,
并在 Home Assistant Core 2021.7 中通过
[PR #51411](https://github.com/home-assistant/core/pull/51411)被 color mode `white` 替代。
---
url: /developers/blog/2022-08-24-globally_accessible_hass.md
---
现在可以通过调用 `core.async_get_hass()` 获取 `HomeAssistant` 实例的引用。
虽然这意味着不再严格要求传递 `hass`,但建议仍然只在传递 `hass` 非常困难或根本无法传递的地方使用 `core.async_get_hass`。
一个可以使用它的示例是 voluptuous validators,它们之前无法访问 `hass`,因为 voluptuous 没有办法将用户数据传递给 validators。
```python
@callback
def async_get_hass() -> HomeAssistant:
"""Return the HomeAssistant instance.
Raises LookupError if no HomeAssistant instance is available.
This should be used where it's very cumbersome or downright impossible to pass
hass to the code which needs it.
"""
```
---
url: /developers/blog/2022-08-25-issue_registry_moved_to_helpers.md
---
Issue registry 已从 `homeassistant.components.repairs` 移至 `homeassistant.helpers`,现在在 bootstrapping 过程中与其他 registries 一起非常早地加载,早于任何 integrations 的设置。
这允许在配置验证期间创建 issues。
---
url: /developers/blog/2022-09-06-media-player-repeat-mode-deprecation.md
---
从 Home Assistant Core 2022.10 开始,以下 media player 常量已被弃用:
* `MEDIA_CLASS_ALBUM`
* `MEDIA_CLASS_APP`
* `MEDIA_CLASS_ARTIST`
* `MEDIA_CLASS_CHANNEL`
* `MEDIA_CLASS_COMPOSER`
* `MEDIA_CLASS_CONTRIBUTING_ARTIST`
* `MEDIA_CLASS_DIRECTORY`
* `MEDIA_CLASS_EPISODE`
* `MEDIA_CLASS_GAME`
* `MEDIA_CLASS_GENRE`
* `MEDIA_CLASS_IMAGE`
* `MEDIA_CLASS_MOVIE`
* `MEDIA_CLASS_MUSIC`
* `MEDIA_CLASS_PLAYLIST`
* `MEDIA_CLASS_PODCAST`
* `MEDIA_CLASS_SEASON`
* `MEDIA_CLASS_TRACK`
* `MEDIA_CLASS_TV_SHOW`
* `MEDIA_CLASS_URL`
* `MEDIA_CLASS_VIDEO`
* `MEDIA_TYPE_ALBUM`
* `MEDIA_TYPE_APP`
* `MEDIA_TYPE_APPS`
* `MEDIA_TYPE_ARTIST`
* `MEDIA_TYPE_CHANNEL`
* `MEDIA_TYPE_CHANNELS`
* `MEDIA_TYPE_COMPOSER`
* `MEDIA_TYPE_CONTRIBUTING_ARTIST`
* `MEDIA_TYPE_EPISODE`
* `MEDIA_TYPE_GAME`
* `MEDIA_TYPE_GENRE`
* `MEDIA_TYPE_IMAGE`
* `MEDIA_TYPE_MOVIE`
* `MEDIA_TYPE_MUSIC`
* `MEDIA_TYPE_PLAYLIST`
* `MEDIA_TYPE_PODCAST`
* `MEDIA_TYPE_SEASON`
* `MEDIA_TYPE_TRACK`
* `MEDIA_TYPE_TVSHOW`
* `MEDIA_TYPE_URL`
* `MEDIA_TYPE_VIDEO`
* `REPEAT_MODE_ALL`
* `REPEAT_MODE_OFF`
* `REPEAT_MODE_ONE`
* `REPEAT_MODES`
请改用新的 `MediaClass`、`MediaType` 和 `RepeatMode` enums。
使用 `STATE_*` 常量来反映 media player state 的做法也被弃用。请改用新的 `MediaPlayerState` enum。
---
url: /developers/blog/2022-09-16-bluetooth-unavailable-api-changes.md
---
对于 Home Assistant Core 2022.10,我们修改了 `async_track_unavailable` bluetooth API,使其向回调发送最后一个 `BluetoothServiceInfoBleak`,而不是 `address`。
以下是一个使用示例:
```python
from homeassistant.components import bluetooth
def _unavailable_callback(info: bluetooth.BluetoothServiceInfoBleak) -> None:
_LOGGER.debug("%s is no longer seen", info.address)
cancel = bluetooth.async_track_unavailable(hass, _unavailable_callback, "44:44:33:11:23:42", connectable=True)
```
---
url: /developers/blog/2022-09-28-deprecate-conversion-utilities.md
---
从 Home Assistant Core 2022.10 开始,以下工具已被弃用:
* `homeassistant/util/distance`
* `homeassistant/util/pressure`
* `homeassistant/util/speed`
* `homeassistant/util/temperature`
* `homeassistant/util/volume`
请改用 `homeassistant/util/unit_conversion` 中相应的静态类:
* `DistanceConverter`
* `PressureConverter`
* `SpeedConverter`
* `TemperatureConverter`
* `VolumeConverter`
---
url: /developers/blog/2022-09-28-new_sensor_device_classes.md
---
已添加多个新的 sensor device classes:
* `distance`,以 `cm`、`ft`、`in`、`km`、`m`、`mi`、`mm`、`yd` 之一测量的距离
* `speed`,以 `ft/s`、`in/d`、`in/h`、`km/h`、`kn`、`m/s`、`mm/d`、`mph` 之一测量的速度
* `volume`,以 `fl. oz.`、`ft³`、`gal`、`L`、`mL`、`m³` 之一测量的体积
* `weight`,以 `g`、`kg`、`lb`、`mg`、`oz`、`µg` 之一测量的质量
与 `pressure` 和 `temperature` sensors 类似,用户可以在 UI 中自由地为使用任何新 device class 的 sensors 选择显示单位。
### 长期统计
长期统计将 `distance` 存储为 `m`,`speed` 存储为 `m/s`,`volume` 存储为 `m³`,`weight` 存储为 `g`。
对于修改为其中一种新 device class 的现有 sensors,统计将继续以 sensor 的 `state_unit` 记录,但用户将获得选项,可以将现有统计转换为标准化单位。
---
url: /developers/blog/2022-09-29-statistics_refactoring.md
---
长期统计现在允许以与存储时不同的单位显示统计。例如,可以将以 `kWh` 存储的统计显示为 `Wh`、`kWh` 或 `MWh`。
这些变更通过一系列 PR 实现:
* https://github.com/home-assistant/core/pull/78031
* https://github.com/home-assistant/core/pull/78578
* https://github.com/home-assistant/core/pull/79370
背景是我们允许覆盖多个 sensor device class 的单位,此覆盖在查看长期统计时也应反映出来。
这会影响调用以下 WS API 的代码作者:
* `recorder/adjust_sum_statistics` - 新增了一个必填参数 `adjustment_unit_of_measurement`,它定义了 `adjustment` 参数使用的单位。
* `recorder/statistics_during_period` - 对于从 sensor entities 生成的统计,返回统计的单位将转换为 sensor 的 `unit_of_measurement`。此行为可以通过传递可选的 `units` 参数来控制。
---
url: /developers/blog/2022-10-10-add-options-to-SelectEntityDescription.md
---
从 Home Assistant Core 2022.11 开始,`options` 作为 `SelectEntityDescription` 的标准属性可用。
如果自定义集成之前实现了自定义的 `options` 属性,可能会导致问题。
请通过删除或重命名自定义 `options` 属性来调整自定义集成。
---
url: /developers/blog/2022-10-14-deprecate-unit-system.md
---
从 Home Assistant Core 2022.11 开始,`IMPERIAL_SYSTEM` 已弃用,由 `US_CUSTOMARY_SYSTEM` 替代。
单位系统的 `is_metric` 和 `name` 属性同样已弃用,不应再使用。
引用该单位系统或这些属性的自定义集成需要进行调整,改为使用实例检查。
正确做法:
```python
if hass.config.units is METRIC_SYSTEM:
pass
if hass.config.units is US_CUSTOMARY_SYSTEM:
pass
```
为避免与未来的单位系统混淆,常量 `CONF_UNIT_SYSTEM_IMPERIAL`
和 `CONF_UNIT_SYSTEM_METRIC` 也已弃用,不应再引用:
* 如果它们曾被用于与单位系统的 `name` 进行比较,
则现在已不再需要。
* 如果它们曾被用于其他目的,则应改为创建本地常量。
---
url: /developers/blog/2022-10-21-code-owner-commands.md
---
Home Assistant [服务中枢](https://github.com/home-assistant/service-hub/) 已被 [Ludeeus](https://github.com/ludeeus) 扩展了一个新功能:代码所有者的 GitHub 命令。
此功能允许任何代码所有者对其集成标记的 issue 进行分诊。从今天起,可以使用以下命令:
* `@home-assistant close`
关闭该 issue。仅当只有一个集成被标记时可用
* `@home-assistant rename New Title`
更改 issue 的标题。仅当只有一个集成被标记时可用
* `@home-assistant unassign `
移除集成标签及相关的 assignee。
当 bot 发布评论以通知代码所有者有新的 issue 或 pull request 时,将包含这些命令([示例](https://github.com/home-assistant/core/issues/80731#issuecomment-1287084569))。
---
url: /developers/blog/2022-10-24-hubs-devices-services-virtual-integrations.md
---
在 2022.11 版本中,我们调整了集成清单,
以更好地区分 hub、device 和 service。此外,
我们还引入了一种新类型的集成:虚拟集成。
## 区分 hub、device 和 service
一个容易混淆的方面是:config entry 可以集成单个设备(ESPHome),
也可以通过 hub 集成一系列设备(Hue),或者集成一个 service(AdGuard)。
我们希望近期在 UI 中开始区分这些类型,
但目前无法做到,因为集成并没有暴露此信息。
我们已经有 [`integration_type`](/developers/creating_integration_manifest.md#integration-type)
清单属性,现在已扩展以支持这些新类型:
* `device`:集成一次集成一个设备。
* `hub`:集成多个设备。
* `service`:集成一个服务。
`hub` 与 `service` 或 `device` 的区别由
集成本身的性质决定。`hub` 提供对多个其他
设备或服务的网关。`service` 和 `device` 是每个 config entry 提供
单个设备或服务的集成。
:::caution
当 [`integration_type`](/developers/creating_integration_manifest.md#integration-type)
未设置时,我们将其自动默认为 `hub`。这是一个临时降级方案。
未来我们将要求显式设置 [`integration_type`](/developers/creating_integration_manifest.md#integration-type)。
请将您现有的(自定义)集成更新为在集成清单中设置正确的 `integration_type`。
:::
## 虚拟集成
有些产品由不以产品名称命名的集成支持。
例如,Roborock 吸尘器通过 Xiaomi Miio 集成进行集成。
也有一些产品线仅支持标准 IoT 协议
如 Zigbee 或 Z-Wave。例如,U-tec ultraloq 通过 Z-Wave 工作,
并且没有专门的集成。
对于终端用户来说,找到如何将这些产品
与 Home Assistant 集成可能会令人困惑。为解决上述情况,我们引入:
[虚拟集成](/developers/creating_integration_manifest.md#virtual-integration)。
虚拟集成不是真正的集成,而是用于帮助用户
找到适合其设备的正确集成。它们只有一个 manifest
文件,没有任何附加代码。
虚拟集成有两种类型:由另一个集成支持的虚拟集成,以及使用现有 IoT 标准的虚拟集成。
[在开发者文档中了解更多信息。](/developers/creating_integration_manifest.md#virtual-integration)
## 移除支持的 brands 功能
虚拟集成取代了之前的"supported brands"功能。该
功能仅被 Home Assistant Core 集成使用,所有这些集成都已
迁移为使用虚拟集成。
因此已无使用该功能的情况,支持的 brands 功能已
在未经弃用期的情况下被移除。
---
url: /developers/blog/2022-10-25-new-precipitation-intensity-units.md
---
从 Home Assistant Core 2022.11 开始,以下常量(用于 volumetric flux,如降水强度)已弃用,由 `UnitOfVolumetricFlux` 枚举替代:
* `PRECIPITATION_INCHES_PER_HOUR` 由 `UnitOfVolumetricFlux.INCHES_PER_HOUR` 替代
* `PRECIPITATION_MILLIMETERS_PER_HOUR` 由 `UnitOfVolumetricFlux.MILLIMETERS_PER_HOUR` 替代
* `SPEED_INCHES_PER_DAY` 由 `UnitOfVolumetricFlux.INCHES_PER_DAY` 替代
* `SPEED_INCHES_PER_HOUR` 由 `UnitOfVolumetricFlux.INCHES_PER_HOUR` 替代
* `SPEED_MILLIMETERS_PER_DAY` 由 `UnitOfVolumetricFlux.MILLIMETERS_PER_DAY` 替代
---
url: /developers/blog/2022-10-26-new-unit-enumerators.md
---
从 Home Assistant Core 2022.11 开始,以下单位常量已弃用,并由相应的 enum 替代:
* `UnitOfEnergy` 枚举器替代 `ENERGY_***` 常量
* `UnitOfLength` 枚举器替代 `LENGTH_***` 常量
* `UnitOfMass` 枚举器替代 `MASS_***` 常量
* `UnitOfPower` 枚举器替代 `POWER_***` 常量
* `UnitOfPressure` 枚举器替代 `PRESSURE_***` 常量
* `UnitOfSpeed` 枚举器替代 `SPEED_***` 常量
* `UnitOfTemperature` 枚举器替代 `TEMP_***` 常量
* `UnitOfVolume` 枚举器替代 `VOLUME_***` 常量
---
url: /developers/blog/2022-11-16-statistics_refactoring.md
---
对长期统计相关 API 做了一些更改,以减少响应大小和数据库负载。
这些更改在[此 PR](https://github.com/home-assistant/core/pull/82131)中实现。
这会影响调用以下 WS API 的代码作者:
* `recorder/statistics_during_period`
* 新增了一个可选参数 `types`,允许提供要包含在响应中的统计特征列表。可能的值有:`"last_reset"`、`"max"`、`"mean"`、`"min"`、`"state"`、`"sum"`
* 时间戳 `start`、`end` 和 `last_reset` 现在是自 UNIX epoch 以来的毫秒整数,而不是 ISO 格式字符串。新格式意味着时间戳可以直接传递给 js `Date()` 构造函数。
* `statistic_id` 不再在返回的 map 中为每个列表项重复。由于返回类型是以 `statistic_id` 为键的 map,因此此前并未使用该字段。
这会影响调用以下 Python API 的代码作者:
* `homeassistant.recorder.statistics.get_last_short_term_statistics`
* 新增了一个参数 `types`,允许提供要包含在响应中的统计特征集合。可能的值有:`"last_reset"`、`"max"`、`"mean"`、`"min"`、`"state"`、`"sum"`
* 时间戳 `start`、`end` 和 `last_reset` 现在是 `datetime` 对象,而不是 ISO 格式字符串。
* `statistic_id` 不再在返回的 map 中为每个列表项重复。由于返回类型是以 `statistic_id` 为键的 map,因此此前并未使用该字段。
* `homeassistant.recorder.statistics.get_latest_short_term_statistics`
* 新增了一个参数 `types`,允许提供要包含在响应中的统计特征集合。可能的值有:`"last_reset"`、`"max"`、`"mean"`、`"min"`、`"state"`、`"sum"`
* 时间戳 `start`、`end` 和 `last_reset` 现在是 `datetime` 对象,而不是 ISO 格式字符串。
* `statistic_id` 不再在返回的 map 中为每个列表项重复。由于返回类型是以 `statistic_id` 为键的 map,因此此前并未使用该字段。
* `homeassistant.recorder.statistics.statistics_during_period`
* 新增了一个参数 `types`,允许提供要包含在响应中的统计特征集合。可能的值有:`"last_reset"`、`"max"`、`"mean"`、`"min"`、`"state"`、`"sum"`
* 时间戳 `start`、`end` 和 `last_reset` 现在是 `datetime` 对象,而不是 ISO 格式字符串。
* `statistic_id` 不再在返回的 map 中为每个列表项重复。由于返回类型是以 `statistic_id` 为键的 map,因此此前并未使用该字段。
---
url: /developers/blog/2022-11-28-more-unit-enumerators.md
---
从 Home Assistant Core 2022.12 开始,以下单位常量已弃用,并由相应的 enum 替代:
* `UnitOfPrecipitationDepth` 枚举器替代 `PRECIPITATION_***` 常量
---
url: /developers/blog/2022-12-01-entity_translations.md
---
集成为其 entity(例如传感器的)state 提供翻译的方法已经改变。
集成不应再将自定义 device class 与 `strings..json` 文件一起使用。相反,entity 应在自身上设置 `translation_key` 属性,并在集成的 `strings.json` 中包含该 `translation_key`。
:::warning
通过 `translation_key` 属性指向翻译目前仅支持具有 `unique_id` 的 entity。
:::
以下 `strings.json` 示例适用于 `translation_key` 属性设置为 `phase` 的 Moon 域 `sensor` entity:
```json
{
"entity": {
"sensor": {
"phase": {
"state": {
"new_moon": "New moon",
"first_quarter": "First quarter",
"full_moon": "Full moon",
"last_quarter": "Last quarter"
}
}
}
}
}
```
更多详情请参见 [`translation`](/developers/internationalization/core.md#state-of-entities) 和 [`entity`](/developers/core/entity.md#generic-properties) 文档。
---
url: /developers/blog/2022-12-05-more-unit-enumerators.md
---
从 Home Assistant Core 2023.1 开始,以下单位常量已弃用,并由相应的 enum 替代:
* `UnitOfApparentPower` 枚举器替代 `POWER_VOLT_AMPERE` 常量
* `UnitOfDataRate` 枚举器替代 `DATA_RATE_***` 常量
* `UnitOfElectricCurrent` 枚举器替代 `ELECTRIC_CURRENT_***` 常量
* `UnitOfElectricPotential` 枚举器替代 `ELECTRIC_POTENTIAL_***` 常量
* `UnitOfFrequency` 枚举器替代 `FREQUENCY_***` 常量
* `UnitOfInformation` 枚举器替代 `DATA_***` 常量
* `UnitOfIrradiance` 枚举器替代 `IRRADIATION_***` 常量
* `UnitOfSoundPressure` 枚举器替代 `SOUND_PRESSURE_***` 常量
* `UnitOfTime` 枚举器替代 `TIME_***` 常量
* `UnitOfVolumeFlowRate` 枚举器替代 `VOLUME_FLOW_RATE_***` 常量
---
url: /developers/blog/2022-12-07-entity-color-changes.md
---
从 Home Assistant Core 2022.12 版本开始,当 entity 处于 active 状态时,我们按 domain 使用不同的颜色。以下 CSS 变量不再使用:
* `--paper-item-icon-active-color`
* `--state-icon-active-color`
这意味着任何之前覆盖过这些 CSS 变量的 theme 将使用内置的 active state 颜色。所有新颜色也都以 CSS 变量的形式提供。
:::caution
由于我们仅正式支持 theme 的 `primary-color` 和 `accent-color` 属性,某些颜色可能会在版本之间增加或删除。
:::
有关 theme 配置的更多详情请参见 [Home Assistant 文档](https://www.home-assistant.io/integrations/frontend/#defining-themes)。
---
url: /developers/blog/2022-12-07-unit_system_changes.md
---
过去几个月,受支持的单位系统以及数值传感器受其影响的方式发生了许多变化。本博客文章旨在总结这些变更。
* 对于 device class 为 `distance`、`gas`、`precipitation`、`precipitation_intensity`、`speed`、`volume`、`water`、`weight` 或 `wind_speed` 的传感器,其 state 使用的单位受单位系统影响并会自动转换。
* 集成可以通过设置 entity 属性 `suggested_unit_of_measurement` 来覆盖单位系统规则。
* 为避免 breaking change,传感器的单位仅在该传感器首次添加到 Home Assistant 时,根据单位系统规则自动转换。这意味着用户系统中已有的传感器不会受到新的单位转换规则的影响。
* 如果用户在偏好设置中更改单位系统,用户系统中已有的传感器的单位不会受到单位系统变更的影响。
* 在大多数情况下,集成不再需要进行任何单位转换,因为现在会自动处理。
* 在大多数情况下,现有实现单位转换的集成特定代码可以被移除,因为在移除自定义代码后,传感器的 state 会自动转换。有关此机制的详细信息,请参见 [core PR #83228](https://github.com/home-assistant/core/pull/83228)。注意:这仅适用于具有 `unique_id` 的传感器。
* 允许用户为集成选择传感器单位的自定义 option flow 在大多数情况下也可以被移除。用户可以改为通过 frontend 的 entity settings 覆盖传感器的单位,如果根据所选单位系统的自动转换不符合用户喜好。注意:这仅适用于具有 `unique_id` 的传感器。
* 温度传感器的行为基本保持不变;温度传感器的 state 单位始终反映单位系统,除非:
* 用户已通过 frontend 的 entity settings 覆盖了单位。
* 温度传感器具有非 None 的 `suggested_unit_of_measurement` 属性。
* `IMPERIAL_SYSTEM` 单位系统已弃用,并由 `US_CUSTOMARY_SYSTEM` 替代。此外,单位系统的 `is_metric` 和 `name` 属性也已弃用,不应再使用。有关这些变更的更多详情请参见[此博客文章](/developers/blog/2022-10-14-deprecate-unit-system.md)。
* 单位转换工具已经过重构和移动,unit converter 现在作为 `homeassistant/util/unit_conversion` 中的静态类提供。有关这些变更的更多详情请参见[此博客文章](/developers/blog/2022-09-28-deprecate-conversion-utilities.md)。
---
url: /developers/blog/2022-12-08-multi-pan-rollback.md
---
**更新:** multi-pan 已修复,从 Home Assistant 2023.2 开始,用户可以重新选择加入实验版本。
在 [Home Assistant 2022.12](https://www.home-assistant.io/blog/2022/12/07/release-202212/) 中,我们软发布了实验性的 multi-pan 功能:允许使用 Home Assistant Yellow 和 Home Assistant SkyConnect 中的单个 radio 同时运行 Zigbee 和 Thread。用户需要在硬件菜单中找到它,确认实验说明并选择加入。
我们刚刚在固件中发现了一个严重的 bug,需要修复。这就是我们决定从 Home Assistant 2022.12.1 开始暂时禁止用户选择加入 multi-pan 的原因。
该 bug 是 Zigbee end-device 无法通过 coordinator 重新加入网络:coordinator 会将其踢出并要求重新加入,反复如此。普通的 EmberZNet 固件不会出现这种行为。它仅影响直接加入 coordinator 的 end device,而不是通过中间 router 加入的设备,并且仅影响那些尝试重新加入网络的设备。
我们正在与 Silicon Labs 合作,尽快解决此问题。一旦解决,我们将重新启用 multi-pan。
启用 multi-pan 会在您的 radio 上安装特殊固件。要禁用 multi-pan,需要重新安装原始的 Zigbee 固件。目前这还无法从 Home Assistant 自动完成。我们将尽快通过浏览器方式更新此帖子的操作说明。
---
url: /developers/blog/2023-01-02-db-schema-v32.md
---
对于 Home Assistant Core 2023.2,数据库 schema 已更改。
此变更影响那些不通过 `recorder` 的 [`history`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/recorder/history.py) API,而是直接访问 `last_updated`、`last_changed` 或 `time_fired` 列的自定义集成。
[`events`](https://data.home-assistant.io/docs/events) 和 [`states`](https://data.home-assistant.io/docs/events/) 的文档已更新,可在 [Data Science Portal](https://data.home-assistant.io/) 上查阅。
---
url: /developers/blog/2023-01-12-bluetooth-adapters.md
---
需要使用 Bluetooth 适配器的集成应在 [`manifest.json`](/developers/creating_integration_manifest.md) 的 [`dependencies`](/developers/creating_integration_manifest.md#dependencies) 中添加 `bluetooth_adapters`。[`manifest.json`](/developers/creating_integration_manifest.md) 条目确保在集成尝试使用所有受支持的远程适配器之前,它们已连接。这取代了在 [`dependencies`](/developers/creating_integration_manifest.md#dependencies) 中添加 `bluetooth` 的需求。
提供 Bluetooth 适配器的集成应在 [`manifest.json`](/developers/creating_integration_manifest.md) 的 [`dependencies`](/developers/creating_integration_manifest.md#dependencies) 中添加 `bluetooth`,并添加到 `bluetooth_adapters` 集成的 [`after_dependencies`](/developers/creating_integration_manifest.md#after-dependencies) 中。
在构建新的 Bluetooth 集成时,请务必查阅 [集成作者的 Best Practices](/developers/bluetooth.md#best-practices-for-integration-authors)。
---
url: /developers/blog/2023-01-20-cover-intent-deprecated.md
---
从 Home Assistant 2023.02 开始,`HassCoverOpen` 和 `HassCoverClose` intents 已弃用。请改用 `HassTurnOn` 和 `HassTurnOff` intents。这样做的原因是某些语言不区分 Open/On 和 Close/Off。
---
url: /developers/blog/2023-01-24-conversation-updates.md
---
Home Assistant 2023.2 版本包含 [PR 86592](https://github.com/home-assistant/core/pull/86592) 和 [PR 86484](https://github.com/home-assistant/core/pull/86484),其中包含对 [conversation agent](/developers/core/entity/conversation.md) API 的 breaking changes,以做未来兼容设计。
* 设置 agent 现在需要 config entry:\`conversation.async\_set\_agent(hass, config\_entry, agent).
* 取消设置 agent 现在通过新端点:\`conversation.async\_unset\_agent(hass, config\_entry)
* `AbstractConversationAgent` API 已更改:
* 移除了所有 onboarding 逻辑
* `async_process` 现在接收新的 `ConversationInput` 参数,参数相同。Language 现在始终被设置。
* `async_process` 现在应始终返回 `ConversationResult`。不再允许返回 `None` 或期望为你进行错误处理。
---
url: /developers/blog/2023-01-25-sensor_rounding.md
---
注意:
此处描述的变更已被回退,并替换为 presentation rounding,更多详情请参见[此博客文章](/developers/blog/2023-02-08-sensor_presentation_rounding.md)。
`SensorEntity` 现在可以在将数值型 `native_value` 转换为 sensor state 时进行 rounding。这是 [core PR #86074](https://github.com/home-assistant/core/pull/86074) 的一部分。rounding 由集成通过设置 `native_precision` 属性来 opt-in。建议集成设置此属性,因为它可确保即使经过单位转换后,小数位数仍然合理。
以下是 PR 描述中变更的摘要:
* 默认情况下不进行任何 rounding
* 集成可以通过设置新属性 `native_precision` 来影响 state precision
* state precision 受单位转换影响
* 从小单位转换到大单位会增加 display precision
* 从大单位转换到小单位会减小 display precision(前提是集成已设置 `native_precision`)
* 从大单位转换到小单位时的最小 precision 为 0,即不会 round 到十位、百位等
* 用户可以从 frontend 覆盖 display precision
* 没有最小 precision,即通过设置负 precision 可以 round 到十位、百位等
* 鼓励集成取消 display rounding,改为设置 `native_precision` 属性
* 在以下情况下,会在 sensor state 的字符串表示中补充尾随零以匹配 precision:
* precision 由用户设置
* `native_precision` 属性不为 `None`
* 进行了单位转换
---
url: /developers/blog/2023-02-03-responses.md
---
为 [Assist](https://www.home-assistant.io/docs/assist) 提供支持的 [intents 仓库](https://github.com/home-assistant/intents/) 有两个重要变更。
首先,intents YAML 中的 data block 现在可以有一个 `response` key。例如:
```yaml
language: en
intents:
HassTurnOn:
data:
- sentences:
- "open {name}"
response: cover
```
必须在 `responses//.yaml` 中定义具有匹配 key 的 response:
```yaml
language: en
responses:
intents:
HassTurnOn:
cover: "Opened {{ slots.name }}"
```
Response 模板采用 [Jinja2 格式](https://www.home-assistant.io/docs/configuration/templating/),可以访问匹配 intent 的 `slots` 以及受影响 entity 的 `state`。
第二个变更来自 [hassil](https://github.com/home-assistant/hassil),它是我们的 [intent template 语法](/developers/voice/intent-recognition/template-sentence-syntax.md)的解析器。除了解析速度提升 8-10 倍之外,模板中的 whitespace 将被按字面处理。
以前,像 `light(s | ing)` 这样的模板会同时匹配 "lights" 和 "lighting"。现在,由于 `|` 周围的额外空格,将匹配 "light s" 和 "light ing"。正确的模板应为 `light(s|ing)`
---
url: /developers/blog/2023-02-06-translations-files-removed-from-core.md
---
我们已从 Home Assistant Core 仓库中移除了所有翻译文件,
并放置了一个 helper script,用于从
翻译字符串文件(`strings.json`)编译英文翻译,
以供开发使用。
以前,所有翻译文件(包括所有语言)都是
Home Assistant Core 仓库的一部分。每天晚上我们都会通过
从 Lokalise 下载翻译并将它们提交到 Core 仓库来更新翻译。
现在,我们将此流程移至构建时。我们每次发布
新版本(包括 beta
和 nightly builds)时,会从 Lokalise 下载最新的
翻译。
这种方法带来了一些好处:
* 我们不再需要将翻译文件提交到 Core 仓库。
这意味着作为开发者,这也不再是一种令人困惑的负担。
* 人们将不再(错误地)尝试通过 GitHub
贡献语言翻译。
* 每个版本,包括 patch、beta 和 nightly 版本,
现在也将拥有最新的翻译。
## 本地开发
对于本地开发,我们有翻译开发 helper script。此
脚本一直存在,但现在它可以一次性为所有集成编译英文
翻译。
```bash
python3 -m script.translations develop --all
```
当设置开发环境时,以及每次在 VSCode 中运行
Home Assistant 时(作为 pre-launch task),此脚本会自动运行。
---
url: /developers/blog/2023-02-07-introducing-PR-drafting-in-reviews.md
---
Home Assistant 收到了大量贡献,这真是太棒了!但是
当有大量 PR 时,就很难追踪它们的状态。
为此,我们在 review 流程中引入了一项新流程,
当 PR 需要更多工作才能再次 review(或合并)时,
会自动将其标记为 draft。
我们已调整 bot,如果 review 请求了更改,则自动将 PR 标记为 draft。一旦更改完成,PR 的作者可以
点击 "Ready for review" 按钮将 PR 从 draft 中取消,使其准备好
再次接受 review。

在点击 "Ready for review" 按钮之前,请确保已处理
所有请求的更改,且所有 CI jobs 和 checks 均成功通过。
## 什么是 draft PR?
Draft PR 是指尚未准备好接受 review 的 PR。它是一种让其他人
知道您正在处理某事的方式,但它尚未准备好接受 review 和
合并。
Draft PR 在 PR 右上角有 "Draft" 标签,并且在 GitHub UI 各处
都以灰色合并图标显示。

这并不意味着您应该打开 PR 来开始处理某事;请
仅在您认为已准备好接受 review 和合并时才打开 PR。然而,
在打开 PR 后,可能出于某种原因需要将其重新设置为 draft 状态。
例如,打开 PR 会自动触发我们的 CI jobs
和 checks。这些 checks 可能会发现您代码中需要调整的问题,
或者确实进行了 review 并请求了更改。
您可以随时通过将 PR 右上角的 "Convert to draft" 链接点击,
将任何 PR 重新设置为 draft。

## 我们为什么要这样做?
作为 reviewer,您会面对大量 PR。有些已准备好接受
review,有些则没有。通常,唯一的方法就是打开
PR 并查看,结果发现它仍在进行中。
这不仅浪费时间,也浪费精力。尤其是
考虑到这种情况会发生在多个 reviewer 身上,且每天在同一个 PR 上发生多次。
PR 的 draft 状态在 GitHub 的所有地方都是可见的。在通知、
搜索以及 PR 的概览中。最重要的是,它易于筛选。
这让 reviewer 能够更好地专注于当前真正需要他们关注的内容。
更多背景信息可以在
[此 Google Document](https://docs.google.com/document/d/11_x2YUmAD07JN7JMM4YIIAWVGTJsOB0UptN8hlmWFWg/edit?usp=sharing) 中找到。
或者,[在此页面阅读有关我们 review 流程的所有内容](/developers/review-process.md)。
---
url: /developers/blog/2023-02-08-sensor_presentation_rounding.md
---
显示 sensor state 时展示的小数位数现在可由用户配置。集成可以通过设置 `suggested_display_precision` 属性来建议
小数位数。鼓励集成移除 display rounding,改为设置 `suggested_display_precision` 属性。
Presentation rounding 由 frontend 完成,同时也由 [core PR #87619](https://github.com/home-assistant/core/pull/87619) 中引入的新 template 函数完成。
显示的小数位数受单位转换影响:
* 从小单位转换到大单位会增加 display precision
* 从大单位转换到小单位会减小 display precision(前提是集成已设置 `suggested_display_precision`)
* 从大单位转换到小单位时的最小 precision 为 0,即不会 round 到十位、百位等
如果用户自行设置了 display precision,则显示的小数位数不受单位转换影响。
注意:
类似的概念(即对 sensor state 进行 rounding)已在[较早的博客文章](/developers/blog/2023-01-25-sensor_rounding.md)中详细介绍,但该变更已被回退。
---
url: /developers/blog/2023-02-09-hassgetstate.md
---
我们新增了一个[内置 intent](/developers/intent_builtin.md):`HassGetState`
一旦我们在 [intents 仓库](https://github.com/home-assistant/intents/) 中添加翻译,此 intent 将使用户能够向 [Assist](https://www.home-assistant.io/docs/assist) 提问。
您可以通过添加 [自定义句法](https://www.home-assistant.io/docs/assist/custom_sentences) 来试用:
```yaml
# Example /custom_sentences/en/get_state.yaml
language: en
intents:
HassGetState:
data:
- sentences:
- what is [in ]
- is {state} [in ]
responses:
intents:
HassGetState:
default: "{{ slots.name }} is {{ state.state_with_unit }}"
lists:
state:
values:
- "on"
- "off"
- open
- closed
- locked
- unlocked
- wet
- dry
```
通过这些句法,您现在可以向 Assist 提问 "what is the outside temperature?"、"is the front door locked?" 或 "is the floor in the basement wet?" 等问题。
当然,这依赖于 entity 名称(或别名)设置正确。例如,一个名为 "outside temperature" 的传感器,以及一个名为 "floor" 的二进制湿度传感器位于 "basement" 区域。
随着我们添加更多翻译,将支持更多类型的问题,如 "which lights are in the living room?" 和 "are any doors unlocked?"
---
url: /developers/blog/2023-02-15-json.md
---
从 Home Assistant Core 2023.3 开始,部分常量和函数在
`homeassistant.helpers.json` 和 `homeassistant.util.json` 之间进行了移动:
* `save_json` 和 `find_paths_unserializable_data` 函数现在应
从 `homeassistant.helpers.json` 导入
* `json_loads` 函数现在应从 `homeassistant.util.json` 导入
* `JSON_ENCODE_EXCEPTIONS` 和 `JSON_DECODE_EXCEPTIONS` 常量现在应
从 `homeassistant.util.json` 导入
---
url: /developers/blog/2023-02-20-snapshot-testing.md
---
Home Assistant [现已支持 snapshot testing](https://github.com/home-assistant/core/pull/88323)
用于 Python 代码库。
Snapshot testing(也称为 approval tests)是将值与
存储的参考值(snapshot)进行断言的测试,
确保代码的输出随时间保持一致。
Snapshot 测试不同于常规的(功能)测试,不能替代
功能测试,但对于测试较大的输出非常有用。
在 Home Assistant 中,例如可用于测试 entity states、device
或 entity registry 条目,或诊断 dump 的输出。
以下是一个诊断测试示例,它使用 snapshot 来断言
诊断 dump 的输出:
```python
# tests/components/example/test_diagnostics.py
async def test_diagnostics(
hass: HomeAssistant,
hass_client: ClientSessionGenerator,
init_integration: MockConfigEntry,
snapshot: SnapshotAssertion,
) -> None:
"""Test diagnostics."""
assert (
await get_diagnostics_for_config_entry(hass, hass_client, init_integration)
== snapshot
)
```
首次运行此测试时将会失败,因为不存在 snapshot。
要创建(或更新)snapshot,请运行带 `--snapshot-update` 标志的 pytest,
这将在该组件的 `snapshots` 目录中创建 snapshot 文件。
Snapshot 文件以测试文件命名,在本例中为:
`tests/components/example/snapshots/test_diagnostics.ambr`。Snapshot 文件
是人类可读的,必须提交到仓库中。
随后的测试运行会将结果与
snapshot 进行比较。如果结果不同,测试将失败。
Snapshot 是一种确保代码输出随时间保持不变的简便方法,
可以大幅减少所需的测试代码量(同时提供)
对完整输出的完整断言。
Home Assistant 中的 snapshot testing 基于 [Syrupy](https://github.com/tophat/syrupy) 构建,
并进行了扩展以处理 Home Assistant 特定的数据结构。
有关测试集成的更多信息,
请参见[我们的文档](/developers/development_testing.md)。
---
url: /developers/blog/2023-02-21-deprecated-callback-removed.md
---
Home Assistant 的 MQTT 集成[不再支持](https://github.com/home-assistant/core/pull/88543)
用于 MQTT subscribe 的弃用 callback 签名。
如果自定义集成仍在使用弃用的 callback 签名进行 MQTT subscribe 的 callback 函数,则会因未更新而中断。如果检测到不支持的 callback 类型,将抛出异常。
以下是不再工作的弃用 callback 函数示例:
```python
async def async_deprecated_callback1(topic: str, payload: ReceivePayloadType, qos: int) -> None:
"""Deprecated async callback example 1."""
...
@callback
def async_deprecated_callback2(topic: str, payload: ReceivePayloadType, qos: int) -> None:
"""Deprecated async callback example 2."""
...
```
以下是一个正确的 callback 签名示例:
```python
@callback
def async_correct_callback(msg: ReceiveMessage) -> None:
"""Callback example 1."""
...
```
---
url: /developers/blog/2023-02-28-custom-tile-features.md
---
在 Home Assistant Core 2022.3 版本中,我们为 [tile card](https://www.home-assistant.io/dashboards/tile/) 添加了自定义 features。如果您是自定义 card 的开发者,现在可以为 tile card 构建自己的 feature,而无需构建整个 card。

```yaml
type: tile
entity: button.push
features:
- type: custom:button-press-tile-feature
```
自定义 tile features 甚至可以使用与自定义 cards 类似的语法添加到 tile card editor 中,就像任何内置 tile feature 一样。
```js
window.customTileFeatures = window.customTileFeatures || [];
window.customTileFeatures.push({
type: "button-press-tile-feature",
name: "Button press",
});
```
更多详情请参见 [custom tile features](/developers/frontend/custom-ui/custom-card-feature.md) 文档。
---
url: /developers/blog/2023-03-27-entity_name_translations.md
---
现在可以翻译 entity 的 name,且这优于在 Python 实现中硬编码自然语言名称。此外,entity components 提供共享翻译,例如用于 binary sensor device class 的翻译,应使用这些翻译以避免重复翻译相同内容。
此外,frontend 现已完全支持翻译 entity state attributes 的 name 和值。
:::warning
通过 `translation_key` 属性指向翻译目前仅支持具有 `unique_id` 的 entity。
此外,翻译 entity name 要求 `has_entity_name` 属性设置为 `True`。
:::
## 翻译 entity name
以下 `strings.json` 示例适用于 `translation_key` 属性设置为 `thermostat_mode` 的 `sensor` entity:
```json
{
"entity": {
"sensor": {
"thermostat_mode": {
"name": "Thermostat mode"
}
}
}
}
```
以下 `strings.json` 示例适用于 `translation_key` 属性设置为 `temperature_sensor` 的 `sensor` entity,其中使用了 `sensor` 集成提供的共享翻译:
```json
{
"entity": {
"sensor": {
"temperature_sensor": {
"name": "[%key:component::sensor::entity_component::temperature::name%]"
}
}
}
}
```
## 翻译 entity attributes
以下 `strings.json` 示例适用于 `translation_key` 属性设置为 `ubercool` 的 `demo` 域 `climate` entity,它包含自定义的 `fan_mode` 和 `swing_mode` 设置:
```json
{
"entity": {
"climate": {
"ubercool": {
"state_attributes": {
"fan_mode": {
"state": {
"auto_high": "Auto High",
"auto_low": "Auto Low",
"on_high": "On High",
"on_low": "On Low"
}
},
"swing_mode": {
"state": {
"1": "1",
"2": "2",
"3": "3",
"auto": "Auto",
"off": "Off"
}
}
}
}
}
}
}
```
更多详情请参见 [`entity name translation`](/developers/internationalization/core.md#name-of-entities)、[`entity attribute translation`](/developers/internationalization/core.md#entity-state-attributes) 和 [`entity`](/developers/core/entity.md#generic-properties) 文档。
---
url: /developers/blog/2023-03-28-calendar_best_practices.md
---
Home Assistant 改进了 Calendar entity 的 best practices,以确保
在所有情况下 calendar triggers/automations 和 UI 均能正常工作。
特别地,现在有更多文档化的期望和不变量强制,包括:
* `CalendarEvent` 属性 `end` 是 exclusive 的。例如,持续一天的全日事件,其 end date 应为 start date 之后的 1 天。
* `CalendarEvent` 可以接受任何时区的 `datetime`。不允许无时区的 floating dates。
* 创建 `CalendarEvent` 时现在会强制验证不变量。
* 由 `async_get_events` 返回的事件应按顺序返回。
* 由 `async_get_events` 返回的全日事件必须在 Home Assistant 本地时区中评估。即,全日事件应按其在本地时间午夜开始的顺序排列。
[Calendar Entity 开发者文档](/developers/core/entity/calendar.md) 已更新,包含更多详细信息。
---
url: /developers/blog/2023-04-04-deprecating_polymer.md
---
Home Assistant frontend 过去使用 [Polymer Library](https://polymer-library.polymer-project.org/3.0/docs/about_30) 来处理 web components。Polymer 此后已被弃用,并由 [Lit](https://lit.dev) 取代。
在过去几年中,我们将大部分 frontend 迁移到了 Lit,只有少数地方和依赖仍在使用 Polymer。
这是好消息,因为 Lit 比 Polymer 快得多也轻量得多。
在 Home Assistant 2023.4 中,我们终于从 Home Assistant 的入口点移除了最后一块 Polymer,这意味着它在应用启动时不会加载,只有在组件需要它时才会加载。
对于自定义 cards 和 panels,我们在 window 对象上提供了 Polymer,以便于访问和使用。但现在几乎不再使用了,而且由于 Home Assistant 不再使用它,它主要是一大段未使用的代码,拖慢了 Home Assistant 的加载速度。
因此我们决定将其移除。
在 Home Assistant 2023.5 中,Home Assistant 将不再提供 Polymer。如果您现在使用 Polymer,我们建议您切换到 Lit。如果您想继续使用 Polymer,则需要自行加载 Polymer。
在 Home Assistant 2023.4 中,每次访问 Polymer 时我们都会记录一条警告。如果您收到日志消息,请找到使用 Polymer 的自定义 card、panel 或 more info,并通知作者此弃用信息。
---
url: /developers/blog/2023-04-13-new_limits_for_add_ons.md
---
随着 Home Assistant OS 10,我们更新到最新的 Docker 发布版本 23.0。在
新的 Docker 版本中,add-on 的最大打开文件描述符数量
更改为无限(接近无限)。
如果您是 add-on 开发者,并且在 Home
Assistant OS 10 上遇到内存不足问题,您可以在
启动服务之前使用 `ulimit -n 1048576` 来应用旧的限制。
背景:在 Home Assistant OS release candidate 阶段,较高的限制对
几个 add-on 来说有问题(Node-RED、Network UPS Tools 和 EMQX
MQTT broker,参见 [Home Assistant OS issue #2438](https://github.com/home-assistant/operating-system/issues/2438))。
在所有情况下,问题都表现为内存不足错误,
而在上一版 Home Assistant OS 发布的相同硬件上则工作正常。并且
在所有三种情况下,内存都是根据允许的
打开文件描述符数量动态分配的(可通过 `prlimit64` 系统调用确定,
返回的限制为 1073741816)。
我们考虑过恢复旧限制;然而,根据 Docker(moby)仓库中的变更,
使用无限作为限制的开销更少。
因此我们决定保留新的默认值。
---
url: /developers/blog/2023-04-30-statistics_impossible_values.md
---
此变更影响 WebSocket API:`recorder/statistic_during_period` 和 `recorder/statistics_during_period`。Home Assistant 项目目前不记录这些 API,因为它们可能会变更。
对于 Home Assistant Core 2023.6,statistics WebSocket API 将不再返回已确定将为空的列。调用方应将缺少值视为与 null 值相同。
为减少数据库开销,如果 statistics backend 提前知道某列的所有行都将为空值,则不再返回该列。
---
url: /developers/blog/2023-05-07-job-home-assistant-core.md
---
Nabu Casa 正在寻找一位有热情且经验丰富的 Python 开发者,全职加入 Home Assistant core 团队担任 Software Engineer。作为我们团队的一员,您将负责 review 贡献并开发与我们的 roadmap 一致的新功能。
[在 Nabu Casa 了解更多](https://nabucasa.com/jobs/home-assistant-core-developer/)
---
url: /developers/blog/2023-05-21-retained-mqtt-messages-filtered.md
---
Home Assistant 的 MQTT 集成处理 MQTT 消息的方式,以及 subscribe 和 unsubscribe 的执行方式已发生变更。
### MQTT subscribe 和 unsubscribe 的执行方式已更新
Subscribe 和 unsubscribe 不再立即发送到 broker。新增了 debouncer 代码,会在 debounce 时间内延迟向 broker 发送 subscribe 并进行合并。在实际执行(un)subscribe 时,(un)subscribe 会被合并为对 broker 的一次调用。这些变更将加快 MQTT 集成以及注册了大量 MQTT entity 的集成的启动速度,尤其是在 subscribe 共享 topic(例如发布 availability)时。在 MQTT debug 日志中,`mid` 编号显示哪些(un)subscribe 调用已合并为对 MQTT broker 的一次调用。
### retained messages 的处理方式已变更
具有 `retain` 标志的 MQTT 消息的处理方式已发生变更,使得现有 subscriber 不再收到因新的 subscriber 订阅同一 topic 而由 broker 重新发送的 retained messages。相反,broker 重新发送的 retained messages 仅传递给新的 subscriber。
新行为避免了相同 retained message 反复向 subscriber 洪泛,并且更好地符合 MQTT 规范,参见 OASIS MQTT Version 3.1.1 规范([关于 RETAIN 标志的使用方式](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc385349265))和规范性声明编号 [MQTT-3.3.1-9](http://docs.oasis-open.org/mqtt/mqtt/v3.1.1/os/mqtt-v3.1.1-os.html#_Toc398718134)。
---
url: /developers/blog/2023-06-14-service-calls.md
---
此变更影响 Service Call API:`hass.services.async_call` 和 `hass.services.call`。
对于 Home Assistant Core 2023.7,service calls 的部分输入参数和返回值已
变更,以准备更好地支持 [Service return values](https://github.com/home-assistant/architecture/discussions/777#discussioncomment-6127898)。
之前,成功时返回 `True`,发生超时返回 `False`。用于设置超时的 `limit`
参数已被移除,布尔返回值也已移除。
如果调用方需要超时,现在应使用 asyncio 自行设置。
---
url: /developers/blog/2023-06-28-entity-name-changes.md
---
Entity 命名发生了以下变更:
* 将 entity 标记为 device 的单一主要功能现在需要通过显式将 entity 的 `name` 属性设置为 `None` 来完成,通过不设置 `name` 属性来隐式将 entity 标记为 device 的单一主要功能已不再支持。
* 某些平台的未命名 entity 现在会根据其 device class 获得默认名称,这包括 `binary_sensor`、`button`、`number` 和 `sensor` entity。
更多详情请参见 [entity 命名文档](/developers/core/entity.md#entity-naming)。
---
url: /developers/blog/2023-06-28-service-description-filters.md
---
服务调用描述已更改以支持过滤。
可以为服务调用添加过滤器,以不显示不支持该服务调用的 entity;也可以为服务调用字段添加过滤器,以不向用户显示被选中 entity 不支持的字段。
例如:
* 仅当 light 支持亮度时,`light.turn_on` 的 `brightness` 服务调用参数才会显示。
* `climate.set_aux_heat` 服务调用仅允许选择支持辅助加热的 climate entity。
此功能在 core [PR #86162](https://github.com/home-assistant/core/pull/86162) 中引入,文档见[此处](/developers/dev_101_services.md#filtering-service-action-fields)。
---
url: /developers/blog/2023-07-07-action-event-custom-cards.md
---
在 Home Assistant Core 2023.7 版本中,我们为自定义卡片引入了 `hass-action`。
如果您是自定义卡片开发者,现在可以通过新的 `hass-action` 在自定义卡片中使用任意[卡片 action](https://www.home-assistant.io/dashboards/actions/)。
示例:
```js
// Define the action config
const actionConfig = {
entity: "sensor.temperature",
tap_action: {
action: "more-info",
},
hold_action: {
action: "assist",
start_listening: true,
},
};
// Open more info on tap action
const event = new Event("hass-action", {
bubbles: true,
composed: true,
});
event.detail = {
config: actionConfig,
action: "tap",
};
this.dispatchEvent(event);
// Open assist dialog on hold action
const event = new Event("hass-action", {
bubbles: true,
composed: true,
});
event.detail = {
config: actionConfig,
action: "hold",
};
this.dispatchEvent(event);
```
---
url: /developers/blog/2023-07-10-vacuum-updates.md
---
Vacuum entity 最近发生了一些变更:
* `VacuumEntity` 基类已被 `StateVacuumEntity` 取代(由 [PR 15573](https://github.com/home-assistant/core/pull/15573) 实现,于 2018 年 8 月合并),现计划在 Home Assistant Core 2024.2.0 中移除。所有 core 集成均已更新,但自定义组件的集成作者需要更新自己的集成。[PR 95920](https://github.com/home-assistant/core/pull/95920) 是最近一个从 `VacuumEntity` 迁移到 `StateVacuumEntity` 的示例。
* `VacuumEntity` 与 `StateVacuumEntity` 所支持的服务有所不同,但文档此前表述有些模糊,导致部分集成实现了来自错误基类的服务。现在 [PR 95833](https://github.com/home-assistant/core/pull/95833) 已对此进行了限制。所有 core 集成均已更新,但自定义组件的集成作者需要更新自己的集成。
* `battery_icon` 与 `battery_level` state 属性已被弃用。集成可以通过在同一 device 上添加一个 device class 为 `battery` 的 `sensor` 来报告电池状态,详情请见架构讨论 [938](https://github.com/home-assistant/architecture/discussions/938)。
* `map` state 属性已被弃用。集成可以通过在同一 device 上添加一个 `image` entity 来提供地图图片,详情请见架构讨论 [939](https://github.com/home-assistant/architecture/discussions/939)。
更多详情,请参阅[`vacuum` 文档](/developers/core/entity/vacuum.md)。
---
url: /developers/blog/2023-07-11-translating-services.md
---
Home Assistant 现在支持翻译 services。此前,service 及其 service 字段的名称和描述被硬编码在每个集成的 `services.yaml` 文件中。
现在,我们已添加使用翻译系统翻译这些名称和描述的支持。这意味着 service 及其 service 字段的名称和描述现在可以被翻译成任何语言。
为实现这一点,每个 service 和 service 字段的 `name` 与 `description` 键从硬编码的 `services.yaml` 文件中移动到每个集成的翻译文件中。
`services.yaml` service 描述更新的示例可在[我们的文档中](/developers/dev_101_services.md#service-action-descriptions)找到。[后端本地化](/developers/internationalization/core.md#service-actions)也已扩展,包含一个已翻译 service 的示例,与 service 描述中的示例相匹配。
service 翻译自 Home Assistant 2023.8 起可用。我们希望这能让 Home Assistant 对非英语用户更加友好。
---
url: /developers/blog/2023-07-21-change-naming-mqtt-entities.md
---
MQTT entities 的命名方式,以及发现的 entity 之间如何共享 device 配置,已发生改变。
### 共享 device 配置
发现的 MQTT entities 可以共享 device 配置,即一个 entity 可以包含完整的 device 配置,其他 entity 仅通过设置必填字段即可链接到该 device。此前必填字段至少包含 `connection` 和 `identifiers` 中的一个,现已扩展为至少包含 `connection` 和 `identifiers` 中的一个,以及 `name`。
### MQTT entities 的命名
MQTT entities 的命名已更改,以符合[entity 命名指南](https://developers.home-assistant.io/docs/core/entity/#entity-naming):
* 所有 MQTT entities 的 `has_entity_name` 将被设置为 `True`
* 未命名的 `binary_sensor`、`button`、`number` 与 `sensor` entities 现在将以其 device class 命名,而不再命名为 `MQTT binary sensor` 等
* 现在允许将 MQTT entity 的 name 设置为 `None`,以标记其为 device 的主要功能
---
url: /developers/blog/2023-07-27-avoiding-unnecessary-callbacks-with-dataupdatecoordinator.md
---
`DataUpdateCoordinator` 现在可以在 API 数据可比较时减少不必要的更新。
使用 `DataUpdateCoordinator` 时,轮询的数据通常预期大部分时间保持不变。例如,如果你轮询一个每周仅开启一次的灯,该数据几乎在所有时间都是相同的。默认行为是在数据更新时始终回调监听器,即使数据并未改变。如果 API 返回的数据可以通过 Python 的 `__eq__` 方法进行比较,则在创建 `DataUpdateCoordinator` 时设置 `always_update=False`,以避免不必要的回调和对 state machine 的写入。
---
url: /developers/blog/2023-08-07-weather_entity_forecast_types.md
---
`WeatherEntity` 现在允许单个 weather entity 支持不同的预报类型,这意味着不再需要为同一地点创建多个 entity,例如一个提供每日预报的 entity 和另一个提供每小时预报的 entity。
提供 `weather` entities 的集成应进行更新,以实现一个或多个新的 async 方法 `async_forecast_daily`、`async_forecast_hourly` 和 `async_forecast_twice_daily`。
对于即将到来的 Home Assistant Core 2024.3 版本,集成应移除已弃用的 `forecast` 属性,并移除为提供多种预报而添加的任何重复 weather entities。
请参阅[`weather` 开发者文档](/developers/core/entity/weather.md#weather-forecasts),了解如何实现新的预报方法。
"Weather Forecast Card" 已更新,如果集成使用了新方法,将为用户提供选择要显示的偏好预报的选项。
---
url: /developers/blog/2023-08-23-sensor-updates.md
---
数值型 sensor 不再允许将 state 设置为[非数字 (Not a Number)](https://en.wikipedia.org/wiki/NaN) 或正无穷/负无穷。
此变更在 [core PR#98110](https://github.com/home-assistant/core/pull/98110) 中引入。
更多详情,请参阅[`sensor` 文档](/developers/core/entity/sensor.md)。
---
url: /developers/blog/2023-08-29-hass-format-state.md
---
在 Home Assistant Core 2023.9 版本中,我们在 `hass` 对象中引入了 3 个新方法,以允许自定义卡片对 entity state 进行带本地化支持格式化:
* `hass.formatEntityState`
* `hass.formatEntityAttributeValue`
* `hass.formatEntityAttributeName`
示例:
```js
hass.formatEntityState(hass.states["cover.living_room_shutter"]);
// 如果用户语言是英语,将返回 "Open"。
// 如果用户语言是法语,将返回 "Ouvert"。
```
更多详情,请参阅[entity state 格式化文档](/developers/frontend/data.md#entity-state-formatting)。
---
url: /developers/blog/2023-08-29-homeassistant-changed-signature.md
---
`HomeAssistant.__init__` 的签名已从无需参数更改为要求以字符串形式传递配置目录。Home Assistant core 仓库之外的脚本、测试等如果创建了 `HomeAssistant` 对象,都需要进行更新。
此变更在 [core PR#98442](https://github.com/home-assistant/core/pull/98442) 中引入。
如果需要保持向后兼容,可以通过以下方式实现:
```python
try:
hass = HomeAssistant() # pylint: disable=no-value-for-parameter
except TypeError:
hass = HomeAssistant(config_dir) # pylint: disable=too-many-function-args
```
---
url: /developers/blog/2023-09-12-ws-entity-source-simplified.md
---
websocket 命令 `entity/source` 已大大简化:
* 不再能够获取单个 entity 的信息
* 响应中仅包含 entities 的 domain,`custom_component`、`config_entry` 和 `source` 不再存在。
### 新响应示例
```json
{
"light.entity_1": {
"domain": "template",
},
"switch.entity_2": {
"domain": "shelly",
},
}
```
### 旧响应示例
```json
{
"light.entity_1": {
"custom_component": false,
"domain": "template",
"source": "platform_config",
},
"switch.entity_2": {
"custom_component": false,
"config_entry": "",
"domain": "shelly",
"source": "config_entry",
},
}
```
此变更在 [core PR#99439](https://github.com/home-assistant/core/pull/99439) 中引入。
---
url: /developers/blog/2023-09-20-excluding-state-attributes-from-recording.md
---
排除 state 属性不参与 recording 的方式已发生改变。
recorder platform 已被两个新属性所取代,可在从 `Entity` 派生的类中设置:
* `_entity_component_unrecorded_attributes: frozenset[str]` - 应由 base component entity 类(例如 `LightEntity`)设置
* `_unrecorded_attributes: frozenset[str]` - 应由派生 platform 类(例如 `HueLight`)设置,以排除额外的、集成特有的属性不参与 recording
更多详情可在[entity 文档](/developers/core/entity.md#excluding-state-attributes-from-recorder-history)中找到。
变更的背景在[架构讨论 #964](https://github.com/home-assistant/architecture/discussions/964)中。
---
url: /developers/blog/2023-10-01-hacktoberfest-2023.md
---
📢 对 Hacktoberfest 2023 参与者来说的好消息!我们要参与了!🎉
这不仅是我们的[10 周年](https://www.home-assistant.io/blog/2023/09/17/10-years-home-assistant/),也是 Hacktoberfest 的第 10 年!恭喜!🎂
Hacktoberfest 是全球性的、为期一个月的开源庆祝活动,一个向所有人开放的活动。无论你是开发者、学习编程的学生、文档编写者还是设计师,你都可以推动 Home Assistant 这类开源项目的增长。欢迎所有背景和技能水平的参与者完成 Hacktoberfest 挑战。当然,我们也乐意帮助你提交 pull requests。
与往年一样,Home Assistant 参与 Hacktoberfest。我们所有的仓库都已加入,无论你想修改[前端](https://github.com/home-assistant/frontend)、[core](https://github.com/home-assistant/core),还是我们的[文档](https://github.com/home-assistant/home-assistant.io)。这些仓库的 issue tracker 都能很好地概览可以贡献的内容。
## 但,我不是开发者?!
如果你不是开发者,对 git、GitHub 或开源还不太熟悉,那么参与文档工作是很好的入门方式。一个相对简单的贡献方式是审查你使用或熟悉的集成的文档,检查内容是否仍然最新且无拼写/语法错误。
我们网站上每一页文档底部都有一个"Edit"按钮。使用该按钮,你可以修改页面上的文本并提出改进建议。
在我们的 Community 论坛上,有一份详细的分步指南说明了操作方式:[Editing the Documentation and Creating a Pull Request on GitHub](https://community.home-assistant.io/t/editing-the-documentation-and-creating-a-pull-request-on-github/9573)。
那么,你还在等什么?在 [Hacktoberfest](https://hacktoberfest.com/) 网站上注册,开始 hack 吧!如果有任何问题,请前往我们的[Discord 聊天服务器](https://www.home-assistant.io/join-chat)。我们有专门的开发者频道,很乐意为你提供帮助。
Happy Hacktoberfest 🎉
---
url: /developers/blog/2023-10-03-conversion-functions-cleanup.md
---
以下工具在 [Home Assistant 2022.10 中被弃用](/developers/blog/2022-09-28-deprecate-conversion-utilities.md)),现在自 2023.11 起已被移除:
* `homeassistant/util/distance`
* `homeassistant/util/pressure`
* `homeassistant/util/speed`
* `homeassistant/util/temperature`
* `homeassistant/util/volume`
请使用 `homeassistant/util/unit_conversion` 中对应的静态类:
* `DistanceConverter`
* `PressureConverter`
* `SpeedConverter`
* `TemperatureConverter`
* `VolumeConverter`
这些已弃用的函数在内置 Home Assistant 集成中已不再使用,使用它们的社区集成在过去 12 个月中应该已经看到过警告。现在尝试导入原始工具将导致错误。
---
url: /developers/blog/2023-10-12-country_selector.md
---
[Selectors](https://www.home-assistant.io/docs/blueprint/selectors/) 已扩展,现在也包含了 `CountrySelector`。
在[config flows](/developers/data_entry_flow_index.md#show-form)中使用它可以让 frontend 自动将国家代码翻译成正确的国家名称。
示例:
```python
vol.Schema(
{
vol.Optional(CONF_COUNTRY): CountrySelector(
CountrySelectorConfig(
countries=["DE", "US"],
)
),
}
)
```
---
url: /developers/blog/2023-11-06-public-addon-config.md
---
Add-on 现在可以有一个用于配置文件或数据文件的公共文件夹,用户可以看到并修改它,但它仍然会与 add-on 一起被备份。
许多 add-on 会要求用户在 add-on 配置中提供文件。或者生成一些文件,希望用户能够查看并可能修改它们。它们通常通过映射文件夹列表中包含 `config` 和/或 `share` 来处理。
但这样做有两个问题:
1. `config` 或 `share` 中的内容都不会与 add-on 一起备份。因此,在恢复后,该 add-on 的备份不包含运行它所需的所有文件。
2. 映射 `config` 的 add-on 拥有的访问权限远多于其应有的权限,因为 `config` 包含了你在 Home Assistant 集成中使用的所有 secrets 和 credentials。
现在为 add-on 开发者提供了一种更好的解决方案。Add-on 可以在映射文件夹列表中包含 `addon_config`。然后,supervisor 将为该 add-on 在 `/addon_configs/` 处创建一个文件夹,并将其映射到 add-on container 内的 `/config`。如果你的 add-on 除了收集用户文件之外,还需要能够在此文件夹中创建和修改文件,则使用 `addon_config:rw`。
要了解更多关于此功能及其用例的信息,请参阅[Add-on advanced options](/developers/apps/configuration.md#app-advanced-options)。
## 与 `/config` 的向后兼容
你可能会注意到新的公共 config 文件夹被映射到 `/config`。此前如果你将 `config` 添加到 `map` 字段中,Home Assistant 的 config 文件夹就会被映射到此处。
此选项旨在取代 add-on 将 Home Assistant 的 config 映射到其 container 的需求。因此,add-on 不能在 `map` 字段中同时包含 `config` 和 `addon_config`。
今后,如果你确实需要将 Home Assistant 的 config 提供给你的 add-on,你应该在 `map` 字段中将 `homeassistant_config` 列为文件夹。然后 Home Assistant 的 config 文件夹将被映射到 container 内的 `/homeassistant`。
## 新的 `addon_configs` 文件夹
一些 add-on 需要访问所有这些 add-on 专用的 config 文件夹。例如:
1. Samba
2. SSH
3. Studio Code Server
本质上,这些 add-on 提供了编辑 Home Assistant 及其 add-on 配置文件的其他手段。类似这些 add-on 应将 `all_addon_configs:rw` 添加到 map 字段中的文件夹列表。这将把整个 add-on config 文件夹映射到 container 内的 `/addon_configs`。
---
url: /developers/blog/2023-11-27-config-validation-and-error-handling.md
---
## Component config 处理与错误处理的变更
Component YAML 配置的处理方式已发生改变。现在可以在发生错误时抛出异常。如果某些自定义集成使用了 `config.async_process_component_config`,它们可能会中断。现在它们可以改用 `config.async_process_component_and_handle_errors`。这个新方法支持在 config 处理过程中发生错误时抛出异常。
从现在起,失败将不再作为持久消息发出通知,因此集成需要实现错误处理,以便在发生失败时通知用户。在 setup 期间如果存在 config 问题,仍然会添加通知。
```python
async def async_process_component_and_handle_errors(
hass: HomeAssistant,
config: ConfigType,
integration: Integration,
raise_on_failure: bool = False,
) -> ConfigType | None:
...
```
在 reload 期间,集成可以使用 `helpers.reload.async_integration_yaml_config`。该 helper 现在在发生失败时也具备抛出异常的能力。
```python
async def async_integration_yaml_config(
hass: HomeAssistant, integration_name: str, *, raise_on_failure: bool = False
) -> ConfigType | None:
...
```
## Config 验证异常(Exceptions)的翻译支持
引入了一种新的 `ConfigValidationError` 异常类。当 config 错误处理过程中发生错误且 `raise_on_failure` 设置为 `True` 时,将抛出此异常。如果该错误是在执行服务调用期间抛出的且不需要 stack trace,它可以被重新抛出为 `ServiceValidationError`。添加了翻译键以允许对错误消息进行本地化。
### 背景
* 背景[讨论](https://github.com/home-assistant/architecture/discussions/992)。
* 实现 [Core PR #102410](https://github.com/home-assistant/core/pull/102410)。
---
url: /developers/blog/2023-11-30-service-exceptions-and-translations.md
---
## 服务调用期间的异常处理
迄今为止,抛出异常的 service 调用会记录 stack trace。由于错误用法(例如无效输入)导致失败的 service 调用不需要 stack trace,如果能向用户提供其本语言的有帮助的错误消息会更好。
为了在这些情况下抑制 stack trace,引入了派生自 `HomeAssistantError` 的新异常类型 `ServiceValidationError`。当 service 因被错误使用而失败时,现在应抛出 `ServiceValidationError` 而不是 `ValueError`。
如果 service 被正确使用但仍然失败,仍然应抛出 `HomeAssistantError`,或者是除 `ServiceValidationError` 之外的 `HomeAssistantError` 子类。
当 service 抛出 `ServiceValidationError` 时,错误消息将显示在 UI 和日志中,但 stack trace 将以 debug 级别记录。对于从 service 调用抛出的其他异常(包括 `HomeAssistantError`),一切不变,仍然以异常严重级别打印完整的 stack trace。
集成应进行更新:当 service 因错误用法而失败时,抛出 `ServiceValidationError` 而不是 `ValueError`;当它因其他预期错误(例如网络错误)而失败时,抛出 `HomeAssistantError`。[了解更多](/developers/core/platform/raising_exceptions.md)。
## 异常的翻译支持
`HomeAssistantError` 异常及其子类(包括 `ServiceValidationError`)现在接受 translation key 以允许本地化。[了解更多](/developers/internationalization/core.md#exceptions)。当 frontend 收到关于异常的信息时,将使用 translation key。如果未作为参数将自定义错误消息传递给异常,translation 缓存中的英文错误消息也将用于在控制台记录消息。
### 背景
* 背景[讨论](https://github.com/home-assistant/architecture/discussions/992)。
* 实现 [Core PR #102592](https://github.com/home-assistant/core/pull/102592)。
---
url: /developers/blog/2023-12-05-scaling-utils.md
---
## 用于缩放亮度的新工具
多个集成实现各自自己的 scaling 算法来缩放亮度。现在在 `homeassistant.util.color` 中引入了新的工具,以简化亮度 scaling 的实现:
```python
def brightness_to_value(low_high_range: tuple[float, float], brightness: int) -> float:
"""Given a brightness_scale convert a brightness to a single value.
Do not include 0 if the light is off for value 0.
Given a brightness low_high_range of (1,100) this function
will return:
255: 100.0
127: ~49.8039
10: ~3.9216
"""
...
```
如果你更倾向于将亮度缩放到一个整数范围,也可以使用 `scale_ranged_value_to_int_range`,详情见[此处](#background)。
```python
def value_to_brightness(low_high_range: tuple[float, float], value: float) -> int:
"""Given a brightness_scale convert a single value to a brightness.
Do not include 0 if the light is off for value 0.
Given a brightness low_high_range of (1,100) this function
will return:
100: 255
50: 127
4: 10
The value will be clamped between 1..255 to ensure valid value.
"""
...
```
这也确保了返回有效的亮度值。
### 背景
为了缩放 fan speed 百分比,我们已经有一些工具在 `homeassistant.utils.percentage` 中:
```python
def ranged_value_to_percentage(
low_high_range: tuple[float, float], value: float
) -> int:
...
```
以及
```python
def percentage_to_ranged_value(
low_high_range: tuple[float, float], percentage: int
) -> float:
...
```
这些百分比工具现在将在 `homeassistant.utils.scaling` 中使用新的通用 scaling 工具:
`scale_ranged_value_to_int_range` 和 `scale_to_ranged_value`
```python
def scale_ranged_value_to_int_range(
source_low_high_range: tuple[float, float],
target_low_high_range: tuple[float, float],
value: float,
) -> int:
"""Given a range of low and high values convert a single value to another range.
Given a source low value of 1 and a high value of 255 and
a target range from 1 to 100 this function
will return:
(1,255), 255: 100
(1,255), 127: 50
(1,255), 10: 4
"""
...
```
以及
```python
def scale_to_ranged_value(
source_low_high_range: tuple[float, float],
target_low_high_range: tuple[float, float],
value: int,
) -> float:
"""Given a range of low and high values convert a single value to another range.
Do not include 0 in a range if 0 means off,
e.g. for brightness or fan speed.
Given a source low value of 1 and a high value of 255 and
a target range from 1 to 100 this function
will return:
(1,255), 255: 100
(1,255), 127: ~49.8039
(1,255), 10: ~3.9216
"""
...
```
## 工具 `int_states_in_range` 和 `states_in_range` 已迁移
这些工具现在位于 `homeassistant.util.scaling` 下。如果它们在自定义集成中被使用,请确保更新 import 到新模块。
---
url: /developers/blog/2023-12-11-entity-description-changes.md
---
`EntityDescription` 类已发生变更;派生的 `dataclasses` 现在应设置 `frozen=True` 和 `kw_only=True`。
将 `frozen` 设置为 `True` 使 `EntityDescription` 实例不可变,这意味着它们在创建后不会被意外更新。
将 `kw_only` 设置为 `True` 确保 base 类中字段的顺序可以更改,而不会破坏用户代码。
在一段以 HA Core 2025.1 结束的弃用期内,仍然可以派生未设置 `frozen=True` 或 `kw_only=True` 的 `dataclasses`,但会被记录日志,并要求用户为自定义集成创建 issue。
一旦 HA Core 2025.1 发布,将不再可能派生未设置 `frozen=True` 或 `kw_only=True` 的 `dataclasses`。
更多详情可在[core PR #105211](https://github.com/home-assistant/core/pull/105211)中找到。
---
url: /developers/blog/2023-12-18-config-entry-minor-version.md
---
Config entry 现在支持 minor 版本。
如果 minor 版本不同但 major 版本相同,即使集成未实现 `async_migrate_entry`,集成 setup 也会被允许继续。这意味着 minor 版本提升是向后兼容的,这与 major 版本提升不同,后者会在用户降级 HA Core 且未从备份恢复配置时导致集成 setup 失败。
### 背景
由于 config entry 数据的版本化会破坏降级到旧版 Home Assistant Core 的过程,此前我们对版本化的态度非常保守。这意味着在大多数情况下,我们不做版本化,集成反而进行一种软升级,例如,对初始版本中不存在的 config entry 数据执行 `dict.get`,在 setup 期间转换数据等。
通过引入类似 storage helper 已提供的 minor 版本,此模式不再被推荐。每当新增或更改的数据不会破坏旧版版本时,应改为提升 minor 版本。
更多详情可在[config entry 迁移文档](/developers/core/integration/config_flow.md#config-entry-migration)和 [core PR #105749](https://github.com/home-assistant/core/pull/105479) 中找到。
---
url: /developers/blog/2023-12-19-constant-deprecation.md
---
近年来,许多常量通过添加指向其替代项的代码注释,被 Enum 或其他值所取代。
现在使用已弃用的常量将生成一条警告日志条目,其中包含使用的集成、替代项以及该常量将从 core 中移除的版本。
将有一年的弃用期,以确保所有自定义集成作者有时间进行调整。
大多数常量应该已经被替换了,因为过去我们发布了多篇关于其弃用的博客文章。部分列于下方:
* [Deprecating all SUPPORT\_\* constants](/developers/blog/2022-04-02-support-constants-deprecation.md)
* [Constant deprecations for 2022.5](/developers/blog/2022-05-03-constants-deprecations.md)
* [AutomationActionType deprecation for 2022.9](/developers/blog/2022-08-15-automation-action-type-deprecation.md)
* [Device tracker deprecations for 2022.9](/developers/blog/2022-07-29-device-tracker_source-type-deprecation.md)
* [Deprecating media player constants](/developers/blog/2022-09-06-media-player-repeat-mode-deprecation.md)
* [Add new precipitation intensity units](/developers/blog/2022-10-25-new-precipitation-intensity-units.md)
* [Introducing new unit enumerators](/developers/blog/2022-10-26-new-unit-enumerators.md)
* [Add more unit enumerators](/developers/blog/2022-11-28-more-unit-enumerators.md)
* [Add more unit enumerators](/developers/blog/2022-12-05-more-unit-enumerators.md)
更多详情可在 [core PR #105736](https://github.com/home-assistant/core/pull/105736) 中找到,或者通过查看弃用 helper 中 [`check_if_deprecated_constant`](https://github.com/home-assistant/core/blob/dev/homeassistant/helpers/deprecation.py#L240) 函数的使用情况。
---
url: /developers/blog/2023-12-28-support-feature-magic-numbers-deprecation.md
---
自 Home Assistant Core 2024.1 起,所有用于 supported features 的 magic numbers 用法均已被弃用,并且每个 entity platform 都提供了一个 `EntityFeature` enum 来替代它们。
将有一年的弃用期,magic numbers 将在 2025.1 中停止工作,以确保所有自定义集成作者有时间进行调整。
这适用于以下 platform:
* **Alarm Control Panel**
请改用新的 [`AlarmControlPanelEntityFeature`](/developers/core/entity/alarm-control-panel.md#supported-features) enum。
* **Camera**
请改用新的 [`CameraEntityFeature`](/developers/core/entity/camera.md#supported-features) enum。
* **Cover**
请改用新的 [`CoverEntityFeature`](/developers/core/entity/cover.md#supported-features) enum。
* **Climate**
请改用新的 [`ClimateEntityFeature`](/developers/core/entity/climate.md#supported-features) enum。
* **Humidifier**
请改用新的 [`HumidifierEntityFeature`](/developers/core/entity/humidifier.md#supported-features) enum。
* **Fan**
请改用新的 [`FanEntityFeature`](/developers/core/entity/fan.md#supported-features) enum。
* **Light**
请改用新的 [`LightEntityFeature`](/developers/core/entity/light.md#supported-features) enum。
* **Lock**
请改用新的 [`LockEntityFeature`](/developers/core/entity/lock.md#supported-features) enum。
* **Media Player**
请改用新的 [`MediaPlayerEntityFeature`](/developers/core/entity/media-player.md#supported-features) enum。
* **Remote**
请改用新的 [`RemoteEntityFeature`](/developers/core/entity/remote.md#supported-features) enum。
* **Siren**
请改用新的 [`SirenEntityFeature`](/developers/core/entity/siren.md#supported-features) enum。
* **Vacuum**
请改用新的 [`VacuumEntityFeature`](/developers/core/entity/vacuum.md#supported-features) enum。
* **Water Heater**
请改用新的 [`WaterHeaterEntityFeature`](/developers/core/entity/water-heater.md#supported-features) enum。
---
url: /developers/blog/2024-01-11-async-show-progress-changes.md
---
`FlowHandler.async_show_progress` 已更新:
* 添加了一个新参数 `progress_task`,它在 Home Assistant core 发布版 2024.8 中将成为必填项
如果传递了 `progress_task`,`FlowManager` 将:
* 在任务完成后向 frontend 发送一个事件
* 如果用户在任务完成前关闭了 config flow 对话框,则取消 `progress_task`
这意味着派生类不再负责 progress task 状态与 UI 之间的交互。
`FlowHandler.async_show_progress` 在未传入 `progress_task` 的情况下被调用时会记录警告。在 Home Assistant core 发布版 2024.8 中,该调用将失败。
更多详情可在[文档](/developers/data_entry_flow_index.md#show-progress--show-progress-done)、[core PR #107668](https://github.com/home-assistant/core/pull/107668) 和 [#107802](https://github.com/home-assistant/core/pull/107802) 中找到。
---
url: /developers/blog/2024-01-17-deprecate-invalid-sensor-uom-state-class-use.md
---
自 Home Assistant Core 2022.12 起,如果具有 device class 的 sensor entity 错误地使用了 unit of measurement 和 state class(会考虑其 device class),则会在日志中发出警告。
无效的用法示例:具有 `SensorDeviceClass.TEMPERATURE` device class 的 sensor entity,但使用了 unit of measurement `%` 而不是 `°C` 或 `°F`;或者使用了 state class `SensorStateClass.TOTAL` 而不是 `SensorStateClass.MEASUREMENT`。
将有一个六个月的弃用期,以确保所有自定义集成作者有时间进行调整。自 Home Assistant Core 2024.8 起,该警告将被异常所取代。
---
url: /developers/blog/2024-01-19-entity-translations-placeholders.md
---
现在可以使用 placeholders 为 entity translation 提供静态值。
你可以通过 entity 的 `translation_placeholders` 属性传递 placeholders。
一个 sensor 示例:
```python
class TestEntity(SensorEntity):
"""Example entity."""
_attr_has_entity_name = True
_attr_translation_key = "temperature"
def __init__(self) -> None:
"""Initialize example entity."""
self._attr_translation_placeholders = {"channel_id": "2"}
self._attr_device_info = DeviceInfo(
name="Example device"
)
```
`strings.json` 文件将如下所示:
```json
{
"entity": {
"sensor": {
"temperature": {
"name": "Temperature channel {channel_id}"
}
}
}
}
```
最终生成的 entity 将被称为 `Example device Temperature channel 2`。
当预期存在 translation placeholder 但 entity 未提供时,会记录一次警告。
在不是稳定版本的系统上(dev、nightly 或 beta),发生此情况时会抛出错误,以便能够迅速捕获问题。
请记住对你的翻译人员友善,因为他们需要理解 placeholder 名称中传入的是什么类型的名称或值 ❤️。
---
url: /developers/blog/2024-01-19-icon-translations.md
---
在 Home Assistant 2024.2 中,我们将引入一种为集成提供 icons 的新方式:Icon 翻译。
Icon 翻译的工作方式类似于我们用于 entities 的常规翻译,后者可以将 entity 的 state 或 entity 属性 state 翻译成任何语言。Icon 翻译的工作方式类似,但并不是将 state 翻译成最终用户语言,而是将 state 翻译为一个 icon。
每个集成现在都可以提供一个 `icons.json` 文件,其中包含从 states 到 icons 的映射。以下是一个 Moon `sensor` entity 的示例,它为每个 state 提供不同的 icon:
```json
{
"entity": {
"sensor": {
"phase": {
"default": "mdi:moon",
"state": {
"new_moon": "mdi:moon-new",
"first_quarter": "mdi:moon-first-quarter",
"full_moon": "mdi:moon-full",
"last_quarter": "mdi:moon-last-quarter"
}
}
}
}
}
```
Icon 翻译也支持翻译 entity 属性 states。
[在我们的文档中了解更多关于 icon 翻译的内容](/developers/core/entity.md#icons)。
## Service 图标
此变更是向后兼容的。entity 现有的 `icon` 属性将继续像以前一样工作。不过,我们建议使用 icon 翻译来替代 `icon` 属性。
此外,集成提供的 services 现在也支持 icons,并可以在相同的 icon 翻译文件中提供。这些 icons 在 Home Assistant UI 中显示 service 时使用,比如在 automation 和 script 编辑器中。以下示例展示了如何为 `light.turn_on` 和 `light.turn_off` services 提供 icons:
```json
{
"services": {
"turn_on": "mdi:lightbulb-on",
"turn_off": "mdi:lightbulb-off"
}
}
```
[在我们的文档中了解更多关于 service icons 的内容](/developers/dev_101_services.md#icons)。
---
url: /developers/blog/2024-01-22-async_services_for_domain.md
---
在 Home Assistant 2024.2 中,我们将引入 `hass.services.async_services_for_domain()`,这是一种按 domain 枚举 services 的新方式,允许集成检查哪些 services 可用,而无需获取系统中的所有 services。我们发现大多数集成只关心它们自己提供的 services,而在集成只关心自己的 services 时,枚举系统中的所有 services 成本很高。
调用 `hass.services.async_services()[DOMAIN]` 以获取特定 domain 的 services 的集成,应将调用替换为 `hass.services.async_services_for_domain(DOMAIN)`。
---
url: /developers/blog/2024-01-23-light-color-mode-changes.md
---
## 背景
引入 light color modes 的主要原因是 light 的 state 不应含糊不清。例如,一个同时支持 color 和可调节 color temperature 的白色光的 light,必须处于 color mode `hs`(举例)或 `color_temp` 中。
然而,effects 使情况变得复杂,因为当同一盏 light 正在渲染 effect 时,`hs_color`、`color_temp` 或 `brightness` state 属性可能都没有意义。
## 变更
### light 渲染 effect 时,对 `color_mode` 的要求不那么严格
当 effect 激活时,允许使用比 light 通常支持的限制性更高的 color modes:
* 支持 colors 的 light 在被 effect 控制时,允许指示 color modes `on_off` 和 `brightness`
* 支持 brightness 的 light 在被 effect 控制时,允许指示 color mode `on_off`
例如,一个 supported\_color\_modes 设置为 `{"hs", "color_temp"}` 的 light,在渲染无法调节的 effect 时允许将其 `color_mode` 设置为 `on_off`,在渲染允许控制 brightness 的 effect 时允许设置为 `brightness`。
### 新增了一个表示无 effect / 关闭 effect 的特殊 effect `EFFECT_OFF`
此前,支持 effects 的 light 没有标准方式来表示当前没有 effect 激活。
现在通过添加预定义 effect `EFFECT_OFF` 来解决此问题,用于指示没有 effect 激活。
更多详情可在[文档](/developers/core/entity/light.md#color-modes)和[架构讨论 #960](https://github.com/home-assistant/architecture/discussions/960)中找到。
---
url: /developers/blog/2024-01-24-climate-climateentityfeatures-expanded.md
---
自 Home Assistant Core 2024.2 起,我们在 `ClimateEntityFeature` 中添加了两个新标志:`TURN_ON`、`TURN_OFF`。
实现 `turn_on` service 调用的集成需要设置 `TURN_ON` feature flag。
实现 `turn_off` service 调用的集成需要设置 `TURN_OFF` feature flag。
将有一个 10 个月的弃用期(2025.1),在此期间 `ClimateEntity` 将代实现相应方法的集成设置这些标志;自 2025.1 起,如果 entity features 未相应设置,集成将无法使用相应方法。
在未设置相应 feature flag 的情况下实现方法将生成一条警告日志条目,引导用户为集成创建 issue。
集成一旦迁移到使用或不使用新的 feature flags,应在 `ClimateEntity` 子类实例中将属性 `_enable_turn_on_off_backwards_compatibility` 设置为 `False`。
这将在弃用期间停止自动设置新的 feature flags,并可在弃用结束后移除。
---
url: /developers/blog/2024-01-30-ha-state-icon-properties-changes.md
---
在 Home Assistant 2024.2 中,集成有了提供 icons 的新方式。为了支持这一新功能,`ha-state-icon` 组件的 properties 已发生变更。
[在我们的文档中了解更多关于 icon 翻译的内容](/developers/core/entity.md#icons)。
如果你是使用此组件的自定义卡片开发者,你必须调整传递给组件的 properties,以避免在自定义卡片中显示错误的 icons。
### 2024.2 之前
```html
```
### 2024.2 之后
```html
```
### 向后兼容
如果你想同时支持 Home Assistant 的旧版和新版,可以传递所有 properties。
```html
```
---
url: /developers/blog/2024-02-12-async_update_entry.md
---
自 Home Assistant 2024.3 起,对 `ConfigEntry` 的修改应使用 `hass.config_entries.async_update_entry`。
直接在 `ConfigEntry` 对象上设置属性已被弃用,并将在 2024.9 及更高版本中开始失败。
直接在 `ConfigEntry` 上设置 `unique_id` 没有弃用期,因为这样做会破坏内部状态,并且会立即开始失败。
以下属性现在必须通过 `hass.config_entries.async_update_entry` 设置:
* data
* minor\_version
* options
* pref\_disable\_new\_entities
* pref\_disable\_polling
* title
* unique\_id
* version
测试必须确保在调用 `hass.config_entries.async_update_entry` 之前,通过 `entry.add_to_hass(hass)` 将 `MockConfigEntry` 对象添加到 Home Assistant 中。
---
url: /developers/blog/2024-02-12-light-color-mode-mandatory.md
---
## 变更
Light entities 现在必须设置 `supported_color_modes` 和 `color_mode` 属性,如果没有这样做,将记录警告并提示用户报告 issue。
此外,如果 light 报告了无效的 `supported_color_modes` 组合,或报告了 `ColorMode.UNKNOWN` 以外的、未包含在 light 的 `supported_color_modes` 中的 `color_mode`,也将记录警告。
在 Home Assistant 2025.3 版本中,警告将被移除,仍未升级到 color mode 或违反 color mode 规则的 lights 将不再工作。
更多详情可在[文档](/developers/core/entity/light.md#color-modes)中找到。
---
url: /developers/blog/2024-02-25-Climate-toggle-service.md
---
自 Home Assistant Core 2024.3 起,我们在 `ClimateEntity` 中添加了一个新的 `toggle` 方法,用户现在可以在 service 调用中调用 `climate.toggle`。
支持 `turn_on` 和 `turn_off` 的集成隐式也支持 `toggle` 方法。
[在我们的文档中了解更多关于 toggle 方法的内容](/developers/core/entity/climate.md#toggle)
示例(默认实现):
```python
async def async_toggle(self) -> None:
"""Toggle the entity."""
if self.hvac_mode == HVACMode.OFF:
await self.async_turn_on()
else:
await self.async_turn_off()
```
---
url: /developers/blog/2024-02-26-single-instance-only-manifest-option.md
---
在 Home Assistant 2024.3 中,我们为 integration manifest 文件引入了一个新的 `single_config_entry` 选项。
此选项允许你设置你的集成仅支持一个 config entry。
Home Assistant 将处理并阻止在已经存在该集成的 config entry 时初始化 config flow。
这样你就不必在 config flow 中实现任何检查。
未在 manifest 中设置此选项并在 config flow 中进行检查的集成,应使用新选项替换该检查。
---
url: /developers/blog/2024-02-27-deprecate-bind-hass-and-hass-components.md
---
自 Home Assistant 2024.3 起,我们弃用 `@bind_hass` decorator 的使用,因此也弃用 `hass.components` 的使用。
使用 `hass.components` 会在日志中发出警告。
建议自定义集成的作者在 Home Assistant 2024.9 之前更新代码,以避免任何问题。
自 Home Assistant 2024.9 起,`@bind_hass` decorator 和 `hass.components` 将被移除,并将不再可用。
## 使用 `@bind_hass` decorator
使用 `@bind_hass` decorator 的集成应进行更新,移除该 decorator,并将 `hass` 对象作为第一个参数传递给函数:
### 新示例
```python
from homeassistant.core import HomeAssistant
from homeassistant.components.persistent_notification import async_create
def create_notification(hass: HomeAssistant, message: str):
"""Create a notification."""
async_create(
hass,
message,
title='Important notification'
)
async def async_setup(hass: HomeAssistant, config):
"""Set up the component."""
create_notification(hass, "You're already using the latest version!")
```
### 旧示例
```python
from homeassistant.core import HomeAssistant
from homeassistant.loader import bind_hass
from homeassistant.components.persistent_notification import async_create
@bind_hass
def create_notification(hass: HomeAssistant, message: str):
"""Create a notification."""
async_create(
hass,
message,
title='Important notification'
)
async def async_setup(hass: HomeAssistant, config):
"""Set up the component."""
create_notification("You're already using the latest version!")
```
## 使用 `hass.components`
使用 `hass.components` 的集成应进行更新,从 integration 包中直接 import 函数和类,并将 `hass` 对象作为第一个参数传递。
请记住在 `manifest.json` 的 `dependencies` 下包含所 import 的组件。
### 新示例
```python
from homeassistant.core import HomeAssistant
from homeassistant.components.persistent_notification import async_create
async def async_setup(hass: HomeAssistant, config):
"""Set up the component."""
async_create(
hass,
"You're already using the latest version!",
title='Important notification'
)
```
### 旧示例
```python
from homeassistant.core import HomeAssistant
async def async_setup(hass: HomeAssistant, config):
"""Set up the component."""
hass.components.persistent_notification.async_create(
"You're already using the latest version!",
title='Important notification'
)
```
---
url: /developers/blog/2024-02-28-device-name-translations.md
---
现在可以为 device name 提供翻译了,包括支持使用静态值作为占位符。
一个带有已翻译 device name 的 sensor 示例:
```python
class TestEntity(SensorEntity):
"""示例 entity。"""
_attr_has_entity_name = True
def __init__(self) -> None:
"""初始化示例 entity。"""
self._attr_device_info = DeviceInfo(
translation_key="n_ch_power_strip",
translation_placeholders={"number_of_sockets": "2"},
)
```
`strings.json` 文件如下所示:
```json
{
"device": {
"n_ch_power_strip": {
"name": "带有 {number_of_sockets} 个插座的电源排插"
}
}
}
```
生成的 device 将被称为 `Power strip with 2 sockets`。
当 device 缺少预期提供的翻译占位符时,会记录一条警告。
当这种情况发生在非稳定版本(dev、nightly 或 beta)的系统上时,会抛出错误,以便能够快速捕获错误。
请记得对您的翻译者保持友善,因为他们需要理解将从占位符名称中传入什么样的名称或值 ❤️。
---
url: /developers/blog/2024-03-05-platform_entity_translations_deprecation.md
---
集成在其 entity 处于其他集成下时为状态提供翻译的方法,例如翻译某个集成的 sensors,在 2022 年 11 月发生了变化,对旧方法的支持将在 Home Assistant Core 2024.5.0 中移除。
一旦 Home Assistant Core 2024.5.0 发布,集成将不能再使用自定义 device class 配合 `strings..json` 文件。取而代之的是,entity 必须设置 `translation_key` 属性,并将该 `translation_key` 包含在集成的 `strings.json` 中。
以下示例 `strings.json` 是针对一个 `translation_key` 属性设置为 `phase` 的 Moon domain `sensor` entity:
```json
{
"entity": {
"sensor": {
"phase": {
"state": {
"new_moon": "New moon",
"first_quarter": "First quarter",
"full_moon": "Full moon",
"last_quarter": "Last quarter"
}
}
}
}
}
```
更多详情请参阅 [`translation`](/developers/internationalization/core.md#state-of-entities) 和 [`entity`](/developers/core/entity.md#generic-properties) 文档。
---
url: /developers/blog/2024-03-09-import_executor_default.md
---
从 Home Assistant 2024.4 开始,所有集成都在 executor 中导入,以避免在代码被导入和执行期间阻塞 event loop。
在极少数情况下,如果集成在导入时创建了与 loop 绑定的对象,这可能会失败,因为在 executor 中没有正在运行的 loop。
自定义集成如果需要更多时间来调整代码以避免创建此类对象,可以选择退出,继续让代码在 event loop 中导入,但需了解系统稳定性将受到影响。
要退出在 executor 中导入,集成可以在其 [`manifest.json`](/developers/creating_integration_manifest.md) 中添加:
`"import_executor": false`
---
url: /developers/blog/2024-03-10-climate-aux-heater-deprecated.md
---
从 Home Assistant Core 2024.4 起,`ClimateEntity` 中的辅助加热器功能已被弃用。
当前实现了 `is_aux_heat` 属性以及 `turn_aux_heat_on`/`turn_aux_heat_off` 方法的集成需要移除这些方法和属性,并转而实现其他 entity(例如 `SwitchEntity`)来提供必要的功能;如果是只读属性,则可使用 `BinarySensorEntity`。
您可以在[这里](https://github.com/home-assistant/architecture/discussions/932)阅读有关此决定的更多信息。
---
url: /developers/blog/2024-03-13-deprecate_add_run_job.md
---
`async_run_job` 和 `async_add_job` 已被弃用,并将在 Home Assistant 2025.4 中移除。此弃用不适用于 sync API `add_job` 方法,该方法计划保留不移除。
转而使用其他 job 方法更高效,因为不需要去判断 job 的调用方式:
如果 callable 是从 config entry 运行的 coroutine 函数:
`entry.async_create_background_task`、`entry.async_create_task`
如果 callable 是从其他位置运行的 coroutine 函数:
`hass.async_create_background_task`、`hass.async_create_task`
如果 callable 应该在 executor 中运行:
`hass.async_add_executor_job`
---
url: /developers/blog/2024-03-15-exception-translations.md
---
### 记录支持翻译的 exception
翻译支持适用于 `HomeAssistantError` 及其子类,如 `ServiceValidationError`。当设置了 `translation_domain` 和 `translation_key`,并且错误消息已添加到 `strings.json` 中时,不再需要为本地 logging 添加错误消息字符串。Home Assistant 会自动从翻译缓存中获取英文错误消息。
在抛出带有翻译支持的 `HomeAssistantError` 或其子类时,我们应该从参数列表中移除 log message,以便从翻译缓存中获取它。
例如:
```python
async def async_select_index(hass: HomeAssistant, index: int) -> None:
"""Setup the config entry for my device."""
try:
check_index(index)
except ValueError as exc:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="invalid_index",
translation_placeholders={
"index": index,
"expected": expected,
},
) from exc
```
错误消息放置在 `strings.json` 中:
```json
{
...
"exceptions": {
"invalid_index": {
"message": "选择了无效的索引,期望值:{expected},实际值:{index}"
}
}
}
```
---
url: /developers/blog/2024-03-20-state_reported_timestamp.md
---
现在,当集成设置 entity 的 state 时,`State` 对象总是会被更新,并且总是会触发一个事件,无论 state 或 state attribute 是否发生了变化。这是通过添加一个新的时间戳 `State.last_reported` 和一个新的事件 `state_reported` 来实现的。
`state_reported` 事件的触发量很大,使用时必须小心,以避免对系统负载产生不利影响:
* 当触发 `state_reported` 时,不会调用订阅所有事件的 event listener。
* 不允许监听所有 entity 的 `state_reported` 事件。
* 当监听 `state_reported` 时,必须使用 `event_filter` 来过滤掉其他 entity 的事件。
* 当监听 `state_reported` 时,必须设置 `run_immediately` 标志。
### 背景
Home Assistant 以前会丢弃那些 state 和 state attribute 均未发生变化的 state 写入,除非集成设置了 `force_update` 标志。这种行为使得集成很难正确地分析数值型 sensor state 的时间序列。这也意味着用户不知道某个集成是否在更新 sensor。
新的时间戳和相关事件在 architecture discussion [#1062](https://github.com/home-assistant/architecture/discussions/1062) 中进行了讨论。
### 对自定义集成的影响
以下 API 存在 breaking changes:
* `hass.bus.async_fire` 的 `time_fired` 参数现在接受 `float` 时间戳,而不是 `datetime` 对象。
* 可以传递给 `hass.bus.async_listen` 的 event filter 签名已更改。
* `Event()` 的 `time_fired: datetime | None` 参数已被替换为 `time_fired_timestamp: float | None`。
* `State()` 的参数字段列表中间新增了一个参数 `last_reported: datetime`。
更多详情请参阅 [`core PR #113511`](https://github.com/home-assistant/core/pull/113511) 和 [`core PR #113798`](https://github.com/home-assistant/core/pull/113798)。
---
url: /developers/blog/2024-03-21-config-entry-reconfigure-step.md
---
从 Home Assistant Core 2024.4 起,config entry 现在可以通过在其 config flow 中添加 `reconfigure` 步骤来进行重新配置。
这并非为了替代可选配置(`OptionsFlow`),而是允许用户在创建 config entry 之后更改设置配置。
### Reconfiguration 与 Reauthentication
`reconfigure` 步骤并不会替代 `reauth` 步骤,它们有不同的目的。
Reauthentication 应该在登录/token/等无效的情况下由集成自动启动,以便用户有机会调整这些设置。
Reconfiguration 由用户从 config entry 的选项菜单启动,应实现用于更新集成正常工作所必需的、非可选的 config entry 数据。认证问题通过 re-authentication flow 处理。([查看 reauthentication](/developers/core/integration/config_flow.md#reauthentication))。
### 示例
示例包括:在搬家或拥有移动房屋时更改 `WeatherEntity` 的纬度和经度、更改本地设备的通信端口等。
要实现 `reconfigure` 步骤,请在您的 config flow 中将其包含如下:
```python
import voluptuous as vol
class ExampleConfigFlow(ConfigFlow, domain=DOMAIN):
"""Example 集成的 config flow。"""
async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None):
"""添加 reconfigure 步骤以允许重新配置 config entry。"""
if user_input is not None:
pass # TODO: 处理用户输入
return self.async_show_form(
step_id="reconfigure",
data_schema=vol.Schema({vol.Required("password"): str}),
)
```
---
url: /developers/blog/2024-03-30-deprecate-hass-helpers.md
---
从 Home Assistant 2024.5 起,我们弃用 `hass.helpers` 的使用。
使用 `hass.helpers` 将在日志中发出警告。
建议自定义集成的作者在 Home Assistant 2024.11 之前更新代码,以防止出现问题。
从 Home Assistant 2024.11 起,`hass.helpers` 将被移除,并且不再可用。
使用 `hass.helpers` 的集成应更新为直接从集成包中导入函数和类,并将 `hass` 对象作为第一个参数传入。
### 新示例
```python
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
async def async_setup(hass: HomeAssistant, config):
"""设置组件。"""
client = async_get_clientsession(hass)
```
### 旧示例
```python
from homeassistant.core import HomeAssistant
async def async_setup(hass: HomeAssistant, config):
"""设置组件。"""
client = hass.helpers.aiohttp_client.async_get_clientsession()
```
---
url: /developers/blog/2024-04-03-build-images-with-uv.md
---
在我们的生产镜像中用 `uv` 替换 `pip` 后,我们的构建流水线(因此发布新版本)快了很多。
`Uv` 是一个由 Rust 编写的极其快速的 Python 包安装器和解析器。它由 [Astral](https://astral.sh/) 开发,并且是开源的。可以在 [GitHub](https://github.com/astral-sh/uv) 上查看它。
在下表中,您可以看到每次构建可以节省大约 5 小时的执行时间。
| Arch | Pip | UV | Savings |
|-----------|--------------|----------|----------|
| aarch64 | 1h 24m 53s | 5m 18s | ~1h 20m |
| armhf | 1h 52m 20s | 6m 2s | ~1h 46m |
| armv7 | 1h 26m 43s | 5m 28s | ~1h 21m |
| amd64 | 22m 10s | 3m 20s | ~19m |
| i386 | 17m 37s | 3m 11s | ~14m |
平均而言,我们运行构建流水线 43 次,因为我们要创建:
* 31 个 nightly(每天一个 nightly)
* 7 个 beta 版本
* 5 个 stable 版本(包括 patch 版本)
总共,我们每月节省约 215 小时。
有了这一巨大的改进,我们现在可以更快速地发布热修复,因为发布新版本的流水线现在大约需要 20 分钟,而不是 2.5 小时。
每月节省的 215 个执行小时可供其他任务使用,使所有开发者和我们社区的 CI 体验更好。
通过将 `pip` 替换为 `uv`,我们通过使用更少的资源来构建镜像,提高了我们的可持续性。
**非常感谢 Astral 开发了这款出色的工具。**
请查看他们的[网站](https://astral.sh/)和提供的产品,例如他们还提供了一个"闪电"般快速的 Python linter/formatter。
---
url: /developers/blog/2024-04-07-deprecate_add_hass_job.md
---
作为提高性能并简化核心 job API 工作的一部分,`async_add_hass_job` 已被弃用,并将在 Home Assistant 2025.5 中移除。
调用应改用 `async_run_hass_job` 替代。
---
url: /developers/blog/2024-04-08-deprecated-backports-and-typing-aliases.md
---
在过去,我们从上游 CPython backport 了一些功能,以便提前使用并改善用户和开发者的体验。Home Assistant 仅支持 Python 3.12,因此这些功能可以直接从 Python 中使用。这些 backports 现在已被弃用,并将于未来移除。
| Deprecated | Replacement | Python version |
| ---------- | ----------- | -------------- |
| `homeassistant.backports.enum.StrEnum` | `enum.StrEnum` | >= 3.11 |
| `homeassistant.backports.functools.cached_property` | `functools.cached_property` | >= 3.8, >= 3.12 (performance improvement) |
此外,一些 typing alias 现在也被弃用了。
| Deprecated | Replacement |
| ---------- | ----------- |
| `homeassistant.helpers.typing.ContextType` | `homeassistant.core.Context` |
| `homeassistant.helpers.typing.EventType` | `homeassistant.core.Event` |
| `homeassistant.helpers.typing.HomeAssistantType` | `homeassistant.core.HomeAssistant` |
| `homeassistant.helpers.typing.ServiceCallType` | `homeassistant.core.ServiceCall` |
---
url: /developers/blog/2024-04-10-new-notify-entity-platform.md
---
### 新的 notify entity platform
notify platform 现在可以作为 [entity platform](https://developers.home-assistant.io/docs/core/entity/notify/) 使用。新的 `notify` platform 的 MVP [实现](https://github.com/home-assistant/core/pull/110950)了方法和 service `send_message`。它接受 `message` 作为必需属性。
与传统的 `notify.notify` service 不同,我们没有将 targets 作为参数,因为它是 entity,我们在调用 `send_message` 时可以针对多个 `notify` entity。
[architecture discussion](https://github.com/home-assistant/architecture/discussions/1041) 仍在进行中,讨论的主题包括要实现的 device class 以及通过[联系人注册表实现 recipient 支持](https://github.com/home-assistant/architecture/discussions/1041#discussioncomment-8947842)。
实现传统 `notify` service 的现有集成将分阶段迁移。第一步是迁移那些仅使用 `message` 作为参数的集成。
确定要迁移的集成如下:
* circuit
* clickatell
* clicksend
* command\_line
* demo
* ecobee
* flock
* free\_mobile
* knx
* mastodon
一旦我们有了 `title` 和/或 `recipient` 支持,就可以迁移更多集成来使用新的 platform。
当集成完成迁移后,用户将需要使用新的 `notify.send_message` service,因此在弃用期结束后,迁移变更将导致自动化失效。
---
url: /developers/blog/2024-04-13-deprecate_async_track_state_change.md
---
`async_track_state_change` 已被弃用,并将在 Home Assistant 2025.5 中移除。应改用 `async_track_state_change_event`。
`async_track_state_change` 总是为 `EVENT_STATE_CHANGED` 创建一个顶层 listener,这会拒绝所有与所需 entity 不匹配的 state changes。当有多个集成使用 `async_track_state_change` 时,这种设计会带来性能问题。自从引入 `async_track_state_change_event` 以来,`async_track_state_change` 在 `core` 中已被逐步淘汰,最后一个实例在 2024.5 中被移除。
使用 `async_track_state_change` 的示例:
```python
from homeassistant.core import State, callback
from homeassistant.helpers.event import async_track_state_change
@callback
def _async_on_change(entity_id: str, old_state: State | None, new_state: State | None) -> None:
...
unsub = async_track_state_change(hass, "sensor.one", _async_on_change)
unsub()
```
使用 `async_track_state_change_event` 替换的示例:
```python
from homeassistant.core import Event, EventStateChangedData, callback
from homeassistant.helpers.event import async_track_state_change_event
@callback
def _async_on_change(event: Event[EventStateChangedData]) -> None:
entity_id = event.data["entity_id"]
old_state = event.data["old_state"]
new_state = event.data["new_state"]
...
unsub = async_track_state_change_event(hass, "sensor.one", _async_on_change)
unsub()
```
---
url: /developers/blog/2024-04-25-always-reload-after-successful-reauth-flow.md
---
## 在成功的 reauthentication 之后始终 reload
为了在成功的 reauthentication flow 之后更新并 reload entry,可以使用 helper `async_update_reload_and_abort`。该 helper 的默认行为已经更改。默认情况下,调用该 helper 时 entry 将始终 reload。如果 entry 需要 reauthentication,并不总是需要更新 entry,例如当账户被临时禁用或 API-key 被临时禁止时。
对于在 entry 未更改的情况下不希望 reload 的场景,可以向 helper 传入 `reload_even_if_entry_is_unchanged=False` 参数。
更多有关此 helper 的信息可以在[这里](/developers/core/integration/config_flow.md#reauthentication)找到。
### 示例
```python
class OAuth2FlowHandler(
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
):
"""Config flow to handle OAuth2 authentication."""
reauth_entry: ConfigEntry | None = None
async def async_step_reauth(self, user_input=None):
"""在 API 认证错误时执行 reauth。"""
self.reauth_entry = self.hass.config_entries.async_get_entry(
self.context["entry_id"]
)
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(self, user_input=None):
"""通知用户需要 reauth 的对话框。"""
if user_input is None:
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema({}),
)
return await self.async_step_user()
async def async_oauth_create_entry(self, data: dict) -> dict:
"""为 reauth 创建 oauth config entry 或更新现有 entry。"""
if self.reauth_entry:
# 仅当 entry 被更新时才 reload
return self.async_update_reload_and_abort(
self.reauth_entry,
data=data,
reload_even_if_entry_is_unchanged=False,
)
return await super().async_oauth_create_entry(data)
```
---
url: /developers/blog/2024-04-30-store-runtime-data-inside-config-entry.md
---
集成通常需要设置和跟踪自定义数据,例如 coordinators、API 连接或代码对象。以前,这些都存储在 `hass.data` 中,这使得跟踪它们变得困难。
随着最近的更改,现在可以使用 `entry.runtime_data` 来存储这些数据。在设置 platform 时,config entry 已经可用,并且会自动清理。卸载后无需再从 `hass.data` 中删除 key。
它还更好地支持了 type-checking。`ConfigEntry` 现在是泛型的,因此可以传递数据类型。为此,请使用 `dataclass` 等类型化的数据结构。为简化注释,建议为其定义一个 type alias。
示例可能如下所示:
```py
# /__init__.py
# type alias 需要以 'ConfigEntry' 作为后缀
type MyConfigEntry = ConfigEntry[MyData]
@dataclass
class MyData:
client: MyClient
other_data: dict[str, Any]
async def async_setup_entry(
hass: HomeAssistant,
entry: MyConfigEntry, # 使用 type alias 替代 ConfigEntry
) -> bool:
client = MyClient(...)
# 赋值 runtime_data
entry.runtime_data = MyData(client, {...})
```
```py
# /switch.py
from . import MyConfigEntry
async def async_setup_entry(
hass: HomeAssistant,
entry: MyConfigEntry, # 使用 type alias 替代 ConfigEntry
async_add_entities: AddEntitiesCallback,
) -> None:
# 从 config entry 访问 runtime data
data = entry.runtime_data
async_add_entities([MySwitch(data.client)])
```
---
url: /developers/blog/2024-05-01-improved-hass-data-typing.md
---
在过去,`hass.data` 面临的挑战之一是正确分配类型信息。由于它的类型为 `dict[str, Any]`,唯一的选择是 annotation assignments 或类似 `cast` 的方式:
```py
data: MyData = hass.data[SOME_KEY]
```
这有几个缺点。不仅需要为每个赋值添加注释,而且 type checker 基本上还会假定该注释总是正确的。特别是在重构过程中,很容易遗漏某个实例,虽然 type-checking 仍然通过,但实际代码却已经损坏。
为了解决这个问题,现在可以使用两个新的 key 类型 `HassKey` 和 `HassEntryKey`。借助一点"魔法",type checker 现在能够推断类型并确保其正确性。甚至在存储数据时也是如此。
示例可能如下所示:
```py
# /__init__.py
from homeassistant.util.hass_dict import HassKey
MY_KEY: HassKey["MyData"] = HassKey(DOMAIN)
@dataclass
class MyData:
client: MyClient
other_data: dict[str, Any]
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
client = MyClient(...)
hass.data[MY_KEY] = MyData(client, {...})
hass.data[MY_KEY] = 1 # mypy error
```
```py
# /switch.py
from . import MY_KEY
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
async_add_entities: AddEntitiesCallback,
discovery_info: DiscoveryInfoType | None = None,
) -> None:
data = hass.data[MY_KEY]
reveal_type(data) # MyData
async_add_entities([MySwitch(data.client)])
```
按 `entry.entry_id` 将数据存储在 dict 中?通常直接将数据存储到 `ConfigEntry` 中会更好。请参见关于此的近期[博客文章](/developers/blog/2024-04-30-store-runtime-data-inside-config-entry.md)。如果这不是一个选项,请使用 `HassEntryKey`。
```py
# /__init__.py
from homeassistant.util.hass_dict import HassEntryKey
MY_KEY: HassEntryKey["MyData"] = HassEntryKey(DOMAIN)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
) -> bool:
client = MyClient(...)
hass.data.setdefault(MY_KEY, {})[entry.entry_id] = MyData(client, {...})
```
---
url: /developers/blog/2024-05-03-notify-title.md
---
### send\_message service notify entity platform 的 Title 选项
最近我们添加了 notify [entity platform](https://developers.home-assistant.io/docs/core/entity/notify/)。新的 `notify` platform 方法实现了 service `send_message`。该 service 现在还将可选的 `title` 作为参数接受。这使得一些新的集成现在可以迁移到使用新的 entity platform:
* cisco\_webex\_teams
* file
* sendgrid
* syslog
* tibber
[architecture discussion](https://github.com/home-assistant/architecture/discussions/1041) 仍在进行中。
当集成完成迁移后,用户将需要使用新的 `notify.send_message` service,因此在弃用期结束后,迁移变更将导致自动化失效。
---
url: /developers/blog/2024-05-09-improving-ci-test-execution.md
---
在 [UV 每月为我们找回 215 个计算小时](/developers/blog/2024-04-03-build-images-with-uv.md) 之后,我们对速度有了更高的追求。
我们的 CI workflow 在每次 commit 时触发,由于贡献量巨大,它在 2024 年 3 月被触发了 6647 次。
完整的运行(即执行整个测试套件)耗时很长。
结果发现,我们用来将测试分成 10 个组的插件效率很低。每个 pytest job 都需要发现所有测试,即使该 job 打算只执行其中一部分。
现在我们有一个单独的 job 来发现所有测试并将它们分成 10 个组。10 个 pytest job 只需要执行所有测试的一个子集。不在每个 test runner 中进行完整的发现,每次完整运行可节省我们 3 小时!
```mermaid
flowchart LR
A[Full run] --> R{Distribute work}
R -->|One| D[Discover tests to run]
R -->|Two| E[Discover tests to run]
R -->|Three| F[Discover tests to run]
D --> D2[Run tests]
E --> E2[Run tests]
F --> F2[Run tests]
AN[Full run] --> RW[Discover tests to run]
RW --> RN{Distribute work}
RN -->|One| D2N[Run tests]
RN -->|Two| E2N[Run tests]
RN -->|Three| F2N[Run tests]
```
对 2024 年 3 月的 6647 个 CI workflow 的简要分析揭示了以下统计:
* 2406 个在终止前被取消
* 1771 个应为 full runs
* 1085 个失败
* 732 个为失败的 full runs
* 3007 个成功终止
* 1629 个 partial runs(仅执行了给定集成的测试)
* 1378 个 full runs
考虑到 1378 个成功的 full runs,如果有了 [#114381](https://github.com/home-assistant/core/pull/114381),我们在 2024 年 3 月将节省约 **4042 小时 ~ 168 天** 的执行时间。如果我还分析了失败/取消的 runs,节省的时间会更多。
每月节省的超过 168 天的执行时间可供其他任务使用,使所有开发者和我们社区的 CI 体验更好。
我们通过使用更少的资源来运行测试套件,提高了可持续性。
**非常感谢 GitHub 为 Home Assistant 提供额外的 CI runner。**
---
url: /developers/blog/2024-05-10-group-integration.md
---
## 使用 group integration 设置 entity platform 的变更
默认情况下,`group` 集成允许将 entity 分组。如果 entity 的默认 `ON`/`OFF` state 分别为 `on` 和 `off`,则默认支持 `grouping`。但是,对于可以分组但具有替代状态的 Entity platform(例如 `cover`(`open`/`closed`)或 `person`(`home`/`not_home`)),或者意图排除的 platform(如 `sensor`),设置方式有所不同。这些 entity platform 必须在 `group.py` 模块中实现 `async_describe_on_off_states`。
在 `async_describe_on_off_states` 中,`domain` 需要是传递给 `registry` 方法 `on_off_states` 和 `exclude_domain` 的第一个参数。在使用 `registry.on_off_state` 注册替代 `ON`/`OFF` state 时,除了 `ON` states 之外,还需要传递默认的 `ON` state。
### 注册替代状态的示例
`registry.on_off_states` 的新签名:
```python
@callback
def on_off_states(
self, domain: str, on_states: set[str], default_on_state:str, off_state: str
) -> None:
"""为当前 domain 注册 on 和 off states。"""
...
```
`vacuum` entity platform 的 `group.py` 示例,注册替代的 `ON`/`OFF` state。请注意,第一个 `ON` state 现在被视为默认的 `ON` state。
```python
"""描述 group states。"""
from typing import TYPE_CHECKING
from homeassistant.const import STATE_OFF, STATE_ON
from homeassistant.core import HomeAssistant, callback
if TYPE_CHECKING:
from homeassistant.components.group import GroupIntegrationRegistry
from .const import DOMAIN, STATE_CLEANING, STATE_ERROR, STATE_RETURNING
@callback
def async_describe_on_off_states(
hass: HomeAssistant, registry: "GroupIntegrationRegistry"
) -> None:
"""描述 group on off states。"""
registry.on_off_states(
DOMAIN, # domain
# 包含所有 group ON states 的 set
{
STATE_ON,
STATE_CLEANING,
STATE_RETURNING,
STATE_ERROR,
},
STATE_ON, # 默认 group ON state
STATE_OFF, # Group OFF state
)
```
### 从 group entity 中排除 entity platform 的示例
`registry.exclude_domain` 的新签名:
```python
@callback
def exclude_domain(self, domain: str) -> None:
"""排除当前 domain。"""
...
```
`sensor` entity platform 的 `group.py` 示例,从 `group` entity 中排除 sensor entity。
```python
"""描述 group states。"""
from typing import TYPE_CHECKING
from homeassistant.core import HomeAssistant, callback
if TYPE_CHECKING:
from homeassistant.components.group import GroupIntegrationRegistry
from .const import DOMAIN
@callback
def async_describe_on_off_states(
hass: HomeAssistant, registry: "GroupIntegrationRegistry"
) -> None:
"""描述 group on off states。"""
registry.exclude_domain(DOMAIN)
```
---
url: /developers/blog/2024-05-10-lock-supports-open-state.md
---
最近我们为 `LockEntity` 添加了 `open` 和 `opening` state。
如果您拥有能够区分 `unlocked`(未锁定但已闩锁)state 和 `open`(未锁定且闩锁已拉开)state 的锁,这将非常有用。
`LockEntity` 通过实现 feature flag `LockEntityFeature.OPEN` 已经支持 [`open` 方法](/developers/core/entity/lock.md#open)。
示例(默认实现):
```python
class MyLock(LockEntity):
@property
def is_opening(self) -> bool:
"""如果锁已打开则返回 true。"""
return self._state == STATE_OPENING
@property
def is_open(self) -> bool:
"""如果锁已打开则返回 true。"""
return self._state == STATE_OPEN
async def async_open(self, **kwargs: Any) -> None:
"""打开门的闩锁。"""
self._state = STATE_OPEN
self.async_write_ha_state()
```
---
url: /developers/blog/2024-05-19-fix_zoneinfo_blocking_io.md
---
如果传递的 timezone 不在缓存中,构造 `ZoneInfo` 对象可能会进行阻塞 I/O 以从磁盘加载 zone info。
现在可以使用 `dt_util.async_get_time_zone` 来替换 `dt_util.get_time_zone`,以便在 event loop 中异步安全地获取 timezone,它不会在 event loop 中执行阻塞 I/O。
`hass.config.set_time_zone` 已被弃用,并由 `hass.config.async_set_time_zone` 替代。`hass.config.set_time_zone` 将在 2025.6 中移除。设置 timezone 仅影响测试,因为任何集成都不应在生产环境中调用此函数。
直接检查 `dt_util.DEFAULT_TIME_ZONE` 已被弃用,应改用 `dt_util.get_default_time_zone()`。
如果您的集成需要在 event loop 中构造 `ZoneInfo` 对象,建议使用 [`aiozoneinfo` 库](https://pypi.org/project/aiozoneinfo/)。
---
url: /developers/blog/2024-05-20-llm-api.md
---
自从作为"语音之年"(Year of the Voice)的一部分在 Home Assistant 中引入 LLMs 以来,我们收到了允许 LLMs 与 Home Assistant 交互的请求。现在,通过将 Home Assistant API 暴露给 LLMs,这已成为可能。
Home Assistant 将附带一个内置的 Assist API,该 API 遵循内置的 conversation agent 也可访问的能力和已暴露的 entity。
与 LLMs 交互的集成应更新其集成以支持 LLM API。
自定义集成的作者可以创建自己的 LLM API,为 LLMs 提供更高级的 Home Assistant 访问权限。
更多信息请参阅 [LLM API 文档](/developers/core/llm.md),以及[此示例 pull request](https://github.com/home-assistant/core/pull/117644)了解如何在您的集成中集成 LLM API。
---
url: /developers/blog/2024-05-22-alarm_control_panel_validation.md
---
`AlarmControlPanelEntity` 现在强制要求对 alarm control panel entity 的 code 进行验证,这些 entity 将 `code_arm_required` 设置为 `True`(默认行为)。当需要 code 但未提供 code 时,service calls 将失败。
以前这完全是可选的,用户无论集成是否需要,都可以跳过 code 输入(因此每个集成需要实现自己的检查)。
由于默认行为是需要 code,因此不需要 code 输入的自定义集成需要将 `code_arm_required` 设置为 `False`,否则用户将始终需要输入 code,无论 service calls 是否需要它。
---
url: /developers/blog/2024-06-12-async_forward_entry_setups.md
---
调用 `hass.config_entries.async_forward_entry_setup` 已被弃用,并将在 Home Assistant 2025.6 中移除。相反,请 await `hass.config_entries.async_forward_entry_setups`,因为它可以一次性加载多个 platform,并且效率更高,因为它不需要为每个 platform 单独使用一个 import executor job。
如果在 config entry 正在设置时调用 `hass.config_entries.async_forward_entry_setups`,必须始终 await 它,以确保它在 config entry 设置完成之前结束。更多详情请参阅[此博客文章](https://developers.home-assistant.io/blog/2022/07/08/config_entry_forwards)。
---
url: /developers/blog/2024-06-18-async_register_static_paths.md
---
`hass.http.register_static_path` 已被弃用,因为它在 event loop 中执行阻塞 I/O,请改用 `await hass.http.async_register_static_paths([StaticPathConfig(url_path, path, cache_headers)])`。
`async_register_static_paths` 的参数与 `register_static_path` 相同,只是它们被包装在 `StaticPathConfig` `dataclass` 中,并接受它们的 `Iterable` 以便一次性注册多个 path,从而避免多个 executor job。
例如,如果您的集成调用了 `hass.http.register_static_path("/integrations/photos", "/config/photos", True)`,现在应改为调用 `await hass.http.async_register_static_paths([StaticPathConfig("/integrations/photos", "/config/photos", True)])`。
`StaticPathConfig` `dataclass` 应从 `homeassistant.components.http` 导入。
`hass.http.register_static_path` 将在 2025.7 中移除。
## 示例
```python
from pathlib import Path
from homeassistant.components.http import StaticPathConfig
should_cache = False
files_path = Path(__file__).parent / "static"
files2_path = Path(__file__).parent / "static2"
await hass.http.async_register_static_paths([
StaticPathConfig("/api/my_integration/static", str(files_path), should_cache),
StaticPathConfig("/api/my_integration/static2", str(files2_path), should_cache)
])
```
---
url: /developers/blog/2024-06-22-excluding-state-attributes-from-recording-match-all.md
---
从 recording 中排除 state attribute 的方式在 2023 年 9 月之前已经更改过。
之前的实现存在局限性,因为没有方法可以处理动态 attribute,也没有简单的方法来简单地排除所有 attribute,而不是逐一列出它们。
现在可以在集成中使用 `MATCH_ALL` 常量来告诉 recording 不要记录任何 attribute,这将自动从 recording 中移除所有 attribute,但 `device_class`、`state_class`、`unit_of_measurement` 和 `friendly_name` 除外。
```python
from homeassistant.const import MATCH_ALL
class ExampleEntity(Entity):
"""一个 entity 的实现。"""
_unrecorded_attributes = frozenset({MATCH_ALL})
```
更多详情请参阅 [entity 文档](/developers/core/entity.md#excluding-state-attributes-from-recorder-history)。
原始更改的背景在 [architecture discussion #964](https://github.com/home-assistant/architecture/discussions/964) 中。
---
url: /developers/blog/2024-07-07-climate-hvacaction-defrosting.md
---
`ClimateEntity` 有一个 `hvac_action` 属性,它描述了 climate entity 当前正在做什么(这与它的 mode 不同)。
我们添加了 `DEFROSTING` 作为 `HVACAction` 的一种可能值,用于表示 entity 当前正在除霜。
除霜是指系统反向运行一段时间以融化积聚的冰块。它通常发生在较冷的环境中,不应与例如通过加热车窗来除霜的汽车混淆。
```python
from homeassistant.components.climate.const import HVACAction
class MyClimateEntity(ClimateEntity):
"""我的 climate entity 的实现。"""
def hvac_action(self) -> HVACAction | None:
"""如果支持,返回当前正在运行的 hvac 操作。"""
return HVACAction.DEFROSTING
```
更多详情请参阅 [climate entity 文档](/developers/core/entity/climate.md#hvac-action)。
原始更改的背景在 [architecture discussion #1090](https://github.com/home-assistant/architecture/discussions/1090) 中。
---
url: /developers/blog/2024-07-16-device-info-model-id.md
---
从 2024.8 开始,您现在可以在 `DeviceInfo` 类中添加一个 model 标识符。该标识符可用于在集成和 frontend 中识别 device model。
例如,Philips Hue ambiance spot 以前被列为"Hue ambiance spot (LTG002)"。现在可以将其拆分,其中 `model` 为"Hue ambiance spot",`model_id` 为"LTG002"。
---
url: /developers/blog/2024-07-16-service-actions.md
---
"Services" 一词让许多用户感到困惑,因为它并不能立即明确指代什么;它可能是 web 或 STT service,但在 Home Assistant 的上下文中,它意味着完全不同的东西(service calls)。
在 Home Assistant 2024.8 中,"Services"(即 service calls)将被重命名为"Actions"。此更改是我们持续努力使 Home Assistant 更用户友好、更容易让新用户理解的一部分。该术语与我们在过去几个月一直实施的 UI 更改相符(在我们的 automations 和 script 编辑器中不再存在 call services)。实际上,用户在自己的 automations 中执行的是 actions。
此更改将反映在 Home Assistant UI、文档以及使用"Services"一词的其他地方。例如,Developer Tools 中的 **"Services"** 选项卡将重命名为 **"Actions"**。
对于开发者而言,无需担心此变更。在开发者文档中,我们将把所有对"services"的引用更新为"service actions",因为我们在后端有不同种类的 actions(例如 device actions)。底层功能将保持不变,过渡是无缝的。
这只是一个术语变更,就像面向终端用户的变更一样。
---
url: /developers/blog/2024-07-19-fan-fanentityfeatures-turn-on_off.md
---
从 Home Assistant Core 2024.8 起,我们为 `FanEntityFeature` 添加了两个新 flag:`TURN_ON`、`TURN_OFF`。
实现了 `turn_on` service call 的集成需要设置 `TURN_ON` feature flag。
实现了 `turn_off` service call 的集成需要设置 `TURN_OFF` feature flag。
将有 6 个月的弃用期(至 2025.2),在此期间 `FanEntity` 将代表实现了相应方法的集成设置这些 flag。从 2025.2 起,如果 entity feature 未相应设置,集成将无法使用相应的方法。
在不设置相应 feature flag 的情况下实现这些方法,将创建一条警告日志,引导用户为集成创建 issue。
集成应在 `FanEntity` 子类实例中将属性 `_enable_turn_on_off_backwards_compatibility` 设置为 `False`,一旦迁移到使用或不使用新的 feature flag 之后。
这将停止在弃用期间自动设置新的 feature flag,并应在弃用结束后移除。
---
url: /developers/blog/2024-07-24-climate-min-max-temperature-check.md
---
从 Home Assistant Core 2024.8 起,我们为 `ClimateEntity` 提供的温度 action call 实现了验证。
集成不再需要在其自己的设置温度方法(`async_set_temperature`/`set_temperature`)中进行此检查。
然而,集成正确指定 `min_temp` 和 `max_temp` 属性非常重要,否则如果验证失败,用户可能无法设置正确的温度。
同样,处理同时可在 `Celsius` 和 `Fahrenheit` 下运行的设备的集成需要相应地转换其 `min_temp` 和 `max_temp` 值。
### 示例
将设备的原生 min/max 值转换为集成指定的 temperature\_unit。
```python
class MyClimateEntity(ClimateEntity):
"""我的 climate entity 的实现。"""
@property
def min_temp(self) -> float:
"""返回最低温度。"""
return TemperatureConverter.convert(
self.device.min_temp, UnitOfTemperature.CELSIUS, self.temperature_unit
)
@property
def max_temp(self) -> float:
"""返回最高温度。"""
return TemperatureConverter.convert(
self.device.max_temp, UnitOfTemperature.CELSIUS, self.temperature_unit
)
```
---
url: /developers/blog/2024-08-05-coordinator_async_setup.md
---
在 Home Assistant 2024.8 中,我们为 data update coordinator 引入了 `_async_setup` 方法。
此方法允许您运行异步代码来准备您的 `DataUpdateCoordinator` 实例,
或加载只需加载一次的数据。
您可以在 coordinator 中重写 `_async_setup`,它将在 `coordinator.async_config_entry_first_refresh()` 期间自动被调用。
它提供与 `_async_update_data` 相同的错误处理,并将相应地处理 `ConfigEntryError`
和 `ConfigEntryAuthFailed`。
## 示例
```python
class MyUpdateCoordinator(DataUpdateCoordinator[MyDataType]):
prereq_data: SomeData
def __init__(
self,
hass: HomeAssistant,
) -> None:
"""初始化 coordinator。"""
super().__init__(hass, _LOGGER, name=DOMAIN, update_interval=SCAN_INTERVAL)
self.my_api = MyApi()
async def _async_setup(self) -> None:
"""执行初始化逻辑。"""
self.prereq_data = await self.my_api.get_initial_data()
async def _async_update_data(self) -> MyDataType:
"""执行通常的更新"""
return await self.my_api.update(self.prereq_data)
```
## 避免检查初始化状态
此更改允许您重构那些在 `_async_update_data` 方法中通过检查初始化变量来加载初始数据的代码,例如
```python
async def _async_update_data(self) -> ...:
if not self.something:
self.something = self.client.fetch()
return self.client.fetch_data()
```
重构为
```python
async def _async_setup(self) -> None:
self.something = self.client.fetch()
async def _async_update_data(self) -> ...:
return self.client.fetch_data()
```
## 更多信息
更多信息请阅读[文档](https://developers.home-assistant.io/docs/integration_fetching_data/)。
---
url: /developers/blog/2024-08-23-lawn-mower-activity-returning.md
---
从 Home Assistant Core 2024.9 起,实现了 `LawnMowerEntity` 的集成可以使用新的 `LawnMowerActivity.RETURNING` state 来指示其割草机正在返回 dock。
更多详情请参阅 [文档](/developers/core/entity/lawn-mower.md#activities)。
## 示例
```python
async def async_dock(self) -> None:
"""开始 dock。"""
self._attr_activity = LawnMowerActivity.RETURNING
self.async_write_ha_state()
```
或者在您的 `lawn_mower` entity 的 activity 属性中返回 `LawnMowerActivity.RETURNING`。
```python
@property
def activity(self) -> LawnMowerActivity:
"""返回割草机的 state。"""
mower = self.mower
if mower.state is RETURNING:
return LawnMowerActivity.RETURNING
...
```
---
url: /developers/blog/2024-08-27-changed-icon-translations-schema.md
---
icon translations schema 已经调整,允许为[service 中的 sections](/developers/dev_101_services.md#grouping-of-service-action-fields)分配 icon。
service 的 icon 现在应按照更明确的 schema 提供,该 schema 允许为 sections 指定 icon。
这允许像这样指定 service icon:
```json
"services": {
"test_service_1": {
"service": "mdi:flask",
"sections": {
"section_1": "mdi:test-tube"
}
}
}
```
旧格式在为期一年的弃用期间得到支持,以便自定义集成有时间迁移到新 schema:
```json
"services": {
"test_service_1": "mdi:flask"
}
```
请参阅 [core PR #124656](https://github.com/home-assistant/core/pull/124656) 以获取实现细节。
### 对自定义卡片的影响
发送到 frontend 的 icon 数据始终遵循新格式,显示 service icon 的自定义卡片需要进行调整。
---
url: /developers/blog/2024-08-27-entity-service-schema-validation.md
---
注册 entity service action 允许集成作者传入:
* 无 schema(`None`)
* 一个字典,它将通过传递给 `cv.make_entity_service_schema` 转换为 voluptuous schema
* 一个自定义的 voluptuous schema
对于第三种情况,当使用不满足以下至少一个条件的自定义 schema 注册 entity action 时,现在将记录一条警告:
* 由 `cv.make_entity_service_schema` 返回的 validator
* 由 `cv.make_entity_service_schema` 返回的 validator,包裹在 `vol.Schema` 中
* 由 `cv.make_entity_service_schema` 返回的 validator,包裹在 `vol.All` 中
在 Home Assistant Core 2025.10 中,将不再能够注册不满足此要求的自定义 schema 的 entity action。
更改的原因是,如果未使用 `cv.make_entity_service_schema`,service 将不会自动支持所有可能的 entity 定向方式。
更多详情请参阅 [开发者文档](/developers/dev_101_services.md#entity-service-actions) 以及 core PR [#124102](https://github.com/home-assistant/core/pull/124102) 和 [#125057](https://github.com/home-assistant/core/pull/125057)。
---
url: /developers/blog/2024-09-02-template-hass-required.md
---
随着 [core PR #89242](https://github.com/home-assistant/core/pull/89242) 的合并(该 PR 已合入 Home Assistant Core 2023.4),在 schema 验证期间创建的 `Template` 对象上将会设置 `Template.hass`。
在此更改之前,在渲染模板之前必须检查并设置 `Template.hass`,这在代码库的许多地方都有实现。
这类代码已从 Home Assistant Core 中移除,这将影响到自定义集成的作者:
* 手动创建 `Template` 对象的自定义集成必须向构造函数传入一个有效的 `hass` 对象。在 config entries 创建模板时尤其如此。如果不传入 `hass` 对象,将触发 deprecation warning,并在 Home Assistant Core 2025.10 中失效。
* 辅助函数 `template.attach` 已不再有任何作用,core 也不再使用它。它已被标记为 deprecated,并计划在 Home Assistant Core 2025.10 中移除。
---
url: /developers/blog/2024-09-10-calories-units.md
---
从 Home Assistant 2024.10 开始,calories 已被添加到 energy [sensor class](/developers/core/entity/sensor.md#available-device-classes) 的 measurement units 中。通常,这些传感器用于测量热能、水加热、食物能量或人体能量消耗。
---
url: /developers/blog/2024-09-11-extending-deprecation-hass-components.md
---
在 2024 年 2 月 27 日,我们[宣布弃用](/developers/blog/2024-02-27-deprecate-bind-hass-and-hass-components.md)了 `@bind_hass` decorator 和 `hass.components` 属性,计划于 Home Assistant 2024.9 版本生效。
由于仍有大量自定义集成在使用它们,并且近期又发布了 HACS v2 更新,我们决定将 deprecation 期限再延长六个月。
这意味着从 Home Assistant 2025.3 开始,`@bind_hass` decorator 和 `hass.components` 将被移除。
我们鼓励所有自定义集成的开发者更新他们的代码,以避免在 Home Assistant 2025.3 发布之前出现任何问题。
---
url: /developers/blog/2024-09-23-constants-media-player-deprecation.md
---
自 Home Assistant Core 2022.5 起,`MediaPlayerEntity` 中使用的 feature flag 常量已被 deprecated,并由 `MediaPlayerEntityFeature` enum 取代。随后在 2022.10 中,repeat mode、media type 和 media class 常量也被 deprecated,并分别由 `RepeatMode`、`MediaType` 和 `MediaClass` 取代。
然而,当时并没有执行正确的 deprecation 流程,因此现在在 2024.10 中我们正式开始为期一年的 deprecation 期,这些常量将从 2025.10 起停止工作,以确保所有自定义集成作者都有时间进行调整。
---
url: /developers/blog/2024-09-23-feature-flag-constants-vacuum-deprecation.md
---
自 Home Assistant Core 2022.5 起,`StateVacuumEntity` 中使用的 feature flag 常量已被 deprecated,并由 `VacuumEntityFeature` enum 取代。
然而,当时并没有执行正确的 deprecation 流程,因此现在在 2024.10 中我们正式开始为期一年的 deprecation 期。这些常量将从 2025.10 起停止工作,以确保所有自定义集成作者都有时间进行调整。
---
url: /developers/blog/2024-09-23-state-constants-camera-deprecation.md
---
自 Home Assistant Core 2024.10 起,`Camera` 中用于返回 state 的常量已被 deprecated,并由 `CameraState` enum 取代。
设有为期一年的 deprecation 期,这些常量将从 2025.10 起停止工作,以确保所有自定义集成作者都有时间进行调整。
由于 `state` 属性本来就不应被覆盖,在大多数情况下,这一变更仅会影响其他 Entity 属性或测试。
---
url: /developers/blog/2024-09-23-state-constants-lock-deprecation.md
---
自 Home Assistant Core 2024.10 起,`LockEntity` 中用于返回 state 的常量已被 deprecated,并由 `LockState` enum 取代。
设有为期一年的 deprecation 期,这些常量将从 2025.10 起停止工作,以确保所有自定义集成作者都有时间进行调整。
由于 `state` 属性本来就不应被覆盖,在大多数情况下,这一变更仅会影响其他 Entity 属性或测试,而不是 `state` 属性本身。
---
url: /developers/blog/2024-09-24-climate-set-temp-validation.md
---
自 Home Assistant Core 2024.10 起,我们增加了对 `set_temperature` 方法参数传递的进一步校验。这意味着集成无需在自己的方法中实现这些校验。
在 2024.10 之前,当 entity 没有设置 `ClimateEntityFeature.TARGET_TEMPERATURE` 时,可以在 action 中设置 `temperature` 参数;当 entity 没有设置 `ClimateEntityFeature.TARGET_TEMPERATURE_RANGE` 时,也可以设置 `target_temp_low`/`target_temp_high` 参数。现在这将不再可行,系统会抛出 `ServiceValidationError`,告知用户在 action 调用中使用了错误的参数。
此外,在设置温度范围时,之前可以将 `target_temp_low` 设置为高于 `target_temp_high` 的值。
现在这将抛出 `ServiceValidationError`,告知用户高值必须高于低值。
---
url: /developers/blog/2024-09-24-device-tracker-entity-description.md
---
device\_tracker 实体的 `source_type` 属性现在始终要求是 `SourceType` enum 值。标准字符串不再有效。
`TrackerEntity` 和 `ScannerEntity` 现在拥有专用的 `ScannerEntityDescription` 和 `TrackerEntityDescription`,当需要将 `EntityDescription` 关联到 `Entity` 时必须使用它们作为基类。
这些实体的 `source_type` 现在也默认为 `SourceType.GPS` 和 `SourceType.ROUTER`,因此可能可以移除覆盖该属性的代码。
同时新增了以下 shorthand 属性:
* `BaseTrackerEntity._attr_source_type`
* `TrackerEntity._attr_latitude`
* `TrackerEntity._attr_location_accuracy`
* `TrackerEntity._attr_location_name`
* `TrackerEntity._attr_longitude`
* `TrackerEntity._attr_source_type`(默认为 `SourceType.GPS`)
* `ScannerEntity._attr_hostname`
* `ScannerEntity._attr_ip_address`
* `ScannerEntity._attr_mac_address`
* `ScannerEntity._attr_source_type`(默认为 `SourceType.ROUTER`)
更多详情请参阅 [device-tracker 文档](/developers/core/entity/device-tracker.md)。
---
url: /developers/blog/2024-09-28-update-version-compare.md
---
随着 [core PR #124797](https://github.com/home-assistant/core/pull/124797) 的合并(该 PR 将合入 Home Assistant Core 2024.10),update 平台新增了一个方法:`version_is_newer()`。
在此更改之前,已安装固件版本、新可用版本与 beta 版本之间的比较逻辑是硬编码的:
```python
def version_is_newer(self, latest_version: str, installed_version: str) -> bool:
"""Return True if latest_version is newer than installed_version."""
return AwesomeVersion(latest_version) > installed_version
```
现在,新方法允许开发者自定义该比较逻辑,编写他们自己的方法即可。
以下是一个示例(为 Shelly gen1 设备实现):
```python
def version_is_newer(self, latest_version: str, installed_version: str) -> bool:
"""Return True if available version is newer then installed version."""
return AwesomeVersion(
latest_version,
find_first_match=True,
ensure_strategy=[AwesomeVersionStrategy.SEMVER],
) > AwesomeVersion(
installed_version,
find_first_match=True,
ensure_strategy=[AwesomeVersionStrategy.SEMVER],
)
```
---
url: /developers/blog/2024-10-01-assist-satellite-entity.md
---
用户通常通过远程语音 satellite 与 [Assist](https://www.home-assistant.io/voice_control/) 进行交互,例如运行 ESPHome 的 [ESP32-S3-BOX-3](https://www.home-assistant.io/voice_control/s3_box_voice_assistant/)、运行 VoIP 的[模拟电话](https://www.home-assistant.io/voice_control/worlds-most-private-voice-assistant/)等。管理这些 satellite 的集成使用了临时的 `binary_sensor` 和 `select` 实体,以允许用户配置 satellite 的[pipeline](/developers/voice/pipelines.md)、基于 pipeline state 进行自动化等。
新的 [`AssistSatelliteEntity`](/developers/core/entity/assist-satellite.md) 提供了一个表示语音 satellite 的 entity。它的 state 会跟随底层的 Assist pipeline,从而便于自动化。此外:
* 新增了一个 [announce action](https://home-assistant.io/integrations/assist_satellite#action-assist_satelliteannounce),可用于在支持的设备上进行广播。
* 还新增了几个 [websocket 命令](/developers/core/entity/assist-satellite.md#websocket-api),提供了一种统一的方式来获取和设置设备上的活动唤醒词。
`esphome` 和 `voip` 集成已经迁移到使用 `AssistSatelliteEntity`,`wyoming` 集成将紧随其后。
---
url: /developers/blog/2024-10-08-state-constants-cover-deprecation.md
---
自 Home Assistant Core 2024.11 起,`CoverEntity` 中用于返回 state 的常量已被 deprecated,并由 `CoverState` enum 取代。
设有为期一年的 deprecation 期,这些常量将从 2025.11 起停止工作,以确保所有自定义集成作者都有时间进行调整。
由于 `state` 属性本来就不应被覆盖,在大多数情况下,这一变更仅会影响其他 Entity 属性或测试,而不是 `state` 属性本身。
更多详情请参阅 [cover 文档](/developers/core/entity/cover.md#states)。
---
url: /developers/blog/2024-10-08-unit-of-conductivity-changes.md
---
`UnitOfConductivity` enum 已从以下内容更改为:
```py
class UnitOfConductivity(StrEnum):
"""Conductivity units."""
SIEMENS = "S/cm"
MICROSIEMENS = "µS/cm"
MILLISIEMENS = "mS/cm"
```
更改为:
```py
class UnitOfConductivity(StrEnum):
"""Conductivity units."""
SIEMENS_PER_CM = "S/cm"
MICROSIEMENS_PER_CM = "µS/cm"
MILLISIEMENS_PER_CM = "mS/cm"
```
旧的 enum 成员可以在为期一年的 deprecation 期内继续使用,以便自定义集成有时间迁移到新的 enum 成员。
实现详情请参阅 [core PR #127919](https://github.com/home-assistant/core/pull/127919)。
---
url: /developers/blog/2024-10-09-extend-deprecation-hass-helpers.md
---
在 2024 年 3 月 30 日,我们[宣布弃用](/developers/blog/2024-03-30-deprecate-hass-helpers.md)了 `hass.helpers` 属性,计划于 Home Assistant 2024.11 版本生效。
由于仍有大量自定义集成在使用它们,并且近期又发布了 HACS v2 更新,我们决定将 deprecation 期限再延长六个月。
这意味着从 Home Assistant 2025.5 开始,`hass.helpers` 将被移除。
我们鼓励所有自定义集成的开发者更新他们的代码,以避免在 Home Assistant 2025.5 发布之前出现任何问题。
---
url: /developers/blog/2024-10-21-reauth-reconfigure-helpers.md
---
ConfigFlow 中新增了一些辅助方法,以简化 reauth 和 reconfigure flows 的管理:
* `self._get_reauth_entry()` 和 `self._get_reconfigure_entry()` 可以随时访问对应的 config entry
* 应优先使用它们,而不是 `self.hass.config_entries.async_get_entry(self.context["entry_id"])`
* config entry 应在需要时(局部变量,每步一次)请求,而不应作为 class 属性缓存
* 如果 steps 与 discovery 或 user flows 共享,在访问 entry 前应检查 `self.source` 是否为 `SOURCE_REAUTH` 或 `SOURCE_RECONFIGURE`
* `self._abort_if_unique_id_mismatch` 允许你在 `unique_id` 与待重新认证或重新配置的 config entry 的 `unique_id` 不匹配时中止
* 应在调用 `self.async_set_unique_id` 之后使用
* 如果 steps 与 discovery 或 user flows 共享,应检查 `self.source` 是否为 `SOURCE_REAUTH` 或 `SOURCE_RECONFIGURE`
* 其他 source 应继续使用 `self._abort_if_unique_id_configured`
* `self.async_update_reload_and_abort` 已调整,可为 reconfigure flows 更新默认消息
* 新消息 `reconfigure_successful` 必须存在于 `strings.json` 中
* `self.async_update_reload_and_abort` 新增参数 `data_updates`,用于将数据更新与已有的数据合并
* 优先于使用 `data` 参数,因为它降低了在 schema 更新时数据丢失的风险
更多详情请参阅 [reconfigure](/developers/core/integration/config_flow.md#reconfigure) 和 [reauthentication](/developers/core/integration/config_flow.md#reauthentication) 文档。
---
url: /developers/blog/2024-10-22-new-alarm-state-property.md
---
自 Home Assistant Core 2024.11 起,我们在 `AlarmControlPanelEntity` 中引入了 `alarm_state` 属性。这个新属性应取代直接设置 `state` 属性。
新的 `alarm_state` 属性应使用新的 `AlarmControlPanelState` enum 返回其 state,而不是像以前那样使用 `STATE_ALARM_*` 常量设置 state。
设有为期一年的 deprecation 期,这些常量将从 2025.11 起停止工作,以确保所有自定义集成作者都有时间进行调整。
### 示例
```python
from homeassistant.components.alarm_control_panel import AlarmControlPanelEntity, AlarmControlPanelState
class MyAlarm(AlarmControlPanelEntity):
"""My alarm."""
@property
def alarm_state(self) -> AlarmControlPanelState | None:
"""Return the state of the alarm."""
if self.device.is_on():
return AlarmControlPanelState.ARMED_AWAY
return AlarmControlPanelState.DISARMED
```
更多详情请参阅 [alarm control panel 文档](/developers/core/entity/alarm-control-panel.md#states)。
---
url: /developers/blog/2024-10-31-core-config-moved.md
---
### 变更摘要
Core config 类的定义——其实例可通过 [`hass.config`](https://developers.home-assistant.io/docs/dev_101_hass/#the-hass-object) 访问——已从 `homeassistant/core.py` 移至 `homeassistant/core_config.py`。迁移是为了让 core 代码更容易阅读和理解。当前从 `homeassistant.core` 导入 `Config` 的自定义集成需要更新为从 `homeassistant.core_config` 导入。
:::info
通常集成不需要使用 core `Config` 类。但有些自定义集成的类型注解有误,将传入集成 `async_setup` 的 `config` 对象标注为了 `Config` 实例:
```py
from homeassistant.core import Config
async def async_setup(hass: HomeAssistant, config: Config) -> bool:
"""Set up the integration."""
```
正确的类型注解应该是这样:
```py
from homeassistant.helpers.typing import ConfigType
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the integration."""
```
:::
### 向后兼容性
直到 Home Assistant Core 2025.11,仍可从 `homeassistant.core` 导入,但这样做会记录一条警告,提示用户在该自定义集成的 bug tracker 上提 issue。
---
url: /developers/blog/2024-10-31-update-entity-changes.md
---
### 变更摘要
`update` entity 已做出以下调整:
* `in_progress` 属性及其对应的 state attribute 现在应仅为一个 `bool`,用于指示是否有更新正在进行,或者在未知时为 `None`。
* 新增了一个属性和对应的 state attribute `update_percentage`,它可以返回一个表示 0 到 100% 进度的 `int` 或 `float`,或者为 None。
* 新增了一个属性和对应的 state attribute `display_precision`,用于在 `update_percentage` 为 `float` 时控制前端显示的十进制位数。
### 向后兼容性
直到 Home Assistant Core 2025.12,`in_progress` 属性中的数值会自动复制到 `update_percentage` state attribute。
### 文档与 core 实现
详情请参阅 [update entity 开发者文档](https://developers.home-assistant.io/docs/core/entity/update)。
PR 列表:
* 新增 [`update_percentage` state attribute](https://github.com/home-assistant/core/pull/128877) 的 PR
* 新增 [`update_percentage` 属性](https://github.com/home-assistant/core/pull/128908) 的 PR
* 新增 [`display_precision` state attribute 和属性](https://github.com/home-assistant/core/pull/128930) 的 PR
* 一个[更新集成 update entity](https://github.com/home-assistant/core/pull/129380) 的示例
---
url: /developers/blog/2024-11-04-reauth-reconfigure-entry-id.md
---
在没有关联到 config entry 的情况下启动 reauth 或 reconfigure flow 已被 deprecated,并将在 2025.12 开始失效。
自定义集成应更新为使用 `entry.async_start_reauth(hass)` 辅助函数来触发 reauth flow。
```python
async def async_press(self) -> None:
"""Handle the button press."""
try:
await self.device.press_button()
except DevicePasswordProtected as ex:
self.entry.async_start_reauth(self.hass)
```
旧的错误代码:
```python
async def async_press(self) -> None:
"""Handle the button press."""
try:
await self.device.press_button()
except DevicePasswordProtected as ex:
# old incorrect code:
self.hass.async_create_task(
hass.config_entries.flow.async_init(DOMAIN, context={"source": SOURCE_REAUTH}
)
)
```
自定义集成还可以在初始化阶段,或在 data update coordinator 的 update 方法内抛出 `ConfigEntryAuthFailed` 异常。
```python
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up integration from a config entry."""
username = entry.data[CONF_USERNAME]
password = entry.data[CONF_PASSWORD]
if not _credentials_valid(username, password):
raise ConfigEntryAuthFailed()
```
启动 reconfigure flow 只能由前端完成,自定义集成不应需要对这些 flow 做任何更改。
更多详情请参阅 [reconfigure](/developers/core/integration/config_flow.md#reconfigure) 和 [reauthentication](/developers/core/integration/config_flow.md#reauthentication) 文档。
---
url: /developers/blog/2024-11-06-custom-card-sections-support.md
---
从 Home Assistant `2024.11` 起,"sections" view 已成为构建 dashboard 时的默认视图。该视图类型的新特性之一是调整卡片尺寸。
虽然调整尺寸功能默认支持所有卡片,但自定义卡片可以实现一个新方法,以提供最佳的默认尺寸以及最小和最大尺寸,确保卡片在任何情况下看起来都良好。
sections 使用 12 列网格。在下面的示例中,卡片默认占用 6 列 2 行,最小尺寸为 2 列 x 2 行。
```js
public getGridOptions() {
return {
rows: 2,
columns: 6,
min_rows: 2,
min_columns: 3,
};
}
```
更多详情请参阅 [custom card sizing](/developers/frontend/custom-ui/custom-card.md#sizing-in-sections-view) 文档。
---
url: /developers/blog/2024-11-12-options-flow.md
---
### 变更摘要
`OptionsFlow` 中新增了几个 helper 属性:
* `self._config_entry_id` 提供 config entry 的 ID
* `self.config_entry` 返回 config entry
### 向后兼容性
直到 Home Assistant Core 2025.12,仍可以手动设置 `self.config_entry`,但这样做会记录一条警告,提示用户在该自定义集成的 bug tracker 上提 issue。
新代码:
```python
@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
) -> OptionsFlowHandler:
"""Create the options flow."""
return OptionsFlowHandler()
class OptionsFlowHandler(OptionsFlow):
"""Options flow handler."""
def __init__(self) -> None:
"""Initialize options flow."""
self._conf_app_id: str | None = None
```
旧的带 `OptionsFlow` 属性的代码:
```python
@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
) -> OptionsFlowHandler:
"""Create the options flow."""
return OptionsFlowHandler(config_entry)
class OptionsFlowHandler(OptionsFlow):
"""Options flow handler."""
def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize options flow."""
self.config_entry = config_entry
self._conf_app_id: str | None = None
```
### OptionsFlowWithConfigEntry 的特殊处理
`OptionsFlowWithConfigEntry` 类的主要目的是提供 `self.config_entry` 属性,而该属性现在已由父类提供。
目前尚无移除 `OptionsFlowWithConfigEntry` 类的计划,但保留它仅为了向后兼容,新代码应避免使用。
希望移除对 `OptionsFlowWithConfigEntry` 引用的自定义集成需要考虑它们是如何引用 `self.options` 的:
* 如果没有引用 `self.options`,则迁移到 `OptionsFlow` 很简单(见 [PR #129651](https://github.com/home-assistant/core/pull/129651))
* 如果你只是读取 options 值,则建议将读取改为 `self.config_entry.options`(见 [PR #129895](https://github.com/home-assistant/core/pull/129895))
* 如果在单个 step 内更新/修改 options 值,则可能需要先复制 options(`options = deepcopy(dict(self.config_entry.options))`,见 [PR #129928](https://github.com/home-assistant/core/pull/129928))
* 如果在多个 step 中更新/修改 options 值,则可能需要在类初始化时复制 options(`self.options = deepcopy(dict(config_entry.options))`,见 [PR #129890](https://github.com/home-assistant/core/pull/129890))
更多详情请参阅 [options flow](/developers/core/integration/options_flow.md) 文档。
---
url: /developers/blog/2024-11-20-integration-quality-scale.md
---
自 2020 年最初的质量等级推出以来,Home Assistant 发生了相当多的变化。
无论是集成的开发方式还是它们能提供的功能都有变化。
然而,质量等级尚未更新以反映这些变化。
在过去的几个月里,我们一直与社区一起制定一套符合 Home Assistant 当前状态的新集成质量等级。
以下是变更概览:
* 质量等级现在分为四个等级:Bronze、Silver、Gold 和 Platinum。新增的 Bronze 等级是今后新 core 集成的最低要求。这虽然暂时略微降低了新集成的门槛,但它可以明确地说明在这个时代对 core 集成有何期望。
* 新增了一个非分级的 legacy 等级,用于那些尚未可通过 UI 配置的集成。该等级会向用户表明,与可通过 UI 配置的集成相比,该集成可能无法满足其预期。
* 为了让该等级体系能够随 Home Assistant 一起发展,并避免未来再次大规模返工,我们为该等级体系设计了一份规则手册,以明确在添加新规则时需要做什么。
* 为了表明项目致力于保持该等级体系的更新,我们创建了一份架构决策记录 (ADR),描述了保持等级体系更新的目标和流程。
* 开发者文档现在有了[一个全新的章节](/developers/core/integration-quality-scale.md)专门介绍集成质量等级。它包含了每条规则及其背后的理由,并提供了示例以展示如何实现。此外,还有一份[检查清单](/developers/core/integration-quality-scale/checklist.md)可用于更改集成的等级。
* 文档现在也在集成的质量中发挥作用,因为集成的用户体验不仅仅取决于代码。
* 为了追踪集成的进度,我们在集成文件夹中新增了[一个 `quality_scale.yaml` 文件](https://github.com/home-assistant/core/blob/dev/homeassistant/components/airgradient/quality_scale.yaml)。该文件将包含集成所遵守的所有规则列表,以及可能的豁免说明及其原因。
在这一切就绪后,你可能会想知道接下来的步骤是什么。
由于这是对等级体系的一次重大返工,我们决定取消当前集成的等级。
这意味着这些集成需要提交 PR,以将其等级恢复到它们认为应有的水平。
我也希望激励社区帮忙尽可能多地将集成提升到 Bronze(或更高)等级。
我还要感谢社区在质量等级方面的贡献以及他们维护集成所付出的努力。
没有社区,Home Assistant 就不可能像今天这样出色 ❤️。
---
url: /developers/blog/2024-11-21-unit-of-measurement-translations.md
---
Home Assistant 2024.12 将支持对自定义 measurement 单位的翻译。在此之前,这些单位都是在各自的集成中硬编码的。
与 entity 名称一样,集成只需在 entity 定义上设置 `translation_key`,并在 `strings.json` 文件中提供单位名称(使用新的 `unit_of_measurement` key):
```json
{
"entity": {
"sensor": {
"subscribers_count": {
"unit_of_measurement": "subscribers"
},
}
}
}
```
详情请参阅我们的 [backend localization 文档](/developers/internationalization/core.md#unit-of-measurement-of-entities)。
---
url: /developers/blog/2024-11-26-camera-deprecations.md
---
在 Home Assistant `2024.11` 中,我们为大多数摄像头添加了 WebRTC。为了支持它,我们需要重构并改进 camera entity。
今天我们要宣布,在即将到来的 Home Assistant `2024.12` 版本中,以下方法已被 deprecated,并将在 Home Assistant `2025.6` 版本中移除:
* `frontend_stream_type` 属性将被移除。从 `2024.11` 起,Home Assistant 将通过检查 camera entity 是否实现了原生 WebRTC 方法来识别前端 stream 类型([#130932](https://github.com/home-assistant/core/pull/130932))。卡片开发者可以使用新的 websocket 命令 `camera/capabilities` 来获取前端 stream 类型。
* `async_handle_web_rtc_offer` 方法将被移除。请使用 `async_handle_async_webrtc_offer` 和 async WebRTC signaling 方式([#131285](https://github.com/home-assistant/core/pull/131285))。
* `async_register_rtsp_to_web_rtc_provider` 方法已被 deprecated。请使用 `async_register_webrtc_provider`,它提供了更大的灵活性并支持 async WebRTC signaling 方式([#131462](https://github.com/home-assistant/core/pull/131462))。
更多关于替代方案的详情请参阅 [camera entity 文档](/developers/core/entity/camera.md)。
---
url: /developers/blog/2024-11-28-dt-util-utc-to-timestamp-deprecation.md
---
Utility 函数 `homeassistant.util.dt.utc_to_timestamp` 已被 deprecated,并将在 Home Assistant Core 2026.1 中移除。调用该函数的自定义集成应改为调用 stdlib 方法 `datetime.datetime.timestamp()`。
弃用的原因是 stdlib 方法与 utility 函数速度一样快。
更多详情请参阅 [core PR](https://github.com/home-assistant/core/pull/131787)。
---
url: /developers/blog/2024-12-03-climate-horizontal-swing.md
---
自 Home Assistant Core 2024.12 起,我们在 `ClimateEntity` 中实现了一个独立的属性和方法,用于控制水平 swing。
支持完全独立的垂直和水平 swing 控制和 state 的集成,现在可以使用之前的 `swing_mode` 仅用于垂直 swing,并使用新的 `swing_horizontal_mode` 来提供水平 swing 的 state 和控制。
不支持独立控制的集成仍应继续使用当前的 `swing_mode` 同时处理垂直和水平支持。
### 示例
以下是在你的 climate entity 中实现 `swing` 和 `swing_horizontal` 的示例要求。
```python
class MyClimateEntity(ClimateEntity):
"""Implementation of my climate entity."""
@property
def supported_features(self) -> ClimateEntityFeature:
"""Return the list of supported features."""
return ClimateEntityFeature.SWING_MODE | ClimateEntityFeature.SWING_HORIZONTAL_MODE
@property
def swing_mode(self) -> str | None:
"""Return the swing setting.
Requires ClimateEntityFeature.SWING_MODE.
"""
return self._attr_swing_mode
@property
def swing_modes(self) -> list[str] | None:
"""Return the list of available swing modes.
Requires ClimateEntityFeature.SWING_MODE.
"""
return self._attr_swing_modes
@property
def swing_horizontal_mode(self) -> str | None:
"""Return the swing setting.
Requires ClimateEntityFeature.SWING_HORIZONTAL_MODE.
"""
return self._attr_swing_horizontal_mode
@property
def swing_horizontal_modes(self) -> list[str] | None:
"""Return the list of available swing modes.
Requires ClimateEntityFeature.SWING_HORIZONTAL_MODE.
"""
return self._attr_swing_horizontal_modes
async def async_set_swing_mode(self, swing_mode: str) -> None:
"""Set new target swing operation."""
await self.api.set_swing_mode(swing_mode)
async def async_set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None:
"""Set new target horizontal swing operation."""
await self.api.set_swing_horizontal_mode(swing_horizontal_mode)
```
更多详情请参阅 [climate 文档](/developers/core/entity/climate.md#swing-modes)。
---
url: /developers/blog/2024-12-08-new-vacuum-state-property.md
---
自 Home Assistant Core 2025.1 起,`StateVacuumEntity` 中用于返回 state 的常量已被 deprecated,并由 `VacuumActivity` enum 取代。
此外,随着这一变更,集成应设置 `activity` 属性,而不是直接设置 `state` 属性。
设有为期一年的 deprecation 期,这些常量将从 2026.1 起停止工作,以确保所有自定义集成作者都有时间进行调整。
### 示例
```python
from homeassistant.components.vacuum import VacuumActivity
class MyVacuumCleaner(StateVacuumEntity):
"""My vacuum cleaner."""
@property
def activity(self) -> VacuumActivity | None:
"""Return the state of the vacuum."""
if self.device.is_cleaning():
return VacuumActivity.CLEANING
return VacuumActivity.DOCKED
```
更多详情请参阅 [vacuum 文档](/developers/core/entity/vacuum.md#states)。
---
url: /developers/blog/2024-12-13-water-heater-entity-description.md
---
在 Water Heater 集成中发现了一个 entity description 的命名错误,我们现在在 2025.1 版本中将 `WaterHeaterEntityEntityDescription` 重命名为 `WaterHeaterEntityDescription`。
旧的 `WaterHeaterEntityEntityDescription` 已被 deprecated,并计划在 2026.1 中移除,建议开发者改用新的 `WaterHeaterEntityDescription`。
更多详情请参阅 core PR: [#132888](https://github.com/home-assistant/core/pull/132888)。
---
url: /developers/blog/2024-12-14-kelvin-preferred-color-temperature-unit.md
---
### 变更摘要
2022 年 10 月,Home Assistant 将首选 color temperature 单位从 mired 迁移到了 kelvin。
现在时候到了,我们需要为相应的 attributes、constants 和 properties 添加 deprecation warnings:
* 弃用 state 和 capability attributes:`ATTR_COLOR_TEMP`、`ATTR_MIN_MIREDS` 和 `ATTR_MAX_MIREDS`
* 弃用 `light.turn_on` service call 中的常量 `ATTR_KELVIN` 和 `ATTR_COLOR_TEMP`
* 弃用 properties:`LightEntity.color_temp`、`LightEntity.min_mireds` 和 `LightEntity.max_mireds`
* 弃用相应的 attributes:`LightEntity._attr_color_temp`、`LightEntity._attr_min_mired` 和 `LightEntity._attr_max_mired`
### 示例
#### 自定义最小/最大 color temperature
```python
class MyLight(LightEntity):
"""Representation of a light."""
# 旧
# _attr_min_mireds = 200 # 5000K
# _attr_max_mireds = 400 # 2500K
# 新
_attr_min_color_temp_kelvin = 2500 # 400 mireds
_attr_max_color_temp_kelvin = 5000 # 200 mireds
```
#### 默认最小/最大 color temperature
```python
from homeassistant.components.light import DEFAULT_MAX_KELVIN, DEFAULT_MIN_KELVIN
class MyLight(LightEntity):
"""Representation of a light."""
# 旧无需设置 _attr_min_mireds / _attr_max_mireds
# 新需要显式设置默认值
_attr_min_color_temp_kelvin = DEFAULT_MIN_KELVIN
_attr_max_color_temp_kelvin = DEFAULT_MAX_KELVIN
```
#### 动态最小/最大 color temperature
```python
from homeassistant.util import color as color_util
class MyLight(LightEntity):
"""Representation of a light."""
# 旧
# def min_mireds(self) -> int:
# """Return the coldest color_temp that this light supports."""
# return device.coldest_temperature
#
# def max_mireds(self) -> int:
# """Return the warmest color_temp that this light supports."""
# return device.warmest_temperature
# 新
def min_color_temp_kelvin(self) -> int:
"""Return the warmest color_temp that this light supports."""
return color_util.color_temperature_mired_to_kelvin(device.warmest_temperature)
def max_color_temp_kelvin(self) -> int:
"""Return the coldest color_temp that this light supports."""
return color_util.color_temperature_mired_to_kelvin(device.coldest_temperature)
```
#### Service 调用
```python
from homeassistant.components.light import ATTR_COLOR_TEMP_KELVIN
from homeassistant.util import color as color_util
class MyLight(LightEntity):
"""Representation of a light."""
def turn_on(self, **kwargs: Any) -> None:
"""Turn on the light."""
# 旧
# if ATTR_COLOR_TEMP in kwargs:
# color_temp_mired = kwargs[ATTR_COLOR_TEMP]
# color_temp_kelvin = color_util.color_temperature_mired_to_kelvin(color_temp_mired)
# 旧
# if ATTR_KELVIN in kwargs:
# color_temp_kelvin = kwargs[ATTR_KELVIN]
# color_temp_mired = color_util.color_temperature_kelvin_to_mired(color_temp_kelvin)
# 新
if ATTR_COLOR_TEMP_KELVIN in kwargs:
color_temp_kelvin = kwargs[ATTR_COLOR_TEMP_KELVIN]
color_temp_mired = color_util.color_temperature_kelvin_to_mired(color_temp_kelvin)
```
### 背景信息
* [关于 Kelvin 温度的社区讨论](https://community.home-assistant.io/t/wth-is-light-temperature-not-in-kelvin/467449/6)
* [Core PR #79591: 迁移到 Kelvin](https://github.com/home-assistant/core/pull/79591)
* [架构讨论 #564](https://github.com/home-assistant/architecture/discussions/564)
---
url: /developers/blog/2024-12-21-moving-to-pydantic-v2.md
---
Pydantic 是 Python 中广泛使用的数据验证库。2023 年 6 月 30 日,Pydantic v2 发布,引入了与 Pydantic v1 不向后兼容的重大变更。
从 Home Assistant Core 2025.1 起,Pydantic v2 将取代 v1。如果你的自定义集成使用了 Pydantic,则必须更新以支持 Pydantic v2,才能在即将发布的版本中继续工作。
在过去一年中,我们的社区努力确保 Home Assistant Core 使用的库与 Pydantic v1 和 v2 都兼容。这种双重兼容性帮助我们尽可能顺畅地过渡到 Pydantic v2。
如需快速迁移,你可以使用 Pydantic v2 中内置的 Pydantic v1 shims。关于在 v1/v2 环境中使用这些 shims 的详细信息,请参阅 [Pydantic migration guide](https://docs.pydantic.dev/latest/migration/#using-pydantic-v1-features-in-a-v1v2-environment)。
---
url: /developers/blog/2024-12-26-area-squared-units.md
---
### 变更摘要
现在为 number 和 sensor entities 新增了一个 `AREA` device class,并支持基于 unit system 的自动单位转换。
同时新增了相应的 `UnitOfArea` unit enumerator 和 `AreaConverter` converter class,以支持新的 device class。
### 向后兼容性
`AREA_SQUARE_METERS` 常量已被 deprecated,并将在 Home Assistant `2025.12` 中移除。
自定义集成应调整为使用 `UnitOfArea.SQUARE_METERS`。
更多详情请参阅 [Number 文档](/developers/core/entity/number.md#available-device-classes) 和 [Sensor 文档](/developers/core/entity/sensor.md#available-device-classes)。
---
url: /developers/blog/2025-01-15-service-info.md
---
### 变更摘要
为了减少对 optional 集成的依赖(这些集成名称本质上是作为 helpers 使用的),以下 ServiceInfo models 已被迁移:
* `DhcpServiceInfo` 从 `homeassistant.components.dhcp` 迁移到 `homeassistant.helpers.service_info.dhcp`
* `SsdpServiceInfo` 从 `homeassistant.components.ssdp` 迁移到 `homeassistant.helpers.service_info.ssdp`
* `UsbServiceInfo` 从 `homeassistant.components.usb` 迁移到 `homeassistant.helpers.service_info.usb`
* `ZeroconfServiceInfo` 从 `homeassistant.components.zeroconf` 迁移到 `homeassistant.helpers.service_info.zeroconf`
要更新你的集成:
1. 按下述示例替换 import 语句
2. 使用新的 imports 测试你的集成
旧的 import 位置已被 deprecated,并将在 Home Assistant 2026.2 中移除。
### 示例
```python
# 旧
# from homeassistant.components.dhcp import DhcpServiceInfo
# from homeassistant.components.ssdp import SsdpServiceInfo
# from homeassistant.components.usb import UsbServiceInfo
# from homeassistant.components.zeroconf import ZeroconfServiceInfo
# 新
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from homeassistant.helpers.service_info.ssdp import SsdpServiceInfo
from homeassistant.helpers.service_info.usb import UsbServiceInfo
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
class MyConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow."""
async def async_step_dhcp(self, discovery_info: DhcpServiceInfo) -> ConfigFlowResult:
"""Handle dhcp discovery."""
...
async def async_step_ssdp(self, discovery_info: SsdpServiceInfo) -> ConfigFlowResult:
"""Handle ssdp discovery."""
...
async def async_step_usb(self, discovery_info: UsbServiceInfo) -> ConfigFlowResult:
"""Handle usb discovery."""
...
async def async_step_zeroconf(self, discovery_info: ZeroconfServiceInfo) -> ConfigFlowResult:
"""Handle zeroconf discovery."""
...
```
---
url: /developers/blog/2025-01-31-energy-distance-units.md
---
### 变更摘要
现在为 number 和 sensor entities 新增了一个 `ENERGY_DISTANCE` device class,用于表示每单位距离消耗的能量,并支持基于 unit system 的自动单位转换。
例如,这个新的 device class 可用于显示电动汽车消耗的电能量的 entities。同时新增了相应的 `UnitOfEnergyDistance` unit enumerator 和 `EnergyDistanceConverter` converter class,以支持新的 device class。
以下单位可用:
* kWh/100km
* mi/kWh
* km/kWh
更多详情请参阅 [Number 文档](/developers/core/entity/number.md#available-device-classes) 和 [Sensor 文档](/developers/core/entity/sensor.md#available-device-classes)
### 倒数单位
实现 `EnergyDistanceConverter` 也为 `BaseUnitConverter` 带来了倒数单位的支持。这简化了添加互为倒数的新单位(例如 kWh/100km 和 km/kWh)的工作。
---
url: /developers/blog/2025-02-16-config-subentries.md
---
Config entries 现在新增了一种称为“config subentry”的数据类型。[Config subentries](/developers/config_entries_index.md#config-subentries) 由 config entry 拥有,并在 `async_setup_entry` 期间设置。Config subentries 由 [config subentry flows](/developers/core/integration/config_flow.md#subentry-flows) 创建,并通过 config subentry reconfigure flows 更新。这与我们创建/编辑 config entries 的方式类似。
这使得集成作者可以更方便地允许用户添加、修改和移除共享某些公共资源(例如 cloud 账号或 MQTT broker)的配置片段。
[架构讨论](https://github.com/home-assistant/architecture/discussions/1070) 提供了更多背景信息。
---
url: /developers/blog/2025-02-17-backup-agents.md
---
在 2025 年 1 月的版本中,我们为备份引入了一项新功能,称为 backup agents。
此前,backup platform 仅用于在创建备份之前暂停或准备集成操作,以及在备份之后执行后续操作。随着 backup agents 的引入,backup platform 现在允许集成添加一个或多个 backup agents,这些 agents 可以将备份上传到某些本地或远程位置。
第一个实现 backup agent 的集成是 Home Assistant Cloud 集成。在 2025 年 2 月的版本中,又有三个集成实现了 backup agents:Google Drive、OneDrive 和 Synology DSM。如果你是一名集成作者,并希望为你喜欢的备份位置添加支持,你可以在[开发者文档](/developers/core/platform/backup.md)中了解更多关于如何实现 backup agents 的信息。
---
url: /developers/blog/2025-02-19-new-config-entry-states.md
---
在卸载和移除 entries 时的 config entry state 转换已被修改:
* 新增了一个状态 `ConfigEntryState.UNLOAD_IN_PROGRESS`,在调用集成的 `async_unload_entry` 之前设置
原因:
* 使编写在最后一条 config entry 卸载之后应运行的清理代码变得更简单
* 改善与 config entries 的 reload 和 unload 相关的问题的调试
* 当集成的 `async_unload_entry` 返回 False 时,config entry state 将被设置为 `ConfigEntryState.FAILED_UNLOAD`
原因:
* 如果 `async_unload_entry` 返回 `False`,我们不能假设集成仍处于已加载状态,很可能它已经部分卸载了自身,尤其是考虑到这是我们推荐的模式:
```py
async def async_unload_entry(hass: HomeAssistant, entry: MyConfigEntry) -> bool:
"""Unload a config entry."""
# async_unload_platforms 在至少有一个 platform 未卸载时返回 False
if (unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS)):
entry.runtime_data.listener()
# 完成与 platforms 无关的清理
return unload_ok
```
* 在调用集成的 `async_remove_entry` 之前,config entry 会从 `hass.config_entries` 中移除
原因:
* 使编写在最后一条 config entry 移除之后应运行的清理代码变得更简单
自定义集成作者需要检查并在必要时更新他们的集成的 `async_unload_entry` 和 `async_remove_entry`。
最常见的需要更新的模式如下:
```python
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
loaded_entries = [
entry
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.state is ConfigEntryState.LOADED
]
if len(loaded_entries) == 1:
# 最后一条 config entry 正在卸载,释放共享资源、注销 services 等。
...
```
如果自定义集成的最低 Home Assistant 版本设置为 2025.3.0,现在可以简化为:
```python
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
if not hass.config_entries.async_loaded_entries(DOMAIN):
# 最后一条 config entry 正在卸载,释放共享资源、注销 services 等。
...
```
如果自定义集成需要与早期版本的 Home Assistant Core 保持向后兼容:
```python
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
other_loaded_entries = [
_entry
for _entry in hass.config_entries.async_loaded_entries(DOMAIN)
if _entry.entry_id != entry.entry_id
]
if not other_loaded_entries:
# 最后一条 config entry 正在卸载,释放共享资源、注销 services 等。
...
```
更多背景信息请参阅 [config entry 文档](/developers/config_entries_index.md) 以及 [home assistant core PR #138522](https://github.com/home-assistant/core/pull/138522)。
---
url: /developers/blog/2025-03-01-config-flow-unique-id.md
---
### 变更摘要
创建带有已在注册表中存在的 unique ID 的 config entry 已被弃用,现在将会记录警告。
### 详情
当 config flow 创建一个具有冲突 unique ID 的 entry 时,旧 entry 当前会被自动移除并替换为新的 config entry。
这可能导致意外的行为,集成应调整为中止 flow。
对于手动 flow,集成应实现 options、reauth、reconfigure,以允许用户更改设置。
对于非用户可见的 flow,集成应在中止前可选择更新现有 entry。
更多详情请参阅 [config flow](/developers/core/integration/config_flow.md#unique-id-requirements) 文档。
---
url: /developers/blog/2025-03-04-backup-agent-api-changes.md
---
`BackupAgent` API 已进行调整,以下方法在找不到备份时应抛出 `BackupNotFound`:
* `BackupAgent.async_delete_backup`
* `BackupAgent.async_download_backup`
* `BackupAgent.async_get_backup`
请参阅 [backup agent 文档](/developers/core/platform/backup.md#backup-agents) 和 [home assistant core PR #139754](https://github.com/home-assistant/core/pull/139754) 以获取更多背景信息。
---
url: /developers/blog/2025-03-14-media-player-toggle-adjusted.md
---
media player 基类中的 toggle action service 实现已进行调整。
media player 现在会在处于 `off` 或 `standby` 状态时开启;在其他所有状态下,将会被关闭。
在此变更之前,如果 media player 处于 `idle` 状态,也会被关闭。这与 `idle` 状态的含义不符,因为它被视为[设备已开启的状态](/developers/core/entity/media-player.md#states)。
自定义集成如果覆盖了 `async_toggle`,可能需要更新其实现。
此变更引入于 [home assistant core PR #78192](https://github.com/home-assistant/core/pull/78192)。
---
url: /developers/blog/2025-03-24-config-subentry-flow-changes.md
---
`ConfigSubentryFlow._reconfigure_entry_id` 已重命名为 `ConfigSubentryFlow._entry_id`,`ConfigSubentryFlow._get_reconfigure_entry` 已重命名为 `ConfigSubentryFlow._get_entry`。
进行此项变更的原因是,sub entry user flow 和 subentry reconfigure flow 都需要访问父 config entry,例如获取 options、data 等。
调用了上述任一重命名方法的自定义集成需要更新其实现。
此变更引入于 [home assistant core PR #141017](https://github.com/home-assistant/core/pull/141017)。
---
url: /developers/blog/2025-03-25-frozen-unit-system.md
---
`UnitSystem` 类已更改为冻结的 data class,所有派生自它的实例现在也是冻结的。
以下派生自 `UnitSystem` 类的 unit system 现在已冻结:
* `METRIC_SYSTEM`
* `US_CUSTOMARY_SYSTEM`
进行此项变更的原因是,Unit system 是不应被修改的常量。修改这些常量的集成可能会破坏 unit 转换,并导致 Home Assistant 其他组件产生不期望的输出。
使用冻结的 data class 时,尝试修改 UnitSystem 常量将会失败:
```python
dataclasses.FrozenInstanceError: cannot assign to field 'temperature_unit'
```
此项变更引入于 [home assistant core PR #140954](https://github.com/home-assistant/core/pull/140954)。
---
url: /developers/blog/2025-04-17-search-media-play.md
---
media player 现在可以允许用户搜索 media,只需添加 `MediaEntityFeature.SEARCH_MEDIA` 并实现 `async_search_media`。用户可以通过搜索查询和一组 `MediaClasses` 来过滤搜索结果。更多信息请参阅[更新的文档](/developers/core/entity/media-player.md#search-media)。
---
url: /developers/blog/2025-04-24-device-tracker-accuracy.md
---
`device_tracker` entity 组件的 `TrackerEntity` 类中 `location_accuracy` 属性(简写为 `_attr_location_accuracy`)的类型已从 `int` 更改为 `float`。
当源值为 `float` 时,不再需要类型转换。
### 示例
```python
class ExampleTrackerEntity(TrackerEntity):
"""Test tracker entity."""
_attr_location_accuracy: float = 2.5
@cached_property
def location_accuracy(self) -> float:
"""Return the location accuracy of the device.
Value in meters.
"""
return self._attr_location_accuracy
```
---
url: /developers/blog/2025-05-22-range-based-icons.md
---
图标翻译现在支持基于数值范围定义图标。这意味着集成作者可以定义根据数值变化的图标,而无需在代码中实现自定义逻辑。
以前,图标翻译仅支持基于 state 的图标,即特定的 state 映射到特定的图标。虽然这对"on"/"off"等离散状态效果很好,但处理电池电量或信号强度等数值时,需要自定义代码。
基于范围的图标定义在 `icons.json` 文件中:
```json
{
"entity": {
"sensor": {
"battery_level": {
"default": "mdi:battery",
"range": {
"0": "mdi:battery-outline",
"10": "mdi:battery-10",
"20": "mdi:battery-20",
"30": "mdi:battery-30",
"40": "mdi:battery-40",
"50": "mdi:battery-50",
"60": "mdi:battery-60",
"70": "mdi:battery-70",
"80": "mdi:battery-80",
"90": "mdi:battery-90",
"100": "mdi:battery"
}
}
}
}
}
```
系统会选择与小于或等于 entity 当前值的高范围值相关联的图标。例如:
* 值为 15 时显示 `mdi:battery-10` 图标
* 值为 45 时显示 `mdi:battery-40` 图标
* 值为 100 时显示 `mdi:battery` 图标
关于实现细节,请参阅[图标翻译文档](/developers/core/integration-quality-scale/rules/icon-translations.md#range-based-icons)。
---
url: /developers/blog/2025-05-26-sensor-default-display-precision.md
---
如果数值型 sensor 没有由集成设置建议的显示精度,Home Assistant 现在将根据 sensor 的 device class 使用默认显示精度。
新的 device class 应添加到 `homeassistant/components/sensor/const.py` 中的 `UNITS_PRECISION`,并附带正确的 unit 和期望的默认显示精度。有关如何选择正确的 unit 和精度,请参阅该常量的 docstring。
以前在发生 unit 转换时对 sensor state 进行的舍入现在已被移除,sensor state 包含完整的原始值。
仍然建议集成在其 sensor 上设置 `suggested_display_precision`。此项变更是一个回退机制,以确保所有数值型 sensor 具有一致的体验。
更多详情请参阅实现 [pull request](https://github.com/home-assistant/core/pull/145013) 和 [Sensor entity 文档](/developers/core/entity/sensor.md)。
---
url: /developers/blog/2025-07-02-vacuum-battery-properties-deprecated.md
---
自 Home Assistant Core 2025.8 起,`StateVacuumEntity` 中的两个电池属性 `battery_level` 和 `battery_icon` 已被弃用。
`battery_level` 和 `battery_icon` 属性应替换为一个独立的 sensor,使用该 sensor 的 `battery` device class,如果不需要 battery sensor device class 的默认图标,还可选择自定义图标。如果有需要,也可考虑提供一个 `charging` device class 的 binary sensor,以指示是否正在充电。
弃用期为一年,从 2026.8 起这两个电池属性将停止工作,以确保所有自定义集成作者有足够时间进行调整。
更多详情请参阅[架构决策](https://github.com/home-assistant/architecture/discussions/938)。
---
url: /developers/blog/2025-07-14-home-assistant-os-16-open-file-limit.md
---
随着 Home Assistant OS 16 的发布,Docker 容器(以及由此扩展的所有 add-on 及其内部进程)的打开文件描述符默认限制已发生变化。Home Assistant OS 现在使用 Systemd 提供的默认值:软限制为 **1024**,硬限制为 **524288** 个打开文件。
这与之前的 OS 版本不同,之前的版本将限制设置为"无限"(实际上超过 10 亿的值)——这也是上游引入的变更——[自 Home Assistant OS 10 起](/developers/blog/2023-04-13-new_limits_for_add_ons.md)。新限制符合现代 Linux 系统上的常见做法,并遵循其他 Linux 发行版逐渐采用的变更。
有关这些限制和最佳实践的更多详情,请参阅 Lennart Poettering 对 [File Descriptor Limits](https://0pointer.net/blog/file-descriptor-limits.html) 的解释。简而言之,需要超过默认软限制 1024 个文件描述符的应用程序,应在启动时显式提高自身的限制,最高不超过系统设定的硬限制(在我们的情况下为 524288)。这种方法确保每个应用程序仅请求其所需的资源,并避免可能导致意外资源耗尽的全局高限制。
### 这对 add-on 开发者意味着什么?
* 如果您的 add-on 应用程序需要打开超过 1024 个文件(socket、pipe 等),您应自行调整限制,例如使用 `ulimit -Sn `,只要 `` 不超过硬限制(可通过 `ulimit -Hn` 检查)。
* 大多数 add-on 可能不会受此变更影响,但部分 add-on——例如处理大量网络连接或访问大量文件(如数据库或文件共享应用程序)的 add-on——可能需要调整其启动流程。
### 如果限制太低会发生什么?
如果 add-on 或其应用程序需要的文件描述符超过默认软限制(1024),且未自行提高限制,则在达到限制后可能无法打开更多文件、socket 或网络连接。这通常会导致"Too many open files"或"No file descriptors available"(`EMFILE`)等错误、网络连接失败或服务异常。
如有任何问题或遇到故障,请参阅[此 GitHub discussion](https://github.com/home-assistant/operating-system/discussions/4166),其中还包含更多细节和背景信息。
---
url: /developers/blog/2025-07-16-media-player-standby-state-deprecated.md
---
根据 [home-assistant/architecture#799](https://github.com/home-assistant/architecture/discussions/799) 的决定,media player 的 state `MediaPlayerState.STANDBY` 已被弃用,并将在 Home Assistant Core 2026.8 中移除。
集成应改用 `MediaPlayerState.IDLE` 或 `MediaPlayerState.OFF`。
更多详情请参阅 [media player 开发文档](/developers/core/entity/media-player.md#states)。
---
url: /developers/blog/2025-07-18-updated-pattern-for-helpers-linking-to-devices.md
---
helper 集成不应将其自己的 config entry 添加到源 entity 的 device 或用户选择的 device。相反,它们应仅将其 entity 链接到源 entity 的 device。
将 helper config entry 添加到其他集成的 device 已不再受支持,并将在 Home Assistant Core 2026.8 中停止工作。
### 背景
架构提案 [home-assistant/architecture#1226](https://github.com/home-assistant/architecture/discussions/1226) 使 device 的连接和标识符在每个集成 domain 中唯一,而非全局唯一。这对大多数集成来说不是问题,但带 config flow 的 helper 集成是例外。
### 建议的变更
#### 将 helper entity 直接链接到源集成的 device
在 helper entity 的构造函数中,将 `self.device_entry` 设置为源 device
```py
class HelperEntity(Entity)
def __init__(hass: HomeAssistant, source_entity_id: str, ...) -> None:
self.device_entry = async_entity_id_to_device(
hass,
source_entity_id,
)
```
#### 不要直接将 helper config entry 添加到源 device
不要将 config entry 直接添加到源 device,这意味着以下模式不再允许
```py
device_registry.async_update_device(
source_device.id,
add_config_entry_id=helper_config_entry.entry_id,
)
```
#### 不要隐式地将 helper config entry 添加到源 device
不要设置 `self._attr_device_info`,也不要覆盖 `device_info` 以返回另一个集成 device 的 identifiers 和 connections,因为这会导致 helper config entry 被添加到源 device。相反,应在 helper entity 中设置 `self.device_entry`,以将 helper entity 链接到其他集成的 device,如上面的示例所示。
#### 清理 device registry
提供了一个 helper 函数 `homeassistant.helpers.helper_integration.async_remove_helper_config_entry_from_source_device` 来辅助清理。核心集成已被修改为在 config entry 迁移步骤中调用此 helper。
#### 处理已移除的 device
如何处理已移除的 device 由 helper 集成自行决定;大多数核心 helper 将 `self.device_entry` 设置为 `None`。请注意,如果传入的 `device_id` 不存在,`DeviceRegistry.async_get` 会返回 `None`。
#### 示例实现
derivative 核心集成之前将其 config entry 添加到源 entity 的 device,现已更新为仅将 helper entity 链接到源 entity 的 device,见核心 [PR #148674](https://github.com/home-assistant/core/pull/148674)。
template 核心集成之前将其 config entry 添加到用户选择的 device,现已更新为仅将 helper entity 链接到用户选择的 device,见核心 [PR #148756](https://github.com/home-assistant/core/pull/148756)。
---
url: /developers/blog/2025-07-31-result-removed-from-flowresult.md
---
`homeassistant.data_entry_flow.FlowResult` typed dict 中的 result 属性已被移除,现在仅在需要时才存在,即 `homeassistant.auth.models.AuthFlowResult` 和 `homeassistant.config_entries.ConfigFlowResult`。
此项变更预计不会影响自定义集成的运行时行为,但自定义集成作者可能需要更新测试以及任何派生自 `homeassistant.data_entry_flow.FlowResult` 且使用了 `result` 属性的类,以消除类型检查器的警告。
---
url: /developers/blog/2025-08-01-suggested-area-removed-from-deviceentry.md
---
`DeviceEntry.suggested_area` 属性已弃用,并将在 HA Core 2026.9 中移除。此外,当 HA Core 2026.9 发布时,`suggested_area` 将不再出现在 `EVENT_DEVICE_REGISTRY_UPDATED` 事件中。
注意:
在 `DeviceInfo` 中设置 `suggested_area`,以及将 `suggested_area` 传递给 `DeviceRegistry.async_get_or_create` 仍然受支持,并会影响创建设备的 area,尽管未来可能会改变。
在自定义集成中,请使用 `DeviceEntry.area_id` 来确定设备的 area。不要访问 `DeviceEntry.suggested_area`。
在弃用期间,访问 `DeviceEntry.suggested_area` 将会记录警告。
更多详情请参阅 [DeviceEntry 文档](/developers/device_registry_index.md#device-properties) 以及弃用了 `DeviceEntry.suggested_area` 的核心 [PR 149730](https://github.com/home-assistant/core/pull/149730)。
---
url: /developers/blog/2025-08-20-micro-sign-encoding.md
---
## 统一使用一种编码来表示测量单位中的 μ
单位前缀 μ 被用于 μV 和 μS 等测量单位常量。
然而 μ 有两种不同的 Unicode 编码:
1. [MICRO SIGN](https://www.compart.com/en/unicode/U+00B5)
2. [Greek Small Letter Mu](https://www.compart.com/en/unicode/U+03BC)
Home Assistant 以前混用了这两种编码,这导致问题,因为两种编码之间的字符串比较会失败,从而导致测量单位验证失败。
我们选择了"Greek Small Letter Mu",因为"MICRO SIGN"编码是为了与旧的 8 位西欧字符集兼容而存在的,Unicode 联盟建议不要使用它。
通过 [#144853](https://github.com/home-assistant/core/pull/144853),我们通过一致地使用 Greek Small Letter Mu 版本来编码 μ,修复了 Home Assistant 核心中的这个问题。在 Python 字面量字符串中,此变体编码为 `"\u03bc"`。
开发者应检查其代码和库是否依赖有歧义的 MICRO SIGN(μ)`"\u00b5"`,并将代码迁移为使用 Greek Small Letter Mu `"\u03bc"` 以避免问题。
`sensor` 和 `number` entity 平台现在包含一个内置功能,可自动转换使用有歧义的 MICRO SIGN(μ)编码的 unit。
## 在 Visual Studio Code 中查找实例
在 Visual Studio Code 中,在 **Search**(文件)面板中启用 **Match Case**,以仅查找所选编码。从上方复制确切的 μ 字符并粘贴到搜索框中。编辑器的页面搜索会匹配所有变体;请使用全局搜索。
## 新的 linter
为了避免新代码或已更改代码中的问题,[#144853](https://github.com/home-assistant/core/pull/144853) 添加了一个 linter,会对使用非首选编码的 μ 的字面量赋值发出警告。
---
url: /developers/blog/2025-09-22-deprecate-hass-argument-service-helpers.md
---
### 变更摘要
向以下 service helpers 提供 `hass` 参数已被弃用:
`verify_domain_control`、`extract_entity_ids`、`async_extract_entities`、`async_extract_entity_ids` 和 `async_extract_config_entry_ids`。
自版本 `2025.1` 起(通过核心 PR [#133062](https://github.com/home-assistant/core/pull/133062)),
`HomeAssistant` 的引用可以作为 `ServiceCall` 对象的属性使用,
因此向上述 helper 传递 `hass` 对象变得多余。
要更新您的集成,只需移除 `hass` 参数。
对 `hass` 参数的支持将在 Home Assistant 2026.10 中移除。
### 示例
#### ID 提取 helper
```python
# 旧方式
# target_entry_ids = await async_extract_config_entry_ids(hass, service_call)
# entity_ids = await async_extract_entity_ids(hass, service_call)
# entities = await service.async_extract_entities(hass, platform_entities.values(), service_call)
# 新方式
target_entry_ids = await async_extract_config_entry_ids(service_call)
entity_ids = await async_extract_entity_ids(service_call)
entities = await service.async_extract_entities(platform_entities.values(), service_call)
```
#### 装饰器 helper
```python
# 旧方式
# @verify_domain_control(hass, DOMAIN)
# async def do_action(call: ServiceCall) -> None:
# ...
# 新方式
@verify_domain_control(DOMAIN)
async def do_action(call: ServiceCall) -> None:
...
```
---
url: /developers/blog/2025-09-25-entity-services-api-changes.md
---
Platform entity service 应通过从集成的 `async_setup` 调用 helper [`service.async_register_platform_entity_service`](/developers/dev_101_services.md?_highlight=async_register_platform_entity_service#entity-service-actions) 来注册,而不是在 platform set up 期间调用 `platform.async_register_entity_service`。
现有集成应迁移到新 API,以确保加载 service 不依赖于 platform setup。
有关迁移示例,请参阅 [core PR 152172](https://github.com/home-assistant/core/pull/152172) 和 [core PR 152047](https://github.com/home-assistant/core/pull/152047)。
---
url: /developers/blog/2025-10-05-coordinator-retrigger.md
---
启用 debouncer 的 update coordinator 现在会在更新进行时接受更新请求。
该请求将被排队,等待当前更新完成后再执行。
考虑以下情况:
```python
async def _update():
a = await get_a()
# A: 用户或其他逻辑在此通过 async_schedule_update() 请求新更新
b = await get_b()
return (a,b)
```
在时间戳 `A` 请求更新的用户或代码,期望链接的 entity 都将获得从该时间点开始的新数据。然而,由于我们以前会忽略该请求,entity 获得的 `a` 值的数据来自被忽略的更新请求之前的时间点。
为避免这种情况,update coordinator 现在将在当前执行更新时收到请求的情况下,安排一次额外的更新。
此变更的一个副作用是,现在可以从 coordinator 的 update 函数内部安排更新。这在例如更新过程中连接丢失时非常有用,我们希望所有 entity 立即指示为不可用,同时希望尽快尝试重新连接,但将其放在下一个更新周期中进行。
---
url: /developers/blog/2025-10-14-device-filter-removed-from-target-selector.md
---
target selector 不再支持 device filter 选项,它已从 [target selector 文档](https://www.home-assistant.io/docs/blueprint/selectors/#target-selector)中移除。
Hassfest 已更新为在 [带有 device filter 的 target 的 service 上失败](https://github.com/home-assistant/core/pull/152794)。
在 Home Assistant Core 2026.11 中,指定了 device filter 的 target selector 验证将会失败。
### 背景
从 target 中提取 entity 的核心 helper 不支持 device filter。当用户选择 floor、area、label 或 category 时,filter 会被忽略。前端在选择 target 的 entity 时也会忽略 device filter。
对所有核心集成的审查发现,没有在 target selector 上正确使用 device filter 的情况。我们没有在 Core 和前端中实现对 device filter 的支持,而是直接从 target selector 中移除了 device filter。
### 对自定义集成和 blueprint 的影响
请更新自定义集成和 blueprint,从 target selector 中移除 device filter。
如果您在 target selector 上使用 device filter 有合理的用例,请通过 Discord 联系我们。
---
url: /developers/blog/2025-10-16-recorder-statistics-api-changes.md
---
注入和修改 statistics 的 Python 和 WS API 已发生变更。
以下是对 WS API 所做的变更:
* WS 命令 `recorder/update_statistics_metadata` 接受指向用于 unit 转换的 unit converter 的 `unit_class`。如果没有兼容的 unit converter,`unit_class` 应设置为 `null`。不指定 `unit_class` 已被弃用,并将在 Home Assistant Core 2026.11 中停止工作。
* WS 命令 `recorder/import_statistics` 中包含的 `metadata` 对象接受指向用于 unit 转换的 unit converter 的 `unit_class`。如果没有兼容的 unit converter,`unit_class` 应设置为 `null`。不指定 `unit_class` 已被弃用,并将在 Home Assistant Core 2026.11 中停止工作。
* WS 命令 `recorder/import_statistics` 中包含的 `metadata` 对象接受 `mean_type`,用于指定 mean 的类型(`0` 表示无 mean,`1` 表示算术 mean,`2` 表示圆周 mean)。`mean_type` 替换了 bool 标志 `has_mean`。不指定 `mean_type` 已被弃用,并将在 Home Assistant Core 2026.11 中停止工作。
* WS 命令 `recorder/list_statistic_ids` 和 `recorder/get_statistics_metadata` 的响应中的 items 包含 `mean_type` 和 `unit_class`。
* WS 命令 `recorder/list_statistic_ids` 和 `recorder/get_statistics_metadata` 的响应中 items 的 `has_mean` 已被弃用,并将在 Home Assistant Core 2026.11 中移除。
以下是对 Python API 所做的变更:
* 函数 `async_update_statistics_metadata` 接受指向用于 unit 转换的 unit converter 的 `new_unit_class`。如果没有兼容的 unit converter,`new_unit_class` 应设置为 `None`。不指定 `new_unit_class` 已被弃用,并将在 Home Assistant Core 2025.11 中停止工作。
* 传递给函数 `async_import_statistics` 和 `async_add_external_statistics` 的 metadata 对象接受指向用于 unit 转换的 unit converter 的 `unit_class`。如果没有兼容的 unit converter,`unit_class` 应设置为 `None`。不指定 `unit_class` 已被弃用,并将在 Home Assistant Core 2025.11 中停止工作。
* 传递给函数 `async_import_statistics` 和 `async_add_external_statistics` 的 metadata 对象接受类型为 `StatisticMeanType` 的 `mean_type`,用于指定 mean 的类型(`NONE`、`ARITHMETIC` 或 `CIRCULAR`)。`mean_type` 替换了 bool 标志 `has_mean`。不指定 `mean_type` 已被弃用,并将在 Home Assistant Core 2026.11 中停止工作。
* 函数 `async_list_statistic_ids` 返回值中的 items 包含 `mean_type` 和 `unit_class`。
* 函数 `async_list_statistic_ids` 返回值中 items 的 `has_mean` 已被弃用,并将在 Home Assistant Core 2026.11 中移除。
---
url: /developers/blog/2025-10-24-service-web-api-changes.md
---
在 `strings.json` 中定义的 action translations 不再包含在 WebSocket `get_services` 命令和 REST `/api/services` 端点的响应中,因为它们不完整,且 Home Assistant 前端并未使用它们。
来自 `services.yaml` 的旧版未翻译的 action 名称和描述仍然保留在 WebSocket 和 REST 响应中。
使用 WebSocket 命令 `frontend/get_translations` 获取完整的 action translations。
更多详情请参阅 [core PR 147120](https://github.com/home-assistant/core/pull/147120)。
---
url: /developers/blog/2025-11-04-convert-interval-deprecation.md
---
`TemperatureConverter` 中的方法 `convert_interval`(允许在 °F 和 °C 区间之间转换)已被弃用,建议改用 `TemperatureDeltaConverter.convert`。
新的 converter 和 device class 现在是处理温度区间的推荐方式。
更多详情请参阅 [core PR 155689](https://github.com/home-assistant/core/pull/155689) 和 [core PR 147358](https://github.com/home-assistant/core/pull/147358)。
---
url: /developers/blog/2025-11-05-config-entry-oauth2-error-handling.md
---
使用 [Application Credentials](https://developers.home-assistant.io/docs/core/platform/application_credentials/) 和 [Configuration via OAuth2](https://developers.home-assistant.io/docs/core/integration/config_flow/#configuration-via-oauth2) 的集成需要更新其错误处理,以在网络断开时正确处理配置。
目前,使用 OAuth2 配置的集成在 `__init__.py` 中的 `async_setup_entry` 里调用 `config_entry_oauth2_flow.async_get_config_entry_implementation`。以前在没有网络时,这会抛出 `ValueError: Implementation not available`,这是一个不可重试的错误,导致集成在网络恢复后需要手动重新配置(见 Issue [153956](https://github.com/home-assistant/core/issues/153956) 和 [144582](https://github.com/home-assistant/core/issues/144582))。[core PR 154579](https://github.com/home-assistant/core/pull/154579) 添加了 `config_entry_oauth2_flow.ImplementationUnavailableError`,并在因缺少网络导致 OAuth2 配置不可用时,在 `config_entry_oauth2_flow.async_get_config_entry_implementation` 中抛出该错误。集成应捕获此错误并抛出 `ConfigEntryNotReady`。
带新异常的行为变更将在 2025.12 中发布。
以下是迁移示例:
```diff
- implementation = await async_get_config_entry_implementation(hass, entry)
+ try:
+ implementation = await async_get_config_entry_implementation(hass, entry)
+ except ImplementationUnavailableError as err:
+ raise ConfigEntryNotReady(
+ "OAuth2 implementation temporarily unavailable, will retry"
+ ) from err
```
新集成将通过 `python3 -m script.scaffold config_flow_oauth2` 生成正确的 `try` / `except` 块。
---
url: /developers/blog/2025-11-10-calculated-state-capability-attributes-removed.md
---
`capability_attributes` 字段已从 `CalculatedState` 中移除。Capability attributes 仍然包含在所有 attributes 中,这些 attributes 在 `CalculatedState` 中仍然可用。
`CalculatedState` 是包含 state 和 attributes 的容器,由 `Entity._async_calculate_state` 返回。
更多详情请参阅 [core PR 151672](https://github.com/home-assistant/core/pull/151672)。
---
url: /developers/blog/2025-11-17-retry-after-update-failed.md
---
使用 [Data Update Coordinator](https://developers.home-assistant.io/docs/integration_fetching_data/#coordinated-single-api-poll-for-data-for-all-entities) 的集成可以通过新参数 `retry_after` 增强 `UpdateFailed` 异常,以延迟下一次计划的刷新指定秒数,然后在 API 恢复后恢复正常节奏。
在轮询 API 因超载而返回 HTTP 429 或在响应头中提供 `Retry-After` 的情况下,集成现在可以遵守这些 backoff 信号。
集成和 API 客户端必须检测这些 backoff 信号并清理 API 期望的 backoff 时间段。`UpdateFailed` 异常接受一个 `retry_after` 参数(以秒为单位的 float),用于延迟下一次计划的刷新。一旦 API 恢复且不再抛出 `UpdateFailed`,集成将恢复其正常的 `update_interval`。
使用示例:
```python
try:
request = await self.client.get_information()
except APIClientRateLimited as err:
raise UpdateFailed(
retry_after=60 # 也可以从 API 响应中获取,或提供默认值
) from err
```
#### ConfigEntryNotReady
`retry_after` 参数在 Update Coordinator 设置阶段(`async_config_entry_first_refresh`)被忽略。如果第一次刷新失败,Home Assistant 将抛出 `ConfigEntryNotReady` 异常,允许 config entry 设置使用内置的 retry 自动重试。一旦 coordinator 设置成功,`retry_after` 将应用于后续的刷新。
---
url: /developers/blog/2025-11-20-labs-preview-features.md
---
我们很高兴宣布 Home Assistant 中用于发布预览功能的新系统:**Labs**。Labs 为集成提供了一种标准化的方式,在功能成为标准之前让用户可以选择加入,从而使我们能够收集反馈并根据实际使用来完善设计。
## 什么是 Labs 预览功能?
Labs 预览功能不同于 beta 测试。虽然 beta 测试评估即将发布的 Home Assistant 版本的稳定性,但 Labs 侧重于完善用户界面和设计。Labs 功能经过全面测试且功能完整,但在我们收集实际使用和反馈的过程中,其设计和行为仍可能改变。这意味着它们可能包含 breaking changes,可能被扩展新功能,甚至如果效果不佳可能会被移除。
可以这样理解:
* **Beta**:评估即将发布的 Home Assistant 版本的稳定性
* **Labs**:经过全面测试的功能,设计和用户界面在不断演进,通过实际使用和反馈进行完善
## 工作原理
集成在其 `manifest.json` 中声明预览功能,并提供反馈、文档和 issue 报告的链接。然后用户可以在 **Settings** → **System** → **Labs** 中启用这些功能,它们会立即激活而无需重启。集成代码会检查功能是否已启用并相应地作出响应。
## 为什么需要 Labs?
我们许多最重要的改进在成为标准之前都需要经过实际测试。Labs 提供:
1. **结构化反馈渠道**:每个功能都有专门的 URL,用于反馈、文档和 issue 报告
2. **运行时激活**:功能可以即时启用和禁用,无需配置更新或重启
3. **清晰的预期**:用户知道他们正在尝试经过全面测试的功能,其设计可能会根据反馈而改变
4. **迭代开发**:将用户反馈直接融入开发过程
## 示例:Kitchen Sink special repair
[Kitchen Sink](https://www.home-assistant.io/integrations/kitchen_sink/) 演示集成包含一个工作示例。启用时,"special repair" 功能会创建一个 repair issue,以演示 Labs 功能如何与其他 Home Assistant 集成交互。请参阅[开发文档](/developers/development/labs.md#complete-example-kitchen-sink-special-repair) 获取完整的实现。
## 开始使用
准备向您的集成添加 Labs 预览功能吗?查看我们的[综合指南](/developers/development/labs.md),涵盖:
* 何时使用 Labs(以及何时不使用)
* 如何在 manifest 中定义功能
* 后端和前端功能的实现模式
* 运行时激活要求
* 测试方法
* 功能生命周期(preview → 标准或移除)
## 接下来做什么?
我们鼓励集成开发者考虑将 Labs 用于:
* 重大的 UI 变更或重新设计
* 受益于实际测试的重大架构变更
* 用户反馈将塑造最终设计的功能
Labs **不适合**用于:
* 永久配置选项(请改用集成 options)
* 可以直接进入发布版本的小改动
* 存在关键 bug 或根本上不完整的功能
## 亲自试一试
想看看 Labs 的实际效果?安装 Kitchen Sink 演示集成,并在 Settings → System → Labs 中启用"Special repair"功能。您将亲身体验预览功能的工作原理。
---
url: /developers/blog/2025-11-21-identify-buttons-diagnostic-category.md
---
表示设备识别机制的 button entity(使用 `IDENTIFY` device class)现要求其 entity category 设置为 `DIAGNOSTIC`。
有关 entity category 的更多详情,请参阅[文档](/developers/core/entity.md#registry-properties)。
---
url: /developers/blog/2025-11-23-mqtt-subscribe-wait.md
---
## 为 MQTT subscription 添加状态回调
使用 MQTT 的集成可能需要等待 subscription 完成后再执行操作。默认行为是 subscription 会被排队并去抖,因此调用者通常不会等待 broker 确认。某些集成必须保证 broker 已完成 subscription。
新的 `mqtt.async_on_subscribe_done` helper 可用于监控 MQTT subscription,以允许执行额外任务。
请确保使用与 MQTT subscription 相同的 QoS。
示例:
```python
from homeassistant.components import mqtt
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Setup integration MQTT subscription monitoring."""
def _on_subscribe_status() -> None:
"""Handle subscription ready signal."""
# Do stuff
# Handle subscription ready status update
await mqtt.async_on_subscribe_done(
hass,
"myintegration/status",
qos=1,
on_subscribe_status=_on_subscribe_status,
)
# Do stuff
```
---
url: /developers/blog/2025-11-25-storage-helper-opt-in-serialize-in-executor.md
---
来自 `homeassistant/helpers/storage.py` 的 `Store` 类接受新的构造函数参数 `serialize_in_event_loop`
如果 `serialize_in_event_loop` 为 `True`(默认值):
* 传递给 `Store.async_delay_save` 的 `data_func` 从 event loop 调用
* 传递给 `Store.async_delay_save` 的 `data_func` 产生的数据在 event loop 中序列化为 JSON
* 传递给 `Store.async_save` 的数据在 event loop 中序列化为 JSON
如果 `serialize_in_event_loop` 为 `False`:
* 传递给 `Store.async_delay_save` 的 `data_func` 从单独的 thread 调用,这意味着它必须是 thread-safe 的,且不得访问 hass 对象
* 传递给 `Store.async_delay_save` 的 `data_func` 产生的数据在单独的 thread 中序列化为 JSON,这意味着它必须是 thread-safe 的
* 传递给 `Store.async_save` 的数据在单独的 thread 中序列化为 JSON,这意味着它必须是 thread-safe 的
此行为已发生变更;以前传递给 `Store.async_delay_save` 的 `data_func` 总是从单独的 thread 调用,其产生的数据或传递给 `Store.async_save` 的数据以前总是由单独的 thread 序列化。
进行此项变更的原因是,之前并未文档说明 `data_func` 将由 event loop 以外的 thread 调用,或 JSON 序列化将在 event loop 以外的 thread 中发生,而 `data_func` 及其产生的数据或传递给 `Store.async_save` 的数据通常并非 thread-safe 的。
更多详情请参阅 [core PR 157158](https://github.com/home-assistant/core/pull/157158) 和 [core PR 157263](https://github.com/home-assistant/core/pull/157263)。
---
url: /developers/blog/2025-11-27-service-translation-placeholders.md
---
现在可以使用 translation placeholders 来处理(自定义)service actions。
[service action 示例](/developers/core/integration-quality-scale/rules/action-setup.md?_highlight=hass.services.async_register#example-implementation) 现在展示了如何在注册 service action 时提供可用的 description placeholders。
将 URL 从 service 描述和 translation strings 中移动到 description placeholders。
---
url: /developers/blog/2026-01-05-pyserial-asyncio-fast.md
---
### 变更摘要
从 `2026.7` 起,在 Home Assistant 中安装 `pyserial-asyncio` 将被阻止。
建议库维护者和自定义集成迁移到 `pyserial-asyncio-fast`。
### 背景
`pyserial-asyncio` 因为执行阻塞式 `sleep` 而阻塞 event loop。该库也不再维护,因此改善状况的努力尚未发布。
`pyserial-asyncio-fast` 被创建为替代方案(见[仓库](https://github.com/home-assistant-libs/pyserial-asyncio-fast)),所有核心集成现已迁移完成。
### 迁移
`pyserial-asyncio-fast` 被设计为 `pyserial-asyncio` 的替代方案,必要的更改非常简单。
#### 依赖项
```python
# 旧方式
install_requires=[
"pyserial-asyncio"
]
# 新方式
install_requires=[
"pyserial-asyncio-fast"
]
```
#### 用法
```python
# 旧方式
import serial_asyncio
async def connect():
conn = await serial_asyncio.open_serial_connection(**self.serial_settings)
# 新方式
import serial_asyncio_fast
async def connect():
conn = await serial_asyncio_fast.open_serial_connection(**self.serial_settings)
```
更多示例请参阅[跟踪 pull request](https://github.com/home-assistant/core/pull/116635)。
---
url: /developers/blog/2026-01-13-replace-pre-commit-with-prek.md
---
通过将 `pre-commit` 替换为 [`prek`](https://prek.j178.dev/),我们可以提升检查的性能。Prek 使用与 `pre-commit` 相同的 `.pre-commit-config.yaml`,是完整的替代方案。由于 `prek` 是用 Rust 编写的,并允许并行执行不同的 job,我们可以更快地检查代码。
新的开发环境将自动安装 `prek`,对于现有的开发环境,请通过运行 `uv pip install requirements_test.txt` 更新测试依赖。
---
url: /developers/blog/2026-02-16-labs-async-listen-deprecation.md
---
`labs` 集成中的 `async_listen` helper 已被弃用,建议改用 `async_subscribe_preview_feature`。
新的 `async_subscribe_preview_feature` 函数提供了更一致的 API,其中 listener callback 会接收到一个包含更新后 feature state 的 `EventLabsUpdatedData` 参数。这消除了需要在 listener 内部分别调用 `async_is_preview_feature_enabled` 来检查当前值的需要。
### 旧用法
```python
from homeassistant.components.labs import async_is_preview_feature_enabled, async_listen
def my_listener() -> None:
if async_is_preview_feature_enabled(hass, DOMAIN, "my_feature"):
# feature 已启用
...
async_listen(
hass,
domain=DOMAIN,
preview_feature="my_feature",
listener=my_listener,
)
```
### 新用法
```python
from homeassistant.components.labs import EventLabsUpdatedData, async_subscribe_preview_feature
async def my_listener(event_data: EventLabsUpdatedData) -> None:
if event_data["enabled"]:
# feature 已启用
...
async_subscribe_preview_feature(
hass,
domain=DOMAIN,
preview_feature="my_feature",
listener=my_listener,
)
```
请注意,新的 listener 是一个协程函数,并接收 `EventLabsUpdatedData` 作为参数。
`async_listen` 将在 Home Assistant 2027.3 中移除。
更多详情请参阅 [core PR #162648](https://github.com/home-assistant/core/pull/162648)。
---
url: /developers/blog/2026-02-18-reconfiguration-support-for-webhook-helper.md
---
使用 webhook config flow helper(`homeassistant.helpers.config_entry_flow.register_webhook_flow`)的集成现已支持 reconfiguration。这使得集成能够重新获取 webhook,或在 Home Assistant 实例 URL 更改时获取更新的 webhook。
使用 webhook config flow helper 的自定义集成必须为 reconfiguration flow 添加 translation strings。
**reconfiguration flow 的示例 translation strings:**
```json
{
"config": {
"abort": {
"reconfigure_successful": "**Reconfiguration was successful**\n\nIn Sleep as Android go to *Settings → Services → Automation → Webhooks* and update the webhook with the following URL:\n\n`{webhook_url}`"
},
"step": {
"reconfigure": {
"description": "Are you sure you want to re-configure the Sleep as Android integration?",
"title": "Reconfigure Sleep as Android"
}
}
}
}
```
更多详情请参阅 [core PR #151729](https://github.com/home-assistant/core/pull/151729)。
---
url: /developers/blog/2026-02-19-oauth-token-request-error-handling.md
---
## 变更摘要
自 `2026.3` 起,我们正在增强 OAuth 2.0 helper 处理 token request 和 refresh token 失败的方式。此变更使错误处理更加健壮,不再依赖于 aiohttp 库,并帮助使用 [Data Update Coordinator](https://developers.home-assistant.io/docs/integration_fetching_data/#coordinated-single-api-poll-for-data-for-all-entities) 的集成自动触发正确的错误处理。
## 有哪些变更
当 OAuth 2.0 token request 或 token refresh 失败时,Home Assistant 会允许底层的 `aiohttp.ClientResponseError` 直接传播到集成。此行为正在变更和增强。
我们引入了三个新的异常,提供 clearer 的语义:
* `OAuth2TokenRequestTransientError` - 可恢复的错误,可以重试。
* `OAuth2TokenRequestReauthError` - 不可恢复的错误,需要重新认证。
* `OAuth2TokenRequestError` - 当上述两个条件都不满足时的基类异常,或允许集成捕获所有 token request 异常。
### Data Update Coordinator
大多数使用 OAuth 2.0 helper 的集成,也使用 Data Update Coordinator。当 token request 或 refresh token 失败时,异常将在 Data Update Coordinator 中冒泡,并触发以下错误处理:
对于不可恢复的错误(400+,除 429(rate limit)外):
* `OAuth2TokenRequestReauthError`:Data Update Coordinator 会在需要抛出异常时抛出 `ConfigEntryAuthFailed`,或启动重新认证 flow。
对于 transient 错误(500+ 和 429):
* `OAuth2TokenRequestTransientError`:Data Update Coordinator 将其视为 `UpdateFailed`,并触发 retry 机制。
这意味着使用 OAuth 2.0 helper 结合 Data Update Coordinator 的集成不需要对这些新异常做任何特殊处理。
### 迁移
目前使用 OAuth 2.0 helper 且明确处理 `aiohttp.ClientResponseError` 的集成应调整其错误处理,以应对新异常。为便于过渡,我们添加了一个兼容层,让新的 OAuth 异常继承自 `aiohttp.ClientResponseError`。捕获此异常类型的现有代码应该继续正常工作。但建议重构代码以使用新异常。详情请参阅代码示例。
#### 迁移代码示例
更新异常处理,然后继续判断是否为(不可)恢复错误。例如:
```python
try:
await auth.async_get_access_token()
except OAuth2TokenRequestReauthError as err:
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN, translation_key="reauth_required"
) from err
except (OAuth2TokenRequestError, ClientError) as err:
raise ConfigEntryNotReady(
translation_domain=DOMAIN, translation_key="auth_server_error"
) from err
```
---
url: /developers/blog/2026-02-23-remove-deprecate-light-features.md
---
### 变更摘要
2022 年 10 月,Home Assistant 将首选色温单位从 mired 迁移到了 kelvin。
在 [2024 年 2 月](/developers/blog/2024-02-12-light-color-mode-mandatory.md),Home Assistant 要求显式提供 `supported_color_modes` 和 `color_mode` 属性(触发了对传统 fallback color mode 支持的弃用)。
在 [2024 年 12 月](/developers/blog/2024-12-14-kelvin-preferred-color-temperature-unit.md),Home Assistant 要求显式支持 Kelvin(触发了对 mired 支持的弃用)。
现在是清理遗留代码并移除相应属性、常量和属性的时候了:
* 移除已弃用的 `ATTR_COLOR_TEMP`、`ATTR_MIN_MIREDS`、`ATTR_MAX_MIREDS`、`ATTR_KELVIN`、`COLOR_MODE_***` 和 `SUPPORT_***` 常量
* 移除已弃用的 state 属性 `ATTR_COLOR_TEMP`、`ATTR_MIN_MIREDS` 和 `ATTR_MAX_MIREDS`
* 移除 `light.turn_on` 服务调用中对 `ATTR_KELVIN` 和 `ATTR_COLOR_TEMP` 参数的已弃用支持
* 移除 entity 中对 `LightEntity.color_temp`、`LightEntity.min_mireds` 和 `LightEntity.max_mireds` 属性的已弃用支持
* 移除 entity 中对 `LightEntity._attr_color_temp`、`LightEntity._attr_min_mireds` 和 `LightEntity._attr_max_mireds` 简写属性的已弃用支持
此外,未提供有效的 `supported_color_modes` 和 `color_mode` 属性将不再可行,并会引发错误。
### 示例
#### 自定义最小/最大色温
```python
class MyLight(LightEntity):
"""Representation of a light."""
# 旧
# _attr_min_mireds = 200 # 5000K
# _attr_max_mireds = 400 # 2500K
# 新
_attr_min_color_temp_kelvin = 2500 # 400 mireds
_attr_max_color_temp_kelvin = 5000 # 200 mireds
```
#### 默认最小/最大色温
```python
from homeassistant.components.light import DEFAULT_MAX_KELVIN, DEFAULT_MIN_KELVIN
class MyLight(LightEntity):
"""Representation of a light."""
# 旧方式无需设置 _attr_min_mireds / _attr_max_mireds
# 新方式需要显式设置默认值
_attr_min_color_temp_kelvin = DEFAULT_MIN_KELVIN
_attr_max_color_temp_kelvin = DEFAULT_MAX_KELVIN
```
#### 动态最小/最大色温
```python
from homeassistant.util import color as color_util
class MyLight(LightEntity):
"""Representation of a light."""
# 旧
# def min_mireds(self) -> int:
# """Return the coldest color_temp that this light supports."""
# return device.coldest_temperature
#
# def max_mireds(self) -> int:
# """Return the warmest color_temp that this light supports."""
# return device.warmest_temperature
# 新
def min_color_temp_kelvin(self) -> int:
"""Return the warmest color_temp that this light supports."""
return color_util.color_temperature_mired_to_kelvin(device.warmest_temperature)
def max_color_temp_kelvin(self) -> int:
"""Return the coldest color_temp that this light supports."""
return color_util.color_temperature_mired_to_kelvin(device.coldest_temperature)
```
#### 在服务调用中检查色温
```python
from homeassistant.components.light import ATTR_COLOR_TEMP_KELVIN
from homeassistant.util import color as color_util
class MyLight(LightEntity):
"""Representation of a light."""
def turn_on(self, **kwargs: Any) -> None:
"""Turn on the light."""
# 旧
# if ATTR_COLOR_TEMP in kwargs:
# color_temp_mired = kwargs[ATTR_COLOR_TEMP]
# color_temp_kelvin = color_util.color_temperature_mired_to_kelvin(color_temp_mired)
# 旧
# if ATTR_KELVIN in kwargs:
# color_temp_kelvin = kwargs[ATTR_KELVIN]
# color_temp_mired = color_util.color_temperature_kelvin_to_mired(color_temp_kelvin)
# 新
if ATTR_COLOR_TEMP_KELVIN in kwargs:
color_temp_kelvin = kwargs[ATTR_COLOR_TEMP_KELVIN]
color_temp_mired = color_util.color_temperature_kelvin_to_mired(color_temp_kelvin)
```
### 背景信息
* [关于 Kelvin 温度的社区讨论](https://community.home-assistant.io/t/wth-is-light-temperature-not-in-kelvin/467449/6)
* [Core PR #79591: 迁移到 Kelvin](https://github.com/home-assistant/core/pull/79591)
* [Architecture discussion #564](https://github.com/home-assistant/architecture/discussions/564)
* [Color modes 文档](/developers/core/entity/light.md#color-modes)
---
url: /developers/blog/2026-02-24-brands-proxy-api.md
---
从 Home Assistant 2026.3 开始,自定义集成可以直接在集成目录中包含自己的品牌图片(图标和 logo)。无需再提交到单独的仓库——只需将图片放入 `brand/` 文件夹即可在 UI 中显示。
## 自定义集成本地品牌图片
在你的自定义集成中添加一个 `brand/` 目录,包含你的图标和 logo 文件:
```text
custom_components/my_integration/
├── __init__.py
├── manifest.json
└── brand/
├── icon.png
└── logo.png
```
支持以下图片文件名:
* `icon.png` / `dark_icon.png`
* `logo.png` / `dark_logo.png`
* `icon@2x.png` / `dark_icon@2x.png`
* `logo@2x.png` / `dark_logo@2x.png`
本地品牌图片会自动优先于 brands CDN 中的图片。无需任何额外配置。
更多详情,请参阅 [integration file structure 文档](/developers/creating_integration_file_structure.md#brand-images---brand)。
## 品牌图片现在通过本地 API 提供
为了使本地品牌图片成为可能,所有品牌图片现在都通过 Home Assistant 本地 API 提供,而不是由浏览器直接从 CDN 获取。
一个新的 `brands` system integration 通过两个端点代理品牌图片:
* `/api/brands/integration/{domain}/{image}` — 集成的图标和 logo
* `/api/brands/hardware/{category}/{image}` — 硬件图片
图片会在本地磁盘上缓存,并使用 stale-while-revalidate 策略提供,因此在网络中断期间仍然可用。
### 对 frontend 的影响
`src/util/brands-url.ts` 中的 `brandsUrl()` 和 `hardwareBrandsUrl()` helper 现在返回本地 API 路径,而不是 CDN URL。如果你的自定义卡片或面板使用了这些 helper,则无需任何更改。
如果你是在手动构建品牌图片 URL,请进行更新:
```typescript
// 旧
const url = `https://brands.home-assistant.io/_/${domain}/icon.png`;
// 新
import { brandsUrl } from "../util/brands-url";
const url = brandsUrl({ domain, type: "icon" });
```
这些端点需要身份验证。`brandsUrl()` helper 会自动处理此问题,通过追加 access token。如果你手动构建 URL,则需通过 `brands/access_token` WebSocket 命令获取 token,并将其作为 `token` 查询参数追加。
---
url: /developers/blog/2026-03-11-backup-upload-progress.md
---
`BackupAgent.async_upload_backup` 方法现在接收一个新的 `on_progress` 回调参数。Backup agents 可以在上传期间定期调用此回调,以报告到目前为止已上传的字节数:
```python
class ExampleBackupAgent(BackupAgent):
async def async_upload_backup(
self,
*,
open_stream: Callable[[], Coroutine[Any, Any, AsyncIterator[bytes]]],
backup: AgentBackup,
on_progress: OnProgressCallback,
**kwargs: Any,
) -> None:
"""Upload a backup."""
...
bytes_uploaded = 0
async for chunk in await open_stream():
await do_upload(chunk)
bytes_uploaded += len(chunk)
on_progress(bytes_uploaded=bytes_uploaded)
...
```
Backup manager 使用这些进度报告来触发 `UploadBackupEvent` 事件,使 frontend 能够向用户显示实时上传进度。
更多详情,请查看 [backup agent 文档](/developers/core/platform/backup.md#backup-agents)。
---
url: /developers/blog/2026-03-25-frontend-component-updates-2026.4.md
---
:::info
我们不官方支持或鼓励自定义卡片开发者使用我们内置的 components。这些 component APIs 随时可能变更,你应该将你的卡片构建为独立组件。
:::
## ha-input
我们持续将基于 Material Design 的 components 迁移到基于 Web Awesome。这次我们迁移了 input 组件,这导致了 API 变更,但视觉效果暂时保持不变。
* `ha-input` 是 `ha-textfield` 的继任者
* `ha-textfield` 的 API 保持不变,但组件已迁移为内部使用 `ha-input`,并将在 2026.5 中移除
* 同时替换 `ha-outlined-text-field`
* `ha-input-search` 替换 `search-input` 和 `search-input-outlined`
* `ha-input-multi` 替换 `ha-multi-textfield`
* `ha-input-copy` 替换 `copy-textfield`
此组件还引入了新的表单背景语义化 theme 变量:
```css
--ha-color-form-background: var(--ha-color-neutral-95);
--ha-color-form-background-hover: var(--ha-color-neutral-90);
--ha-color-form-background-disabled: var(--ha-color-neutral-80);
```
## 日期选择器
我们终于通过用 [Cally](https://wicky.nillia.ms/cally/) 替换 date 和 date range picker 移除了 Vue 2 依赖。
---
url: /developers/blog/2026-03-25-frontend-dialogs.md
---
## 问题
由 dialog manager 管理的每个 dialog 只打开一次,并在整个应用程序生命周期内保留在 DOM 中。这会导致:
* **更多内存占用**:Dialogs 即使在不可见时也会堆积在 DOM 中
* **因缺少 state 重置而导致的更多 bugs**:Dialog state 在多次打开之间持久存在,导致过期数据或意外行为
## 解决方案:DialogMixin
我们使用 `DialogMixin` 实现了一种处理 dialogs 的新方式。这种方式具有以下特点:
* **Dialogs 在打开时创建,关闭时销毁**:无需在关闭时手动重置 dialog 的 state
* **关闭事件自动处理**:Dialog mixin 负责清理工作
* **Subscribe mixin 现在可以在 dialogs 中使用**:由于 dialogs 被正确销毁,subscriptions 会被自动清理
* **使用正常的 Lit lifecycle methods**:使用 `connectedCallback` 在 dialog 打开时初始化,而不是依赖 `showDialog` 方法
## 示例
查看 `ha-dialog-date-picker` 作为参考实现。`DialogMixin` 如果可用的话,会将 dialog params 添加到 `this.params`。
---
url: /developers/blog/2026-03-25-frontend-lazy-context.md
---
## 什么是 context?
在 Home Assistant frontend 中,[Context](https://lit.dev/docs/data/context/) 是一种在 component 树中共享数据的方式,无需显式地通过每一层作为 property 传递。通过 context 提供特定的数据片段,并仅在需要的地方消费它们,而不是将 `hass` 对象层层传递到多个 component 层级。
使用 context 而非传递整个 `hass` 对象的关键优势:
* **更易于使用**:Components 可以直接消费所需数据,无需父级 components 将其向下传递。这减少了 prop drilling,使 components 更加自包含且可复用。
* **减少不必要的 component re-renders**:当 component 以 property 形式接收 `hass` 时,`hass` 的任何变更都会触发该 component 及其所有子组件的 re-render——即使该 component 只关心数据的一个小子集。通过 context 仅提供 component 所需的特定数据,可以确保 components 仅在实际依赖发生变更时才 re-render,从而实现更好的性能和更响应的 UI。
## 引入 LazyContext
我们引入了一种新的 `LazyContext` 模式,它应替换传统的基于订阅的方式以及 `SubscribeMixin` 的使用。以前,components 会订阅数据源并手动管理订阅生命周期,这往往导致样板代码,且如果订阅未正确清理,则可能导致内存泄漏。
`LazyContext` 简化了这一点:
* **Lazy loading**:数据仅在 component 实际消费该 context 时才被获取
* **Automatic cleanup**:Subscriptions 被自动管理
* **Shared state**:消费同一 context 的多个 components 共享单个订阅
* **Optimized re-renders**:仅在数据变更时,消费该 context 的 components 才会 re-render
这种方式集中了 data-fetching 逻辑,使你更容易推理数据何时以及如何流经应用程序。
## 示例
### 定义一个 LazyContext
要定义一个 lazy context,使用 `LazyContextProvider` 并提供一个 fetch 函数:
```ts
new LazyContextProvider(this, {
context: labelsContext,
subscribeFn: (connection, setValue) => subscribeLabelRegistry(connection, setValue),
})
```
### 使用 lit 消费 context
要在 component 中消费 context,使用 `@consume` decorator:
```ts
@state()
@consume({ context: labelsContext, subscribe: true })
private _labels?: LabelRegistryEntry[];
```
查看更新后的 custom card 示例,以在 vanilla JS 中使用它:[Custom card example](/developers/frontend/custom-ui/custom-card.md#defining-your-card)。
### 使用 @transform 处理派生数据
*仅在 home-assistant frontend codebase 内可用*
`@transform` decorator 允许你从 context value 派生数据,确保 component 仅在转换后的值实际变更时才 re-render。
```ts
@state()
@consume({ context: statesContext, subscribe: true })
@transform({
transformer: function (this: HuiButtonCard, entityStates: HassEntities) {
return this._config?.entity ? entityStates?.[this._config?.entity] : undefined;
},
watch: ["_config"],
})
private _stateObj?: HassEntity;
```
使用 `@transform`,即使完整的 states 对象更新,你的 component 也仅会在转换后的结果(`_stateObj`)实际变更时才 re-render。`watch` 选项允许你指定应触发 transformer 函数重新求值的附加属性——在这种情况下,当 `_config` 变更时,transformer 会再次运行以提取正确的 entity state。
---
url: /developers/blog/2026-03-30-infrared-entity-platform.md
---
Home Assistant 现在有了一个 `infrared` entity platform,它将 IR 发射器硬件与它们所控制的设备解耦。每个设备集成不再直接与特定 IR 硬件通信,而是由发射器集成(如 `esphome`)暴露 `InfraredEntity` 实例,设备集成(如 `lg_infrared`)通过 helper 函数向其发送命令。
查看 [architecture discussion](https://github.com/home-assistant/architecture/discussions/1316) 了解完整背景。
## 架构
Infrared domain 位于两类集成之间:
* **发射器集成**(ESPHome、Broadlink 等)实现 `InfraredEntity` base class 以提供特定硬件的 IR 传输。
* **消费方集成**(LG、Samsung、Daikin 等)使用 helper 函数通过可用的发射器发送特定设备的 IR 命令。
用户在消费方集成的 config flow 期间选择要使用哪个发射器。
## 实现一个发射器集成
发射器集成通过继承 `InfraredEntity` 并实现 `async_send_command` 来提供 `infrared` platform。Command 的 `get_raw_timings()` 方法返回与协议无关的 timing 数据,硬件可以据此传输:
```python
from homeassistant.components.infrared import InfraredCommand, InfraredEntity
class MyInfraredEntity(InfraredEntity):
"""My IR transmitter."""
async def async_send_command(self, command: InfraredCommand) -> None:
"""Send an IR command."""
timings = [
interval
for timing in command.get_raw_timings()
for interval in (timing.high_us, -timing.low_us)
]
await self._device.transmit(
carrier_frequency=command.modulation,
timings=timings,
)
```
原始与协议无关的 timings 应转换为硬件所需的具体格式。
## 构建消费方集成
消费方集成通过向发射器 entity 发送命令来控制 IR 设备。它们不直接与 IR 硬件交互。
**1. 在 `manifest.json` 中声明依赖**:
```json
{
"dependencies": ["infrared"]
}
```
**2. 在 config flow 中让用户选择发射器**:
```python
from homeassistant.components import infrared
emitters = infrared.async_get_emitters(hass)
if not emitters:
return self.async_abort(reason="no_emitters")
```
**3. 使用 helper 函数和 [`infrared-protocols`](https://github.com/home-assistant-libs/infrared-protocols) 库发送 IR 命令**:
```python
from infrared_protocols.codes.lg.tv import LGTVCode, make_lg_tv_command
from homeassistant.components import infrared
await infrared.async_send_command(
hass,
self._infrared_entity_id,
make_lg_tv_command(LGTVCode.VOLUME_UP),
context=self._context,
)
```
## IR 协议和 codes
IR protocol 编码器及设备 code 集合存放在 Home Assistant 之外的 [`infrared-protocols`](https://github.com/home-assistant-libs/infrared-protocols) 库中。核心集成所使用的常见协议(NEC、Samsung 等)和知名设备 codes 应贡献到那里。对于小众或专有协议,也可以使用单独的第三方库。
更多详情,请参阅 [infrared entity 文档](/developers/core/entity/infrared.md)。
---
url: /developers/blog/2026-04-02-builder-migration.md
---
传统的 `home-assistant/builder` container 以及旧的 `home-assistant/builder` GitHub Action 已被废弃。我们建议你按照本文所述,迁移所有 app(原 add-ons)的 GitHub workflows 和 Dockerfiles。
## 变更内容及原因
旧版 builder 在单个特权 Docker-in-Docker container 中使用 QEMU 模拟运行每种架构的 build。这速度慢、需要高权限,而且已经熟悉 Docker 的用户还需要学习如何使用自定义的 Home Assistant builder container。旧版 builder 还存在不必要的维护开销。如今,builder 所做的一切都可以完全用 Docker BuildKit 替代,GitHub Actions runners 原生支持它,并且在需要时内置了带 QEMU 模拟的多架构支持。
对于 CI 来说,替代方案是一组专注的 [composite GitHub Actions](https://github.com/home-assistant/builder),它们将构建任务委托给 runner 的原生 Docker 并使用 Docker BuildKit。在 CI 之外,迁移意味着你的 `Dockerfile` 现在成为构建 app 镜像的唯一可信来源,你可以直接使用 `docker build` 在本地构建和测试你的 app,而无需使用 builder container。
## 迁移过程
迁移包括两部分:更新 Dockerfiles 和更新 GitHub Actions workflows。
### 更新 Dockerfiles
新的 build workflow 不再使用 `build.yaml`。将内容移动到 `Dockerfile` 中,如下所示:
* **`build_from`** — 用 `Dockerfile` 中的 `FROM` 语句替换 `build.yaml` 中的 `build_from` key:
```dockerfile
FROM ghcr.io/home-assistant/base:latest
```
由于 base images 现在作为 multi-platform manifests 发布,通常不再需要为每种架构定义 base images。`build-image` action 仍然提供 `BUILD_ARCH` 作为 build argument,如果你需要在 base image 名称模板中使用它,可以在 `Dockerfile` 中使用。
* **`labels`** — 将所有自定义 Docker labels 直接移动到 `Dockerfile` 中,使用 `LABEL` 语句:
```dockerfile
LABEL \
org.opencontainers.image.title="Your awesome app" \
org.opencontainers.image.description="Description of your app." \
org.opencontainers.image.source="https://github.com/your/repo" \
org.opencontainers.image.licenses="Apache License 2.0"
```
如果你正在创建自定义 workflow,请注意旧版 builder 会自动添加 `io.hass.type`、`io.hass.name`、`io.hass.description` 和 `io.hass.url` labels。新 action 不会推断这些值,因此通过 `build-image`(或类似)action 的 `labels` input 显式添加它们。
* **`args`** — 将自定义 build arguments 作为带默认值的 `ARG` 定义移动到 `Dockerfile` 中:
```dockerfile
ARG MY_BUILD_ARG="default-value"
```
`ARG` 中的默认值替代了之前通过 `build.yaml` 的 `args` 字典提供的值。如果需要,仍然可以在 build 时使用 `--build-arg` 覆盖。
将 `build.yaml` 的内容迁移后,你可以将该文件从仓库中删除。
### 更新 GitHub Actions workflows
移除任何使用 `home-assistant/builder@master` 的 workflow steps,并替换为新的 composite actions。查看我们示例 app 仓库中的 [example workflow](https://github.com/home-assistant/apps-example/blob/main/.github/workflows/builder.yaml) 以获得完整的可工作示例。或者,根据需要,在更自定义的 workflow 中使用 [individual actions](https://github.com/home-assistant/builder?tab=readme-ov-file#example-workflow)。
### 镜像命名
引用已发布的 app image 的首选方式现在是**通用的(multi-arch)名称**,不带架构前缀:
```yaml
# config.yaml
image: "ghcr.io/my-org/my-app"
```
`{arch}` 占位符(例如 `ghcr.io/my-org/{arch}-my-app`)仍然作为兼容性后备方案受支持,但建议改用通用名称,让 manifest 处理平台解析。
### 本地构建
更新 `Dockerfile` 后,你可以使用 `docker build` 直接构建 app 镜像——详情请参阅 [Local app testing](/developers/apps/testing.md)。
## 由 Supervisor 在本地构建的 Apps
为了向后兼容,Supervisor 仍然会读取 `build.yaml` 文件(如果存在),并用从该文件中读取的值填充 image build arguments。这将产生警告,并会在未来的某个时候被移除,因此建议迁移到上述基于 Dockerfile 的方式。
---
url: /developers/blog/2026-04-07-entity-id-mismatched-domain-deprecated.md
---
直接在 entity 上设置 `entity_id` 的集成现在会被验证,以确保 domain 部分与 platform 的 domain 匹配。例如,`light` entity 必须使用 `light.my_light`,而不能使用 `cover.my_light`。
使用错误 domain 设置 entity ID 会记录一条 deprecation 警告,并且将在 **Home Assistant 2027.5 中停止工作**。
在大多数情况下,集成不应设置 `entity_id`——Home Assistant 会自动生成它。
---
url: /developers/blog/2026-04-15-doorbell-standard-event-type.md
---
Doorbell event entities 现在有了一个标准的 `ring` event type。使用 `EventDeviceClass.DOORBELL` 的集成必须在它们的 `event_types` 列表中包含 `DoorbellEventType.RING`。
查看 [architecture discussion](https://github.com/home-assistant/architecture/discussions/1363) 了解完整背景。
## 原因
以前,每个集成使用自己的字符串来表示"门铃被按下"事件——`ding`、`ring`、`doorbell_chime`、`single_press` 等。这种不一致性使得无法构建适用于所有集成的通用 doorbell 自动化。
新的 `DoorbellEventType.RING` 标准 event type 通过确保每个 doorbell 集成在门铃被按下时触发通用的 `ring` 事件来解决这个问题。
## 需要做什么
从 `homeassistant.components.event` 导入 `DoorbellEventType`,并在你的 doorbell entity 的 `event_types` 中包含 `DoorbellEventType.RING`。每当门铃被按下时触发它:
```python
from homeassistant.components.event import DoorbellEventType, EventDeviceClass, EventEntity
class MyDoorbellEvent(EventEntity):
_attr_device_class = EventDeviceClass.DOORBELL
_attr_event_types = [DoorbellEventType.RING]
@callback
def _async_handle_event(self) -> None:
"""Handle the doorbell press event."""
self._trigger_event(DoorbellEventType.RING)
self.async_write_ha_state()
```
除了标准的 `ring` type 之外,仍然允许额外的自定义 event types(例如 `double_press`、`long_press`)。
不包含 `DoorbellEventType.RING` 的 doorbell entities 会记录一条 deprecation 警告,并且将在 **Home Assistant 2027.4 中停止工作**。
更多详情,请参阅 [event entity 文档](/developers/core/entity/event.md)。
---
url: /developers/blog/2026-04-20-legacy-device-tracker-deprecation.md
---
## 摘要
传统的(非 config-entry)device tracker platform API 已弃用,并将在 Home Assistant 2027.5 版本中移除。
在 12 个月的弃用期结束时,所有剩余的 legacy device tracker platforms 都将从核心仓库中移除,实现 legacy API 的自定义集成将停止工作。
集成作者需要更新集成以实现 [modern device tracker platform API](https://developers.home-assistant.io/docs/core/entity/device-tracker)。
## 背景
Config entry device trackers 于 2019 年 5 月推出,这意味着集成作者在 2027 年 5 月支持被移除时,已有 8 年时间迁移集成。
截至今天(2026 年 4 月),大多数广泛使用的核心 device tracker 集成已经迁移。
请注意,尚未迁移的最受欢迎的集成 xiaomi\_miio 具有广泛的功能混合,包括 device tracker 以外的其他功能。
弃用 legacy device tracker API 的提案已在 [architecture proposal 1375](https://github.com/home-assistant/architecture/discussions/1375) 中获得批准。
### 核心集成列表,按报告使用量排序
该列表生成于 2026 年 3 月的 https://analytics.home-assistant.io/
```text
Integration Installations API Type Details
----------------------------------------------------------------------------------------------------
mobile_app 415,204 modern tracker TrackerEntity
mqtt 233,161 modern tracker TrackerEntity
zha 126,903 modern scanner ScannerEntity
ibeacon 89,099 modern unknown BaseTrackerEntity
fritz 42,934 modern scanner ScannerEntity
ping 33,439 modern scanner ScannerEntity
unifi 33,136 modern scanner ScannerEntity
xiaomi_miio 12,866 legacy scanner async_scan_devices
nmap_tracker 7,397 modern scanner ScannerEntity
icloud 5,772 modern tracker TrackerEntity
freebox 5,312 modern scanner ScannerEntity
asuswrt 4,686 modern scanner ScannerEntity
tile 4,212 modern tracker TrackerEntity
renault 3,646 modern tracker TrackerEntity
private_ble_device 3,231 modern unknown BaseTrackerEntity
keenetic_ndms2 3,080 modern scanner ScannerEntity
owntracks 3,022 modern tracker TrackerEntity
snmp 2,923 legacy scanner async_scan_devices
devolo_home_network 2,910 modern scanner ScannerEntity
tesla_fleet 2,798 modern tracker TrackerEntity
netgear 2,782 modern scanner ScannerEntity
mikrotik 2,761 modern scanner ScannerEntity
tplink_omada 2,708 modern scanner ScannerEntity
starlink 2,250 modern tracker TrackerEntity
tractive 2,144 modern tracker TrackerEntity
volvo 2,077 modern tracker TrackerEntity
husqvarna_automower 1,924 modern tracker TrackerEntity
tessie 1,562 modern tracker TrackerEntity
bluetooth_le_tracker 1,011 legacy
traccar_server 978 modern tracker TrackerEntity
huawei_lte 827 modern scanner ScannerEntity
gpslogger 813 modern tracker TrackerEntity
teslemetry 720 modern tracker TrackerEntity
traccar 609 modern tracker TrackerEntity
luci 590 legacy scanner scan_devices
subaru 502 modern tracker TrackerEntity
mysensors 474 modern tracker TrackerEntity
starline 460 modern tracker TrackerEntity
geofency 443 modern tracker TrackerEntity
google_maps 389 legacy
locative 378 modern tracker TrackerEntity
opnsense 355 legacy scanner scan_devices
fing 266 modern scanner ScannerEntity
ruckus_unleashed 232 modern scanner ScannerEntity
synology_srm 193 legacy scanner scan_devices
unifi_direct 190 legacy scanner scan_devices
mqtt_json 182 legacy
vodafone_station 166 modern scanner ScannerEntity
xiaomi 143 legacy scanner scan_devices
demo 141 legacy
ubus 111 legacy scanner scan_devices
bt_smarthub 95 legacy scanner scan_devices
aprs 93 legacy
nrgkick 81 modern tracker TrackerEntity
ddwrt 79 legacy scanner scan_devices
fressnapf_tracker 74 modern tracker TrackerEntity
linksys_smart 69 legacy scanner scan_devices
fortios 53 legacy scanner scan_devices
swisscom 51 legacy scanner scan_devices
tomato 45 legacy scanner scan_devices
quantum_gateway 42 legacy scanner scan_devices
aruba 24 legacy scanner scan_devices
meraki 24 legacy
sky_hub 22 legacy scanner async_scan_devices
ituran 19 modern tracker TrackerEntity
cisco_ios 10 legacy scanner scan_devices
upc_connect 10 legacy scanner async_scan_devices
cisco_mobility_express 6 legacy scanner scan_devices
arris_tg2492lg 2 legacy scanner async_scan_devices
bbox 1 legacy scanner scan_devices
actiontec 0 legacy scanner scan_devices
autoskope 0 modern tracker TrackerEntity
bt_home_hub_5 0 legacy scanner scan_devices
cppm_tracker 0 legacy scanner scan_devices
fleetgo 0 legacy
hitron_coda 0 legacy scanner scan_devices
lojack 0 modern tracker TrackerEntity
thomson 0 legacy scanner scan_devices
```
---
url: /developers/blog/2026-04-21-registering-custom-dashboard-strategies.md
---
从 Home Assistant 2026.5 开始,你现在可以注册自定义 dashboard strategies,就像你可以使用 [custom cards](/developers/frontend/custom-ui/custom-card.md) 一样,在 **Community dashboards** 部分下通过 **新的 dashboard** 对话框更容易地发现并添加它们。
以前,你必须引导用户创建一个空白 dashboard,在 YAML 模式下编辑,然后粘贴你的自定义 strategy。现在你可以注册一个友好的名称、描述和文档。
要注册你的 strategy,用包含以下 keys 的对象调用 `window.customStrategies.push()`:
* `type`:不带 `custom:` 前缀的 strategy type,例如 `"my-demo"`。
* `strategyType`:设置为 `"dashboard"` 以注册 dashboard strategy。
* `name`:strategy 的友好名称。
* `description`(可选):strategy 的简短描述。
* `documentationURL`(可选):指向 strategy 文档的 URL。目前在 strategy UI 中不会显示,但将来可能会显示。
示例:
```js
window.customStrategies = window.customStrategies || [];
window.customStrategies.push({
type: "my-demo",
strategyType: "dashboard",
name: "My demo dashboard",
description: "A starter dashboard generated from JavaScript.",
documentationURL: "https://example.com/my-demo-dashboard",
});
```
此元数据独立于自定义元素本身。你的 strategy 仍然需要用类似 `ll-strategy-dashboard-my-demo` 的 tag 注册,用户仍然需要在 Home Assistant 发现它之前加载该资源。你可以为此使用 HACS,因为像 custom cards 一样,其他资源也可以添加。
查看更新后的 [custom strategies](/developers/frontend/custom-ui/custom-strategy.md) 文档,其中包含示例代码和更多详情。
---
url: /developers/blog/2026-04-24-radio-frequency-entity-platform.md
---
Home Assistant 现在有了一个 `radio_frequency` entity platform,它将 RF 收发器硬件与所控制的设备解耦。每个设备集成不再直接与特定 RF 硬件通信,而是由发射器集成(如 `esphome`)暴露 `RadioFrequencyTransmitterEntity` 实例,设备集成通过 helper 函数向其发送命令。
这与 [`infrared` entity platform](/developers/blog/2026-03-30-infrared-entity-platform.md) 类似,并已在 [architecture discussion #1365](https://github.com/home-assistant/architecture/discussions/1365) 中获得批准。
## 架构
`radio_frequency` domain 位于两类集成之间:
* **发射器集成**(ESPHome、Broadlink 等)实现 `RadioFrequencyTransmitterEntity` base class 以提供特定硬件的 RF 传输。
* **消费方集成**(车库门开启器、RF 遥控器、无线开关等)使用 helper 函数通过可用的发射器发送特定设备的 RF 命令。
用户在消费方集成的 config flow 期间选择要使用哪个发射器,并根据设备的工作频率进行筛选。
## 实现一个发射器集成
发射器集成通过继承 `RadioFrequencyTransmitterEntity`、声明其 `supported_frequency_ranges` 并实现 `async_send_command` 来提供 `radio_frequency` platform:
```python
from rf_protocols import RadioFrequencyCommand
from homeassistant.components.radio_frequency import RadioFrequencyTransmitterEntity
class MyRadioFrequencyTransmitterEntity(RadioFrequencyTransmitterEntity):
"""My RF transmitter."""
@property
def supported_frequency_ranges(self) -> list[tuple[int, int]]:
"""Return the list of (min_hz, max_hz) ranges this hardware can transmit on."""
return [(300_000_000, 348_000_000), (433_050_000, 434_790_000)]
async def async_send_command(self, command: RadioFrequencyCommand) -> None:
"""Send an RF command."""
await self._device.transmit(
frequency=command.frequency,
modulation=command.modulation,
timings=command.get_raw_timings(),
)
```
Base class 会将最后发送命令的时间戳作为 entity state 跟踪,因此发射器集成只需处理实际传输。
## 构建消费方集成
消费方集成通过向发射器 entity 发送命令来控制 RF 设备。它们不直接与 RF 硬件交互。下面的代码片段改编自 [`honeywell_string_lights`](https://github.com/home-assistant/core/pull/168450) 集成,该集成使用此 platform 驱动一套 Honeywell String Lights。
**1. 在 `manifest.json` 中声明依赖**:
```json
{
"dependencies": ["radio_frequency"]
}
```
**2. 从 [`rf-protocols`](https://github.com/home-assistant-libs/rf-protocols) 库加载设备的命令**:
```python
from rf_protocols import get_codes
COMMANDS = get_codes("honeywell/string_lights")
```
每个加载的命令会暴露设备使用的频率和调制方式,config flow 需要用这些信息筛选发射器。
**3. 在 config flow 中让用户选择发射器**,使用样本命令根据设备所需的频率和调制方式进行筛选:
```python
from rf_protocols import RadioFrequencyCommand
from homeassistant.components.radio_frequency import async_get_transmitters
from homeassistant.exceptions import HomeAssistantError
sample_command: RadioFrequencyCommand = await self.hass.async_add_executor_job(
COMMANDS.load_command, "turn_on"
)
try:
transmitters = async_get_transmitters(
self.hass, sample_command.frequency, sample_command.modulation
)
except HomeAssistantError:
return self.async_abort(reason="no_transmitters")
if not transmitters:
return self.async_abort(reason="no_compatible_transmitters")
```
目前只支持 `ModulationType.OOK`(on-off keying),后续版本可以添加其他调制方式。
**4. 使用 helper 函数和存储的发射器 entity ID 发送 RF 命令**:
```python
from homeassistant.components.radio_frequency import async_send_command
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn on the light."""
command = await self.hass.async_add_executor_job(
COMMANDS.load_command, "turn_on"
)
await async_send_command(self.hass, self._transmitter, command)
self._attr_is_on = True
self.async_write_ha_state()
```
## RF 协议和 codes
RF protocol 编码器及设备 code 集合存放在 Home Assistant 之外的 [`rf-protocols`](https://github.com/home-assistant-libs/rf-protocols) 库中。常见协议和知名设备 codes 应贡献到那里。对于小众或专有协议,也可以使用单独的第三方库。
更多详情,请参阅 [radio frequency entity 文档](/developers/core/entity/radio-frequency.md)。
---
url: /developers/blog/2026-04-27-pyserial-to-serialx.md
---
与 serial port 通信的现有集成和库应从 `pyserial`、`pyserial-asyncio` 和 `pyserial-asyncio-fast` 迁移到 [`serialx`](https://github.com/puddly/serialx)。这个新库在所有平台上都原生支持 asyncio,将使你的集成和库能够利用 Home Assistant 中的 [ESPHome serial proxies](https://esphome.io/components/serial_proxy/),并包含对 asyncio event loop 稳定性的关键修复。
## 背景
多年来,`pyserial` 一直是 Python 中的事实标准 serial 库,并对所有流行平台有广泛支持。然而,其 API 早于 Python 中的 asyncio,且仅支持 sync。后来发布了 `pyserial-asyncio` 包以桥接 `pyserial` 与 asyncio。不幸的是,`pyserial-asyncio` 的开发从未达到 1.0 稳定性或跨平台支持,而且 `pyserial` 和 `pyserial-asyncio` 在近五年内都没有 PyPI 发布。我们 fork 了 `pyserial-asyncio` 并发布了 [`pyserial-asyncio-fast`](/developers/blog/2026-01-05-pyserial-asyncio-fast.md),以修复影响 Core event loop 稳定性的未解决问题。
我们从零开发了 `serialx`,作为一个现代的 Python serial 库,具有原生的 sync 和 async APIs。它与 `serial`、`serial_asyncio` 和 `serial_asyncio_fast` 模块兼容,允许现有包以极少的变更(如果有的话)进行迁移。
## 迁移
`serialx` 文档有一个 [详尽的迁移指南](https://puddly.github.io/serialx/how-to/pyserial-migration.html),提供了更多细节。
大多数包只需在 `setup.py` 或 `pyproject.toml` 文件中用 `serialx` 替换 `pyserial`、`pyserial-asyncio` 和 `pyserial-asyncio-fast`,并更新异常处理,将宽泛捕获 `SerialException` 替换为更细粒度的错误处理(如 `OSError` 和 `TimeoutError`)。
直接构造 sync `serial.Serial()` 对象的包应迁移到使用 `serialx.serial_for_url()` helper,以确保自动兼容所有支持的协议。
### 给 agent 的 prompt
迁移本身是机械性的,将以下指令粘贴到你选择的 agent 中:
> 将我的代码从 pyserial、pyserial-asyncio 和 pyserial-asyncio-fast 迁移到 serialx,使用
> https://raw.githubusercontent.com/puddly/serialx/refs/heads/dev/docs/how-to/pyserial-migration.md
---
url: /developers/blog/2026-05-04-frontend-component-updates-2026.5.md
---
## 组件更新
### ha-progress-bar
一个新的 component 替换了我们 codebase 中的 `mwc-progress-bar`,并且完全可主题化。查看这个 [PR](https://github.com/home-assistant/frontend/pull/51489) 了解一个完全自定义的 progress bar。
新的 component 特定 tokens:
```css
--ha-progress-bar-indicator-color
--ha-progress-bar-indicator-background
--ha-progress-bar-track-color
--ha-progress-bar-track-height
--ha-progress-bar-border-radius
--ha-progress-bar-animation-duration
--ha-progress-bar-indicator-highlight-image
--ha-progress-bar-indicator-highlight-width
--ha-progress-bar-indicator-highlight-height
```
### ha-switch
`ha-switch` 已迁移到 webawesome。它现在有很多 CSS properties 以方便自定义。我们还移除了旧 switch 中的一些 tokens:
已移除的 tokens:
```css
--switch-unchecked-button-color
--switch-unchecked-track-color
--switch-unchecked-color
--switch-checked-button-color
--switch-checked-track-color
--switch-checked-color
```
新的 component 特定 tokens:
```css
--ha-switch-size
--ha-switch-thumb-size
--ha-switch-width
--ha-switch-background-color
--ha-switch-thumb-background-color
--ha-switch-background-color-hover
--ha-switch-thumb-background-color-hover
--ha-switch-checked-background-color
--ha-switch-checked-thumb-background-color
--ha-switch-checked-background-color-hover
--ha-switch-checked-thumb-background-color-hover
--ha-switch-border-color
--ha-switch-thumb-border-color
--ha-switch-thumb-border-color-hover
--ha-switch-checked-border-color
--ha-switch-checked-thumb-border-color
--ha-switch-checked-border-color-hover
--ha-switch-checked-thumb-border-color-hover
--ha-switch-thumb-box-shadow
--ha-switch-disabled-opacity
--ha-switch-required-marker
--ha-switch-required-marker-offset
```
### ha-checkbox
`ha-checkbox` 也已迁移到 webawesome,并获得了新的 CSS properties。你不再能为其使用 MDC tokens,但我们添加了一组 tokens 来自定义新的 checkbox。
新的 component 特定 tokens:
```css
--ha-checkbox-size
--ha-checkbox-border-color
--ha-checkbox-border-color-hover
--ha-checkbox-background-color
--ha-checkbox-background-color-hover
--ha-checkbox-checked-background-color
--ha-checkbox-checked-background-color-hover
--ha-checkbox-checked-icon-color
--ha-checkbox-checked-icon-scale
--ha-checkbox-border-radius
--ha-checkbox-border-width
--ha-checkbox-required-marker
--ha-checkbox-required-marker-offset
```
### ha-textarea
`ha-textarea` 已迁移到 webawesome,并获得了新的 CSS properties。你不再能为其使用 MDC tokens,但我们添加了一组 tokens 来自定义新的 textarea。
API 有细微变化。要使你的 textarea 自动增大尺寸,将 `resize` prop 设置为 `auto`。
### ha-adaptive-popover
你可能已经知道(比较新的)`ha-adaptive-dialog` component。它通过结合 dialog 和 bottom sheet 来实现,在桌面端显示 dialog,在移动端显示 bottom sheet。
我们添加了 `ha-adaptive-popover` 作为 adaptive dialog 的 popover 对应组件。它在桌面端显示 popover,在移动端显示 bottom sheet。它基于 `ha-adaptive-dialog`,目前用于 tile card 的 date picker 功能。
### 移除 ha-fab
`ha-fab` 已被移除,我们现在直接使用普通的 `ha-button`,因为 position 样式一直是由父级 component 完成的。
## 样式更新
### Box shadow 令牌
我们添加了新的全局 box shadow tokens:`--ha-box-shadow-s`、`--ha-box-shadow-m`、`--ha-box-shadow-l`
但我们也移除了旧的 box shadow tokens:
```css
--ha-color-shadow-light
--ha-color-shadow-dark
--ha-shadow-offset-x-...
--ha-shadow-blur-...
--ha-shadow-spread-..
```
### 表面颜色
在下一个版本中,我们计划改变处理 surface 背景颜色的方式。Tokens 现已引入,但目前仅用于 ha-tooltip。
新的 tokens:
```css
--ha-color-surface-default
--ha-color-surface-low
--ha-color-surface-lower
--ha-color-surface-default-inverted
--ha-color-surface-low-inverted
--ha-color-surface-lower-inverted
```
---
url: /developers/blog/2026-05-04-frontend-context-groups-decorators.md
---
上个版本我们引入了 lazy context,我们希望在传递 hass 对象方面更多地使用 hass context 和 lazy context。我们发现一些小任务经常需要订阅很多 contexts,因此我们将 Hass contexts 分组为逻辑组。
## 上下文组
新增以下 context groups:
* registriesContext:包含所有 registries(device、entity、area 等)
* internationalizationContext:包含所有 internationalization 相关数据(locale、localize 等)
* apiContext:包含所有 API 相关方法(callService、callWS 等)
* connectionContext:包含所有 connection 相关数据(connection、connected、hassUrl 等)
* uiContext:包含所有 UI 相关数据(themes、panels、dockedSidebar 等)
* configContext:包含所有 config 相关数据(auth、config、user 等)
## 消费上下文条目装饰器
在上一篇 [blogpost](/developers/blog/2026-03-25-frontend-lazy-context.md) 中,我展示了如何使用 `@transform` 与 context 配合。`@transform` 功能强大,但有时你只需要一个 context entry,并不想定义 transform 函数。
为此,我们添加了直接消费单个 context entry 的 decorators。
你传入一个定义 entity ID 来源的数组(通常是 config 属性),decorator 会消费正确的 context 并将 entity ID 映射到正确的 registry entry。
它还会监视定义的路径,并在需要时更新 entry。
新的 decorators:
* @consumeEntityState
* @consumeEntityStates
* @consumeEntityRegistryEntry
使用示例:
```ts
@state()
@consumeEntityRegistryEntry({ entityIdPath: ["_config", "entity"] })
_entity?: EntityRegistryDisplayEntry;
```
我们将来可能会添加更多这类 decorators,例如用于 areas、devices 等。
## 弃用 contexts
以下 contexts 仍然可用,但可能在未来版本中被移除。请使用新的 context groups 替代。
* connectionSingleContext
* localizeContext
* localeContext
* configSingleContext
* themesContext
* selectedThemeContext
* userContext
* userDataContext
* panelsContext
* authContext
---
url: /developers/blog/2026-05-07-config-entry-listener-together-with-reloading-methods.md
---
从 Home Assistant Core 2026.6 开始,在 config flow 中将 config entry listener 与任何 reloading methods 一起使用已被弃用,并将在 2026.12 产生错误。
## 背景
在 config flow 中将 config entry listener 与任何 reloading methods 一起使用,可能导致集成重新加载两次和/或产生 race condition。
## 可能的解决方案
* 移除 config entry listener,只依赖 config flow 中的 reloading methods。
* 用 `async_update_and_abort()` 替代 `async_update_reload_and_abort()`。
* 在调用 `_abort_if_unique_id_configured()` 时设置 `reload_on_update=False`。
更多详情可在 [core PR](https://github.com/home-assistant/core/pull/169198) 中找到。
---
url: /developers/blog/2026-05-11-format-entity-name-helper.md
---
从 Home Assistant 2026.4 开始,`hass` 对象暴露了一个 `formatEntityName` helper。这是内置卡片(tile card、entity rows 等)用来从 entity 的 registry 上下文(entity、device、area、floor)计算其显示名称的同一函数。自定义卡片可以使用它,以产生与 dashboard 其余部分保持一致的名称。
给定一个名为 `Temperature` 的温度传感器,位于一个名为 `Thermostat` 的设备上:
```js
const stateObj = hass.states["sensor.living_room_thermostat_temperature"];
hass.formatEntityName(
stateObj,
[{ type: "device" }, { type: "entity" }],
{ separator: " · " }
); // "Thermostat · Temperature"
```
Frontend 还提供了一个 `entity_name` selector。如果你的卡片使用了 [内置 form editor](/developers/frontend/custom-ui/custom-card.md#using-the-built-in-form-editor),你可以向用户提供与内置卡片相同的名称选择器——接受自由格式的字符串或 registry items 的组合。
查看更新后的 [data 文档](/developers/frontend/data.md#hassformatentitynamestateobj-name-options),了解完整的参考和更多示例。
---
url: /developers/blog/2026-05-11-mqtt-publish-api-changes.md
---
将来,MQTT publish API 将要求为 `qos` 和 `retain` 显式提供值。对这两个参数传递 `None` 将不再受支持。
自定义集成应更新代码以接受默认值,或传递有效的 typed arguments。
`qos` 和 `retain` 从 `None` 回退到有效值的功能将在 HA Core 2027.6 中停止工作。
新的 API 签名为:
```python
def publish(
hass: HomeAssistant,
topic: str,
payload: PublishPayloadType,
qos: int = 0,
retain: bool = False,
encoding: str | None = DEFAULT_ENCODING,
) -> None:
"""Publish message to a MQTT topic."""
hass.create_task(async_publish(hass, topic, payload, qos, retain, encoding))
```
以及
```python
async def async_publish(
hass: HomeAssistant,
topic: str,
payload: PublishPayloadType,
qos: int = 0,
retain: bool = False,
encoding: str | None = DEFAULT_ENCODING,
) -> None:
"""Publish message to a MQTT topic."""
```
---
url: /developers/blog/2026-05-11-mqtt-publish-api-message-expiry-interval.md
---
MQTT publish API 现在支持设置 message expiry interval。
以前,保留消息会存储在 broker 中,直到被替换或显式清除。设置了 `message_expiry_interval`(以秒为单位)后,发布的消息——包括保留消息——将在指定间隔后自动过期。
此选项仅在使用 MQTT 协议版本 5 时受支持;在使用早期协议版本时会被忽略。
新的 API 签名为:
```python
def publish(
hass: HomeAssistant,
topic: str,
payload: PublishPayloadType,
qos: int = 0,
retain: bool = False,
encoding: str | None = DEFAULT_ENCODING,
*,
message_expiry_interval: int | None = None,
) -> None:
"""Publish message to a MQTT topic."""
```
以及
```python
async def async_publish(
hass: HomeAssistant,
topic: str,
payload: PublishPayloadType,
qos: int = 0,
retain: bool = False,
encoding: str | None = DEFAULT_ENCODING,
*,
message_expiry_interval: int | None = None,
) -> None:
"""Publish message to a MQTT topic."""
```
---
url: /developers/blog/2026-05-13-condition-script-api-changes.md
---
## 摘要
Condition 和 script APIs 已经发生变更。
Conditions 现在是 condition classes 的实例,通过调用 `async_check` 方法进行求值,通过调用 `async_unload` 方法进行丢弃。此外,conditions 可以选择性地实现 `_async_setup` 或 `_async_unload` 方法。注意,condition 的使用者无需调用 condition 的 `async_setup` 方法。
在弃用期间(以 Home Assistant Core 2027.1 的发布结束),可以像 callable 一样使用 condition 对象。
Scripts 也拥有一个 `async_unload` 方法,当不再需要脚本时必须调用它。
## 对自定义集成的影响
### 创建 conditions 或 scripts 的自定义集成
创建 condition 对象的自定义集成应通过调用 `async_check` 方法来求值,并在不再需要该 condition 时调用 `async_unload` 方法。
示例:
```python
from homeassistant.helpers.condition import (
async_condition_from_config,
async_validate_condition_config,
)
# 验证 condition config
validated_config = await async_validate_condition_config(hass, config)
# 创建一个 condition
condition = await async_condition_from_config(hass, validated_config)
...
# 求值 condition
result = condition.async_check(...)
...
# 丢弃 condition
condition.async_unload()
```
创建 scripts 的自定义集成应在不再需要脚本时调用 `async_unload` 方法。
示例:
```python
from homeassistant.helpers.script import (
Script,
async_validate_actions_config,
)
# 验证 script config
validated_config = await async_validate_actions_config(hass, config)
# 创建一个 script
script = Script(hass, validated_config, ...)
...
# 执行 script
result = await script.async_run(...)
...
# 丢弃 script
await script.async_unload()
```
### 提供 condition platform 的自定义集成
提供 condition platform 的集成无需更改,但如果 platform 需要执行 async 初始化或 teardown,可以实现 `_async_setup` 和 `_async_unload` 方法。
示例:
```python
from homeassistant.core import HomeAssistant
from homeassistant.helpers.condition import (
Condition,
ConditionCheckParams,
ConditionConfig,
)
from homeassistant.helpers.typing import ConfigType
class CustomCondition(Condition):
"""A custom condition."""
@classmethod
async def async_validate_config(
cls, hass: HomeAssistant, config: ConfigType
) -> ConfigType:
"""Validate config."""
...
def __init__(self, hass: HomeAssistant, config: ConditionConfig) -> None:
"""Initialize condition."""
super().__init__(hass, config)
...
async def _async_setup(self) -> None:
"""Set up the condition checker."""
...
def _async_unload(self) -> None:
"""Clean up any resources held by the checker."""
...
def _async_check(self, **kwargs: Unpack[ConditionCheckParams]) -> bool:
"""Check the condition."""
...
```
---
url: /developers/blog/2026-05-20-browse-media-source-root-class.md
---
`media_source` 集成中的 `BrowseMediaSource` class 已加强限制。`domain` 参数现在是一个必需的 `str`,而不是 `str | None`,特殊的"列出所有 media source"的 root node 已移动到它自己的 class `RootBrowseMediaSource` 中。
以前,`domain` 之所以可选,只是为了表示一种边界情况:在浏览 `media-source://` 且未选择特定 source 时返回的顶层 node。这使得类型提示对 99% 的情况来说具有误导性——每个实际的 media source 都有一个 domain——并且增加了一个消费者需要考虑的 `None` 分支。将 root 拆分到单独的 class 中移除了该分支。
## 变更内容
* `BrowseMediaSource.__init__` 现在需要 `domain: str`。
* 新的 `RootBrowseMediaSource` class 表示列出所有可用 media sources 的 root browse node。它硬编码 `domain=None` 和 `identifier=None`,并使用 `media-source://` 作为其 content ID。
* `media_source.async_browse_media()` 和 `MediaSourceItem.async_browse()` 现在返回 `BrowseMediaSource | RootBrowseMediaSource`。
## 对自定义集成的影响
大多数集成无需任何更改。如果你实现了 `media_source.py` platform,你已经在向 `BrowseMediaSource` 传递自己的 `domain`——这将继续工作。
你只有在以下情况下才需要采取行动:
* **你将 `domain=None` 传递给 `BrowseMediaSource`。** 这不再被允许。改为设置你的集成 domain。
* **你调用 `media_source.async_browse_media()` 并为结果添加了类型注解。** 将类型提示更新为 `BrowseMediaSource | RootBrowseMediaSource`,或者在使用 domain 特定属性之前用 `isinstance()` 进行收窄:
```python
from homeassistant.components.media_source import (
BrowseMediaSource,
async_browse_media,
)
result = await async_browse_media(hass, media_content_id)
if isinstance(result, BrowseMediaSource):
# 在这里 result.domain 保证是 str
...
```
更多详情,请参阅更新后的 [media source platform 文档](/developers/core/platform/media_source.md)。
---
url: /developers/blog/2026-05-26-advanced-mode-config-flow-deprecation.md
---
## 摘要
用户资料中的 [advanced mode 即将移除](https://github.com/OpenHomeFoundation/roadmap/issues/54),这意味着集成再也不能在 data entry flows 中检查 advanced mode 是否已启用。
集成作者需要更新集成,在 UI 中使用替代的用户友好方式来展示额外选项,例如将额外选项分组到一个 section 中。
### `FlowHandler.show_advanced_options`
`FlowHandler.show_advanced_options` 属性已被弃用,并将在 Home Assistant Core 2027.6 发布时移除。在弃用期间,`FlowHandler.show_advanced_options` 无条件返回 `True`,以避免由该 flag 控制选项的用户无法访问。
### `FlowHandler.context['show_advanced_options']`
`FlowHandler.context` 中不再存在 `show_advanced_options` key。
## 背景
用户资料中的 Advanced mode 开关是一个单一的 binary 开关,它控制着 Home Assistant 中一系列不相关功能的集合,从 app(add-on)可见性(Terminal & SSH)到配置选项和 UI 元素,我们在过去一年中一直在着手移除它。
更详细的解释,请参阅 [roadmap issue #54](https://github.com/OpenHomeFoundation/roadmap/issues/54)。
---
url: /developers/blog/2026-05-27-custom-card-suggestions.md
---
从 Home Assistant 2026.6 开始,自定义卡片可以作为建议显示在 card picker 中。当用户选择一个 entity 时,选择加入的自定义卡片会显示在内置建议下方的 **Community** 部分下。
要加入,在你的 `window.customCards` 条目中添加一个 `getEntitySuggestion` 函数。它接收 `hass` 对象和被选中的 entity id,并返回一个建议(如果该 entity 不受支持则返回 `null`):
```js
window.customCards.push({
type: "my-card",
name: "My Card",
getEntitySuggestion: (hass, entityId) => {
if (entityId.split(".")[0] !== "light") {
return null;
}
return {
config: { type: "custom:my-card", entity: entityId },
};
},
});
```
你也可以返回一个建议数组,以提供多个变体,每个变体都有自己的 `label`。
仅当卡片对该 entity 有意义时才建议你的卡片。使用 `hass` 对象检查 domain、device class 或 supported features,否则返回 `null`。为每个 entity 都建议你的卡片会使 picker 变得杂乱。
更多详情,请参阅 [custom card 文档](/developers/frontend/custom-ui/custom-card.md#suggesting-your-card-for-an-entity)。
---
url: /developers/blog/2026-05-27-frontend-component-updates-2026.6.md
---
## 组件更新
### ha-radio updates
`ha-radio` 已从我们的 codebase 中移除,我们现在使用基于 webawesome 的 `ha-radio-group` 配合 `ha-radio-option`。不再需要在 `ha-radio` 周围使用 `ha-formfield`,你可以使用新的 CSS properties 自定义 radio group 和 options。
新的 component 特定 tokens:
```css
--ha-radio-group-required-marker
--ha-radio-group-required-marker-offset
--ha-radio-option-active-color
--ha-radio-option-heigh
--ha-radio-option-toggle-size
--ha-radio-option-border-width
--ha-radio-option-border-color
--ha-radio-option-border-color-hover
--ha-radio-option-background-color
--ha-radio-option-background-color-hover
--ha-radio-option-checked-background-color
--ha-radio-option-checked-icon-color
--ha-radio-option-checked-icon-scale
--ha-radio-option-control-margin
```
### ha-drawer updates
`ha-drawer` 已更新为使用 webawesome drawer component。API 基本相同,只是现在使用 `--ha-sidebar-width` 而不是 `--mdc-drawer-width`。
### top bar
* `ha-top-app-bar` 已被完全移除。
* `ha-top-app-bar-fixed` 已从 MWC 迁移到纯 Lit。
* `ha-two-pane-top-app-bar-fixed` 已重写为扩展新实现,而不是 Material base code。
* `ha-header-bar` 已从 Material top-app-bar 样式包装器重写为原生 Lit component。
`--ha-top-app-bar-width` token 替换了 `--mdc-top-app-bar-width`。
## 新装饰器
### @consumeLocalize
继上个版本介绍的 [context entry decorators](/developers/blog/2026-05-04-frontend-context-groups-decorators.md) 之后,我们为从 `internationalizationContext` 读取最常见的单个字段——`localize` 函数——添加了一个快捷方式。
之前:
```ts
@state()
@consume({ context: internationalizationContext, subscribe: true })
@transform({
transformer: ({ localize }) => localize,
})
private _localize!: LocalizeFunc;
```
之后:
```ts
@state()
@consumeLocalize()
private _localize!: LocalizeFunc;
```
当 component 只需要 `localize` 函数时,使用 `@consumeLocalize()`。对于从 `internationalizationContext` 读取的其他单字段(例如 `locale`、`language`),继续使用 `@consume` + `@transform`。
---
url: /developers/blog/2026-06-15-device-tracker-changes.md
---
## 摘要
Device tracker entity model 最近发生了多项变更:
* `battery_level` 属性已弃用
* `TrackerEntity` 的 `location_name` 属性已弃用
* 引入了新的 entity base class `BaseScannerEntity`
* 用户可以将 scanners 与 home zone 之外的其他 zones 关联
* `TrackerEntity` 有一个新的 `in_zones` 属性
* `BaseScannerEntity` 和 `ScannerEntity` 有一个新的 state 属性 `in_zones`
* 引入了新的 capability 属性 `tracking_type`
* 计算 `TrackerEntity` 的 state 时,zones 现在按大小计算,再按到中心的距离计算
## 详情
### `battery_level` 的弃用
`battery_level` 属性已在所有 device tracker base classes 中弃用,并将在 Home Assistant Core 2027.7 中停止工作。集成应通过 battery sensor 来传达电池电量。
更多详情可在 [architecture proposal #627](https://github.com/home-assistant/architecture/discussions/627) 中找到
### `location_name` 的弃用
`TrackerEntity` 的 `location_name` 属性已弃用,并将在 Home Assistant Core 2027.7 中停止工作。
拥有 device trackers 且不知道或不希望报告精确坐标、目前使用 `location_name` 报告 zone 名称的集成,应改为通过 `in_zones` 属性报告一组 zone entity IDs。
使用 `location_name` 提供额外上下文的 device trackers,可以通过单独的 sensor 或额外的 state 属性来实现。
更多详情可在 [architecture proposal #1387](https://github.com/home-assistant/architecture/discussions/1387) 中找到
### `BaseScannerEntity` base class 的引入
[`BaseScannerEntity`](/developers/core/entity/device-tracker.md#basescannerentity) class 应被拥有 scanner 但不追踪与 WLAN 或其他本地网络连接状态的集成使用,例如追踪与 BLE beacon 连接的 scanners。
### 用户可以将 `BaseScannerEntity` 和 `ScannerEntity` 与任意 zone 关联
`BaseScannerEntity` 和 `ScannerEntity` 将关联的 zone 作为 entity registry option 存储。Base class 会在连接时将 entity 的 state 设置为关联 zone 的名称,并将 `in_zones` state 属性设置为包含关联 zone 的所有 zones。
更多详情可在 [architecture proposal #1389](https://github.com/home-assistant/architecture/discussions/1389) 中找到
### `in_zones` state 属性的引入
Device tracker entities 的 state 中有一个新的 state 属性 `in_zones`。该 state 属性由 `BaseScannerEntity` 和 `ScannerEntity` 自动计算。`TrackerEntity` 如果 `in_zones` 属性不为 `None`,将从 [`in_zones` 属性](https://developers.home-assistant.io/docs/core/entity/device-tracker#properties-2) 派生出 `in_zones` state 属性,如果为 `None`,则从报告的位置计算。
`in_zones` state 属性是一个 zone entity IDs 的列表,按大小排序,最小的 zone 在前,然后按到中心的距离排序。
### `tracking_type` capability 属性的引入
Device tracker entities 的 state 中有一个新的 capability 属性 `tracking_type`。该 state 属性由 `BaseScannerEntity` 和 `ScannerEntity` 设置为 `connection`,由 `TrackerEntity` 设置为 `location`。集成不应覆盖此行为。
---
url: /developers/blog/2026-06-23-frontend-component-updates-2026.7.md
---
## 组件更新
### Component sizes 使用 Web Awesome 名称
`ha-button`、`ha-button-toggle-group` 和 `ha-slider` 现在使用简短的 Web Awesome size 名称。
对于 `ha-button`,使用:
```html
Save
```
支持的值是 `xs`、`s`、`m`、`l` 和 `xl`。
对于 `ha-button-toggle-group`,使用 `s` 或 `m`:
```html
```
`ha-slider` 使用 `s` 或 `m`。
如果你的自定义卡片或 editor 仍然在这些 components 上使用 `small`、`medium` 或 `large`,请将它们迁移到 `s`、`m` 或 `l` 等简短值。
### 虚拟列表
我们添加了两个用于大数据集的 list components:
* `ha-list-virtualized`
* `ha-list-selectable-virtualized`
当 picker 或 dialog 能够渲染足够多行从而影响到滚动或初始渲染时间时使用它们。Virtualized list 仅渲染可见行,同时保留 `ha-list-base` 的 roving-tabindex 键盘导航。
Rows 通过 `aria-setsize` 和 `aria-posinset` 暴露 accessibility metadata,因此辅助技术仍然能获得完整的 list 位置,即使只有部分 list 在 DOM 中。
对于可选择的 lists,渲染 `ha-list-item-option` rows。
## Context 和 editor 基础设施
### Dirty state 跟踪
Dialogs 和 editors 现在有共享的 dirty-state 基础设施:
* `DirtyStateProviderMixin`
* `dirtyStateContext`
* `isDirtyState`
* `isEffectiveDirtyState`
对于需要阻止 scrim 关闭、仅在编辑后启用 Save、或与子 components 协调 dirty state 的新 dialogs 或 editors,使用 `DirtyStateProviderMixin`。
```ts
class MyDialog extends DirtyStateProviderMixin()(LitElement) {
public openDialog() {
this._initDirtyTracking({ type: "shallow" }, this._state);
}
private _stateChanged(state: MyState) {
this._updateDirtyState(state);
}
}
```
`isDirtyState` 是原始比较,通常适用于启用 Save。`isEffectiveDirtyState` 可以忽略等效的 config 输出,例如当 editor 将显式默认值规范化为相同的有效 config 时。
### 相关上下文
Pages 和 editors 现在可以为附近的 pickers 发布 related context:
* `relatedContext`
* `fireRelatedContext`
* `fireEntityRelatedContext`
当 card editor、badge editor、automation trace page 或类似表面知道当前的 entity、device 或 area 时,它可以提供该 context。Entity pickers 和 add-element searches 然后可以优先考虑相关的 entities、devices 和 areas。
```ts
fireEntityRelatedContext(this, "light.kitchen");
```
当 editor 不再有 related item 时,用 `undefined` 清除 context。
### 窄视口上下文
`narrowViewportContext` 暴露主 Home Assistant viewport 是否处于 narrow layout。
只需要 narrow-layout state 的 components 可以消费此 context,而不是通过多层 properties 接收 `narrow`。
```ts
@consume({ context: narrowViewportContext, subscribe: true })
private _narrow!: boolean;
```
## Lovelace 更新
### Strategy regeneration 控制
Lovelace strategies 现在可以避免不必要的 regeneration。
Strategies 可以声明 `registryDependencies` 以仅对它们依赖的 registries 使用默认的 reference-change 检查:
```ts
static registryDependencies = ["entities", "areas"] as const;
```
对于自定义逻辑,实现 `shouldRegenerate()`:
```ts
static shouldRegenerate(config, oldHomeAssistant, newHomeAssistant) {
return oldHomeAssistant.entities !== newHomeAssistant.entities;
}
```
如果两者都未提供,strategies 保持先前的默认行为,并在 entities、devices、areas 或 floors 发生变更时重新生成。
---
url: /developers/blog/2026-06-30-async-initialize-triggers-home-assistant-start-deprecated.md
---
`async_initialize_triggers` 的专用 `home_assistant_start` flag 已弃用,并将在 Home Assistant Core 2027.8 中移除。在弃用期间,该参数不再有任何效果。
该 flag 之所以存在,是因为 `homeassistant` start trigger 是一个伪 trigger:它不像真正的 trigger 那样运作,而是依赖于触发器 API 的调用者传递 `home_assistant_start=True`,以便 `async_initialize_triggers` 在启动期间触发该 trigger。
Start trigger 已被重写为像真正的 trigger 一样工作,因此不再需要该 flag。`async_initialize_triggers` 的调用者只需停止传递 `home_assistant_start` 即可。
为了使新实现成为可能,重写添加了 `HomeAssistant.async_add_startup_job`,它注册一个 job,该 job 将在所有对 `EVENT_HOMEASSISTANT_START` 的 listeners 执行之后、但在 `EVENT_HOMEASSISTANT_STARTED` 触发之前被调用。这镜像了已用于 homeassistant shutdown trigger 的方法,并避免了在 core states 和 events 之间已复杂的关 系中再添加另一个 core state 和 event。
更多详情,请参阅 [core PR 175160](https://github.com/home-assistant/core/pull/175160)。
---
url: /developers/blog/2026-06-30-new-unit-enumerators.md
---
从 Home Assistant Core 2026.7 开始,以下 unit 常量已被弃用,并由相应的 enum 替代:
* `UnitOfDensity` enumerator 替代了质量除以体积的 `CONCENTRATION_***` 常量
(`"g/m³"`、`"mg/m³"`、`"μg/m³"`、`"μg/ft³"`)
* `UnitOfRatio` enumerator 替代了无单位的比率 `CONCENTRATION_***` 常量
(`"ppm"`、`"ppb"`)
`CONCENTRATION_PARTS_PER_CUBIC_METER` 仅被单个集成使用,已弃用且没有替代 unit。
请注意,将 `PERCENTAGE` 常量用作测量单位时也已弃用,即使常量本身并未弃用。
---
url: /developers/blog/2026-07-03-media-source-search.md
---
Media sources 现在可以实现搜索。通过在你的 `MediaSource` 中添加 `async_search_media` 方法,用户可以直接从 media browser 中搜索你的媒体。
```python
from homeassistant.components.media_player import SearchMedia, SearchMediaQuery
async def async_search_media(
self, item: MediaSourceItem, query: SearchMediaQuery
) -> SearchMedia:
"""Search media."""
results = [...] # list of BrowseMediaSource items
return SearchMedia(result=results)
```
要告诉 media browser 哪些 items 可以搜索,请在浏览时返回的 `BrowseMediaSource` items(通常是目录)上将 `can_search` flag 设置为 `True`。其他集成可以通过新的 `media_source.async_search_media` helper 触发搜索。
更多信息,请参阅[更新后的文档](/developers/core/platform/media_source.md#searching-media)。
---
url: /developers/blog/2026-07-05-modernizing-modbus.md
---
:::info Update — 2026 年 7 月 16 日
我们正在重新评估本文中描述的方法的 Home Assistant 方面。基础保持不变:一切仍将围绕 [`modbus-connection`](https://home-assistant-libs.github.io/modbus-connection/) PyPI 包构建,基于它构建的设备库仍然是正确的投资。我们重新思考的是连接在 Home Assistant 本身内部的呈现方式,我们希望专注于能够产生更好的用户体验。如果你正在开发设备集成,请暂缓将其连接到下面描述的 `modbus_connection` 集成——我们将很快在此分享更新后的方法。
:::
Modbus 无处不在地出现在现代家庭中:太阳能逆变器、电表、热泵以及各种进入室内的工业设备。Home Assistant 长期以来通过基于 YAML 的 `modbus` 集成来支持这些设备,用户在其中手写 register maps 配置。该集成不会消失,现有配置将继续工作。但手写 register maps 将理解设备协议的负担加在了每个用户身上,而且它不符合 Home Assistant 其他部分所采取的 config-flow、UI 优先方向。
因此我们添加了一种使用 Modbus 的新方式:基于集成的方法,设备集成拥有设备特定知识,用户只需在 UI 中选择设备,就像任何其他集成一样。
## 共享连接
Modbus 连接是一个单一的、独占的资源:同一时间只能有一方在总线上通信。一个串行(RS-485)总线,或一个 TCP-to-serial 网关,可以同时承载多个设备,有时来自不同的制造商。如果两个集成各自向同一总线打开自己的连接,它们就会争夺总线,而从历史上看,Home Assistant 完全不支持在集成之间共享总线。
新的 [`modbus_connection`](https://github.com/home-assistant/core/tree/dev/homeassistant/components/modbus_connection) 集成通过将连接变成设备集成路由通过而不是拥有的东西来解决这个问题。用户在 UI 中设置一次连接,`modbus_connection` 保持其打开并管理其生命周期,包括在断线后重新连接。设备集成则从该共享连接中借用所需内容,而不是管理自己的连接。我们重新编写了 [Modbus 开发者文档](/developers/modbus/introduction.md) 来介绍其工作原理,并附带示例代码。
## 独立库
`modbus_connection` 底层的连接抽象位于 [`modbus-connection`](https://home-assistant-libs.github.io/modbus-connection/),这是我们为此目的设计并发布在 PyPI 上的新库。它不绑定于 Home Assistant,可以在任何 Python 项目中独立使用。它提供一个通用的、与后端无关的接口,因此设备库作者无需考虑底层 Modbus 实现,只需针对一个 API 编写代码,它随附设备建模框架和 `pytest` 插件,使构建和测试设备库变得简单。
这将关注点保持在它们所属的位置。设备库是一个普通的 PyPI 包,知道如何与特定设备通信,而 Home Assistant 中的消费方集成将该库连接到共享连接并暴露 entities。两者都可以独立开发和测试。
更多背景,请参阅我们的[研究](https://gist.github.com/balloob/b9fa91ba1a0914a9787f8f6ceb637b83)。
## 开始构建
有了这些新的构建模块,现在可以将一组 Modbus 的 YAML 配置转换为可以通过 UI 设置的制造商特定集成。如果你(对)这项工作感兴趣,请到 [Home Assistant Discord 的 #modbus 频道](https://discord.com/channels/330944238910963714/1347329854495916044) 看看,我们很乐意提供帮助。
如果你正在使用 AI agent,可以给它以下 prompt:
> 我想使用新的 Modbus Connection 集成为 Home Assistant 创建一个新集成,文档如下:https://developers.home-assistant.io/docs/modbus/introduction
>
> 我们想要转换为设备库的 YAML 可以在这里找到:*TODO INSERT LOCATION OF MODBUS YAML!*
>
> 本任务的交付物将是 3 个文件夹:
>
> * 首先基于 YAML 创建设备库。以 https://github.com/Tom-Bom-badil/trovis-modbus/ 作为确切示例,包括如何使用 component models、所有 GitHub Actions、helper scripts 和 README。该库作为独立设备库,不应提及 Home Assistant。查看 modbus\_connection 的源码以了解所有支持的字段。
>
> * 创建一个可以贡献给 Home Assistant core 的集成,遵循这个示例:https://github.com/home-assistant/core/tree/trovis557x-integration/homeassistant/components/trovis557x
>
> * 创建一个自定义集成版本,将设备库 vendorize,使其可以通过 HACS 由社区测试。遵循此模板 https://github.com/ludeeus/integration\_blueprint
---
url: /developers/blog/2026-07-20-ai-policy.md
---
AI 辅助开发已经成为许多贡献者日常现实的一部分,对我们维护者来说也是如此。这可能是一件好事:AI 工具帮助人们编写代码、改进英语,并在不熟悉的 codebase 中找到方向。但它也有不利的一面:未经审核的 AI 输出作为贡献提交,会耗费维护者大量时间,而维护者时间是开源项目最稀缺的资源。
今天,我们发布了 [Open Home Foundation AI Policy](/developers/ai_policy.md)。它适用于所有 Open Home Foundation 项目,包括 `home-assistant` 和 `home-assistant-libs` GitHub 组织中的每个仓库。
该政策归结为几点:
* AI 工具作为辅助是被欢迎的。你仍然对你提交的一切负责。
* 不允许自主 agent 贡献。自主创建的 pull requests 和 issues 将被关闭。
* 你必须理解并能够用自己的话解释你提交的每个变更。这包括自己回答维护者的问题,而不是让 AI 代劳。
* 使用 AI 改进你自己撰写的文本的语法或清晰度是可以的。对于非英语母语者来说这确实有用,我们感谢这种努力。
我们正在将政策推广到两个组织中的每个仓库。每个仓库在其根目录获得一个 `AI_POLICY.md` 文件,现有的贡献指南和 `AGENTS.md` 文件将新增对其的引用,使人类和他们的 AI 工具都了解我们的期望。
在[开发者文档](/developers/ai_policy.md)中阅读完整政策。如果贡献不符合该政策,它将被关闭。如果你认为你的贡献被错误地这样处理,请联系维护者,我们会解决。
---
url: /developers/blog/2026-07-21-device-registry-single-config-entry.md
---
## 摘要
Device 现在由单个 config entry 拥有,并由单个(或无)config subentry 拥有。Devices 不再跨集成合并:由多个集成支持的物理设备现在由每个 config entry 一个 device 表示,而不是单个共享 device。
以前绑定到多个 config entries 的 devices,在 device registry 加载时会被拆分为每个 config entry 一个 device。Entity registry 会被更新,使 entities 指向正确的 device。
**大多数集成不直接与 device registry 交互,无需任何更改。** 与它直接交互的集成需要处理下面列出的弃用。
此实现在 core [PR #175785](https://github.com/home-assistant/core/pull/175785) 中,原因在 architecture proposal [home-assistant/architecture#1226](https://github.com/home-assistant/architecture/discussions/1226) 中有描述。这些变更将在 Home Assistant Core 2026.8 中落地。
## 背景
直到现在,由多个集成支持的物理设备会被合并为单个共享 device。这是通过以全局唯一的 connections 和 identifiers 来标识 devices 来实现的,因此例如 device tracker 和指向同一 MAC 地址的原生集成最终会落在同一个 device 上。
这导致了一些问题:
* 对于 device 信息(如名称或型号)没有单一可信来源;冲突的值被丢弃而不是保留。
* 用户获得令人困惑的体验,device page 包含来自多个集成的 entity 大杂烩。
* 存在长期 bug,修改 device 的 connections 和 identifiers 会导致多个 devices 拥有相同的 connections,违反了 device registry 的原始设计。
新行为是通过使 identifiers 和 connections 在每个 config entry 中唯一,而不是全局唯一来实现的。
## 弃用项
使用下面已弃用的功能会在运行时记录警告。除非另有说明,已弃用的功能将持续支持到 Home Assistant Core 2027.8。
### `DeviceEntry.config_entries`
已弃用,改用 `DeviceEntry.config_entry_id`。该属性作为兼容 shim 保留,返回一个包含 device 单个 config entry 的 set。
### `DeviceEntry.config_entries_subentries`
已弃用,改用 `DeviceEntry.config_entry_id` 和 `DeviceEntry.config_subentry_id`。该属性作为兼容 shim 保留。
### `DeviceEntry.primary_config_entry`
已弃用,改用 `DeviceEntry.config_entry_id`。Device 现在属于单个 config entry,这就是它的 primary config entry。
### 读取 composite device 的 config entries
`DeviceEntry.config_entries`、`DeviceEntry.config_entries_subentries` 和 `DeviceEntry.primary_config_entry` 仅对普通 devices(属于单个 config entry)弃用。当与合成的 composite device(向后兼容解析为迁移前 composite device id 返回的只读 device,见[向后兼容](#向后兼容))交互时,它们并未弃用。此类 device 跨多个 config entries,而 `config_entry_id` 和 `config_subentry_id` 无法表示,因此这三个报告跨拆分 device 的并集的属性,仍然是读取该信息的方式。
### `DeviceInfo["via_device"]` 和 `DeviceRegistry.async_get_or_create(via_device=...)`
已弃用,改用 `via_device_id`。由于 identifiers 在每个 config entry 中唯一,一个 identifier 对不再明确指向单个 device,这就是为什么 `via_device` 被弃用。
同时传递 `via_device` 和 `via_device_id` 会引发 `HomeAssistantError`。
### `DeviceRegistry.async_update_device()` 的 config entry 参数
`add_config_entry_id`、`add_config_subentry_id`、`remove_config_entry_id` 和 `remove_config_subentry_id` 参数都已弃用。Device 属于单个 config entry 和 subentry,因此添加和移除 config entries 不再有意义;device 取而代之被移动或移除。
要将 device 移动到另一个 config entry 或 subentry,传递新的 `new_config_entry_id` 和 `new_config_subentry_id` 参数:
```py
device_registry.async_update_device(
device.id,
new_config_entry_id=config_entry.entry_id,
new_config_subentry_id=subentry.subentry_id,
)
```
使用旧参数移动 device 需要集成进行多次 `async_update_device` 调用,将 device 添加到新的 config entry 和 subentry,然后从旧的移除,对于仅在相同 config entry 内更改 subentry 的 device 还有单独的情况。上面显示的单个调用取代了所有这些。此外,device registry 现在在 device 移动到已启用的 config entry 时清除 `CONFIG_ENTRY` disable,因此集成不再需要手动在移动过程中携带 `disabled_by` flag。
相关地,`async_update_device` 现在验证 `disabled_by` flag 相对于拥有 config entry 的 disabled 状态。对 disabled config entry 上的 device 设置 `disabled_by=None`,或对 enabled config entry 上的 device 设置 `disabled_by=DeviceEntryDisabler.CONFIG_ENTRY` 是不一致的;此类值现在被忽略并记录,并将在 Home Assistant Core 2027.8 中引发。
核心集成已作为示例进行了更新:[PR #176662](https://github.com/home-assistant/core/pull/176662) 中的 `openai_conversation`、[PR #176663](https://github.com/home-assistant/core/pull/176663) 中的 `scrape`、[PR #176664](https://github.com/home-assistant/core/pull/176664) 中的 `waqi` 以及 [PR #176665](https://github.com/home-assistant/core/pull/176665) 中的 `wolflink`。
要移除 device,调用 `DeviceRegistry.async_remove_device()`:
```py
device_registry.async_remove_device(device.id)
```
核心集成已在 PRs [#176669](https://github.com/home-assistant/core/pull/176669)、[#176671](https://github.com/home-assistant/core/pull/176671)、[#176672](https://github.com/home-assistant/core/pull/176672) 和 [#176673](https://github.com/home-assistant/core/pull/176673) 中更新为以这种方式移除 devices。
### `DeviceRegistry.async_get_device()`
已弃用。Identifiers 和 connections 在每个 config entry 中唯一,因此按 identifiers 或 connections 查找可以设计性地匹配多个 devices,`async_get_device` 返回的内容因此是模糊的。
当知道拥有 config entry 时,用新方法 `DeviceRegistry.async_get_device_by_identifier()` 或 `DeviceRegistry.async_get_device_by_connection()` 限定在该 config entry 范围内查找 device。每个方法接受单个 identifier 或 connection 元组加上 config entry id,因此查找不再模糊:
```py
# 之前
device = device_registry.async_get_device(identifiers={(DOMAIN, serial_number)})
# 之后
device = device_registry.async_get_device_by_identifier(
(DOMAIN, serial_number), entry.entry_id
)
```
在 entity 内,优先使用 `self.device_entry` 而不是 registry 查找。如果你确实需要匹配某个 key 的每个 device(可能跨 config entries),使用 `DeviceRegistry.async_get_devices()`,它返回一个 list。
核心集成已迁移到新方法,`heos` 在 core [PR #176932](https://github.com/home-assistant/core/pull/176932) 中是一个示例。
在弃用期间,`async_get_device` 会如 [向后兼容](#向后兼容) 中所述解析模糊查找。注意,这种向后兼容的解析只通过 `DeviceRegistry` 的查找方法(如 `async_get()` 和 `async_get_device()`)发生;直接与 `devices` 容器交互(例如 `DeviceRegistry.devices.get(device_id)`)不会合成 composite device。
### 将 helper config entry 添加到其他集成的 device 上
Helper 集成不得将它们的 config entry 添加到 source entity 的 device 或用户选择的 device 上,它们应该将自己的 entities 链接到该 device。这是此处描述变更的直接后果:device 现在属于单个 config entry,因此 helper config entry 不能再添加到由其他集成拥有的 device 上。
这已在去年 [Updated guidelines for helper integrations linking to other integration's device](/developers/blog/2025-07-18-updated-pattern-for-helpers-linking-to-devices.md) 中宣布,并将在 Home Assistant Core 2026.8 中停止工作。
### `helpers.device.async_device_info_to_link_from_entity()` 和 `async_device_info_to_link_from_device_id()`
这两个 helper 现在总是返回 `None`。
它们返回一个携带另一个 device 的 identifiers 和 connections 的 `DeviceInfo`,这会隐式地将调用者的 config entry 添加到该 device。拥有单个 config entry 的 device 无法表示这种情况,它会静默地派生一个重复 device。
改为在 entity 的构造函数中设置 `self.device_entry` 将 helper entity 链接到 device:
```py
self.device_entry = async_entity_id_to_device(hass, source_entity_id)
```
这些 helper 将在 Home Assistant Core 2027.8 中移除。
### `helpers.helper_integration.async_handle_source_entity_changes(add_helper_config_entry_to_device=...)`
该参数不再有任何效果,应从调用中移除。
当 source entity 移动到另一个 device 时,`async_handle_source_entity_changes` 现在仅更新 helper entity 以链接到新 device,不再从旧 device 移除 helper config entry 并添加到新 device。
传递该参数在 Home Assistant Core 2027.8 之前被接受,并会记录警告。
### 清理 helper devices
用于从 config entry 迁移步骤清理 helper 集成 devices 的 helper 已在 core [PR #176714](https://github.com/home-assistant/core/pull/176714) 中从 `async_remove_helper_config_entry_from_source_device` 重命名为 `homeassistant.helpers.helper_integration.async_remove_helper_devices`。旧名称作为弃用别名保留,将持续工作到 Home Assistant Core 2027.8。新签名为:
```py
def async_remove_helper_devices(
hass: HomeAssistant,
*,
helper_config_entry_id: str,
source_device_id: str | None,
remove_all_devices: bool = False,
keep_device_ids: Collection[str] = (),
) -> None:
```
### `helpers.device.async_remove_stale_devices_links_keep_entity_device()` 和 `async_remove_stale_devices_links_keep_current_device()`
两者现在都是 no-ops。改为从 helper 的 `async_setup_entry` 中用 `remove_all_devices=True` 调用 `async_remove_helper_devices`:
```py
async_remove_helper_devices(
hass,
helper_config_entry_id=entry.entry_id,
source_device_id=entry.options.get(CONF_DEVICE_ID),
remove_all_devices=True,
)
```
`template` helper 已在 core [PR #176900](https://github.com/home-assistant/core/pull/176900) 中作为示例进行了迁移。这些函数将在 Home Assistant Core 2027.8 中移除。
## Devices 只能有一个 config subentry
Device 不能再绑定到多于一个 config subentry。这是一个没有向后兼容 shim 的 breaking change;将多个 subentries 附加到同一 device 的集成必须为每个 subentry 创建一个 device。
### 示例:`telegram_bot`
`telegram_bot` 集成已在 core [PR #176606](https://github.com/home-assistant/core/pull/176606) 中为此进行了调整,可以作为示例。
它以前有一个由每个 chat 共享的单个 bot device,每个 chat 的 subentry 附加到同一个 device。现在它为每个 chat 创建一个单独的 device,作为 via device 链接到 bot device。Config entry 迁移将每个 chat 的 notify entity 移动到自己的 device 上,并从 bot device 剥离 chat subentries,使 bot device 不再拥有 subentry。
注意,该 PR 是在添加 `via_device_id` 之前编写的,新代码应改用 `via_device_id` 而不是 `via_device`。
当[child devices](https://github.com/home-assistant/architecture/discussions/1414) 引入时,用 via device 建模此类结构的集成应迁移到 child devices。
## 将 entity 链接到拆分 device
迁移前的 composite device id 不再指向真实 device。尝试通过将 id 传递给 `EntityRegistry.async_get_or_create(device_id=...)` 或 `EntityRegistry.async_update_entity(device_id=...)` 将 entity 链接到此类 id,会被忽略并记录警告,而不是被应用。然后会创建一个没有 device 的新 entity,现有的 entity 保持其当前 device。传递真正不存在的 device id 仍会像以前一样引发 `ValueError`。
存储的 device 是一个没有 entity 的 config entry 所拥有的拆分的 composite device 的 entities,在 registry 加载时会被从 device 分离;拥有集成预期会将它们重新链接。
改为将 entities 链接到拆分 device 之一,用 `async_get_device_by_identifier` 或 `async_get_device_by_connection` 查找它。
## Device registry 事件
拆分迁移前的 composite device 发生在 registry 从存储加载时,在任何 listeners 运行之前,因此不会发出 `EVENT_DEVICE_REGISTRY_UPDATED` events;devices 在启动时已经拆分。
对于订阅 `EVENT_DEVICE_REGISTRY_UPDATED` 或使用 `async_track_device_registry_updated_event` 并检查 payload 的集成,有两点变更:
* `update` 事件的 `changes` dict 用 `config_entry_id` 和 `config_subentry_id` keys 报告 device 移动,取代先前的 `config_entries` 和 `config_entries_subentries`。
* 更新或移除迁移前的 composite device id 会将操作转发到每个拆分 device,因此每个拆分 device 发出一个 event,而不是为 composite id 发出单个 event。
Device 现在属于单个 config entry,因此它不能再丢失一个 config entry 同时继续存在用于另一个。以前观察 `update` 事件中 `config_entries` 或 `config_entries_subentries` keys 变更的集成(通常用于检测其 config entry 从与其他集成共享的 device 中移除),现在可能只需要处理 `remove` events:device 丢失 config entry 意味着 device 被移除。
## 向后兼容
拆分 devices 会改变自定义集成可能依赖的假设,存储在 automations 和 scripts 中的 device ids 不再作为 devices 存在。为了缓解这一点,device registry 做出最大努力尝试保持未修改的自定义集成正常工作,通过将迁移前的 composite device id 解析为它被拆分成的 devices。
这是最大努力,不是保证。Shims 无法覆盖自定义集成与 device registry 交互的每种方式,对拆分 devices 之间模糊的操作根本无法应用。对 462 个直接与 device registry 交互的自定义集成的 AI 辅助分析表明,至少 90% 预期会不受影响地工作,这也意味着有些不会。请将你的集成迁移到新 API,而不是依赖这些 shims;它们将在 Home Assistant Core 2027.8 中移除。
在弃用期间:
* `DeviceRegistry.async_get()` 在传入迁移前 composite device 的 id 时会合成一个只读恢复的 composite device。其 identifiers、connections 和 config entries 是拆分 devices 的并集。合成仅在 `async_get()` 中发生;直接与 `devices` 容器交互(例如 `DeviceRegistry.devices.get(device_id)`)不会合成 composite,对迁移前 composite device id 返回 `None`。
* `DeviceRegistry.async_get_device()` 在可能时将通过匹配多个 config entries 的 identifiers 或 connections 的查找解析为单个 device,优先选择 config entry domain 与查找的 identifier 匹配的 device。如果剩余匹配是一个迁移前 composite device 的拆分,则返回跨它们的只读 composite。对于共享 identifier 或 connection 的独立 devices,优先选择由调用集成拥有的 device,回退到第一个匹配。
* `DeviceRegistry.async_update_device()` 和 `DeviceRegistry.async_remove_device()` 将调用转发到每个拆分 device。重写 device 身份或移动它的参数在拆分 devices 之间是模糊的;它们被忽略并向违规集成报告。
* Entity registry `get_entries_for_device_id()` 和 `async_entries_for_device()` 将迁移前 composite device id 展开为它被拆分成的 devices 的 entities。
* 针对迁移前 composite device id 的 actions 级联到拆分 devices。
* 用户自定义(area、floor、labels、name)在 device 拆分时保留。
新方法 `DeviceRegistry.async_get_devices_for_composite_device_id()` 返回迁移前 composite device 被拆分成的 devices。`DeviceRegistry.async_is_composite_device_id()` 返回一个 device id 是否是迁移前 composite device id,即已被拆分为每个 config entry 一个 device 且不再指向注册 device 的 id。
---
url: /developers/blog/2026-07-22-button-standard-event-types.md
---
Button event entities 现在有一组标准 event types,由新的 `ButtonEventType` enum 提供。使用 `EventDeviceClass.BUTTON` 的集成,当交互映射到其中一个 type 时,应使用这些 type 而不是自定义字符串。
查看 [architecture discussion](https://github.com/home-assistant/architecture/discussions/1377) 了解完整背景。
## 原因
以前,每个集成为自己选择的 button 交互使用不同的字符串——`single`、`click`、`hold`、`double_press` 等等。这种不一致性使得无法构建跨集成工作的通用 button 自动化,也阻止了 frontend 提供有意义的触发器建议。
新的 `ButtonEventType` 标准 event types 通过为每个 button 集成提供共享词汇来解决常见交互。
## 事件类型
`ButtonEventType` 定义了六个标准 event types:
* `ButtonEventType.PRESS_START`:button 被按下。
* `ButtonEventType.PRESS_END`:button 在短暂按下后释放(标准的"click")。
* `ButtonEventType.LONG_PRESS_START`:button 被按住超过了持续时间阈值。
* `ButtonEventType.LONG_PRESS_END`:button 在长时间按住后释放。
* `ButtonEventType.MULTI_PRESS_ONGOING`:检测到多按序列中的中间按下。
* `ButtonEventType.MULTI_PRESS_END`:多按序列完成。
`MULTI_PRESS_ONGOING` 和 `MULTI_PRESS_END` events 在其 event data 中包含 `multi_press_count` 属性(`ATTR_MULTI_PRESS_COUNT` 常量),指示按下次数。
**这些都不是强制的。** 与 doorbell 的 `ring` event 不同,这里没有必需的 type。每个集成只映射其硬件实际能产生的交互,并在 `event_types` 中列出这些。
## 需要做什么
从 `homeassistant.components.event` 导入 `ButtonEventType`,并在 entity 的 `event_types` 中包含你的设备支持的 types。当交互发生时触发它们:
```python
from homeassistant.components.event import (
ATTR_MULTI_PRESS_COUNT,
ButtonEventType,
EventDeviceClass,
EventEntity,
)
class MyButtonEvent(EventEntity):
_attr_device_class = EventDeviceClass.BUTTON
_attr_event_types = [
ButtonEventType.PRESS_END,
ButtonEventType.LONG_PRESS_END,
ButtonEventType.MULTI_PRESS_END,
]
@callback
def _async_handle_multi_press(self, count: int) -> None:
"""Handle a completed multi-press sequence."""
self._trigger_event(
ButtonEventType.MULTI_PRESS_END,
{ATTR_MULTI_PRESS_COUNT: count},
)
self.async_write_ha_state()
```
### 单事件设备
如果设备每次交互只发出一个 event,没有单独的按下和释放,将其映射到匹配的 `_end` type(短按用 `PRESS_END`,按住用 `LONG_PRESS_END`,以此类推)。这在不合成硬件从不发送的 events 的情况下,保持跨设备"button 被按下"触发的一致性。
## 无需迁移
此变更仅添加共享常量;没有弃用,也没有集成被迫迁移。自定义 event types 仍然可以与标准 types 一起使用。当它适合你的设备时采用 `ButtonEventType`。
更多详情,请参阅 [event entity 文档](/developers/core/entity/event.md)。
---
url: /developers/blog/2026-07-31-frontend-component-updates-2026.8.md
---
## 组件更新
### ha-split-panel
我们添加了 `ha-split-panel`,它是对 Web Awesome split panel component 的 Home Assistant 包装。
当 Home Assistant 的 page、dialog 或 tool 需要可调整大小的双窗格布局时使用它。自定义卡片作者可以使用 Home Assistant frontend components,但 Home Assistant 内部 UI APIs 可能会变更。
```html
Editor
Preview
```
新的 component-specific tokens:
```css
--ha-split-panel-divider-width
--ha-split-panel-divider-hit-area
--ha-split-panel-min
--ha-split-panel-max
--ha-split-panel-grip-display
```
### ha-tile-info updates
`ha-tile-info` 为自定义卡片和 tile-like surfaces 获得更多 layout 控制。
新的 component-specific tokens:
```css
--ha-tile-info-gap
--ha-tile-info-min-height
--ha-tile-info-primary-min-height
--ha-tile-info-primary-line-clamp
```
当 primary 文本需要换行到多行时使用 `--ha-tile-info-primary-line-clamp`,并使用 min-height tokens 在部分 tiles 有 secondary 文本而另一些没有时保持 rows 对齐。
## Form 和 selector 更新
### 条件 ha-form 字段
`ha-form` schemas 现在支持通过 `visible` 实现条件字段可见性。
```ts
[
{
name: "advanced",
selector: { boolean: {} },
},
{
name: "advanced_name",
visible: { field: "advanced", value: true },
selector: { text: {} },
},
]
```
支持的 operators 是:
```ts
"eq"
"not_eq"
"in"
"not_in"
"exists"
"not_exists"
```
你也可以用 `and`、`or` 和 `not` 组合条件。
隐藏字段不会被渲染,并在验证期间被跳过,因此当 form 字段依赖于另一个值时,请使用 `visible` 而不是自定义的仅 frontend 隐藏逻辑。
### Selector 新增
Text selector 现在支持 HTML pattern 验证:
```ts
{
text: {
pattern: "[a-z0-9_]+",
validation_message: "Use lowercase letters, numbers, and underscores",
},
}
```
这对单值和多值 text selectors 都适用。
Entity selectors 现在可以通过 entity 的 device 属性进行筛选:
```ts
{
entity: {
filter: {
domain: "sensor",
device: {
manufacturer: "Home Assistant",
model: "Connect ZBT-1",
},
},
},
}
```
还添加了一个新的 `ui_clock_date_format` selector,用于 clock card 的 date format editor。
## Lovelace 更新
### state\_color 正在迁移到 color
`entities` 和 `glance` cards 现在支持 `color` 作为 `state_color` 的替代。
之前:
```yaml
type: entities
state_color: true
entities:
- light.kitchen
```
之后:
```yaml
type: entities
color: state
entities:
- light.kitchen
```
用 `color: state` 对应旧的 `state_color: true` 行为,用 `color: none` 对应 `state_color: false`。
## Custom panels 和 apps
### Safe-area 处理
Custom panels 和 add-on app iframes 现在默认获得 safe-area padding,因此内容保持在 notches、状态栏和主屏指示符之外。
已经自行处理 safe areas 的 custom panels 可以选择退出:
```yaml
panel_custom:
- name: my-panel
module_url: /local/my-panel.js
handle_safe_area: true
```
对于基于 iframe 的 custom panels,Home Assistant 将解析后的 safe-area 值作为 CSS variables 转发到 iframe document:
```css
--safe-area-inset-top
--safe-area-inset-right
--safe-area-inset-bottom
--safe-area-inset-left
```
Add-on app iframes 在订阅 Home Assistant properties 时也可以选择自行管理 safe area:
```js
window.parent.postMessage(
{
type: "home-assistant/subscribe-properties",
handleSafeArea: true,
},
"*"
);
```
Properties message 随后会包含 `safeAreaInsets`。
## Context 和 editor 基础设施
### 全局 dirty state
`DirtyStateProviderMixin` 现在还会发布全局 dirty state。
当任何连接的 dirty-state provider 有未保存的变更时,会设置 `window.isDirtyState`,Home Assistant 会触发 `dirty-state-changed` event。
```ts
window.addEventListener("dirty-state-changed", (ev) => {
console.log(ev.detail.isDirty);
});
```
这对于需要避免干扰具有未保存变更的活动 editors 或 dialogs 的共享基础设施非常有用。
---
url: /developers/blog/2026-08-19-device-registry-websocket-api-changes.md
---
## 摘要
本文描述了 device registry **WebSocket API** 的变更——由 frontend、自定义卡片和其他 WebSocket clients 消费的 commands 和 device serialization。它们源于 device registry 的两项变更:
* **将每个 device 限制为单个 config entry**(Home Assistant Core 2026.8)。此变更的 Python API 方面在 companion post [Devices are restricted to a single config entry and at most one subentry](/developers/blog/2026-07-21-device-registry-single-config-entry.md) 中描述。
* **引入 [child devices](#device-list-中的-child-devices)**(Home Assistant Core 2026.9,[architecture proposal #1414](https://github.com/home-assistant/architecture/discussions/1414))。
大多数 clients 只通过 `config/device_registry/list` 读取 devices,新的 device fields 是 additive 的,因此无需任何更改即可保持工作——但遍历 device list 的 clients 应准备好遇到 child devices,它们被序列化得不同。移除 devices 或检查 device 的 config entries 的 clients 请继续阅读。
## 新的 device fields:`config_entry_id` 和 `config_subentry_id`
`config/device_registry/list` 返回的每个 device,以及 `EVENT_DEVICE_REGISTRY_UPDATED` payload 中的每个 device,现在都携带两个新 fields:
* `config_entry_id` — device 所属的单个 config entry 的 id。
* `config_subentry_id` — device 所属的单个 config subentry 的 id,或 `null`。
它们取代了以前的 fields,这些 fields 建模了可以跨多个 config entries 和 subentries 的 device:
* `config_entries` — config entry ids 的 list。
* `config_entries_subentries` — 从 config entry id 到 subentry ids list 的 map。
* `primary_config_entry` — device 的 primary config entry 的 id。
旧 fields **为了向后兼容而保留,并已弃用**;计划在家 Home Assistant Core 2027.8 中移除。在弃用期间,它们从新值派生:device 将 `config_entries` 报告为单元素 list `[config_entry_id]`,将 `config_entries_subentries` 报告为 `{config_entry_id: [config_subentry_id]}`,`primary_config_entry` 等于 `config_entry_id`。
更新你的 client 以读取 `config_entry_id` 和 `config_subentry_id`。
## Device list 中的 child devices {#device-list-中的-child-devices}
Home Assistant Core 2026.9 引入了 **child devices**([architecture proposal #1414](https://github.com/home-assistant/architecture/discussions/1414),core [PR #178666](https://github.com/home-assistant/core/pull/178666))。Child device 是 parent device 的轻量级逻辑部分:它没有自己的硬件或固件 metadata,并通过 `parent_device_id` 引用其 parent。Parent 必须由相同的 config entry 注册,并属于相同的 config subentry。
`config/device_registry/list` 现在将 child devices 与普通 devices 一起返回,因此其结果是两种 entry 的混合。Child device 以更小的 field 集合进行序列化:
```json
{
"id": "child1234",
"parent_device_id": "abcd1234",
"config_entry_id": "wxyz5678",
"config_subentry_id": null,
"area_id": null,
"name": "Left channel",
"name_by_user": null,
"labels": [],
"identifiers": [["demo", "left"]],
"disabled_by": null,
"created_at": 1723987200.0,
"modified_at": 1723987200.0
}
```
与普通 device 相比,child device **没有** `connections`、`via_device_id`、`configuration_url`、`entry_type`、`manufacturer`、`model`、`model_id`、`hw_version`、`sw_version`、`serial_number`、`primary_config_entry`、`config_entries` 或 `config_entries_subentries`。读取 device list 的 clients 不能假设每个 entry 都携带这些 fields。
区分两者的可靠方式是 `parent_device_id` field:它仅出现在 child devices 上且非 null。普通 device 携带的是 `via_device_id`。
没有自己 `area_id` 的 child device 继承其 parent 的 area,因此 client 在解析 child device 的 area 时,如果 child 的 `area_id` 为 `null`,应回退到 parent device。
两个相关 commands 也理解 child devices:
* `config/device_registry/update` 接受 child device id——设置 `area_id`、`disabled_by`、`labels` 或 `name_by_user`——并以上述缩减序列化返回更新后的 child device。
* `config/device_registry/list_linked_devices` 总是为 child device 返回空的 `linked_devices` list,因为 child 共享其 parent 的 per-config-entry identifier 命名空间,并且永远不会链接到其他 config entries 的 devices。
## 新 command:`config/device_registry/remove`
一个新的 WebSocket command 按 id 移除 device:
```json
{
"type": "config/device_registry/remove",
"device_id": "abcd1234"
}
```
由于 device 现在恰好属于一个 config entry,从该 config entry 移除它就移除了 device。该 command 需要 admin,并且以错误 `Cannot remove a composite device` 拒绝[composite device id](#composite-devices)。
它取代了 `config/device_registry/remove_config_entry`,该 command 同时接收 `device_id` 和 `config_entry_id`:
```json
{
"type": "config/device_registry/remove_config_entry",
"config_entry_id": "wxyz5678",
"device_id": "abcd1234"
}
```
该 command 仍然有效但已**弃用**:它记录警告,并将在 Home Assistant Core 2027.9 中移除。它的 `config_entry_id` 参数现在仅用于检查它与 device 的 config entry 匹配——不匹配会失败并返回 `Config entry not in device`——无论传递哪个 config entry,device 都会被移除。更新 clients 调用 `config/device_registry/remove` 并去掉 `config_entry_id`。
在 core [PR #178319](https://github.com/home-assistant/core/pull/178319) 中实现。
## 新 command:`config/device_registry/list_linked_devices`
由于 connections 和 identifiers 现在在每个 config entry 中唯一,由多个集成支持的物理设备由每个 config entry 一个 device 表示,而不是单个共享 device。该 command 返回与给定 device 共享 connection 或 identifier 的其他 devices——在不同 config entries 下表示相同物理硬件的 sibling devices:
```json
{
"type": "config/device_registry/list_linked_devices",
"device_id": "abcd1234"
}
```
结果:
```json
{
"linked_devices": ["ef567890", "12ab34cd"]
}
```
被查询的 device 本身被排除在结果之外。这让 client(例如)可以从 device page 链接到表示相同硬件的其他 devices。在 core [PR #177449](https://github.com/home-assistant/core/pull/177449) 中实现。
## 新 command:`config/device_registry/list_composite_splits` {#composite-devices}
当 device registry 加载时,每个跨多个 config entries 的迁移前 device 被拆分为每个 config entry 一个 device,原始的("composite")device id 不再指向注册的 device。Composite device ids 仍被 automations、scripts、dashboards 和 target pickers 引用,因此该 command 将每个 composite device id 映射到它被拆分成的 devices:
```json
{
"type": "config/device_registry/list_composite_splits"
}
```
结果:
```json
{
"old_composite_id": {
"split_ids": ["ef567890", "12ab34cd"],
"primary_id": "ef567890"
}
}
```
对于每个 composite device id,`split_ids` 列出替代 device ids,`primary_id` 是继承了 composite 先前 primary config entry 的拆分,或 `null`。用它将存储的 composite device id 解析为当前 device——例如,在存储的 id 是 composite id 时保持 device picker 工作。在 core [PR #176693](https://github.com/home-assistant/core/pull/176693) 中实现。
Companion post 的 [Backwards compatibility](/developers/blog/2026-07-21-device-registry-single-config-entry.md#backwards-compatibility) 部分描述了弃用期间行为的其余部分,例如针对 composite device id 的 actions 级联到拆分 devices。
## Device registry 事件
订阅 `device_registry_updated` events(`EVENT_DEVICE_REGISTRY_UPDATED`)的 clients 看到两个变更,镜像了 Python API:
* `update` 事件的 `changes` map 用 `config_entry_id` 和 `config_subentry_id` keys 报告 device 移动,取代先前的 `config_entries` 和 `config_entries_subentries`。
* Device 现在属于单个 config entry,因此它不能再丢失一个 config entry 同时继续存在用于另一个。Device 丢失其 config entry 现在是一个 `remove` event 而不是 `update`。
详情请参阅 companion post 中的 [Device registry events](/developers/blog/2026-07-21-device-registry-single-config-entry.md#device-registry-events)。
---
url: /developers/blog/index.md
---
# 博客
## 2026
* [Device registry WebSocket API 变更](/developers/blog/2026-08-19-device-registry-websocket-api-changes.md)
* [Frontend component updates 2026.8](/developers/blog/2026-07-31-frontend-component-updates-2026.8.md)
* [Button event entities 的标准 event types](/developers/blog/2026-07-22-button-standard-event-types.md)
* [Devices 限制为单个 config entry,最多一个 subentry](/developers/blog/2026-07-21-device-registry-single-config-entry.md)
* [介绍 Open Home Foundation AI Policy](/developers/blog/2026-07-20-ai-policy.md)
* [在 Home Assistant 中现代化 Modbus](/developers/blog/2026-07-05-modernizing-modbus.md)
* [Media sources 现在可以搜索](/developers/blog/2026-07-03-media-source-search.md)
* [引入新的 unit enumerators](/developers/blog/2026-06-30-new-unit-enumerators.md)
* [async\_initialize\_triggers 的 home\_assistant\_start flag 弃用](/developers/blog/2026-06-30-async-initialize-triggers-home-assistant-start-deprecated.md)
* [Frontend component updates 2026.7](/developers/blog/2026-06-23-frontend-component-updates-2026.7.md)
* [Device tracker entity models 的变更](/developers/blog/2026-06-15-device-tracker-changes.md)
* [Frontend component updates 2026.6](/developers/blog/2026-05-27-frontend-component-updates-2026.6.md)
* [Card picker 中的自定义卡片建议](/developers/blog/2026-05-27-custom-card-suggestions.md)
* [Data entry flow 中 advanced mode 的弃用](/developers/blog/2026-05-26-advanced-mode-config-flow-deprecation.md)
* [BrowseMediaSource:domain 现在是必需的](/developers/blog/2026-05-20-browse-media-source-root-class.md)
* [Condition 和 script APIs 的变更](/developers/blog/2026-05-13-condition-script-api-changes.md)
* [MQTT publish API 支持 message expiry interval](/developers/blog/2026-05-11-mqtt-publish-api-message-expiry-interval.md)
* [MQTT publish API 变更](/developers/blog/2026-05-11-mqtt-publish-api-changes.md)
* [在自定义卡片中格式化 entity 名称](/developers/blog/2026-05-11-format-entity-name-helper.md)
* [弃用 config flow 中使用 reloading methods 的 config entry listener](/developers/blog/2026-05-07-config-entry-listener-together-with-reloading-methods.md)
* [Frontend context groups、新的 context decorators 和已弃用的 contexts](/developers/blog/2026-05-04-frontend-context-groups-decorators.md)
* [Frontend component updates 2026.5](/developers/blog/2026-05-04-frontend-component-updates-2026.5.md)
* [认真对待 serial:从 pyserial 迁移到 serialx](/developers/blog/2026-04-27-pyserial-to-serialx.md)
* [用于 RF 设备集成的新 radio frequency entity platform](/developers/blog/2026-04-24-radio-frequency-entity-platform.md)
* [注册自定义 dashboard strategies](/developers/blog/2026-04-21-registering-custom-dashboard-strategies.md)
* [Legacy device tracker platform API 弃用](/developers/blog/2026-04-20-legacy-device-tracker-deprecation.md)
* [Doorbell event entities 的标准 event type](/developers/blog/2026-04-15-doorbell-standard-event-type.md)
* [Domain 不匹配的 Entity IDs 已弃用](/developers/blog/2026-04-07-entity-id-mismatched-domain-deprecated.md)
* [将 app builds 迁移到 Docker BuildKit](/developers/blog/2026-04-02-builder-migration.md)
* [用于 IR 设备集成的新 infrared entity platform](/developers/blog/2026-03-30-infrared-entity-platform.md)
* [Frontend lazy context](/developers/blog/2026-03-25-frontend-lazy-context.md)
* [Frontend 新的 dialogs 方式](/developers/blog/2026-03-25-frontend-dialogs.md)
* [Frontend component updates 2026.4](/developers/blog/2026-03-25-frontend-component-updates-2026.4.md)
* [Backup agents 现在可以报告上传进度](/developers/blog/2026-03-11-backup-upload-progress.md)
* [自定义集成现在可以自带品牌图片](/developers/blog/2026-02-24-brands-proxy-api.md)
* [移除已弃用的 light features](/developers/blog/2026-02-23-remove-deprecate-light-features.md)
* [OAuth 2.0 helper 错误处理的变更](/developers/blog/2026-02-19-oauth-token-request-error-handling.md)
* [webhook helper 的 reconfiguration 支持](/developers/blog/2026-02-18-reconfiguration-support-for-webhook-helper.md)
* [Labs 中的 async\_listen 已弃用](/developers/blog/2026-02-16-labs-async-listen-deprecation.md)
* [用 prek 替代 pre-commit](/developers/blog/2026-01-13-replace-pre-commit-with-prek.md)
* [解决 pyserial-asyncio 阻塞 event loop 的问题](/developers/blog/2026-01-05-pyserial-asyncio-fast.md)
## 2025
* [为 service action translations 引入 description placeholders](/developers/blog/2025-11-27-service-translation-placeholders.md)
* [Store data 在 worker thread 中序列化现需 opt-in](/developers/blog/2025-11-25-storage-helper-opt-in-serialize-in-executor.md)
* [为 MQTT subscription 添加状态回调](/developers/blog/2025-11-23-mqtt-subscribe-wait.md)
* [设备识别按钮现被归类为 diagnostic](/developers/blog/2025-11-21-identify-buttons-diagnostic-category.md)
* [引入 Labs:在成为标准之前预览功能](/developers/blog/2025-11-20-labs-preview-features.md)
* [Data Update Coordinator 现支持 Retry After](/developers/blog/2025-11-17-retry-after-update-failed.md)
* [CalculatedState 中的 capability\_attributes 字段已移除](/developers/blog/2025-11-10-calculated-state-capability-attributes-removed.md)
* [无网络连接时 oauth2 配置的改进错误处理](/developers/blog/2025-11-05-config-entry-oauth2-error-handling.md)
* [TemperatureConverter.convert\_interval 弃用](/developers/blog/2025-11-04-convert-interval-deprecation.md)
* [Service translations 已从 WebSocket get\_services 和 REST /api/services 中移除](/developers/blog/2025-10-24-service-web-api-changes.md)
* [Recorder statistics API 的变更](/developers/blog/2025-10-16-recorder-statistics-api-changes.md)
* [target selector 中的 device filter 已被移除](/developers/blog/2025-10-14-device-filter-removed-from-target-selector.md)
* [Update coordinator 现允许重新触发](/developers/blog/2025-10-05-coordinator-retrigger.md)
* [注册 platform entity service 的改进 API](/developers/blog/2025-09-25-entity-services-api-changes.md)
* [弃用 service helpers 中的 hass 参数](/developers/blog/2025-09-22-deprecate-hass-argument-service-helpers.md)
* [统一测量单位中 μ 的编码](/developers/blog/2025-08-20-micro-sign-encoding.md)
* [DeviceEntry.suggested\_area 属性已弃用并将被移除](/developers/blog/2025-08-01-suggested-area-removed-from-deviceentry.md)
* [FlowResult typed dict 中的 result 属性已被移除](/developers/blog/2025-07-31-result-removed-from-flowresult.md)
* [关于 helper 集成链接到其他集成 device 的更新指南](/developers/blog/2025-07-18-updated-pattern-for-helpers-linking-to-devices.md)
* [media player 的 STANDBY state 已弃用](/developers/blog/2025-07-16-media-player-standby-state-deprecated.md)
* [自 OS 16 起 add-on 中打开文件限制的处理](/developers/blog/2025-07-14-home-assistant-os-16-open-file-limit.md)
* [Vacuum 电池属性已弃用](/developers/blog/2025-07-02-vacuum-battery-properties-deprecated.md)
* [Sensor device classes 现拥有默认显示精度](/developers/blog/2025-05-26-sensor-default-display-precision.md)
* [图标翻译现已支持范围](/developers/blog/2025-05-22-range-based-icons.md)
* [Device tracker TrackerEntity location accuracy 属性类型变更](/developers/blog/2025-04-24-device-tracker-accuracy.md)
* [在 media player 中搜索](/developers/blog/2025-04-17-search-media-play.md)
* [UnitSystem dataclass 现已冻结](/developers/blog/2025-03-25-frozen-unit-system.md)
* [ConfigSubentryFlow 的变更](/developers/blog/2025-03-24-config-subentry-flow-changes.md)
* [Media player toggle 操作已变更](/developers/blog/2025-03-14-media-player-toggle-adjusted.md)
* [BackupAgent API 的变更](/developers/blog/2025-03-04-backup-agent-api-changes.md)
* [对 config flow unique ID 的新检查](/developers/blog/2025-03-01-config-flow-unique-id.md)
* [Config entry state 转换已更改](/developers/blog/2025-02-19-new-config-entry-states.md)
* [Backup agents](/developers/blog/2025-02-17-backup-agents.md)
* [支持 config subentries](/developers/blog/2025-02-16-config-subentries.md)
* [每距离能量单位](/developers/blog/2025-01-31-energy-distance-units.md)
* [迁移 dhcp/ssdp/usb/zeroconf ServiceInfo models](/developers/blog/2025-01-15-service-info.md)
## 2024
* [新增面积 device class](/developers/blog/2024-12-26-area-squared-units.md)
* [迁移到 Pydantic v2](/developers/blog/2024-12-21-moving-to-pydantic-v2.md)
* [使用 Kelvin 作为首选 color temperature 单位](/developers/blog/2024-12-14-kelvin-preferred-color-temperature-unit.md)
* [WaterHeaterEntityDescription 名称已更改](/developers/blog/2024-12-13-water-heater-entity-description.md)
* [Vacuum 新增 state 属性](/developers/blog/2024-12-08-new-vacuum-state-property.md)
* [Climate entity 现在支持独立的水平 swing](/developers/blog/2024-12-03-climate-horizontal-swing.md)
* [Utility 函数 homeassistant.util.dt.utc\_to\_timestamp 已弃用](/developers/blog/2024-11-28-dt-util-utc-to-timestamp-deprecation.md)
* [Camera API 变更](/developers/blog/2024-11-26-camera-deprecations.md)
* [翻译 measurement 单位](/developers/blog/2024-11-21-unit-of-measurement-translations.md)
* [集成质量等级](/developers/blog/2024-11-20-integration-quality-scale.md)
* [OptionsFlow 新增辅助属性](/developers/blog/2024-11-12-options-flow.md)
* [Sections view 中自定义卡片支持尺寸设置](/developers/blog/2024-11-06-custom-card-sections-support.md)
* [Reauth 和 reconfigure flows 必须关联到一个 config entry](/developers/blog/2024-11-04-reauth-reconfigure-entry-id.md)
* [Update entity 的变更](/developers/blog/2024-10-31-update-entity-changes.md)
* [Core config 类已迁移](/developers/blog/2024-10-31-core-config-moved.md)
* [Alarm control panel 新增 state 属性与 state enum](/developers/blog/2024-10-22-new-alarm-state-property.md)
* [reauth 和 reconfigure flows 的新辅助函数与最佳实践](/developers/blog/2024-10-21-reauth-reconfigure-helpers.md)
* [延长 hass.helpers 的 deprecation 期限](/developers/blog/2024-10-09-extend-deprecation-hass-helpers.md)
* [UnitOfConductivity enum 的变更](/developers/blog/2024-10-08-unit-of-conductivity-changes.md)
* [弃用 cover 相关 state 常量](/developers/blog/2024-10-08-state-constants-cover-deprecation.md)
* [介绍 Assist satellite 实体](/developers/blog/2024-10-01-assist-satellite-entity.md)
* [Update 平台的版本比较现在可以自定义覆盖](/developers/blog/2024-09-28-update-version-compare.md)
* [device\_tracker 强制使用 SourceType 并新增 shorthand 属性和 EntityDescription](/developers/blog/2024-09-24-device-tracker-entity-description.md)
* [Climate set\_temperature 方法新增额外校验](/developers/blog/2024-09-24-climate-set-temp-validation.md)
* [弃用 lock 相关 state 常量](/developers/blog/2024-09-23-state-constants-lock-deprecation.md)
* [弃用 camera 相关 state 常量](/developers/blog/2024-09-23-state-constants-camera-deprecation.md)
* [弃用 Vacuum 相关 feature flag 常量](/developers/blog/2024-09-23-feature-flag-constants-vacuum-deprecation.md)
* [弃用 Media Player 相关常量](/developers/blog/2024-09-23-constants-media-player-deprecation.md)
* [延长 @bind\_hass 和 hass.components 的 deprecation 期限](/developers/blog/2024-09-11-extending-deprecation-hass-components.md)
* [energy 类传感器新增卡路里单位](/developers/blog/2024-09-10-calories-units.md)
* [Template.hass 在渲染模板时不再自动设置](/developers/blog/2024-09-02-template-hass-required.md)
* [entity action schema 的验证](/developers/blog/2024-08-27-entity-service-schema-validation.md)
* [icon translations schema 的变更](/developers/blog/2024-08-27-changed-icon-translations-schema.md)
* [LawnMowerActivity 中的新 returning 类型](/developers/blog/2024-08-23-lawn-mower-activity-returning.md)
* [使用 setup 方法设置 DataUpdateCoordinator](/developers/blog/2024-08-05-coordinator_async_setup.md)
* [Climate entity 现在验证 action call 中提供的温度](/developers/blog/2024-07-24-climate-min-max-temperature-check.md)
* [FanEntity 中的新 entity feature flag](/developers/blog/2024-07-19-fan-fanentityfeatures-turn-on_off.md)
* [Services 现在称为 Actions](/developers/blog/2024-07-16-service-actions.md)
* [为 DeviceInfo 添加 model\_id](/developers/blog/2024-07-16-device-info-model-id.md)
* [新的 HVACAction DEFROSTING](/developers/blog/2024-07-07-climate-hvacaction-defrosting.md)
* [使用 MATCH\_ALL 从 recording 中排除所有 state attribute](/developers/blog/2024-06-22-excluding-state-attributes-from-recording-match-all.md)
* [通过 `async_register_static_paths` 使 http path 注册变为 async 安全](/developers/blog/2024-06-18-async_register_static_paths.md)
* [将设置转发到 config entry platform](/developers/blog/2024-06-12-async_forward_entry_setups.md)
* [Alarm Control Panel entity 的 code 验证](/developers/blog/2024-05-22-alarm_control_panel_validation.md)
* [将 Home Assistant API 暴露给 LLMs](/developers/blog/2024-05-20-llm-api.md)
* [处理时区而不阻塞 event loop](/developers/blog/2024-05-19-fix_zoneinfo_blocking_io.md)
* [LockEntity 支持 open/opening state](/developers/blog/2024-05-10-lock-supports-open-state.md)
* [使用 group integration 设置 entity platform 的变更](/developers/blog/2024-05-10-group-integration.md)
* [我们如何设法加快 CI 速度,每月节省 168+ 天的执行时间](/developers/blog/2024-05-09-improving-ci-test-execution.md)
* [notify entity platform 实现第二阶段](/developers/blog/2024-05-03-notify-title.md)
* [改进 hass.data 的 typing](/developers/blog/2024-05-01-improved-hass-data-typing.md)
* [在 config entry 中存储 runtime 数据](/developers/blog/2024-04-30-store-runtime-data-inside-config-entry.md)
* [在成功的 re-auth flow 之后始终 reload](/developers/blog/2024-04-25-always-reload-after-successful-reauth-flow.md)
* [用 `async_track_state_change_event` 替换 `async_track_state_change`](/developers/blog/2024-04-13-deprecate_async_track_state_change.md)
* [新的 notify entity platform](/developers/blog/2024-04-10-new-notify-entity-platform.md)
* [弃用旧的 backports 和 typing alias](/developers/blog/2024-04-08-deprecated-backports-and-typing-aliases.md)
* [弃用 `async_add_hass_job`](/developers/blog/2024-04-07-deprecate_add_hass_job.md)
* [uv 如何每月为 Home Assistant 节省 215 个计算小时](/developers/blog/2024-04-03-build-images-with-uv.md)
* [弃用 hass.helpers 的使用](/developers/blog/2024-03-30-deprecate-hass-helpers.md)
* [Config Entries 现在可以提供 reconfigure 步骤](/developers/blog/2024-03-21-config-entry-reconfigure-step.md)
* [新的 state 时间戳 State.last\_reported](/developers/blog/2024-03-20-state_reported_timestamp.md)
* [使用翻译抛出 exception](/developers/blog/2024-03-15-exception-translations.md)
* [弃用 `async_run_job` 和 `async_add_job`](/developers/blog/2024-03-13-deprecate_add_run_job.md)
* [Climate entity 的辅助加热器已弃用](/developers/blog/2024-03-10-climate-aux-heater-deprecated.md)
* [在 executor 中导入集成以避免阻塞 event loop](/developers/blog/2024-03-09-import_executor_default.md)
* [对单独文件中的平台翻译的支持将被移除](/developers/blog/2024-03-05-platform_entity_translations_deprecation.md)
* [引入 device name 翻译功能](/developers/blog/2024-02-28-device-name-translations.md)
* [弃用 @bind\_hass 和 hass.components 的使用](/developers/blog/2024-02-27-deprecate-bind-hass-and-hass-components.md)
* [新的 single instance only manifest 选项](/developers/blog/2024-02-26-single-instance-only-manifest-option.md)
* [新的 Climate entity toggle 方法](/developers/blog/2024-02-25-Climate-toggle-service.md)
* [lights 现在必须设置 color mode](/developers/blog/2024-02-12-light-color-mode-mandatory.md)
* [修改 config entries](/developers/blog/2024-02-12-async_update_entry.md)
* [ha-state-icon 的 properties 变更](/developers/blog/2024-01-30-ha-state-icon-properties-changes.md)
* [Climate entity 中的新 entity features](/developers/blog/2024-01-24-climate-climateentityfeatures-expanded.md)
* [lights 显示 effect 时 light color mode 的变更](/developers/blog/2024-01-23-light-color-mode-changes.md)
* [枚举 services](/developers/blog/2024-01-22-async_services_for_domain.md)
* [Icon 翻译](/developers/blog/2024-01-19-icon-translations.md)
* [引入 entity translation placeholders](/developers/blog/2024-01-19-entity-translations-placeholders.md)
* [弃用 sensor unit of measurement 和 state class 的无效用法](/developers/blog/2024-01-17-deprecate-invalid-sensor-uom-state-class-use.md)
* [FlowManager.async\_show\_progress 的变更](/developers/blog/2024-01-11-async-show-progress-changes.md)
## 2023
* [弃用所有 supported features 的 magic numbers](/developers/blog/2023-12-28-support-feature-magic-numbers-deprecation.md)
* [已弃用的常量会产生日志条目](/developers/blog/2023-12-19-constant-deprecation.md)
* [Config entry 现在支持 minor 版本](/developers/blog/2023-12-18-config-entry-minor-version.md)
* [EntityDescription 数据类的变更](/developers/blog/2023-12-11-entity-description-changes.md)
* [新的 scaling 工具与 import 变更](/developers/blog/2023-12-05-scaling-utils.md)
* [服务调用期间的异常处理与翻译支持](/developers/blog/2023-11-30-service-exceptions-and-translations.md)
* [配置处理与错误处理](/developers/blog/2023-11-27-config-validation-and-error-handling.md)
* [公开 Add-on 配置](/developers/blog/2023-11-06-public-addon-config.md)
* [Country selector](/developers/blog/2023-10-12-country_selector.md)
* [移除已弃用的单位转换工具](/developers/blog/2023-10-03-conversion-functions-cleanup.md)
* [Home Assistant 参与 Hacktoberfest 2023!](/developers/blog/2023-10-01-hacktoberfest-2023.md)
* [排除 state 属性不参与 recording 的新方式](/developers/blog/2023-09-20-excluding-state-attributes-from-recording.md)
* [websocket 命令 entity/source 已修改](/developers/blog/2023-09-12-ws-entity-source-simplified.md)
* [HomeAssistant.**init** 要求向其传递字符串](/developers/blog/2023-08-29-homeassistant-changed-signature.md)
* [Entity state 格式化](/developers/blog/2023-08-29-hass-format-state.md)
* [数值型 sensor 不再允许将 state 设置为 NaN 或 inf](/developers/blog/2023-08-23-sensor-updates.md)
* [Weather entity 预报类型](/developers/blog/2023-08-07-weather_entity_forecast_types.md)
* [使用 DataUpdateCoordinator 避免不必要的回调](/developers/blog/2023-07-27-avoiding-unnecessary-callbacks-with-dataupdatecoordinator.md)
* [MQTT entities 命名变更为符合 HA 指南](/developers/blog/2023-07-21-change-naming-mqtt-entities.md)
* [翻译 services](/developers/blog/2023-07-11-translating-services.md)
* [Vacuum entity 集成更新](/developers/blog/2023-07-10-vacuum-updates.md)
* [自定义卡片的 Action 事件](/developers/blog/2023-07-07-action-event-custom-cards.md)
* [服务调用描述过滤器](/developers/blog/2023-06-28-service-description-filters.md)
* [Entity 命名变更](/developers/blog/2023-06-28-entity-name-changes.md)
* [Service Call API 变更](/developers/blog/2023-06-14-service-calls.md)
* [近期 MQTT 变更以提升整体性能](/developers/blog/2023-05-21-retained-mqtt-messages-filtered.md)
* [Job:Home Assistant Core 开发者](/developers/blog/2023-05-07-job-home-assistant-core.md)
* [Statistics WebSocket API 变更](/developers/blog/2023-04-30-statistics_impossible_values.md)
* [Supervisor Add-ons 的新限制](/developers/blog/2023-04-13-new_limits_for_add_ons.md)
* [Deprecating Polymer](/developers/blog/2023-04-04-deprecating_polymer.md)
* [Calendar best practices](/developers/blog/2023-03-28-calendar_best_practices.md)
* [翻译 entity 的 name 和 attributes](/developers/blog/2023-03-27-entity_name_translations.md)
* [自定义 tile features](/developers/blog/2023-02-28-custom-tile-features.md)
* [已移除 MQTT subscribe 的弃用 callback 签名](/developers/blog/2023-02-21-deprecated-callback-removed.md)
* [新增 snapshot testing 支持](/developers/blog/2023-02-20-snapshot-testing.md)
* [重构 json helper 和 utility](/developers/blog/2023-02-15-json.md)
* [HassGetState intent](/developers/blog/2023-02-09-hassgetstate.md)
* [显示 sensor state 时使用的小数位数现在可配置](/developers/blog/2023-02-08-sensor_presentation_rounding.md)
* [在 review 流程中引入 PR drafting](/developers/blog/2023-02-07-introducing-PR-drafting-in-reviews.md)
* [翻译文件已从 Core 仓库中移除](/developers/blog/2023-02-06-translations-files-removed-from-core.md)
* [Intent Responses 和 Whitespace](/developers/blog/2023-02-03-responses.md)
* [Sensor entity 现在可以对数值进行 rounding](/developers/blog/2023-01-25-sensor_rounding.md)
* [为 Conversation 集成做未来兼容设计](/developers/blog/2023-01-24-conversation-updates.md)
* [Cover intents 已弃用](/developers/blog/2023-01-20-cover-intent-deprecated.md)
* [2023.2 及以后版本的 Bluetooth 更新](/developers/blog/2023-01-12-bluetooth-adapters.md)
* [数据库 schema v32 更新](/developers/blog/2023-01-02-db-schema-v32.md)
## 2022
* [Multi-pan 暂时禁用](/developers/blog/2022-12-08-multi-pan-rollback.md)
* [单位系统相关变更汇总](/developers/blog/2022-12-07-unit_system_changes.md)
* [Entity state 颜色变更](/developers/blog/2022-12-07-entity-color-changes.md)
* [添加更多单位枚举器](/developers/blog/2022-12-05-more-unit-enumerators.md)
* [翻译 entity 的 state](/developers/blog/2022-12-01-entity_translations.md)
* [添加更多单位枚举器](/developers/blog/2022-11-28-more-unit-enumerators.md)
* [长期统计 API 的变更](/developers/blog/2022-11-16-statistics_refactoring.md)
* [引入新的单位枚举器](/developers/blog/2022-10-26-new-unit-enumerators.md)
* [添加新的降水强度单位](/developers/blog/2022-10-25-new-precipitation-intensity-units.md)
* [引入虚拟集成并区分 hub、device 和 service](/developers/blog/2022-10-24-hubs-devices-services-virtual-integrations.md)
* [代码所有者的 GitHub 命令](/developers/blog/2022-10-21-code-owner-commands.md)
* [弃用 IMPERIAL\_SYSTEM 单位系统及相关常量和属性](/developers/blog/2022-10-14-deprecate-unit-system.md)
* [为 SelectEntityDescription 添加 options](/developers/blog/2022-10-10-add-options-to-SelectEntityDescription.md)
* [长期统计现在跟踪源实体的单位](/developers/blog/2022-09-29-statistics_refactoring.md)
* [新的 sensor device classes](/developers/blog/2022-09-28-new_sensor_device_classes.md)
* [弃用单位转换工具](/developers/blog/2022-09-28-deprecate-conversion-utilities.md)
* [2022.10 的 Bluetooth async\_track\_unavailable API 变更](/developers/blog/2022-09-16-bluetooth-unavailable-api-changes.md)
* [弃用 media player 常量](/developers/blog/2022-09-06-media-player-repeat-mode-deprecation.md)
* [Issue registry 已移至 homeassistant.helpers](/developers/blog/2022-08-25-issue_registry_moved_to_helpers.md)
* [全局可访问的 HomeAssistant 对象(hass)](/developers/blog/2022-08-24-globally_accessible_hass.md)
* [LightEntity 不再支持 white\_value](/developers/blog/2022-08-18-light_white_value_removed.md)
* [2022.9 的 AutomationActionType 弃用](/developers/blog/2022-08-15-automation-action-type-deprecation.md)
* [2022.9 的 Bluetooth passive sensor API 变更](/developers/blog/2022-08-08-bluetooth-api-changes.md)
* [2022.9 的 Device tracker 弃用](/developers/blog/2022-07-29-device-tracker_source-type-deprecation.md)
* [采用一种新的实体命名方式](/developers/blog/2022-07-10-entity_naming.md)
* [Store 现在是一个 Generic 类](/developers/blog/2022-07-08-generic-store.md)
* [等待 config entry platforms](/developers/blog/2022-07-08-config_entry_forwards.md)
* [重构 Weather entity 以支持单位转换](/developers/blog/2022-06-27-weather_entity_refactoring.md)
* [重构 Number entity 以支持单位转换](/developers/blog/2022-06-14-number_entity_refactoring.md)
* [避免在 config entry 设置时重新加载](/developers/blog/2022-06-13-unsafe_reloads_during_entry_setup.md)
* [弃用 Data Entry Flow 常量](/developers/blog/2022-06-07-dataflow-constants-deprecation.md)
* [在 update coordinator 中支持 context](/developers/blog/2022-06-02-update_coordinator-context.md)
* [Media Player 更新:enqueue 变更,添加 announce](/developers/blog/2022-05-31-media-player-updates.md)
* [Logbook API 移除 entity\_matches\_only 标志](/developers/blog/2022-05-24-entity_matches_only_removed_logbook_card.md)
* [我们 Docker base images 上的 S6-Overlay 3.x 更新](/developers/blog/2022-05-12-s6-overlay-base-images.md)
* [ServiceInfo model 改进和弃用](/developers/blog/2022-05-03-serviceinfo-model-improvements.md)
* [2022.5 的常量弃用](/developers/blog/2022-05-03-constants-deprecations.md)
* [TTS say service 使用 media source IDs](/developers/blog/2022-04-26-tts-say-media-source.md)
* [告别 time\_changed 事件](/developers/blog/2022-04-20-saying-goodbye-to-event_time_changed.md)
* [Calendar Data Model 改进和弃用](/developers/blog/2022-04-18-calendar-data-model.md)
* [Local OAuth2 更新](/developers/blog/2022-04-16-local-oauth2.md)
* [弃用所有 SUPPORT\_\* 常量](/developers/blog/2022-04-02-support-constants-deprecation.md)
* [2022.4 中的开发者新功能](/developers/blog/2022-03-30-2022.4-new-dev-features.md)
* [引入 update entity](/developers/blog/2022-03-20-update-entity.md)
* [选择支持手动移除设备](/developers/blog/2022-02-28-opt-in-remove-device.md)
* [Paper Elements 正在被移除](/developers/blog/2022-02-18-paper-elements.md)
## 2021
* [Device configuration URL 和 entity categories](/developers/blog/2021-10-26-config-entity.md)
* [新的 sensor state class:total](/developers/blog/2021-09-20-state_class_total.md)
* [Supervisor 更新](/developers/blog/2021-08-24-supervisor_update.md)
* [新 sensor state class:total\_increasing](/developers/blog/2021-08-16-state_class_total.md)
* [温度转换移至 SensorEntity](/developers/blog/2021-08-12-sensor_temperature_conversion.md)
* [用于长期统计的新 sensor properties](/developers/blog/2021-05-25-sensor_attributes.md)
* [升级到 Lit 2.0](/developers/blog/2021-05-19-lit-2.0.md)
* [用 python-dateutil 替换 pytz](/developers/blog/2021-05-07-switch-pytz-to-python-dateutil.md)
* [Astral 升级到 2.2 版本](/developers/blog/2021-04-09-astral-2.2.md)
* [Custom integration 变更](/developers/blog/2021-01-29-custom-integration-changes.md)
## 2020
* [System Health 和 Templates](/developers/blog/2020-11-09-system-health-and-templates.md)
* [add-ons 即将发生的变更](/developers/blog/2020-10-29-supervisor-update.md)
* [Lazy-loading more-info controls](/developers/blog/2020-10-02-lazyMoreInfo.md)
* [Lovelace:Custom View Layouts](/developers/blog/2020-09-30-customViewChanges.md)
* [运行 Home Assistant 时提升 Python 速度 40%](/developers/blog/2020-07-13-alpine-python.md)
* [Lovelace:getCardSize 现在可以是 async](/developers/blog/2020-06-01-getCardSize.md)
* [Entity class names](/developers/blog/2020-05-14-entity-class-names.md)
* [Custom icon sets](/developers/blog/2020-05-09-custom-iconsets.md)
* [custom integrations 的 Logos](/developers/blog/2020-05-08-logos-custom-integrations.md)
* [Instance URL helper](/developers/blog/2020-05-08-instance-url-helper.md)
* [custom Lovelace 的 Translations](/developers/blog/2020-04-23-frontend-translations.md)
* [custom components 的 Hassfest](/developers/blog/2020-04-16-hassfest.md)
* [我们 Docker 容器的 S6 Overlay](/developers/blog/2020-04-12-s6-overlay.md)
* [Translations 2.0](/developers/blog/2020-04-10-translation-handling.md)
* [0.106 中可能影响 custom cards 的变更](/developers/blog/2020-02-18-106-custom-card-changes.md)
* [Z-Wave integration 更新](/developers/blog/2020-02-04-new-zwave.md)
## 2019
* [Home Assistant 1.0 中的 Simple Mode](/developers/blog/2019-10-05-simple-mode.md)
* [Hacktoberfest 2019](/developers/blog/2019-09-27-hacktoberfest.md)
* [采用 Black](/developers/blog/2019-07-31-black.md)
* [Building All The Things](/developers/blog/2019-07-19-building-all-the-things.md)
* [Climate Cleanup](/developers/blog/2019-07-03-climate-cleanup.md)
* [物联网与现代 Web](/developers/blog/2019-05-22-internet-of-things-and-the-modern-web.md)
* [介绍 Integrations](/developers/blog/2019-04-12-new-integration-structure.md)
* [我能拥有用户权限吗?](/developers/blog/2019-03-11-user-permissions.md)
* [伟大的迁移](/developers/blog/2019-02-19-the-great-migration.md)
## 2018
* [弃用 homeassistant.remote](/developers/blog/2018-08-13-deprecating-remote-package.md)
* [试用新的 auth 系统](/developers/blog/2018-07-02-trying-new-auth.md)
* [0.71:改进的 custom panels 和 custom UI 的少量变更](/developers/blog/2018-06-01-071-custom-panels.md)
* [Custom UI 和 Custom Panels 更新](/developers/blog/2018-05-22-custom-ui-panels-api.md)
---
url: /developers/bluetooth.md
---
### 面向集成作者的最佳实践
* 需要使用蓝牙适配器的集成应在 [`manifest.json`](/developers/creating_integration_manifest.md) 中的 [`dependencies`](/developers/creating_integration_manifest.md#dependencies) 添加 `bluetooth_adapters`。[`manifest.json`](/developers/creating_integration_manifest.md) 条目可确保所有受支持的远程适配器在集成尝试使用它们之前均已连接。
* 调用 `bluetooth.async_get_scanner` API 获取 `BleakScanner` 实例并将其传递给你的库。返回的 scanner 可以避免运行多个 scanner 带来的开销,而这开销是显著的。此外,封装后的 scanner 在用户更改蓝牙适配器设置后将继续正常工作。
* 避免在连接之间复用 `BleakClient`,因为这会降低连接的可靠性。
* 连接超时时间至少设置为十(10)秒,因为 `BlueZ` 在首次连接新设备或已更新设备时必须解析服务。连接时瞬态错误很常见,且首次尝试并不总能成功。`bleak-retry-connector` PyPI 包可以省去快速可靠建立设备连接的繁琐操作。
### 可连接与不可连接的蓝牙控制器
Home Assistant 支持远程蓝牙控制器。某些控制器仅支持监听 advertisement 数据,不支持连接设备。由于许多设备只需要接收 advertisement,我们引入了可连接设备(connectable devices)和不可连接设备(non-connectable devices)的概念。如果设备不需要主动连接,则将 `connectable` 参数设置为 `False` 以选择接收来自不支持发出出站连接的控制器的数据。当 `connectable` 设置为 `False` 时,将同时提供来自可连接和不可连接控制器的数据。
`connectable` 的默认值为 `True`。如果集成中有些设备需要连接而另一些不需要,`manifest.json` 应为相应设备正确设置该标志。如果无法构建 matcher 来区分相似的设备,请在 config flow discovery `BluetoothServiceInfoBleak` 中检查 `connectable` 属性,并拒绝需要出站连接的设备 flow。
---
url: /developers/config_entries_index.md
---
配置条目(Config entries)是由 Home Assistant 持久存储的配置数据。配置条目由用户通过 UI 创建。UI 流程由集成定义的 [config flow handler](/developers/core/integration/config_flow.md) 驱动。
创建后,用户可以删除配置条目。此外,用户还可以通过集成定义的 [reconfigure step](/developers/core/integration/config_flow.md#reconfigure) 或 [options flow handler](/developers/core/integration/options_flow.md) 修改配置条目。
### 配置子条目
配置条目可以将存储的配置数据逻辑地分离为子条目,用户可以通过 UI 将子条目添加到现有配置条目中。例如,一个提供天气预报的集成,配置条目存储认证信息,每个需要预报天气的位置则作为子条目存储。
与配置条目类似,子条目可以选择性地支持 reconfigure step。
## 生命周期
| 状态 | 描述 |
| ----- | ----------- |
| not loaded | 配置条目尚未加载。这是创建配置条目或重启 Home Assistant 时的初始状态。 |
| setup in progress | 尝试加载配置条目时的中间状态。 |
| loaded | 配置条目已加载。 |
| setup error | 尝试设置配置条目时发生错误。 |
| setup retry | 配置条目的依赖项尚未就绪。Home Assistant 将在未来自动重试加载此配置条目。重试间隔将自动增加。 |
| migration error | 配置条目必须迁移到较新版本,但迁移失败。 |
| unload in progress | 尝试卸载配置条目时的中间状态。 |
| failed unload | 尝试卸载配置条目但失败,可能是因为不支持卸载或抛出了异常。 |
更多关于如何暴露错误和请求重试的信息,参见 [Handling Setup Failures](/developers/integration_setup_failures.md#integrations-using-async_setup_entry)。
## 设置条目
启动时,Home Assistant 首先调用[普通集成 setup](/developers/creating_component_index.md),
然后对每个条目调用方法 `async_setup_entry(hass, entry)`。如果在运行时创建了新的 Config Entry,Home Assistant 也会调用 `async_setup_entry(hass, entry)` ([示例](https://github.com/home-assistant/core/blob/f18ddb628c3574bc82e21563d9ba901bd75bc8b5/homeassistant/components/hassio/__init__.py#L522))。
### 对于 platforms
如果集成包含 platforms,它需要将 Config Entry 的设置转发给 platform。这可以通过在 config entry manager 上调用转发函数来完成([示例](https://github.com/home-assistant/core/blob/f18ddb628c3574bc82e21563d9ba901bd75bc8b5/homeassistant/components/hassio/__init__.py#L529)):
```python
await hass.config_entries.async_forward_entry_setups(config_entry, ["light", "sensor", "switch"])
```
Platform 要支持 config entries,需要添加 setup entry 函数([示例](https://github.com/home-assistant/core/blob/f18ddb628c3574bc82e21563d9ba901bd75bc8b5/homeassistant/components/hassio/__init__.py#L522)):
```python
async def async_setup_entry(
hass: HomeAssistant,
config_entry: MyConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up entry."""
```
## 卸载条目
集成可以选择性地支持卸载配置条目。卸载条目时,集成需要清理所有 entity、取消订阅任何事件监听器并关闭所有连接。要实现这一点,在集成中添加 `async_unload_entry(hass, entry)`([示例](https://github.com/home-assistant/core/blob/f18ddb628c3574bc82e21563d9ba901bd75bc8b5/homeassistant/components/hassio/__init__.py#L534))。在调用 `async_unload_entry` 之前,配置条目的状态被设置为 `ConfigEntryState.UNLOAD_IN_PROGRESS`。
对于每个你转发了配置条目的 platform,也需要转发卸载操作。
```python
async def async_unload_entry(hass: HomeAssistant, entry: MyConfigEntry) -> bool:
"""Unload a config entry."""
```
如果你需要在 platform 中清理 entity 使用的资源,让 entity 实现 [`async_will_remove_from_hass`](/developers/core/entity.md#async_will_remove_from_hass) 方法。
## 删除条目
如果集成需要在条目被删除时清理代码,可以定义删除函数 `async_remove_entry`。在调用 `async_remove_entry` 之前,配置条目已从 `hass.config_entries` 中删除。
```python
async def async_remove_entry(hass: HomeAssistant, entry: MyConfigEntry) -> None:
"""Handle removal of an entry."""
```
## 将配置条目迁移到新版本
如果配置条目的版本发生变化,必须实现 `async_migrate_entry` 以支持旧条目的迁移。详细内容见 [config flow 文档](/developers/core/integration/config_flow.md#config-entry-migration)。
```python
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
"""Migrate old entry."""
```
## 修改配置条目
`ConfigEntry` 对象(包括 data 和 options)绝不应被集成直接修改,集成必须调用 `async_update_entry`,其用法在 [config flow 文档](/developers/core/integration/config_flow.md#config-entry-migration) 中有示例说明。
## 订阅配置条目状态变更
如果你希望收到 `ConfigEntry` 的 `state` 变更通知(例如,从 `ConfigEntryState.LOADED` 变为 `ConfigEntryState.UNLOAD_IN_PROGRESS`),可以添加一个监听器,该监听器会在 `async_on_state_change` 中被通知。此辅助方法还返回一个回调,你可以调用它以再次移除监听器。因此,订阅变更直到条目卸载的写法为 `entry.async_on_unload(entry.async_on_state_change(notify_me))`。
---
url: /developers/core/bluetooth/api.md
---
### 订阅 Bluetooth 发现
一些集成可能需要立即知道设备被发现的时间。Bluetooth 集成提供了一个注册 API,用于在发现匹配特定 key 值的新设备时接收 callbacks。`manifest.json` 中 `bluetooth` 的匹配格式相同。除了 `manifest.json` 中使用的 matchers 外,`address` 也可用作 matcher。
函数 `bluetooth.async_register_callback` 提供了此功能。该函数返回一个 callback,调用它将取消注册。
以下示例展示了注册以在 Switchbot 设备附近时接收 callbacks:
```python
from homeassistant.components import bluetooth
...
@callback
def _async_discovered_device(service_info: bluetooth.BluetoothServiceInfoBleak, change: bluetooth.BluetoothChange) -> None:
"""Subscribe to bluetooth changes."""
_LOGGER.warning("New service_info: %s", service_info)
entry.async_on_unload(
bluetooth.async_register_callback(
hass, _async_discovered_device, {"service_uuid": "cba20d00-224d-11e6-9fb8-0002a5d5c51b", "connectable": False}, bluetooth.BluetoothScanningMode.ACTIVE
)
)
```
以下示例展示了注册以获取 HomeKit 设备的 callbacks:
```python
from homeassistant.components import bluetooth
...
entry.async_on_unload(
bluetooth.async_register_callback(
hass, _async_discovered_homekit_device, {"manufacturer_id": 76, "manufacturer_data_first_byte": 6}, bluetooth.BluetoothScanningMode.ACTIVE
)
)
```
以下示例展示了注册以获取 Nespresso Prodigios 的 callbacks:
```python
from homeassistant.components import bluetooth
...
entry.async_on_unload(
bluetooth.async_register_callback(
hass, _async_nespresso_found, {"local_name": "Prodigio_*")}, bluetooth.BluetoothScanningMode.ACTIVE
)
)
```
以下示例展示了注册以获取 address 为 `44:33:11:22:33:22` 的设备的 callbacks:
```python
from homeassistant.components import bluetooth
...
entry.async_on_unload(
bluetooth.async_register_callback(
hass, _async_specific_device_found, {"address": "44:33:11:22:33:22")}, bluetooth.BluetoothScanningMode.ACTIVE
)
)
```
#### 请求按需 active 扫描
当 matcher 以特定 `address` 为目标且 `mode` 不是 `PASSIVE` 时,callback 注册也可以选择将该 address 加入 `AUTO` 模式 scanners 使用的 active-scan 调度器。这使得集成可以按适合其设备的 cadence 请求短时间 active-scan windows,而无需强制整个系统进入持续 active 扫描。
传递 `scan_interval`(窗口开始之间的秒数)和/或 `scan_duration`(每个窗口的秒数)作为关键字参数。两者都是可选的;省略时,使用 habluetooth 的默认值(5 分钟间隔,10 秒持续)。有效窗口被限制在 habluetooth 允许范围内。如果 matcher 中没有 `address`,则跳过 active-scan 请求;callback 本身仍正常触发。
```python
from homeassistant.components import bluetooth
...
entry.async_on_unload(
bluetooth.async_register_callback(
hass,
_async_specific_device_found,
{"address": "44:33:11:22:33:22"},
bluetooth.BluetoothScanningMode.ACTIVE,
scan_interval=600.0,
scan_duration=10.0,
)
)
```
#### 控制历史记录重放顺序
注册 callback 时,Bluetooth 集成会重放缓存的 advertisements,以便新的 subscriber 立即看到所有已知设备。重放的顺序以及是否重放都可以通过 `replay` 关键字参数控制,它接受一个 `BluetoothCallbackReplay` 值:
| Value | Behavior |
|---|---|
| `OLDEST_FIRST`(默认) | 按首次看到 advertisement 的顺序重放。 |
| `NEWEST_FIRST` | 先重放最新的 advertisement。当消费者希望立即对当前设备状态做出响应时很有用。 |
| `DISABLED` | 完全跳过重放。对于仅关心未来实时 advertisements 的消费者很有用。 |
```python
from homeassistant.components import bluetooth
...
entry.async_on_unload(
bluetooth.async_register_callback(
hass,
_async_discovered_device,
{"service_uuid": "cba20d00-224d-11e6-9fb8-0002a5d5c51b", "connectable": False},
bluetooth.BluetoothScanningMode.ACTIVE,
replay=bluetooth.BluetoothCallbackReplay.NEWEST_FIRST,
)
)
```
### 获取共享的 BleakScanner 实例
需要 `BleakScanner` 实例的集成应调用 `bluetooth.async_get_scanner` API。此 API 返回围绕单个 `BleakScanner` 的 wrapper,允许集成共享而不会使系统过载。
```python
from homeassistant.components import bluetooth
scanner = bluetooth.async_get_scanner(hass)
```
### 判断 scanner 是否正在运行
Bluetooth 集成可能已经设置好,但没有 connectable 适配器或 remotes。可以使用 `bluetooth.async_scanner_count` API 来确定是否有能够接收 advertisements 或生成可用于连接设备的 `BLEDevice` 的 scanner 正在运行。如果没有任何 scanner 能生成 connectable `BLEDevice` 对象,集成可能希望在设置期间抛出更有用的错误。
```python
from homeassistant.components import bluetooth
count = bluetooth.async_scanner_count(hass, connectable=True)
```
### 通过 source 访问 scanner
`bluetooth.async_scanner_by_source` API 提供了通过其 source(MAC 地址)访问特定 Bluetooth scanner 的方法。这主要面向实现 Bluetooth client 并需要直接与 scanner 交互的集成。
```python
from homeassistant.components import bluetooth
scanner = bluetooth.async_scanner_by_source(hass, "AA:BB:CC:DD:EE:FF")
if scanner is not None:
# 检查 scanner 属性(只读)
if scanner.current_mode is not None:
_LOGGER.debug("Scanner mode: %s", scanner.current_mode)
```
### 访问所有当前 scanners
`bluetooth.async_current_scanners` API 提供了访问所有当前活动 Bluetooth scanners 列表的方法,用于调试、diagnostics 和 scanner 状态的 introspection。此 API 返回所有已注册的 scanners(包括 connectable 和 non-connectable)作为 scanner 对象列表。
```python
from homeassistant.components import bluetooth
scanners = bluetooth.async_current_scanners(hass)
for scanner in scanners:
# 检查 scanner 属性(只读)
if scanner.current_mode is not None:
_LOGGER.debug("Scanner %s is in mode %s", scanner.source, scanner.current_mode)
```
:::warning 关于 Scanner APIs 的重要信息
`async_scanner_by_source` 和 `async_current_scanners` 返回的 scanner 对象来自 `habluetooth` 包,它们的接口在 Home Assistant 发布之间不保证保持稳定。**你只能检查 scanner 属性,绝不能修改它们。** 直接修改 scanner 对象可能会破坏 Home Assistant 的 Bluetooth 功能。
**不要:**
* 更改 scanner 属性或调用修改状态的 methods
* 存储对 scanners 的引用超出你直接使用的范围
* 假设 scanner 接口在将来版本中保持不变
**应:**
* 仅将 scanners 用于只读检查、调试和 diagnostics
* 访问简单的属性,如 `source` 和 `current_mode`
* 处理属性可能为 `None` 的情况
:::
### 订阅不可用 callbacks
要在 Bluetooth 栈不再看到设备时收到 callback,请调用 `bluetooth.async_track_unavailable` API。出于性能原因,设备不再被看到后最多可能需要五分钟才能收到 callback。
如果 `connectable` 参数设置为 `True`,如果任何 `connectable` controller 都能到达该设备,则该设备将被视为可用。如果只有 non-connectable controllers 能到达该设备,则该设备将被视为不可用。如果参数设置为 `False`,如果任何 controller 都能看到该设备,则该设备将被视为可用。
```python
from homeassistant.components import bluetooth
def _unavailable_callback(info: bluetooth.BluetoothServiceInfoBleak) -> None:
_LOGGER.debug("%s is no longer seen", info.address)
cancel = bluetooth.async_track_unavailable(hass, _unavailable_callback, "44:44:33:11:23:42", connectable=True)
```
### 查询可用性超时
可用性基于自设备最后一次已知广播以来的时间。此超时根据设备的常规广播模式自动学习。你可以使用 `bluetooth.async_get_learned_advertising_interval` API 来查询它。
```python
from homeassistant.components import bluetooth
learned_interval = bluetooth.async_get_learned_advertising_interval(hass, "44:44:33:11:23:42")
```
如果 advertising interval 尚未知,这将返回 `None`。在这种情况下,unavailability tracking 将尝试该 address 的 fallback interval。以下示例返回由集成手动设置的 interval:
```python
from homeassistant.components import bluetooth
bluetooth.async_set_fallback_availability_interval(hass, "44:44:33:11:23:42", 64.0)
fallback_interval = bluetooth.async_get_fallback_availability_interval(hass, "44:44:33:11:23:42")
```
如果没有针对设备的 learned interval 或 fallback interval,则使用硬编码的安全默认 interval:
```python
from homeassistant.components import bluetooth
default_fallback_interval = bluetooth.FALLBACK_MAXIMUM_STALE_ADVERTISEMENT_SECONDS
```
### 通过 address 获取 bleak `BLEDevice`
集成应通过调用 `bluetooth.async_ble_device_from_address` API 来避免启动额外 scanner 以解析 address 的开销,该 API 返回最接近可到达该设备的已配置 `bluetooth` 适配器的 `BLEDevice`。如果没有适配器能到达该设备,`bluetooth.async_ble_device_from_address` API 将返回 `None`。
假设集成希望从 `connectable` 和 non-connectable controllers 接收数据。在这种情况下,当它想要发起出站连接时,只要至少有一个 `connectable` controller 在范围内,它就可以将 `BLEDevice` 交换为 `connectable` 的一个。
```python
from homeassistant.components import bluetooth
ble_device = bluetooth.async_ble_device_from_address(hass, "44:44:33:11:23:42", connectable=True)
```
### 解释设备不可达的原因
当 `async_ble_device_from_address` 返回 `None` 或无法建立连接时,`bluetooth.async_address_reachability_diagnostics` API 返回一个人类可读的字符串,解释原因,适合嵌入错误或对日志消息中。传递一个 `BluetoothReachabilityIntent` 来描述你对设备的需求,因为相关事实各不相同:仅消费 advertisements 的调用者不关心 connectable 路径或连接槽位,而想要连接的设备则关心。
该字符串报告 address 是否在 connectable history 中、是否仅通过 non-connectable advertisements 看到、或从未看到过;哪些 scanners 当前看到它(及其 RSSI 和槽位分配);以及注册、扫描和 connectable 的 scanners 数量。它还特别指出了所有 scanner 都因忙于连接而暂停的情况,这意味着根本无法接收 advertisements。
返回的字符串仅供人类使用;其措辞不稳定,因此不要解析它。
```python
from homeassistant.components import bluetooth
from homeassistant.components.bluetooth import BluetoothReachabilityIntent
reason = bluetooth.async_address_reachability_diagnostics(
hass, "44:44:33:11:23:42", BluetoothReachabilityIntent.CONNECTION
)
```
### 获取设备的最新 `BluetoothServiceInfoBleak`
最新的 advertisement 和设备数据可以通过 `bluetooth.async_last_service_info` API 获取,它从具有最佳 RSSI 的指定 connectable 类型的 scanner 返回 `BluetoothServiceInfoBleak`。
```python
from homeassistant.components import bluetooth
service_info = bluetooth.async_last_service_info(hass, "44:44:33:11:23:42", connectable=True)
```
### 检查设备是否存在
要确定设备是否仍然存在,请调用 `bluetooth.async_address_present` API。如果你的集成需要设备存在才能认为其可用,此调用很有用。
```python
from homeassistant.components import bluetooth
bluetooth.async_address_present(hass, "44:44:33:11:23:42", connectable=True)
```
### 获取所有已发现设备
要访问先前发现设备的列表,请调用 `bluetooth.async_discovered_service_info` API。只有仍然存在且的设备才在缓存中。
```python
from homeassistant.components import bluetooth
service_infos = bluetooth.async_discovered_service_info(hass, connectable=True)
```
### 按每个 Bluetooth 适配器获取所有已发现设备和 advertisement 数据
要独立访问先前发现设备和每个适配器接收到的 advertisement 数据列表,请调用 `bluetooth.async_scanner_devices_by_address` API。该调用返回 `BluetoothScannerDevice` 对象列表。相同的设备和 advertisement 数据可能出现多次,每个到达它的 Bluetooth 适配器出现一次。
```python
from homeassistant.components import bluetooth
device = bluetooth.async_scanner_devices_by_address(hass, "44:44:33:11:23:42", connectable=True)
# device.ble_device 是 bleak `BLEDevice`
# device.advertisement 是 bleak `AdvertisementData`
# device.scanner 是找到该设备的 scanner
```
### 触发设备重新发现
当 configuration entry 或 device 从 Home Assistant 中移除时,触发其 address 的重新发现,以确保它们无需重启 Home Assistant 即可设置为已设置。如果集成在每个 configuration entry 下管理多个设备,可以利用 device registry 的 Bluetooth connection property。
```python
from homeassistant.components import bluetooth
bluetooth.async_rediscover_address(hass, "44:44:33:11:23:42")
```
### 触发一次性 active 扫描
对于 config flow discovery 和其他一次性探测,`bluetooth.async_request_active_scan` 在所有 `AUTO` 模式 scanner 上运行按需 active sweep,而无需等待周期性 rediscovery cadence。它等待 `duration` 秒,以便调用者随后可以读取新发现的 advertisements。`duration` 是可选的;省略时,使用 habluetooth 的按需 sweep duration。调度器将值限制在其允许范围内。并发调用者去重为单个 bus 级 window。
只有 `AUTO` 模式 scanners 受影响;`PASSIVE` 和 `ACTIVE` scanners 是用户明确的选择,保持不变。
```python
from homeassistant.components import bluetooth
await bluetooth.async_request_active_scan(hass)
```
### 清除重新发现的匹配历史
Bluetooth 集成跟踪每个设备已看到哪些 advertisement 字段(manufacturer\_data UUIDs、service\_data UUIDs、service\_uuids),以确定何时触发 discovery。它只检查 UUIDs 是否曾被看到过,而不检查其内容是否已更改。
对于状态更改但保持相同 UUIDs 的设备(如工厂重置或在操作状态之间转换的设备),你可以清除 match history,以便当设备使用不同内容再次广播时允许重新发现。
`bluetooth.async_clear_address_from_match_history` API 清除特定 address 的 match history,而不会立即重新触发 discovery。这与 `async_rediscover_address` 不同,后者清除历史记录并立即使用缓存数据重新触发 discovery。
在以下情况下使用此 API:
* 设备被工厂重置(状态更改但 UUIDs 保持不变)
* 设备在具有相同 advertisement UUIDs 的操作状态之间转换
* 你想要为未来的 rediscovery 做准备而不立即触发 flow
```python
from homeassistant.components import bluetooth
# 清除 match history,允许未来的 advertisements 触发 discovery
bluetooth.async_clear_address_from_match_history(hass, "44:44:33:11:23:42")
```
:::warning 性能考虑
不要对 advertisement 数据频繁更改的设备使用此 API(例如,在 advertisement 数据中更新温度读数的 sensors)。清除此类设备的 match history 将在每次 advertisement 更改时触发新的 discovery flow,这可能会使系统不堪重负并造成糟糕的用户体验。
此 API 旨在用于不频繁的状态更改,如工厂重置或主要操作模式转换,而不是常规数据更新。
:::
### 清除缓存的 advertisement 历史
为减少开销,当 `manufacturer_data`、`service_data`、`service_uuids` 和 `name` 字段都与同一 address 先前看到的 advertisement 匹配时,Bluetooth manager 会丢弃 advertisements。这意味着发出不变的 "I am awake" advertisement 的设备将只将第一个数据包交付给你的 callback;后续相同的数据包将被静默去重。
`bluetooth.async_clear_advertisement_history` API 清除单个 address 的缓存 advertisement 状态,使下一个 advertisement 被视为新数据并分发给 callbacks,即使 payload 与前一个完全相同。
这对于通过 GATT 连接到设备读取传感器数据的集成很有用,并且需要知道设备何时再次唤醒;GATT session 结束后,调用 `async_clear_advertisement_history` 以便下一个唤醒 advertisement 被交付。
```python
from homeassistant.components import bluetooth
# 从设备断开后,清除缓存的 advertisement
# 以便下一个相同的 "I am awake" 数据包被分发给 callbacks
bluetooth.async_clear_advertisement_history(hass, "44:44:33:11:23:42")
```
:::note
这仅清除 advertisement 去重状态;它不影响 integration matcher history。如果你还需要未来的 advertisements 重新触发 discovery flows,请使用 `async_clear_address_from_match_history` 或 `async_rediscover_address`。
:::
### 等待特定 advertisement
要等待特定 advertisement,请调用 `bluetooth.async_process_advertisements` API。
```python
from homeassistant.components import bluetooth
def _process_more_advertisements(
service_info: BluetoothServiceInfoBleak,
) -> bool:
"""等待 manufacturer_data 中包含 323 的 advertisement。"""
return 323 in service_info.manufacturer_data
service_info = await bluetooth.async_process_advertisements(
hass,
_process_more_advertisements,
{"address": discovery_info.address, "connectable": False},
BluetoothScanningMode.ACTIVE,
ADDITIONAL_DISCOVERY_TIMEOUT
)
```
当 `mode` 不是 `PASSIVE` 且 matcher 包含 `address` 时,`timeout` 也会作为 `scan_duration` 转发给 active-scan 调度器,以便在等待 advertisement 时 `AUTO` 模式 scanners 为该 address 翻转 ACTIVE。
### 注册外部 scanner
提供 Bluetooth adapter 的集成应在 `manifest.json` 的 [`dependencies`](/developers/creating_integration_manifest.md#dependencies) 中添加 `bluetooth`,并被添加到 `bluetooth_adapters` 集成的 [`after_dependencies`](/developers/creating_integration_manifest.md#after-dependencies)。
要注册 external scanner,请调用 `bluetooth.async_register_scanner` API。scanner 必须继承自 `BaseHaScanner`。
如果 scanner 需要 connection slot 管理以避免使适配器过载,请通过 `connection_slots` 参数以整数形式传递连接槽位数。
```python
from homeassistant.components import bluetooth
cancel = bluetooth.async_register_scanner(hass, scanner, connection_slots=slots)
```
scanner 需要将 advertisement 数据以 `BluetoothServiceInfoBleak` 对象的形式提供给中央 Bluetooth manager。将数据发送到中央 manager 所需的 callback 可以通过 `bluetooth.async_get_advertisement_callback` API 获取。
```python
callback = bluetooth.async_get_advertisement_callback(hass)
callback(BluetoothServiceInfoBleak(...))
```
### 移除外部 scanner
要永久移除 external scanner,请使用 scanner 的 `source`(MAC 地址)调用 `bluetooth.async_remove_scanner` API。这将移除与该 scanner 关联的任何 advertisement history。
```python
from homeassistant.components import bluetooth
bluetooth.async_remove_scanner(hass, source)
```
---
url: /developers/core/bluetooth/bluetooth_fetching_data.md
---
## 选择获取数据的方法
如果设备通知更新的主要方式是 Bluetooth advertisements,且其主要功能是 sensor、binary sensor 或触发事件:
* 如果所有 sensors 都通过 Bluetooth advertisements 更新:[`PassiveBluetoothProcessorCoordinator`](#passivebluetoothprocessorcoordinator)
* 如果某些 sensors 需要 active connection:[`ActiveBluetoothProcessorCoordinator`](#activebluetoothprocessorcoordinator)
如果设备通知更新的主要方式是 Bluetooth advertisements,且其主要功能**不是** sensor、binary sensor 或触发事件:
* 如果所有 entities 都通过 Bluetooth advertisements 更新:[`PassiveBluetoothCoordinator`](#passivebluetoothcoordinator)
* 如果需要 active connections:[`ActiveBluetoothDataUpdateCoordinator`](#activebluetoothcoordinator)
如果你的设备仅通过 active Bluetooth connection 通信而不使用 Bluetooth advertisements:
* [`DataUpdateCoordinator`](/developers/integration_fetching_data.md)
## BluetoothProcessorCoordinator
`ActiveBluetoothProcessorCoordinator` 和 `PassiveBluetoothProcessorCoordinator` 大幅减少了创建主要功能为 sensor、binary sensors 或触发事件的集成所需的代码。通过将传入 processor coordinators 的数据格式化为 `PassiveBluetoothDataUpdate` 对象,框架可以负责按需创建 entities,并允许使用最小的 `sensor` 和 `binary_sensor` platform 实现。
这些框架要求来自 library 的数据被格式化为 `PassiveBluetoothDataUpdate`,如下所示:
```python
@dataclasses.dataclass(frozen=True)
class PassiveBluetoothEntityKey:
"""Key for a passive bluetooth entity.
Example:
key: temperature
device_id: outdoor_sensor_1
"""
key: str
device_id: str | None
@dataclasses.dataclass(frozen=True)
class PassiveBluetoothDataUpdate(Generic[_T]):
"""Generic bluetooth data."""
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` 示例:
```python
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 homeassistant.const import Platform
PLATFORMS: list[Platform] = [Platform.SENSOR]
from your_library import DataParser
_LOGGER = logging.getLogger(__name__)
type ExampleConfigEntry = ConfigEntry[PassiveBluetoothProcessorCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: ExampleConfigEntry) -> bool:
"""Set up example BLE device from a config entry."""
address = entry.unique_id
data = DataParser()
coordinator = PassiveBluetoothProcessorCoordinator(
hass,
_LOGGER,
address=address,
mode=BluetoothScanningMode.ACTIVE,
update_method=data.update,
)
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(
# 仅在所有 platforms 都有机会订阅后才开始
coordinator.async_start()
)
return True
```
`sensor.py` 示例:
```python
from homeassistant.components.bluetooth.passive_update_processor import (
PassiveBluetoothDataProcessor,
PassiveBluetoothDataUpdate,
PassiveBluetoothEntityKey,
PassiveBluetoothProcessorEntity,
)
from homeassistant.components.sensor import SensorEntity
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import ExampleConfigEntry
def sensor_update_to_bluetooth_data_update(parsed_data):
"""Convert a sensor update to a Bluetooth data update."""
# 此函数必须将 parsed_data
# 从 library 的 update_method 转换为 `PassiveBluetoothDataUpdate`
# 参见上面的结构
return PassiveBluetoothDataUpdate(
devices={},
entity_descriptions={},
entity_data={},
entity_names={},
)
async def async_setup_entry(
hass: HomeAssistant,
entry: ExampleConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the example BLE sensors."""
coordinator = entry.runtime_data
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):
"""Representation of an example BLE sensor."""
@property
def native_value(self) -> float | int | str | None:
"""Return the native value."""
return self.processor.entity_data.get(self.entity_key)
```
### ActiveBluetoothProcessorCoordinator
`ActiveBluetoothProcessorCoordinator` 的功能与 `PassiveBluetoothProcessorCoordinator` 几乎相同,
但还会基于 `needs_poll_method` 和 `poll_method`
函数在设备的 Bluetooth advertisement 更改时建立 active connection 来轮询数据。`sensor.py` 的实现
与 `PassiveBluetoothProcessorCoordinator` 相同。
使用 `ActiveBluetoothProcessorCoordinator` 的集成 `__init__.py` 的 `async_setup_entry` 示例:
```python
import logging
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__)
type ExampleConfigEntry = ConfigEntry[ActiveBluetoothProcessorCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: ExampleConfigEntry) -> bool:
"""Set up example BLE device from a config entry."""
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:
# 我们没有可用于轮询该设备的 Bluetooth controller
raise RuntimeError(
f"No connectable device found for {service_info.device.address}"
)
return await data.async_poll(connectable_device)
coordinator = ActiveBluetoothProcessorCoordinator(
hass,
_LOGGER,
address=address,
mode=BluetoothScanningMode.PASSIVE,
update_method=data.update,
needs_poll_method=_needs_poll,
poll_method=_async_poll,
# 我们将使用来自 non-connectable 设备的 advertisements,
# 因为如果需要轮询它们,我们会用 BLEDevice 换取 connectable 的一个
connectable=False,
)
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(
# 仅在所有 platforms 都有机会订阅后才开始
coordinator.async_start()
)
return True
```
#### 请求 active 扫描节奏
`PassiveBluetoothProcessorCoordinator` 和 `ActiveBluetoothProcessorCoordinator` 接受两个可选的关键字参数 `scan_interval` 和 `scan_duration`,当 coordinator 启动时它们会被转发给 Bluetooth manager。它们请求任何 `AUTO` 模式 scanner 为该 coordinator 的 address 提供周期性 active scan window,以便设备持续发出包含完整 payload 的 advertisements,而无需永久将 scanner 切换为 `ACTIVE` 模式。
这对于在某些时间未被 actively scanned 后会回退到 connectable poll 的设备很有用;选择一个短于该回退时间的 `scan_interval` 和一个足够长的 `scan_duration` 以捕获下一个 advertisement。
```python
coordinator = ActiveBluetoothProcessorCoordinator(
hass,
_LOGGER,
address=address,
mode=BluetoothScanningMode.PASSIVE,
update_method=data.update,
needs_poll_method=_needs_poll,
poll_method=_async_poll,
connectable=False,
# 请求 AUTO 模式的 scanners 每 165 秒为 ACTIVE 翻转 10 秒
scan_interval=165.0,
scan_duration=10.0,
)
```
两个参数都默认为 `None`,让底层调度器保持其内置 cadence。`PASSIVE` 和 `ACTIVE` scanners 是用户明确的选择,不受影响;只有 `AUTO` 模式的 scanners 会遵循该请求。
## BluetoothCoordinator
`ActiveBluetoothDataUpdateCoordinator` 和 `PassiveBluetoothCoordinator` coordinators 的功能类似于
`DataUpdateCoordinators`,只是它们由传入的 advertisement 数据驱动,而不是轮询。
:::note
`_async_handle_unavailable` 回调依赖于 Bluetooth 栈检测到设备已停止广播。在 macOS 上,CoreBluetooth 会缓存 advertisement 数据,可能不会将设备消失暴露给应用层,因此即使设备已停止广播,`_async_handle_unavailable` 也可能永远不会触发。
:::
### PassiveBluetoothCoordinator
以下是一个 `PassiveBluetoothDataUpdateCoordinator` 的示例。传入的数据通过 `_async_handle_bluetooth_event` 接收,并由集成的 library 处理。
```python
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]
):
"""Class to manage fetching example data."""
def __init__(
self,
hass: HomeAssistant,
logger: logging.Logger,
ble_device: BLEDevice,
device: YourLibDevice,
) -> None:
"""Initialize example data coordinator."""
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:
"""Handle the device going unavailable."""
@callback
def _async_handle_bluetooth_event(
self,
service_info: bluetooth.BluetoothServiceInfoBleak,
change: bluetooth.BluetoothChange,
) -> None:
"""Handle a Bluetooth event."""
# 你的设备应处理传入的 advertisement 数据
```
### ActiveBluetoothDataUpdateCoordinator
以下是一个 `ActiveBluetoothDataUpdateCoordinator` 的示例。传入的数据通过 `_async_handle_bluetooth_event` 接收,并由集成的 library 处理。
传递给 `needs_poll_method` 的方法在每次 Bluetooth advertisement 更改时被调用,以确定是否应调用传递给 `poll_method` 的方法来与设备建立 active connection 以获取额外数据。
```python
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]
):
"""Class to manage fetching example data."""
def __init__(
self,
hass: HomeAssistant,
logger: logging.Logger,
ble_device: BLEDevice,
device: YourLibDevice,
) -> None:
"""Initialize example data coordinator."""
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:
"""Poll the device."""
@callback
def _async_handle_unavailable(
self, service_info: bluetooth.BluetoothServiceInfoBleak
) -> None:
"""Handle the device going unavailable."""
@callback
def _async_handle_bluetooth_event(
self,
service_info: bluetooth.BluetoothServiceInfoBleak,
change: bluetooth.BluetoothChange,
) -> None:
"""Handle a Bluetooth event."""
# 你的设备应处理传入的 advertisement 数据
```
---
url: /developers/core/entity.md
---
关于实体的通用介绍,请参见 [entities architecture](/developers/architecture/devices-and-services.md)。
## 基础实现
下面是一个 switch entity 的示例,它在内存中跟踪其 state。
此外,示例中的 switch 代表设备的主要 feature,
意味着 entity 与其 device 同名。
请查阅 [Entity naming](#entity-naming) 了解如何为 entity 设置自己的名称。
```python
from homeassistant.components.switch import SwitchEntity
class MySwitch(SwitchEntity):
_attr_has_entity_name = True
def __init__(self):
self._is_on = False
self._attr_device_info = ... # 用于自动 device 注册
self._attr_unique_id = ...
@property
def is_on(self):
"""If the switch is currently on or off."""
return self._is_on
def turn_on(self, **kwargs):
"""Turn the switch on."""
self._is_on = True
def turn_off(self, **kwargs):
"""Turn the switch off."""
self._is_on = False
```
构建一个 switch entity 就这么多内容!继续阅读以了解更多,或者查看 [视频教程](https://youtu.be/Cfasc9EgbMU?t=737)。
## 更新 entity
Entity 代表一个 device。有多种策略可以让你的 entity 与 device 的 state 保持同步,其中最常用的是 polling。
### 轮询
在 polling 模式下,Home Assistant 会定时(取决于 component 的更新间隔)向 entity 请求最新的 state。当 `should_poll` 属性返回 `True`(默认值)时,Home Assistant 会轮询该 entity。你可以使用 `update()` 或异步方法 `async_update()` 来实现更新逻辑。该方法应从 device 获取最新的 state,并将其存储在 instance variable 中,以便 properties 返回它。
### 订阅更新
当你订阅 updates 时,你的代码需要负责通知 Home Assistant 有新的更新可用。请确保 `should_poll` 属性返回 `False`。
每当通过订阅收到新的 state 时,你可以通过调用 `schedule_update_ha_state()` 或异步回调 `async_schedule_update_ha_state()` 来通知 Home Assistant 有更新可用。如果希望 Home Assistant 在将更新写入 Home Assistant 之前先调用你的 update method,请将布尔值 `True` 传入该方法。
## 通用属性
Entity 基类有一些在所有 Home Assistant entity 中通用的属性。这些属性可以添加到任何 entity 中,无论其类型如何。所有这些属性都是可选的,不需要实现。
这些属性在 state 写入 state machine 时总是会被调用。
:::tip
Properties 应该只从内存返回信息,而不要执行 I/O(如网络请求)。请实现 `update()` 或 `async_update()` 来获取数据。
由于这些 properties 在 state 写入 state machine 时总是会被调用,因此在 property 中应尽可能少地执行工作。
为了避免在 property 方法中进行计算,请设置相应的 [entity 类属性或实例属性](#entity-class-or-instance-attributes),或者如果值永不变化,请使用 [entity descriptions](#entity-description)。
:::
| Name | Type | Default | Description
| ------------------------ | ------------------------------| ------- | -----------
| assumed\_state | `bool` | `False` | 如果 state 基于我们的假设而非从 device 读取,则返回 `True`。
| attribution | `str \| None` | `None` | API 提供商要求的品牌 text。
| available | `bool` | `True` | 指示 Home Assistant 是否能够读取 state 或控制底层 device,更多详情请见 [entity-unavailable](/developers/core/integration-quality-scale/rules/entity-unavailable.md)。
| device\_class | `str \| None` | `None` | 对 device 类型的额外分类。每个 domain 都有自己的定义。device class 可能附带对计量单位和受支持 features 的额外要求。
| entity\_picture | `str \| None` | `None` | 用于显示 entity 的图片 URL。
| extra\_state\_attributes | `dict \| None` | `None` | 存储在 state machine 中的额外信息。它应该是进一步解释 state 的信息,而不应该是 firmware 版本之类的静态信息。
| has\_entity\_name | `bool` | `False` | 如果 entity 的 `name` 属性代表 entity 本身(新集成所必需),则返回 `True`。下面有更详细的说明。
| name | `str \| None` | `None` | Entity 的名称。避免硬编码自然语言名称,请改用 [translated name](/developers/internationalization/core.md#name-of-entities)。
| should\_poll | `bool` | `True` | Home Assistant 是否应向 entity 检查更新的 state。如果设为 `False`,entity 需要通过调用其中一个 [schedule update 方法](integration_fetching_data.md#push-vs-poll) 来通知 Home Assistant 新更新。
| state | `str \| int \| float \| None` | `None` | Entity 的 state。在大多数情况下,这由 domain base entity 实现,集成不应自行实现。
| supported\_features | `int \| None` | `None` | 标志 entity 支持的 features。各 domain 有自己的定义。
| translation\_key | `str \| None` | `None` | 用于在集成的 `strings.json` 的 [`entity` section](/developers/internationalization/core.md#state-of-entities) 中查找 entity state 的 translations,并将 state 翻译为匹配的 [icon](#icons)。 |
| translation\_placeholders | `dict \| None` | `None` | [translated entity name](/developers/internationalization/core.md#name-of-entities) 的占位符定义。
:::warning
允许更改 `device_class`、`supported_features` 或包含在 domain 的 `capability_attributes` 中的任何属性。但是,由于这些 entity 属性通常不应发生变化,且某些 entity consumers 可能无法以较高的速率更新它们,我们建议仅在绝对必要时更改它们,并保持适度的间隔。
例如,此类更改会导致 voice assistant 集成与支持它们的云服务重新同步。
:::
:::warning
产生大量 state 变更的 entity 在 `extra_state_attributes` 也频繁变化时,会迅速增加 database 的大小。通过移除非关键 attributes 或创建额外的 `sensor` entity,来最小化这些 entity 的 `extra_state_attributes` 数量。
:::
## Registry 属性
以下属性用于填充 entity 和 device registries。每次 entity 被添加到 Home Assistant 时都会读取它们。这些属性只有在 `unique_id` 不为 None 时才会生效。
| Name | Type | Default | Description
| ------------------------------- | --------------------------------------- | ------- | -----------
| device\_info | `DeviceInfo \| None` | `None` | [Device registry](/developers/device_registry_index.md) 描述符,用于 [automatic device registration](/developers/device_registry_index.md#automatic-registration-through-an-entity)。
| entity\_category | `EntityCategory \| None` | `None` | 非 primary entity 的分类。对于允许更改 device 配置的 entity(例如 switch entity,可以开启和关闭 switch 的背景照明),设为 `EntityCategory.CONFIG`。对于暴露 device 的配置参数或 diagnostics 但不允许更改的 entity(例如显示 RSSI 或 MAC address 的 sensor),设为 `EntityCategory.DIAGNOSTIC`。对于触发 device 识别机制的 button entity(使用 `IDENTIFY` device class),也请使用它。 |
| entity\_registry\_enabled\_default | `bool` | `True` | 指示 entity 首次添加到 entity registry 时应启用还是禁用。这包括快速变化的 diagnostic entity 或假定较少使用的 entity。例如,暴露 RSSI 或 battery voltage 的 sensor 通常应设为 `False`,以防止这些 entity 引起不必要的(记录的)state 变更或 UI 杂乱。 |
| entity\_registry\_visible\_default | `bool` | `True` | 指示 entity 首次添加到 entity registry 时应隐藏还是可见。 |
| unique\_id | `str \| None` | `None` | 该 entity 的唯一标识符。它必须在平台内(如 `light.hue`)唯一。它不应可由用户配置或更改。[了解更多。](entity_registry_index.md#unique-id-requirements) |
## 附加属性
以下属性也可用于 entity,但应谨慎使用。这些属性在 state 写入 state machine 时总是会被调用。
| Name | Type | Default | Description
| ------------------------------- | ---------------------------- | ------- | -----------
| capability\_attributes | `dict \| None` | `None` | 存储在 entity registry 中的 state attributes。该属性由 domain base entity 实现,集成不应自行实现。
| force\_update | `bool` | `False` | 每次更新都写入 state machine,即使数据相同。示例用法:当你直接从连接的 sensor 读取值而不是从 cache 读取时。请谨慎使用,会频繁写入 state machine。 |
| icon | `str \| None` | `None` | 在前端使用的 icon。不建议使用此属性。[关于使用 icons 的更多信息](#icons)。 |
| state\_attributes | `dict \| None` | `None` | Base domain 的 state attributes。该属性由 domain base entity 实现,集成不应自行实现。
| unit\_of\_measurement | `str \| None` | entity state 所表示的计量单位。在大多数情况下(例如 `number` 和 `sensor` domain),这由 domain base entity 实现,集成不应自行实现。
## 系统属性
以下属性由 Home Assistant 使用和控制,集成不应 override。
| Name | Type | Default | Description
| ------- | ------- | ------- | -----------
| enabled | `bool` | `True` | 指示 entity 是否在 entity registry 中启用。如果平台不支持 entity registry,也返回 `True`。被禁用的 entity 不会添加到 Home Assistant。 |
## Entity 命名
避免将 entity 的名称设置为硬编码的英文字符串,而是,名称应该被 [translated](/developers/internationalization/core.md#name-of-entities)。名称不应被翻译的示例包括专有名词、model 名称以及由第三方库提供的名称。
一些 entity 会根据其 device class 自动命名,这包括 [`binary_sensor`](/developers/core/entity/binary-sensor.md)、[`button`](/developers/core/entity/button.md)、[`number`](/developers/core/entity/number.md) 和 [`sensor`](/developers/core/entity/sensor.md) entity,在许多情况下不需要命名。
例如,一个未命名且 device class 设为 `temperature` 的 sensor 将被命名为 "Temperature"。
:::note
如果 entity 提供了 entity name 的 translations,所使用的名称取决于创建时的系统(backend)语言,而不是用户的 UI 语言。例如,如果你的 backend 设为德语,新 entity 将以德语命名——即使用户后来将 UI 切换为法语。更改 backend 语言只影响更改后创建的 entity;现有 entity 保留其原始名称。
:::
### `has_entity_name` True(新集成必需)
Entity 的 name 属性只标识 entity 所代表的数据点,不应包含 device 名称或 entity 类型。因此,对于代表其 device 功耗的 sensor,这应该是 "Power usage"。
如果 entity 代表 device 的单一主要 feature,该 entity 的 name 属性通常应返回 `None`。
Device 的"主要 feature"例如是智能灯泡的 `LightEntity`。
`friendly_name` state attribute 通过将 entity name 与 device name 组合生成,规则如下:
* Entity 不是 device 的成员:`friendly_name = entity.name`
* Entity 是 device 的成员且 `entity.name` 不为 `None`:`friendly_name = f"{device.name} {entity.name}"`
* Entity 是 device 的成员且 `entity.name` 为 `None`:`friendly_name = f"{device.name}"`
`entity_id` 通过将 entity name 与 device name 组合生成,规则如下:
* Entity 不是 device 的成员,例如 helper "Everyone is home":`entity_id = binary_sensor.everyone_is_home`
* Entity 是 device 的成员且 `entity.name` 不为 `None`,例如名为 "nightlight" 的 device 的电池:`entity_id = sensor.nightlight_battery`
* Entity 是 device 的成员且 `entity.name` 为 `None`,例如名为 "nightlight" 的 device 的 light:`entity_id = light.nightlight`
Entity name 应以大写开头,其余单词为小写(除非是专有名词或大写缩写)。
#### 作为 device 主要 feature 的 switch entity 示例
*注意:示例使用类属性实现 properties,其他实现方式请参见 [Property implementation](#property-implementation)。*
*注意:示例不完整,必须实现 `unique_id` 属性,并且 entity
必须 [与 device 注册](/developers/device_registry_index.md#defining-devices)。*
```python
from homeassistant.components.switch import SwitchEntity
class MySwitch(SwitchEntity):
_attr_has_entity_name = True
_attr_name = None
```
#### 作为非 device 主要 feature 或非 device 一部分的 switch entity 示例:
*注意:示例使用类属性实现 properties,其他实现方式*
*请参见 [Property implementation](#property-implementation)。*
*注意:如果 entity 是 device 的一部分,必须实现 `unique_id` 属性,并且 entity
必须 [与 device 注册](/developers/device_registry_index.md#defining-devices)。*
```python
from homeassistant.components.switch import SwitchEntity
class MySwitch(SwitchEntity):
_attr_has_entity_name = True
@property
def translation_key(self):
"""Return the translation key to translate the entity's name and states."""
return "my_switch"
```
#### 非翻译的 switch entity 示例(非 device 主要 feature 或非 device 一部分):
```python
from homeassistant.components.switch import SwitchEntity
class MySwitch(SwitchEntity):
_attr_has_entity_name = True
@property
def name(self):
"""Name of the entity."""
return "Model X"
```
### `has_entity_name` 未实现或为 False(已弃用)
Entity 的 name 属性可以是 device 名称与 entity 所代表的数据点的组合。
## Property 实现
### Property 函数
为每个 property 编写 property method 只需要几行代码,
例如
```python
class MySwitch(SwitchEntity):
@property
def icon(self) -> str | None:
"""Icon of the entity."""
return "mdi:door"
...
```
### Entity 类属性或实例属性
另一种较短的形式是,按照以下任一模式设置 Entity 类属性或实例属性:
```python
class MySwitch(SwitchEntity):
_attr_icon = "mdi:door"
...
```
```python
class MySwitch(SwitchEntity):
def __init__(self, icon: str) -> None:
self._attr_icon = icon
...
```
这与第一个示例完全相同,但依赖于基类中 property 的默认
实现。该属性的名称以 `_attr_` 开头,后跟 property 名称。例如,默认
的 `device_class` property 返回 `_attr_device_class` 类属性。
并非所有 entity 类都支持使用 `_attr_` 属性来实现其 entity
特定属性,请参阅相应
entity 类的文档以获取详细信息。
:::tip
如果集成需要访问自己的 properties,应访问 property(`self.name`),而不是类属性或实例属性(`self._attr_name`)。
:::
### Entity 描述
设置 entity property 的第三种方法是使用 entity description。为此,请在 `Entity` 实例上设置一个名为 `entity_description` 的属性,其值为 `EntityDescription` 实例。Entity description 是一个 dataclass,其 attributes 对应于大多数可用的 `Entity` properties。每个支持 entity platform 的 entity 集成(例如 `switch` 集成)都会定义自己的 `EntityDescription` 子类,使用 entity descriptions 的实现 platform 应使用该子类。
默认情况下,`EntityDescription` 实例有一个必需的 attribute 名为 `key`。这是一个字符串,旨在对实现 platform 的所有 entity descriptions 保持唯一。该 attribute 的一个常见用例是将其包含在描述 entity 的 `unique_id` 中。
使用 entity descriptions 的主要好处是,它以一种声明式的方式定义了 platform 的不同 entity types,当存在许多不同 entity type 时,这使得代码更易读。
### 示例
下面的代码片段示例说明了何时应实现 property 函数、何时使用类属性或实例属性以及何时使用 entity descriptions 的最佳实践。
```py
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from example import ExampleDevice, ExampleException
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
EntityCategory,
UnitOfElectricCurrent,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import DOMAIN, LOGGER
@dataclass(kw_only=True)
class ExampleSensorEntityDescription(SensorEntityDescription):
"""Describes Example sensor entity."""
exists_fn: Callable[[ExampleDevice], bool] = lambda _: True
value_fn: Callable[[ExampleDevice], StateType]
SENSORS: tuple[ExampleSensorEntityDescription, ...] = (
ExampleSensorEntityDescription(
key="estimated_current",
native_unit_of_measurement=UnitOfElectricCurrent.MILLIAMPERE,
device_class=SensorDeviceClass.CURRENT,
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda device: device.power,
exists_fn=lambda device: bool(device.max_power),
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Example sensor based on a config entry."""
device: ExampleDevice = entry.runtime_data
async_add_entities(
ExampleSensorEntity(device, description)
for description in SENSORS
if description.exists_fn(device)
)
class ExampleSensorEntity(SensorEntity):
"""Represent an Example sensor."""
entity_description: ExampleSensorEntityDescription
_attr_entity_category = (
EntityCategory.DIAGNOSTIC
) # 这对所有 ExampleSensorEntity 实例是通用的
def __init__(
self, device: ExampleDevice, entity_description: ExampleSensorEntityDescription
) -> None:
"""Set up the instance."""
self._device = device
self.entity_description = entity_description
self._attr_available = False # 这会覆盖默认值
self._attr_unique_id = f"{device.serial}_{entity_description.key}"
def update(self) -> None:
"""Update entity state."""
try:
self._device.update()
except ExampleException:
if self.available: # 读取当前 state,不需要加 _attr_ 前缀
LOGGER.warning("Update failed for %s", self.entity_id)
self._attr_available = False # 设置 property 值
return
self._attr_available = True
# 这里不需要检查 device 是否可用
self._attr_native_value = self.entity_description.value_fn(
self._device
) # 更新 "native_value" property
```
## 生命周期钩子
使用这些 lifecycle hooks 在发生某些事件时执行代码。所有 lifecycle hooks 都是异步方法。
### `async_added_to_hass()`
当 entity 被分配 entity\_id 和 hass 对象后,且第一次写入 state machine 之前调用。示例用途:恢复 state、订阅 updates 或设置 callback/dispatch function/listener。
### `async_will_remove_from_hass()`
当 entity 即将从 Home Assistant 移除时调用。示例用途:断开与 server 的连接或取消订阅 updates。
## 图标
Home Assistant 中的每个 entity 都有一个 icon,用作前端中更易识别 entity 的视觉指示器。Home Assistant 使用 [Material Design Icons](https://materialdesignicons.com/) icon 集。
在大多数情况下,Home Assistant 会根据 entity 的 domain、`device_class` 和 `state` 自动选择一个 icon。如果可能,优先使用默认 icon,以提供一致的体验并避免用户混淆。但是,也可以 override 默认值并为 entity 提供自定义 icon。
无论提供什么 icon,用户总可以在前端按照自己的喜好自定义 icon。
有两种方式为 entity 提供自定义 icon:通过提供 icon translations 或通过提供 icon identifier。
### 图标翻译
这是为 entity 提供自定义 icon 的首选方式。Icon translations 的工作方式类似于 [常规 translations](/developers/internationalization/core.md#state-of-entities),但它们不是翻译 entity 的 state,而是将 entity 的 states 翻译为 icons。
请注意,translated states 必须像所有其他 translation key 一样是 `snake_case`。
Entity 的 `translation_key` 属性定义了要使用的 icon translation。该属性用于在集成的 `icons.json` 文件的 `entity` section 中查找 translation。
为了区分 entity 及其 translations,请提供不同的 translation keys。以下示例展示了 Moon domain `sensor` entity 的 `icons.json`,其 `translation_key` 属性设为 phase:
```json
{
"entity": {
"sensor": {
"phase": {
"default": "mdi:moon",
"state": {
"new_moon": "mdi:moon-new",
"first_quarter": "mdi:moon-first-quarter",
"full_moon": "mdi:moon-full",
"last_quarter": "mdi:moon-last-quarter"
}
}
}
}
}
```
请注意,icons 以 `mdi:` 开头,后跟一个 [identifier](https://materialdesignicons.com/)。当 entity 的 state 不在 `state` section 中时,使用 `default` icon。`state` section 是可选的,如果未提供,`default` icon 将用于所有 states。
在前端显示 state attributes 的 icons 时,也可以为 entity state attributes 提供 icons。示例包括 climate presets 和 fan modes。无法为其他 state attributes 提供 icons。以下示例为 `climate` entity 提供 icons,其 `translation_key` 属性设为 `ubercool`。该 entity 有一个 `preset_mode` state attribute,可以设为 `vacation` 或 `night`。前端将在例如 climate card 中使用它们。
请注意,translated state attributes 必须像所有其他 translation key 一样是 `snake_case`。
```json
{
"entity": {
"climate": {
"ubercool": {
"state_attributes": {
"preset_mode": {
"default": "mdi:confused",
"state": {
"vacation": "mdi:umbrella-beach",
"night": "mdi:weather-night"
}
}
}
}
}
}
}
```
### 图标属性
为 entity 提供 icon 的另一种方法是设置 entity 的 `icon` 属性,该属性返回一个引用 `mdi` icon 的字符串。由于该属性是一个 method,与 icon translations 不同,它可以根据自定义逻辑返回不同的 icons。例如,可以像下面示例中那样根据 state 计算 icon,或者根据不属于 entity state 的内容返回不同的 icons。
```python
class MySwitch(SwitchEntity):
@property
def icon(self) -> str | None:
"""Icon of the entity, based on time."""
if now().hour < 12:
return "mdi:weather-night"
return "mdi:weather-sunny"
...
```
无法通过 `icon` 属性为 state attributes 提供 icons。请注意,不建议使用 `icon` 属性;优先使用上述的 icon translations。
## 从 recorder history 中排除状态属性
不适合进行 state history 记录的 state attributes 应通过将其包含在 `_entity_component_unrecorded_attributes` 或 `_unrecorded_attributes` 中来排除在 state history 记录之外。
* `_entity_component_unrecorded_attributes: frozenset[str]` 可在 base component 类中设置,例如在 `light.LightEntity` 中
* `_unrecorded_attributes: frozenset[str]` 可在集成的 platform 中设置,例如在 platform `hue.light` 中定义的 entity 类中。
`MATCH_ALL` 常量可用于排除所有 attributes,而无需逐一列出。对于提供未知 attributes 的集成,或者只是想排除所有而不逐一列出时,这非常有用。
使用 `MATCH_ALL` 常量不会停止对 `device_class`、`state_class`、`unit_of_measurement` 和 `friendly_name` 的记录,因为它们还可能服务于其他目的,因此不应排除在记录之外。
被排除在记录之外的 platform state attributes 示例包括 `image` entity 的 `entity_picture` attribute(一段时间后将会失效)和 `fan` entity 的 `preset_modes` attribute(不太可能变化)。
被排除在记录之外的集成特定 state attributes 示例包括 platform `trafikverket.camera` 中的 `description` 和 `location` state attributes(不会变化)。
:::tip
`_entity_component_unrecorded_attributes` 和 `_unrecorded_attributes` 必须声明为类属性;实例属性将被忽略。
:::
## 更改 entity model
如果你想为 entity 或其任何 subtype(light、switch 等)添加新 feature,你需要先在我们的 [architecture repo](https://github.com/home-assistant/architecture/discussions) 中提出。只考虑各种厂商通用的 features。
---
url: /developers/core/entity/ai-task.md
---
AI Task 实体为 Home Assistant 中的 AI 驱动任务执行提供了一个框架。它使集成能够为根据自然语言指令生成数据、内容或执行结构化任务提供 AI 能力。
AI Task 实体派生自 [`homeassistant.components.ai_task.AITaskEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/ai_task/entity.py)。实体状态会跟踪最后一次活动的时间戳,用于监控目的。
## 属性
*此实体没有属性。*
## 支持的功能
支持的功能通过使用 `AITaskEntityFeature` 枚举中的值来定义,并使用按位或(`|`)运算符进行组合。
| 值 | 描述
| ----- | -----------
| `GENERATE_DATA` | 实体可以根据自然语言指令生成数据。
| `SUPPORT_ATTACHMENTS` | 实体支持作为生成数据和图像任务一部分的附件。
| `GENERATE_IMAGE` | 实体可以根据自然语言指令生成图像。
## 方法
### 生成数据
此方法根据自然语言指令处理数据生成任务。
```python
from homeassistant.components.ai_task import AITaskEntity, GenDataTask, GenDataTaskResult
class MyAITaskEntity(AITaskEntity):
"""Represent an AI Task entity."""
async def _async_generate_data(
self, task: GenDataTask, chat_log: ChatLog
) -> GenDataTaskResult:
"""Handle a generate data task."""
# Process the task instructions and generate appropriate data
# Use the chat_log to maintain conversation context. A common
# pattern is to share an implementation between conversation and AI
# task entities to process the chat log.
# await self._async_handle_chat_log(
# chat_log,
# task.structure,
# task.attachments
# )
text = ...
if not task.structure:
return GenDataTaskResult(
conversation_id=chat_log.conversation_id,
data=text
)
data = ... # process the text to match the structure
return GenDataTaskResult(
conversation_id=chat_log.conversation_id,
data=data
)
```
`GenDataTask` 对象包含以下数据:
| 名称 | 类型 | 描述
| ---- | ---- | -----------
| `name` | `str` | 任务的名称/标识符
| `instructions` | `str` | 给 AI 的自然语言指令
| `structure` | `vol.Schema` | `None` | 用于结构化输出验证的可选 schema
| `attachments` | `list[conversation.Attachment]` | `None` | 包含在任务中的附件列表。
### 生成图像
此方法根据自然语言指令处理图像生成任务。只有当实体设置了 `AITaskEntityFeature.GENERATE_IMAGE` 功能时,才会调用此方法。
```python
from homeassistant.components.ai_task import AITaskEntity, GenImageTask, GenImageTaskResult
class MyAITaskEntity(AITaskEntity):
"""Represent an AI Task entity."""
async def _async_generate_image(
self, task: GenImageTask, chat_log: ChatLog
) -> GenImageTaskResult:
"""Handle a generate image task."""
# Process the task instructions and generate the image.
image_data = ...
return GenImageTaskResult(
conversation_id=chat_log.conversation_id,
image_data=image_data,
mime_type="image/png",
)
```
`GenImageTask` 对象包含以下数据:
| 名称 | 类型 | 描述
| ---- | ---- | -----------
| `name` | `str` | 任务的名称/标识符
| `instructions` | `str` | 给 AI 的自然语言指令
| `attachments` | `list[conversation.Attachment]` | `None` | 与指令一起包含的可选附件列表。
返回的 `GenImageTaskResult` 对象包含以下数据:
| 名称 | 类型 | 描述
| ---- | ---- | -----------
| `image_data` | `bytes` | 模型生成的原始图像数据
| `conversation_id` | `str` | 对话的唯一标识符
| `mime_type` | `str` | 生成图像的 MIME 类型
| `width` | `int` | `None` | 生成图像的可选宽度
| `height` | `int` | `None` | 生成图像的可选高度
| `model` | `str` | `None` | 用于生成图像的可选模型
| `revised_prompt` | `str` | `None` | 用于生成图像的可选修订提示词
## 结构化输出 schema
`structure` 参数允许你使用 Home Assistant 的 [selector 系统](https://www.home-assistant.io/docs/blueprint/selectors/) 来定义所生成数据的预期格式:
```python
{
"yes_no_field": {
"description": "Description of the field",
"required": True/False, # 可选,默认为 False
"selector": {
"boolean": {} # Selector 类型
}
},
"text_field": {
"description": "Description of the text field",
"required": True/False, # 可选,默认为 False
"selector": {
"text": {} # Selector 类型
}
},
"number_field": {
"description": "Description of the number field",
"required": True/False, # 可选,默认为 False
"selector": {
"number": {
"min": 18, # 可选的最小值
"max": 100, # 可选的最大值
}
}
},
}
```
---
url: /developers/core/entity/air-quality.md
---
## 属性
:::caution Deprecated
Air Quality 实体已被弃用,不应再使用。取而代之,请为这些测量值使用独立的 sensors。
仍依赖 Air Quality Entity 的集成应进行迁移。
:::
:::caution
Air Quality 实体不支持用于 [属性实现](/developers/core/entity.md#entity-class-or-instance-attributes) 的属性简写
:::
| 名称 | 类型 | 默认值 | 描述
| ---- | ---- | ------- | -----------
| particulate\_matter\_2\_5 | `str \| int \| float \| None` | **必填** | 颗粒物 2.5(<= 2.5 μm)浓度。
| particulate\_matter\_10 | `str \| int \| float \| None` | `None` | 颗粒物 10(<= 10 μm)浓度。
| particulate\_matter\_0\_1 | `str \| int \| float \| None` | `None` | 颗粒物 0.1(<= 0.1 μm)浓度。
| air\_quality\_index | `str \| int \| float \| None` | `None` | Air Quality Index (AQI)。
| ozone | `str \| int \| float \| None` | `None` | O3(臭氧)浓度。
| carbon\_monoxide | `str \| int \| float \| None` | `None` | CO(一氧化碳)浓度。
| carbon\_dioxide | `str \| int \| float \| None` | `None` | CO2(二氧化碳)浓度。
| sulphur\_dioxide | `str \| int \| float \| None` | `None` | SO2(二氧化硫)浓度。
| nitrogen\_oxide | `str \| int \| float \| None` | `None` | N2O(氧化氮)浓度。
| nitrogen\_monoxide | `str \| int \| float \| None` | `None` | NO(一氧化氮)浓度。
| nitrogen\_dioxide | `str \| int \| float \| None` | `None` | NO2(二氧化氮)浓度。支持的单位:`ppb`、`ppm`、`µg/m³`。
属性必须遵循 `unit_system` 中定义的单位。
---
url: /developers/core/entity/alarm-control-panel.md
---
Alarm control panel entity 控制一个 alarm。从 [`homeassistant.components.alarm_control_panel.AlarmControlPanelEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/alarm_control_panel/__init__.py) 派生 platform entity。
## 属性
:::tip
Properties 应该只从内存返回信息,而不要执行 I/O(如网络请求)。请实现 `update()` 或 `async_update()` 来获取数据。
:::
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
| alarm\_state | `AlarmControlPanelState \| None` | **必需** | **States** 中列出的 alarm 值之一。
| code\_arm\_required | bool | `True` | 执行 arm actions 时是否需要 code。
| code\_format | `CodeFormat \| None` | `None` | **Code formats** section 中列出的 states 之一。
| changed\_by | `str \| None` | `None` | 上次变更的触发者。
### 状态
设置 state 应在 `alarm_state` property 中返回一个 `AlarmControlPanelState` 枚举值。
| Value | Description
| ----- | -----------
| `DISARMED` | Alarm 处于 disarmed 状态(`off`)。
| `ARMED_HOME` | Alarm 处于 home mode 的 armed 状态。
| `ARMED_AWAY` | Alarm 处于 away mode 的 armed 状态。
| `ARMED_NIGHT` | Alarm 处于 night mode 的 armed 状态。
| `ARMED_VACATION` | Alarm 处于 vacation mode 的 armed 状态。
| `ARMED_CUSTOM_BYPASS` | Alarm 处于 custom bypass mode 的 armed 状态,其中一个或多个 zones 或 sensors 被 bypass(禁用),且被 bypass 的 zones 和 sensors 的组合与标准的 away、home 或 night modes 不对应。不要使用此 state 来指示断开连接、故障或电量低的 sensor;请通过专门的 sensor entity 报告这些情况。
| `PENDING` | Alarm 处于 pending 状态(即将 `triggered`)。
| `ARMING` | Alarm 正在 arming。
| `DISARMING` | Alarm 正在 disarming。
| `TRIGGERED` | Alarm 已 triggered。
## 支持的功能
Supported features 通过使用 `AlarmControlPanelEntityFeature` enum 中的值来定义,
并使用按位或(`|`)运算符组合。
| Constant | Description |
|----------|--------------------------------------|
| `AlarmControlPanelEntityFeature.ARM_AWAY` | Alarm 支持 away mode 的 arming。
| `AlarmControlPanelEntityFeature.ARM_CUSTOM_BYPASS` | Alarm 支持带 bypass 的 arming。
| `AlarmControlPanelEntityFeature.ARM_HOME` | Alarm 支持 home mode 的 arming。
| `AlarmControlPanelEntityFeature.ARM_NIGHT` | Alarm 支持 night mode 的 arming。
| `AlarmControlPanelEntityFeature.ARM_VACATION` | Alarm 支持 vacation mode 的 arming。
| `AlarmControlPanelEntityFeature.TRIGGER` | Alarm 可以远程触发。
### 代码格式
Supported code formats 通过使用 `CodeFormat` enum 中的值来定义。
| Value | Description
| ----- | -----------
| `None` | 不需要 code。
| `CodeFormat.NUMBER` | Code 是数字(前端显示十键 pad)。
| `CodeFormat.TEXT` | Code 是字符串。
## 方法
### 解除警报
发送 disarm 命令。
```python
class MyAlarm(AlarmControlPanelEntity):
# 实现以下方法之一。
def alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command."""
async def async_alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command."""
```
### 布防 - 在家
发送 arm home 命令。
```python
class MyAlarm(AlarmControlPanelEntity):
# 实现以下方法之一。
def alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command."""
async def async_alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command."""
```
### 布防 - 外出
发送 arm away 命令。
```python
class MyAlarm(AlarmControlPanelEntity):
# 实现以下方法之一。
def alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command."""
async def async_alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command."""
```
### 布防 - 夜间
发送 arm night 命令。
```python
class MyAlarm(AlarmControlPanelEntity):
# 实现以下方法之一。
def alarm_arm_night(self, code: str | None = None) -> None:
"""Send arm night command."""
async def async_alarm_arm_night(self, code: str | None = None) -> None:
"""Send arm night command."""
```
### 布防 - 度假
发送 arm vacation 命令。
```python
class MyAlarm(AlarmControlPanelEntity):
# 实现以下方法之一。
def alarm_arm_vacation(self, code: str | None = None) -> None:
"""Send arm vacation command."""
async def async_alarm_arm_vacation(self, code: str | None = None) -> None:
"""Send arm vacation command."""
```
### 触发警报
发送 alarm trigger 命令。
```python
class MyAlarm(AlarmControlPanelEntity):
# 实现以下方法之一。
def alarm_trigger(self, code: str | None = None) -> None:
"""Send alarm trigger command."""
async def async_alarm_trigger(self, code: str | None = None) -> None:
"""Send alarm trigger command."""
```
### 布防 - 自定义绕过
发送 arm custom bypass 命令。
```python
class MyAlarm(AlarmControlPanelEntity):
# 实现以下方法之一。
def alarm_arm_custom_bypass(self, code: str | None = None) -> None:
"""Send arm custom bypass command."""
async def async_alarm_arm_custom_bypass(self, code: str | None = None) -> None:
"""Send arm custom bypass command."""
```
---
url: /developers/core/entity/assist-satellite.md
---
Assist Satellite 实体表示设备的由 Assist pipeline 驱动的语音助手功能。拥有此类实体的设备允许用户使用语音控制 Home Assistant。
Assist satellite 实体派生自 [`homeassistant.components.assist_satellite.AssistSatelliteEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/assist_satellite/__init__.py)。
## 属性
| 名称 | 类型 | 默认值 | 描述 |
|----------------------|------------------------|-------------------|-----------------------------------------------------------------------|
| `pipeline_entity_id` | `str \| None` | `None` | 带有 [pipeline id](/developers/voice/pipelines.md) 的 `select` 实体的 Id,或 `None`。 |
| `vad_sensitivity_entity_id` | `str \| None` | `None` | 带有 [语音活动检测灵敏度](https://github.com/home-assistant/core/blob/dev/homeassistant/components/assist_pipeline/vad.py) 的 `select` 实体的 Id,或 `None`。 |
| `tts_options` | `dict \| None` | `None` | 在回复时传递给 [text-to-speech 系统](https://www.home-assistant.io/integrations/tts/) 的选项。 |
## 状态
`AssistSatelliteEntity` 的状态跟随其当前正在运行的 [pipeline](/developers/voice/pipelines.md)。`AssistSatelliteState` 枚举存储了可能的状态。
:::tip
当 text-to-speech 响应**播放完毕**时,必须在你的实体上调用 `tts_response_finished` 方法,以返回 `IDLE` 状态。
:::
| 常量 | 描述 |
|--------------|--------------------------------------------------------------------------|
| `IDLE` | 设备正在等待用户输入,例如唤醒词或按键。 |
| `LISTENING` | 设备正在将语音指令的音频流式传输到 Home Assistant。 |
| `PROCESSING` | Home Assistant 正在处理语音指令。 |
| `RESPONDING` | 设备正在播放回复。 |
## 支持的功能
支持的功能通过使用 `AssistSatelliteEntityFeature` 枚举中的值来定义,并使用按位或(`|`)运算符进行组合。
| 值 | 描述 |
|------------|---------------------------------------------------|
| `ANNOUNCE` | 设备支持远程触发的公告。实现 `async_announce` 方法以播放 `AssistSatelliteAnnouncement` 中提供的 `media_id`。此方法应仅在设备上的公告播放完毕后才返回。 |
| `START_CONVERSATION` | 设备支持远程触发的对话,即播放公告然后听取一个或多个语音指令。实现 `async_start_conversation` 方法以播放 `AssistSatelliteAnnouncement` 中提供的 `media_id`,然后听取语音指令。此方法应仅在公告播放完毕后才返回。 |
## 方法
### 运行 pipeline 和处理事件
Satellite 实体应仅使用 `async_accept_pipeline_from_satellite` 方法运行 [Assist pipeline](/developers/voice/pipelines.md)。通过实现 `on_pipeline_event` 方法来处理 [来自 pipeline 的事件](/developers/voice/pipelines.md#events)。
运行 pipeline 时,satellite 实体的 [状态](#states) 会自动更新,`RESPONDING` 到 `IDLE` 除外。当 satellite 在设备上播放完回复后,开发者必须调用 `tts_response_finished` 方法。
### 获取配置
`async_get_configuration` 方法必须返回一个(缓存的)`AssistSatelliteConfiguration`。如果实体必须与设备通信以检索配置,这应在初始化期间完成。
有一个 [websocket 命令](#getting-the-satellite-configuration) 可用于获取实体的配置。
### 设置配置
`async_set_configuration` 方法更新设备的配置,并且只有在设备和 Home Assistant 的 `AssistSatelliteConfiguration` 同步后才返回。
有一个 [websocket 命令](#setting-the-active-wake-words) 可用于设置活动的唤醒词。
### 公告
如果设备具有 `ANNOUNCE` [支持的功能](#supported-features),则应实现 `async_announce` 方法,以公告 `AssistSatelliteAnnouncement` 内提供的 `media_id`。如果提供了 `preannounce_media_id`,应在 `media_id` 之前播放。
`async_announce` 方法应仅在设备上的公告播放完毕后才返回。
有一个 [announce 操作](https://home-assistant.io/integrations/assist_satellite#action-assist_satelliteannounce) 可用于自动化公告。
### 开始对话
如果设备具有 `START_CONVERSATION` [支持的功能](#supported-features),则应实现 `async_start_conversation` 方法,以执行以下操作:
1. 公告 `AssistSatelliteAnnouncement` 内的 `preannounce_media_id`(如果已提供)
2. 公告 `AssistSatelliteAnnouncement` 内提供的 `media_id`,然后
3. 听取一个或多个后续的语音指令
`async_start_conversation` 方法应仅在设备上的公告播放完毕后才返回。对话将在用户和 satellite 之间继续。
有一个 [start conversation 操作](https://home-assistant.io/integrations/assist_satellite#action-assist_satellitestart_conversation) 可用于自动化对话。
## WebSocket API
### 拦截唤醒词
该集成提供了一个 websocket API,用于拦截唤醒词的检测并向用户公告。语音向导使用此功能来帮助用户上 hand 并熟悉唤醒词。
```json
{
"type": "assist_satellite/intercept_wake_word",
"entity_id": "assist_satellite.living_room"
}
```
实体 id 必须是支持 `ANNOUNCE` 功能的 Assist satellite 实体。
一旦检测到唤醒词,将返回如下响应:
```json
{
"wake_word_phrase": "okay nabu"
}
```
### 获取 satellite 配置
可以使用以下命令获取 satellite 的当前配置,包括可用和活动的唤醒词:
```json
{
"type": "assist_satellite/get_configuration",
"entity_id": ENTITY_ID
}
```
将返回如下响应:
```json
{
"active_wake_words": [
"1234"
],
"available_wake_words": [
{
"id": "1234",
"trained_languages": [
"en"
],
"wake_word": "okay nabu"
},
{
"id": "5678",
"trained_languages": [
"en"
],
"wake_word": "hey jarvis"
}
],
"max_active_wake_words": 1,
"pipeline_entity_id": "select.pipeline_entity",
"vad_entity_id": "select.vad_entity"
}
```
`active_wake_words` 列表包含 `available_wake_words` 中唤醒词的 ids。
`pipeline_entity_id` 包含控制设备将运行的 pipeline 的 select 实体的 id。
`vad_entity_id` 包含具有语音活动检测器 (VAD) 灵敏度级别的 select 实体的 id。
### 设置活动的唤醒词
使用以下命令设置活动的唤醒词:
```json
{
"type": "assist_satellite/set_wake_words",
"entity_id": ENTITY_ID,
"wake_word_ids": ["1234", "5678"]
}
```
`wake_word_ids` 必须包含来自 `assist_satellite/get_configuration` 命令的 `available_wake_words` 列表中的 ids。
`wake_word_ids` 的大小也不应超过 `max_active_wake_words`。
---
url: /developers/core/entity/binary-sensor.md
---
Binary sensor 是一种只能有两个 states 的 sensor。从 [`homeassistant.components.binary_sensor.BinarySensorEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/binary_sensor/__init__.py) 派生 entity platforms。
## 属性
:::tip
Properties 应该只从内存中返回信息,不要执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
| is\_on| `bool \| None` | `None` | **必需**。Binary sensor 当前是 on 还是 off。
| device\_class | `BinarySensorDeviceClass \| None` | `None` | Binary sensor 的类型。
### 可用的设备类型
| Constant | Description
| ----- | -----------
| `BinarySensorDeviceClass.BATTERY` | On 表示电量低,Off 表示正常。
| `BinarySensorDeviceClass.BATTERY_CHARGING` | On 表示正在充电,Off 表示未充电。
| `BinarySensorDeviceClass.CO` | On 表示检测到一氧化碳,Off 表示无一氧化碳(正常)。
| `BinarySensorDeviceClass.COLD` | On 表示温度低,Off 表示正常。
| `BinarySensorDeviceClass.CONNECTIVITY` | On 表示已连接,Off 表示已断开连接。
| `BinarySensorDeviceClass.DOOR` | On 表示打开,Off 表示关闭。
| `BinarySensorDeviceClass.GARAGE_DOOR` | On 表示打开,Off 表示关闭。
| `BinarySensorDeviceClass.GAS` | On 表示检测到燃气,Off 表示无燃气(正常)。
| `BinarySensorDeviceClass.HEAT` | On 表示温度高,Off 表示正常。
| `BinarySensorDeviceClass.LIGHT` | On 表示检测到光线,Off 表示无光线。
| `BinarySensorDeviceClass.LOCK` | On 表示打开(未锁定),Off 表示关闭(已锁定)。
| `BinarySensorDeviceClass.MOISTURE` | On 表示湿润,Off 表示干燥。
| `BinarySensorDeviceClass.MOTION` | On 表示检测到移动,Off 表示无移动(正常)。
| `BinarySensorDeviceClass.MOVING` | On 表示正在移动,Off 表示未移动(已停止)。
| `BinarySensorDeviceClass.OCCUPANCY` | On 表示有人,Off 表示无人(正常)。
| `BinarySensorDeviceClass.OPENING` | On 表示打开,Off 表示关闭。
| `BinarySensorDeviceClass.PLUG` | On 表示已插上,Off 表示未插上。
| `BinarySensorDeviceClass.POWER` | On 表示检测到电流,Off 表示无电流。
| `BinarySensorDeviceClass.PRESENCE` | On 表示在家,Off 表示外出。
| `BinarySensorDeviceClass.PROBLEM` | On 表示检测到问题,Off 表示无问题(正常)。
| `BinarySensorDeviceClass.RUNNING` | On 表示正在运行,Off 表示未运行。
| `BinarySensorDeviceClass.SAFETY` | On 表示不安全,Off 表示安全。
| `BinarySensorDeviceClass.SMOKE` | On 表示检测到烟雾,Off 表示无烟雾(正常)。
| `BinarySensorDeviceClass.SOUND` | On 表示检测到声音,Off 表示无声音(正常)。
| `BinarySensorDeviceClass.TAMPER` | On 表示检测到拆改,Off 表示无拆改(正常)。
| `BinarySensorDeviceClass.UPDATE` | On 表示有 update 可用,Off 表示已是最新。应避免使用此 device class,请考虑改用 [`update`](/developers/core/entity/update.md) entity。
| `BinarySensorDeviceClass.VIBRATION` | On 表示检测到振动,Off 表示无振动。
| `BinarySensorDeviceClass.WINDOW` | On 表示打开,Off 表示关闭。
---
url: /developers/core/entity/button.md
---
Button entity 是一种可以触发事件/动作指向 device 或 service 的 entity,但从 Home Assistant 的角度来看它保持无状态。
它类似于真实的一次性 switch、push-button 或其他形式的无状态 switch。然而,它不适合实现实际的物理按钮;button entity 的唯一目的是在 Home Assistant 内提供一个虚拟按钮。
Button entity 从 [`homeassistant.components.button.ButtonEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/button/__init__.py) 派生,
对于控制 device features(包括但不限于以下)很有用:
* 升级 firmware
* 重启/重新启动 device
* 冲泡一杯咖啡
* 重置某些内容(如 counter、filter usage)
如果你想表示一个可以开启和关闭的东西(因此具有实际的 state),应改用 `switch` entity。如果想在 Home Assistant 中集成真实的、物理的无状态按钮 device,可以通过触发自定义 events 来实现。Button entity 不适合这些情况。
## 属性
由于该集成是无状态的,它没有提供任何特定的 property。
其他在所有 entity 中通用的 properties(如 `device_class`、`icon`、`name` 等)仍然适用。
## 方法
### 按下
Press method 可用于触发指向 device 或 service 的动作。
当用户按下按钮或调用了按下按钮的动作时,由 Home Assistant 调用。
```python
class MyButton(ButtonEntity):
# 实现以下方法之一。
def press(self) -> None:
"""Handle the button press."""
async def async_press(self) -> None:
"""Handle the button press."""
```
### 可用的设备类型
选项性地指定 entity 的类型。它可能会映射到 Google device types。
| Constant | Description
| ----- | -----------
| `ButtonDeviceClass.IDENTIFY` | Button entity 用于识别 device。
| `ButtonDeviceClass.RESTART` | Button entity 用于重启 device。
| `ButtonDeviceClass.UPDATE` | Button entity 用于更新 device 的软件。应避免使用此 device class,请考虑改用 [`update`](/developers/core/entity/update.md) entity。
---
url: /developers/core/entity/calendar.md
---
Calendar entity 是一种代表一组具有开始和结束日期及/或时间的 events 的 entity,对 automations 很有用。Calendar entity 从 [`homeassistant.components.calendar.CalendarEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/calendar/__init__.py) 派生。
Calendar 集成应与 rfc5545 兼容,并可选项遵循 rfc5546 中建立的 patterns 支持 event 创建。支持 recurring events 的集成负责处理 recurring events 的 expansion,例如在返回 expansion 后事件序列(作为单独独立 events)的 service 或 API 中。
## 属性
:::tip
Properties 应该只从内存返回信息,而不要执行 I/O(如网络请求)。请实现 `update()` 或 `async_update()` 来获取数据。
:::
| Name | Type | Default | Description |
| ----- | ------------- | --------------------- | ------------------------------------------------------- |
| event | `CalendarEvent \| None` | **必需** | 当前或下一个即将到来的 `CalendarEvent`,或 `None`。 |
| initial\_color | `str` | `None` | 用作 frontend 中日历初始颜色的十六进制 color 字符串(例如 `"#16a765"`)。 |
### 状态
`CalendarEntity` 的 state 类似于 binary sensor,反映是否存在 active event:
| Constant | Description |
| ----------- | ------------------------------------------- |
| `STATE_ON` | 日历有 active event。 |
| `STATE_OFF` | 日历没有 active event。 |
Calendar entity 有一个 `event` property,返回当前或下一个即将到来的 `CalendarEvent`,用于确定 state。Calendar entity 的实现负责确定下一个即将到来的 event,
包括正确排序 events 以及在 Home Assistant timezone 中解释 all-day events。Entity 应调用 `homeassistant.util.dt.now` 来获取当前时间,该时间具有设置为 Home Assistant timezone 的 `tzinfo` 值,或者检查 `homeassistant.components.util.dt.DEFAULT_TIMEZONE`。
## 支持的功能
Supported features 通过使用 `CalendarEntityFeature` enum 中的值来定义,
并使用按位或(`|`)运算符组合。
| Value | Description |
| ------------------- | ------------------------------------------------------------------ |
| `CREATE_EVENT` | Entity 实现了允许创建 events 的 methods。 |
| `DELETE_EVENT` | Entity 实现了允许删除 events 的 methods。 |
| `UPDATE_EVENT` | Entity 实现了允许更新 events 的 methods。 |
## 方法
### 获取 events
Calendar entity 可以返回发生在特定时间范围内的 events。给实现者的注意事项:
* `start_date` 是下界,应用于 event 的 `end`(exclusive)。它具有本地 Home Assistant timezone 的 `tzinfo`。
* `end_date` 是上界,应用于 event 的 `start`(exclusive)。它与 `start_date` 具有相同的 `tzinfo`。
* Recurring events 应被展平并作为单独的 `CalendarEvent` 返回。
Calendar entity 负责按顺序返回 events,包括正确排序 all-day events。All-day event 应排序为在 Home Assistant timezone 中的午夜开始(例如从 start/end time 参数的 `tzinfo`,
或使用 `homeassistant.util.dt.start_of_local_day`)。注意,all-day events 仍应在 `CalendarEvent` 中设置 `datetime.date`,而不是带时间的日期。
```python
import datetime
from homeassistant.core import HomeAssistant
from homeassistant.components.calendar import CalendarEntity
class MyCalendar(CalendarEntity):
async def async_get_events(
self,
hass: HomeAssistant,
start_date: datetime.datetime,
end_date: datetime.datetime,
) -> list[CalendarEvent]:
"""Return calendar events within a datetime range."""
```
### 订阅 calendar events
Frontend 和其他 consumers 可以通过 `calendar/event/subscribe` WebSocket API 订阅实时 calendar event updates。该订阅完全由 `CalendarEntity` 基类处理——集成开发者除了现有的 `async_get_events` 方法外不需要实现任何东西。
当 calendar entity 的 state 改变时(例如 event 开始或结束),基类会自动获取订阅时间范围内的最新 events,并将它们推送给所有 active subscribers。Updates 会进行 debounce 处理以避免过多调用 `async_get_events`。
#### 通知 subscribers
在创建、更新或删除 calendar events 时,state 不会自动更新。如果集成需要在 state 变更之外通知 subscribers(例如在 CRUD 操作之后),应调用 `CalendarEntity.async_update_event_listeners` 将更新的 events 推送给所有 active subscribers。
#### WebSocket API
**订阅 events:**
```json
{
"type": "calendar/event/subscribe",
"entity_id": "calendar.my_calendar",
"start": "2025-01-01T00:00:00+00:00",
"end": "2025-01-31T23:59:59+00:00"
}
```
订阅立即返回请求时间范围内的当前 events,然后在 entity state 改变时推送 updates。下面的示例仅显示内部的 `event` payload;实际的 WebSocket frame 还包含 `id` 和 `type` 字段:
```json
{
"event": {
"events": [
{
"start": "2025-01-15T09:00:00+00:00",
"end": "2025-01-15T10:00:00+00:00",
"summary": "Team meeting",
"description": "Weekly sync",
"location": "Room 1"
}
]
}
}
```
列表中的每个 event 仅包含有值的字段。可能的字段有 `start`、`end`、`summary`、`description` 和 `location`。如果在获取 events 时发生错误,`events` 将为 `null`。
### 创建 events
Calendar entity 可以通过指定 `CREATE_EVENT` supported feature 来支持创建 events。支持 mutation 的集成必须处理 rfc5545 fields 和最佳实践,例如保留所设置的新未知 fields 和 recurring events。
```python
from homeassistant.components.calendar import CalendarEntity
class MyCalendar(CalendarEntity):
async def async_create_event(self, **kwargs: Any) -> None:
"""Add a new event to calendar."""
```
### 删除 events
Calendar entity 可以通过指定 `DELETE_EVENT` supported feature 来支持删除 events。支持 mutation 的集成必须支持 rfc5545 recurring events。
有三种删除 recurring events 的方式:
* 仅指定 `uid` 将删除整个 series
* 指定 `uid` 和 `recurrence_id` 将删除 series 中特定的 event instance
* 指定 `uid`、`recurrence_id` 和 `recurrence_range` 值可能删除从 `recurrence_id` 开始的范围内的 events。当前 rfc5545 允许 [range](https://www.rfc-editor.org/rfc/rfc5545#section-3.2.13) 值 `THISANDFUTURE`。
```python
from homeassistant.components.calendar import CalendarEntity
class MyCalendar(CalendarEntity):
async def async_delete_event(
self,
uid: str,
recurrence_id: str | None = None,
recurrence_range: str | None = None,
) -> None:
"""Delete an event on the calendar."""
```
### 更新 events
Calendar entity 可以通过指定 `UPDATE_EVENT` supported feature 来支持更新 events。支持 mutation 的集成必须支持 rfc5545 recurring events。
有三种更新 recurring events 的方式:
* 仅指定 `uid` 将更新整个 series
* 指定 `uid` 和 `recurrence_id` 将更新 series 中特定的 event instance
* 指定 `uid`、`recurrence_id` 和 `recurrence_range` 值可能更新从 `recurrence_id` 开始的范围内的 events。当前 rfc5545 允许 [range](https://www.rfc-editor.org/rfc/rfc5545#section-3.2.13) 值 `THISANDFUTURE`。
```python
from homeassistant.components.calendar import CalendarEntity
class MyCalendar(CalendarEntity):
async def async_update_event(
self,
uid: str,
event: dict[str, Any],
recurrence_id: str | None = None,
recurrence_range: str | None = None,
) -> None:
"""Update an event on the calendar."""
```
## 日历事件
`CalendarEvent` 代表 calendar 上的一个单独 event。
| Name | Type | Default | Description |
| ----------- | ---------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| start | datetime or date | **必需** | 事件开始(inclusive)。必须在 `end` 之前。`start` 和 `end` 必须是同一类型。作为 datetime 时,必须有 timezone。 |
| end | datetime or date | **必需** | 事件结束(exclusive)。必须在 `start` 之后。作为 datetime 时,必须有与 start 相同的 timezone。 |
| summary | string | **必需** | 事件的标题或摘要。 |
| location | string | `None` | 事件的地理位置。 |
| description | string | `None` | 事件的详细描述。 |
| uid | string | `None` | 事件的唯一标识符(mutation 必需) |
| recurrence\_id | string | `None` | 针对 recurring event 的特定 instance 的可选标识符(recurring events 的 mutation 必需) |
| rrule | string | `None` | 重复规则字符串,例如 `FREQ=DAILY` |
## 颜色管理
Calendar entity 可以选项性地为在 frontend 中显示提供默认颜色,方法是将 `initial_color` 设为十六进制 color 字符串(例如 `"#16a765"`)。该颜色在 entity 首次添加时自动存储在 entity registry options 中,用户可以通过 entity settings UI 自定义。
---
url: /developers/core/entity/camera.md
---
Camera entity 可以显示 images,还可以选项性地显示 video stream。从 [`homeassistant.components.camera.Camera`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/camera/__init__.py) 派生 platform entity。
## 属性
:::tip
Properties 应该只从内存返回信息,而不要执行 I/O(如网络请求)。请实现 `update()` 或 `async_update()` 来获取数据。
:::
| Name | Type | Default | Description |
| ------------------------ | ------------------------------------| ------- | --------------------------------------------------------------------------------------------------- |
| brand | `str \| None` | `None` | Camera 的品牌(manufacturer)。 |
| frame\_interval | `float` | 0.5 | Stream 中 frames 之间的间隔。 |
| is\_on | `bool` | `True` | 指示 camera 是否开启。 |
| is\_recording | `bool` | `False` | 指示 camera 是否正在 recording。用于确定 `state`。 |
| is\_streaming | `bool` | `False` | 指示 camera 是否正在 streaming。用于确定 `state`。 |
| model | `str \| None` | `None` | Camera 的 model。 |
| motion\_detection\_enabled | `bool` | `False` | 指示 camera 是否启用了 motion detection。 |
| use\_stream\_for\_stills | `bool` | `False` | 确定是否使用 `Stream` 集成来生成 still images |
### 状态
State 通过设置上述 properties 来定义。Resulting state 使用 `CameraState` enum 返回以下成员之一。
| Value | Description |
|-------------|-----------------------------------------|
| `RECORDING` | Camera 当前正在 recording。 |
| `STREAMING` | Camera 当前正在 streaming。 |
| `IDLE` | Camera 当前处于 idle。 |
## 支持的功能
Supported features 通过使用 `CameraEntityFeature` enum 中的值来定义,
并使用按位或(`|`)运算符组合。
| Value | Description |
| -------- | -------------------------------------------- |
| `ON_OFF` | Device 支持 `turn_on` 和 `turn_off` |
| `STREAM` | Device 支持 streaming |
## 方法
### 摄像头图像
当传入 width 和 height 时,应在最佳基础上进行 scaling。如果 camera 无法进行 scaling,UI 将回退到在 display 层进行 scaling。
* 返回满足最小 width 和最小 height 的最小 image。
* 在对 image 进行 scaling 时,必须保留 aspect ratio。如果 aspect ratio 与请求的 height 或 width 不同,则返回 image 的 width 和/或 height 预期大于请求值。
* 如果底层 camera 能够对 image 进行 scaling,则传递 width 和 height。
* 如果集成无法对 image 进行 scaling 且返回 jpeg image,则在请求时它会自动由 camera 集成进行 scaling。
```python
class MyCamera(Camera):
# 实现以下方法之一。
def camera_image(
self, width: int | None = None, height: int | None = None
) -> bytes | None:
"""Return bytes of camera image."""
raise NotImplementedError()
async def async_camera_image(
self, width: int | None = None, height: int | None = None
) -> bytes | None:
"""Return bytes of camera image."""
```
### 流源
Stream source 应返回一个 ffmpeg 可用的 URL(例如 RTSP URL)。需要 `CameraEntityFeature.STREAM`。
具有 stream source 的 camera entity 默认使用 `StreamType.HLS` 来告诉 frontend 使用带有 `stream` component 的 HLS feed。该 stream source 也会用于 `stream` 的 recording。
```python
class MyCamera(Camera):
async def stream_source(self) -> str | None:
"""Return the source of the stream."""
```
Camera entity 渲染 still image 的一种常见方式是将 stream source 传递给 `ffmpeg` component 中的 `async_get_image`。
### WebRTC 流
WebRTC-enabled cameras 可以通过与 Home Assistant frontend 建立直接连接来使用。这种用法需要 `CameraEntityFeature.STREAM`,并且集成必须实现以下两个 methods 以支持原生 WebRTC:
* `async_handle_async_webrtc_offer`:初始化 WebRTC stream。所有异步传入的消息/错误应通过 `send_message` callback 转发到 frontend。
* `async_on_webrtc_candidate`:Frontend 会在发送 offer 后用所有传入的 candidate 调用它。
以下 method 可以选项性地实现:
* `close_webrtc_session`(可选):Frontend 会在 stream 关闭时调用它。可用于清理资源。
WebRTC streams 不使用 `stream` component,也不支持 recording。
通过实现 WebRTC methods,frontend 假设 camera 仅支持 WebRTC,因此不会回退到 HLS。
```python
class MyCamera(Camera):
async def async_handle_async_webrtc_offer(
self, offer_sdp: str, session_id: str, send_message: WebRTCSendMessage
) -> None:
"""Handle the async WebRTC offer.
Async 意味着处理 offer 可能需要一些时间,responses/message
将通过 send_message callback 发送。
该方法由具有 CameraEntityFeature.STREAM 的 cameras 使用
覆盖此方法的集成还必须实现 async_on_webrtc_candidate。
集成可以使用原生 WebRTC 实现进行覆盖。
"""
async def async_on_webrtc_candidate(self, session_id: str, candidate: RTCIceCandidate) -> None:
"""Handle a WebRTC candidate."""
@callback
def close_webrtc_session(self, session_id: str) -> None:
"""Close a WebRTC session."""
```
### WebRTC 提供商
集成可以使用 `homeassistant.components.camera.webrtc` 中的库,从现有 camera 的 stream source 提供 WebRTC stream。集成可以实现 `CameraWebRTCProvider` 并通过 `async_register_webrtc_provider` 注册它。
### 开启
```python
class MyCamera(Camera):
# 实现以下方法之一。
def turn_on(self) -> None:
"""Turn on camera."""
async def async_turn_on(self) -> None:
"""Turn on camera."""
```
### 关闭
```python
class MyCamera(Camera):
# 实现以下方法之一。
def turn_off(self) -> None:
"""Turn off camera."""
async def async_turn_off(self) -> None:
"""Turn off camera."""
```
### 启用移动侦测
```python
class MyCamera(Camera):
# 实现以下方法之一。
def enable_motion_detection(self) -> None:
"""Enable motion detection in the camera."""
async def async_enable_motion_detection(self) -> None:
"""Enable motion detection in the camera."""
```
### 禁用移动侦测
```python
class MyCamera(Camera):
# 实现以下方法之一。
def disable_motion_detection(self) -> None:
"""Disable motion detection in camera."""
async def async_disable_motion_detection(self) -> None:
"""Disable motion detection in camera."""
```
---
url: /developers/core/entity/climate.md
---
Climate entity 控制 temperature、humidity 或 fans,例如 A/C systems 和 humidifiers。从 [`homeassistant.components.climate.ClimateEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/climate/__init__.py) 派生 platform entity。
## 属性
:::tip
Properties 应该只从内存返回信息,而不要执行 I/O(如网络请求)。请实现 `update()` 或 `async_update()` 来获取数据。
:::
| Name | Type | Default | Description |
| ----------------------- | ----------------------------------- | ------------------------------------ | -------------------------------------------------------------------------- |
| current\_humidity | `float \| None` | `None` | 当前的 humidity。 |
| current\_temperature | `float \| None` | `None` | 当前的 temperature。 |
| fan\_mode | `str \| None` | **ClimateEntityFeature.FAN\_MODE 必需** | 当前的 fan mode。 |
| fan\_modes | `list[str] \| None` | **ClimateEntityFeature.FAN\_MODE 必需** | 可用 fan modes 的列表。 |
| hvac\_action | `HVACAction \| None` | `None` | 当前正在执行的动作。见下文。 |
| hvac\_mode | `HVACMode \| None` | **必需** | 选择的 operation mode。见下文。用于确定 `state`。 |
| hvac\_modes | `list[HVACMode]` | **必需** | 可用 operation modes 的列表。见下文。 |
| max\_humidity | `float` | `DEFAULT_MAX_HUMIDITY`(值 == 99) | 最大 humidity。 |
| max\_temp | `float` | `DEFAULT_MAX_TEMP`(值 == 35 °C) | `temperature_unit` 中的最大 temperature。 |
| min\_humidity | `float` | `DEFAULT_MIN_HUMIDITY`(值 == 30) | 最小 humidity。 |
| min\_temp | `float` | `DEFAULT_MIN_TEMP`(值 == 7 °C) | `temperature_unit` 中的最小 temperature。 |
| precision | `float` | 根据 `temperature_unit` | 系统中 temperature 的 precision。默认为 `UnitOfTemperature.CELSIUS` 使用十分位,否则使用整数。 |
| preset\_mode | `str \| None` | **ClimateEntityFeature.PRESET\_MODE 必需** | 当前活动的 preset。 |
| preset\_modes | `list[str] \| None` | **ClimateEntityFeature.PRESET\_MODE 必需** | 可用的 presets。 |
| swing\_mode | `str \| None` | **ClimateEntityFeature.SWING\_MODE 必需** | Swing 设置。 |
| swing\_modes | `list[str] \| None` | **ClimateEntityFeature.SWING\_MODE 必需** | 返回可用 swing modes 的列表,如果实现了 horizontal swing,则仅返回 vertical modes。 |
| swing\_horizontal\_mode | `str \| None` | **ClimateEntityFeature.SWING\_HORIZONTAL\_MODE 必需** | Horizontal swing 设置。 |
| swing\_horizontal\_modes | `list[str] \| None` | **ClimateEntityFeature.SWING\_HORIZONTAL\_MODE 必需** | 返回可用 horizontal swing modes 的列表。 |
| target\_humidity | `float \| None` | `None` | Device 试图达到的 target humidity。 |
| target\_humidity\_step | `int \| None` | `None` | 在针对 device 的 action call 中 target humidity 可以增减的 supported step size。 |
| target\_temperature | `float \| None` | `None` | 当前设置要达到的 temperature。 |
| target\_temperature\_high | `float \| None` | **TARGET\_TEMPERATURE\_RANGE 必需** | 目标 temperature 的上界 |
| target\_temperature\_low | `float \| None` | **TARGET\_TEMPERATURE\_RANGE 必需** | 目标 temperature 的下界 |
| target\_temperature\_step | `float \| None` | `None` | 目标 temperature 可以增减的 supported step size |
| temperature\_unit | str | **必需** | 系统的 temperature 计量单位(`UnitOfTemperature.CELSIUS` 或 `UnitOfTemperature.FAHRENHEIT`)。 |
### HVAC 模式
HVAC mode 是要求 device 执行的行为。
你只能使用 `HVACMode`
enum 提供的内置 HVAC modes。
对于修改一个或多个 modes 行为的 device options,请添加一个 preset。
| Name | Description |
| -------------------- | ------------------------------------------------------------------- |
| `HVACMode.OFF` | Device 已关闭。 |
| `HVACMode.HEAT` | Device 设置为加热到目标 temperature。 |
| `HVACMode.COOL` | Device 设置为冷却到目标 temperature。 |
| `HVACMode.HEAT_COOL` | Device 设置为加热/冷却到目标 temperature range。 |
| `HVACMode.AUTO` | Device 设置为 schedule、learned behavior、AI。 |
| `HVACMode.DRY` | Device 设置为 dry/humidity mode。 |
| `HVACMode.FAN_ONLY` | Device 只开启 fan。没有加热或冷却操作。 |
### HVAC 动作
HVAC action 描述 device 为了完成请求的 HVAC mode 和 preset 而正在执行的 *当前* 动作,
由 device 自己的 control algorithms 决定。
你只能使用 `HVACAction` enum 提供的内置 HVAC actions。
:::note
除非 device 报告额外的信息,否则可能无法准确确定正确的 HVAC action。
例如:
* 具有 hysteresis 的 thermostat 在目标 temperature 附近有一个 ambiguous temperature range,它可能正在执行动作,也可能处于 idle。
* 可变功率 device 在达到目标 temperature 后可能会继续以较低的功率运行,而不是变成 idle。
对于无法准确确定 HVAC action 的 devices,不要实现 `hvac_action`。
:::
| Name | Description |
| ----------------------- | ----------------------------------------------------------------------------------------------------------- |
| `HVACAction.OFF` | HVAC mode 为 `HVACMode.OFF`。除非更改 mode,否则 device 不会执行任何动作。 |
| `HVACAction.PREHEATING` | Device 的 heat source 正在运行,但尚未达到 operating temperature。 |
| `HVACAction.HEATING` | Device 正在向 space 添加热量。 |
| `HVACAction.COOLING` | Device 正在从 space 移除热量。 |
| `HVACAction.DRYING` | Device 正在从 space 中的空气中移除 moisture。 |
| `HVACAction.FAN` | Device 开启 fan 以仅循环或通风 air。 |
| `HVACAction.IDLE` | Device 当前未执行任何动作,但如果 conditions 改变,可能会开始执行动作。 |
| `HVACAction.DEFROSTING` | Device 正在移除累积的 ice。 |
### 预设
Device 可能有不同的 presets,它可能希望向用户展示。常见的 presets 是 "Away" 或 "Eco"。有一些内置的 presets 会提供 translations,但也允许添加自定义 presets。
| Name | Description |
| ---------- | ------------------------------------------------------ |
| `NONE` | 没有活跃的 preset |
| `ECO` | Device 正在运行节能模式 |
| `AWAY` | Device 处于 away mode |
| `BOOST` | Device 将所有阀门开到最大 |
| `COMFORT` | Device 处于 comfort mode |
| `HOME` | Device 处于 home mode |
| `SLEEP` | Device 为睡眠做好准备 |
| `ACTIVITY` | Device 对 activity 作出反应(例如 movement sensors) |
### 风扇模式
Device 的 fan 可以有不同 states。有一些内置的 fan modes,但也允许使用自定义 fan modes。
| Name |
| ------------- |
| `FAN_ON` |
| `FAN_OFF` |
| `FAN_AUTO` |
| `FAN_LOW` |
| `FAN_MEDIUM` |
| `FAN_HIGH` |
| `FAN_TOP` |
| `FAN_MIDDLE` |
| `FAN_FOCUS` |
| `FAN_DIFFUSE` |
### 摆风模式
Device fan 可以有不同 swing modes,它希望用户了解/控制。
:::note
对于不能独立控制 vertical 和 horizontal swing 的集成,所有可能的 options 都应列在 `swing_modes` 中;否则 `swing_modes` 提供 vertical 支持,`swing_horizontal_modes` 应提供 horizontal 支持。
:::
| Name | Description |
| ------------------ | ------------------------------------------------- |
| `SWING_OFF` | Fan 未摆动。 |
| `SWING_ON` | Fan 正在摆动。 |
| `SWING_VERTICAL` | Fan 正在 vertical 摆动。 |
| `SWING_HORIZONTAL` | Fan 正在 horizontal 摆动。 |
| `SWING_BOTH` | Fan 正在 horizontal 和 vertical 摆动。 |
### 水平摆风模式
Device fan 可以有不同 horizontal swing modes,它希望用户了解/控制。
:::note
仅当集成能够独立控制 vertical 和 horizontal swing 时,才应实现此项。在这种情况下,`swing_modes` property 提供 vertical 支持,`swing_horizontal_modes` 提供 horizontal 支持。
:::
| Name | Description |
| ------------------ | ------------------------------------------------- |
| `SWING_OFF` | Fan 未摆动。 |
| `SWING_ON` | Fan 正在摆动。 |
## 支持的功能
Supported features 通过使用 `ClimateEntityFeature` enum 中的值来定义,
并使用按位或(`|`)运算符组合。
| Value | Description |
| -------------------------- | ------------------------------------------------------------------------------------------- |
| `TARGET_TEMPERATURE` | Device 支持 target temperature。 |
| `TARGET_TEMPERATURE_RANGE` | Device 支持 ranged target temperature。用于 HVAC modes `heat_cool` 和 `auto` |
| `TARGET_HUMIDITY` | Device 支持 target humidity。 |
| `FAN_MODE` | Device 支持 fan modes。 |
| `PRESET_MODE` | Device 支持 presets。 |
| `SWING_MODE` | Device 支持 swing modes。 |
| `SWING_HORIZONTAL_MODE` | Device 支持 horizontal swing modes。 |
| `TURN_ON` | Device 支持 turn on。 |
| `TURN_OFF` | Device 支持 turn off。 |
## 方法
### 设置 HVAC 模式
```python
class MyClimateEntity(ClimateEntity):
# 实现以下方法之一。
def set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new target hvac mode."""
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new target hvac mode."""
```
### 开启
```python
class MyClimateEntity(ClimateEntity):
# 实现以下方法之一。
# `turn_on` method 应将 `hvac_mode` 设置为除
# `HVACMode.OFF` 之外的任何值,方法是从 service action
# handler 中 optimistic 地设置它,或通过下一次 state update
def turn_on(self) -> None:
"""Turn the entity on."""
async def async_turn_on(self) -> None:
"""Turn the entity on."""
```
### 关闭
```python
class MyClimateEntity(ClimateEntity):
# 实现以下方法之一。
# `turn_off` method 应通过
# 从 service action handler 中 optimistic 地设置,或通过
# 下一次 state update 将 `hvac_mode` 设置为 `HVACMode.OFF`
def turn_off(self) -> None:
"""Turn the entity off."""
async def async_turn_off(self) -> None:
"""Turn the entity off."""
```
### 切换
```python
class MyClimateEntity(ClimateEntity):
# 不需要强制实现 `toggle` method,因为 base implementation
# 会根据当前 HVAC mode 调用 `turn_on`/`turn_off`。
# 如果实现,`toggle` method 应通过
# 从 service action handler 中 optimistic 地设置
# 或通过下一次 state update,将 `hvac_mode` 设置为正确的 `HVACMode`
def toggle(self) -> None:
"""Toggle the entity."""
async def async_toggle(self) -> None:
"""Toggle the entity."""
```
### 设置预设模式
```python
class MyClimateEntity(ClimateEntity):
# 实现以下方法之一。
def set_preset_mode(self, preset_mode: str) -> None:
"""Set new target preset mode."""
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new target preset mode."""
```
### 设置风扇模式
```python
class MyClimateEntity(ClimateEntity):
# 实现以下方法之一。
def set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
```
### 设置湿度
```python
class MyClimateEntity(ClimateEntity):
# 实现以下方法之一。
def set_humidity(self, humidity: int) -> None:
"""Set new target humidity."""
async def async_set_humidity(self, humidity: int) -> None:
"""Set new target humidity."""
```
### 设置摆风模式
```python
class MyClimateEntity(ClimateEntity):
# 实现以下方法之一。
def set_swing_mode(self, swing_mode: str) -> None:
"""Set new target swing operation."""
async def async_set_swing_mode(self, swing_mode: str) -> None:
"""Set new target swing operation."""
```
### 设置水平摆风模式
```python
class MyClimateEntity(ClimateEntity):
# 实现以下方法之一。
def set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None:
"""Set new target horizontal swing operation."""
async def async_set_swing_horizontal_mode(self, swing_horizontal_mode: str) -> None:
"""Set new target horizontal swing operation."""
```
### 设置温度
:::note
`ClimateEntity` 有内置 validation,确保 `target_temperature_low` 参数小于或等于 `target_temperature_high` 参数。因此,集成不需要在自己的实现中对此进行 validation。
:::
```python
class MyClimateEntity(ClimateEntity):
# 实现以下方法之一。
def set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
```
---
url: /developers/core/entity/conversation.md
---
Conversation 实体允许用户与 Home Assistant 对话。
Conversation 实体派生自 [`homeassistant.components.conversation.ConversationEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/conversation/entity.py)。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。
:::
| 名称 | 类型 | 默认值 | 描述
| ---- | ---- | ------- | -----------
| supported\_languages | `list[str]` | `Literal["*"]` | **必填** | 服务支持的语言。如果支持所有语言,则返回 `"*"`。
## 支持的功能
支持的功能通过使用 `ConversationEntityFeature` 枚举中的值来定义,并使用按位或(`|`)运算符进行组合。
| 值 | 描述
| -------------------------- | -------------------------------------------------------------------------------------------
| `CONTROL` | 实体能够控制 Home Assistant。
## 方法
### 处理消息
此方法用于处理传入的聊天消息。
```python
from homeassistant.components.conversation import ChatLog, ConversationEntity
class MyConversationEntity(ConversationEntity):
"""Represent a conversation entity."""
async def _async_handle_message(
self,
user_input: ConversationInput,
chat_log: ChatLog,
) -> ConversationResult:
"""Call the API."""
# Add the response to the chat log.
chat_log.async_add_assistant_content_without_tools(
AssistantContent(
agent_id=user_input.agent_id,
content="Test response",
)
)
response = intent.IntentResponse(language=user_input.language)
response.async_set_speech("Test response")
return agent.ConversationResult(
conversation_id=None,
response=response,
continue_conversation=False,
)
```
`ConversationInput` 对象包含以下数据:
| 名称 | 类型 | 描述
| ---- | ---- | -----------
| `text` | `str` | 用户输入
| `context` | `Context` | 附加到 HA 中操作的 HA context
| `conversation_id` | `Optional[str]` | 可用于跟踪多轮对话。如果不支持则返回 None
| `language` | `str` | 文本的语言。如果用户未提供,则设置为 HA 配置的语言。
*我们曾将 `async_process` 作为处理消息的方法来推荐。现已更改为 `_async_handle_message` 以自动包含 chat log。此更改向后兼容。*
#### 聊天记录
chat log 对象允许 conversation 实体读取对话历史,并向其中添加消息和工具调用。
有关完整的类型化 API,请参阅 [Python 接口](https://github.com/home-assistant/core/blob/dev/homeassistant/components/conversation/chat_log.py)。
### 准备
一旦 Home Assistant 知道请求即将到来,我们将让 conversation 实体为之做好准备。这可用于加载语言模型或其他资源。此函数为可选项。
```python
class MyConversationEntity(ConversationEntity):
"""Represent a conversation entity."""
async def async_prepare(self, language: str | None = None) -> None:
"""Prepare a language for processing."""
```
---
url: /developers/core/entity/cover.md
---
Cover entity 控制 opening 或 cover,例如 garage door 或 window shade。从 [`homeassistant.components.cover.CoverEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/cover/__init__.py) 派生 platform entity。
:::note
Cover entity 应仅用于控制 opening 或 cover 的 devices。
对于其他类型的 device entities(如 [Number](/developers/core/entity/number.md)),应改用它们,即使过去并非如此。
:::
## 属性
:::tip
Properties 应该只从内存返回信息,而不要执行 I/O(如网络请求)。请实现 `update()` 或 `async_update()` 来获取数据。
:::
| Name | Type | Default | Description
| ----------------------- | ---- | ------- | -----------
| current\_cover\_position | `int \| None` | `None` | Cover 的当前位置,其中 0 表示 closed,100 表示 fully open。
| current\_cover\_tilt\_position | `int \| None` | `None` | Cover 的当前 tilt position,其中 0 表示 closed/no tilt,100 表示 open/maximum tilt。
| is\_closed | `bool \| None` | **必需** | Cover 是否 closed。用于确定 `state`。
| is\_closing | `bool \| None` | `None` | Cover 是否正在 closing。用于确定 `state`。
| is\_opening | `bool \| None` | `None` | Cover 是否正在 opening。用于确定 `state`。
### 状态
State 通过设置上述 properties 来定义。Resulting state 使用 `CoverState` enum 返回以下成员之一。
| Value | Description |
|-------------|--------------------------------------------------------------------|
| `CLOSED` | Cover 已 closed。 |
| `CLOSING` | Cover 正在 closing。 |
| `OPENING` | Cover 正在 opening。 |
| `OPEN` | Cover 已 open。 |
### 设备类型
| Constant | Description
|----------|-----------------------|
| `CoverDeviceClass.AWNING` | 控制 awning,例如外部可伸缩 window、door 或 patio cover。
| `CoverDeviceClass.BLIND` | 控制 blinds,即连接的 slats,可以展开或折叠以覆盖 opening,或倾斜以部分覆盖 opening,例如 window blinds。
| `CoverDeviceClass.CURTAIN` | 控制 curtains 或 drapes,通常是悬挂在 window 或 door 上方的 fabric,可以拉开。
| `CoverDeviceClass.DAMPER` | 控制减少 air flow、sound 或 light 的机械 damper。
| `CoverDeviceClass.DOOR` | 控制提供进入通常属于结构一部分的区域 access 的 door。
| `CoverDeviceClass.GARAGE` | 控制提供进入 garage access 的 garage door。
| `CoverDeviceClass.GATE` | 控制提供进入 driveway 或其他区域 access 的 gate。Gates 位于结构外部,通常属于 fence 的一部分。
| `CoverDeviceClass.SHADE` | 控制 shades,即连续的 material plane 或连接的 cells,可以展开或折叠覆盖 opening,例如 window shades。
| `CoverDeviceClass.SHUTTER` | 控制 shutters,即连接的 slats,可以 swing out/in 以覆盖 opening,或倾斜以部分覆盖 opening,例如室内或室外 window shutters。
| `CoverDeviceClass.WINDOW` | 控制物理 window,可以打开和关闭,或可以倾斜。
## 支持的功能
Supported features 通过使用 `CoverEntityFeature` enum 中的值来定义,
并使用按位或(`|`)运算符组合。
| Value | Description |
| ------------------- | -------------------------------------------------------------------------------- |
| `OPEN` | Cover 支持被 open。 |
| `CLOSE` | Cover 支持被 closed。 |
| `SET_POSITION` | Cover 支持在 open 和 closed 之间移动到特定 position。 |
| `STOP` | Cover 支持停止当前 action(open、close、set position) |
| `OPEN_TILT` | Cover 支持 tilt open。 |
| `CLOSE_TILT` | Cover 支持 tilt closed。 |
| `SET_TILT_POSITION` | Cover 支持在 open 和 closed 之间移动到特定 tilt position。 |
| `STOP_TILT` | Cover 支持停止当前 tilt action(open、close、set position) |
## 方法
### 打开
仅当设置了 `CoverEntityFeature.OPEN` 标志时,才实现此 method。
```python
class MyCover(CoverEntity):
# 实现以下方法之一。
def open_cover(self, **kwargs: Any) -> None:
"""Open the cover."""
async def async_open_cover(self, **kwargs: Any) -> None:
"""Open the cover."""
```
### 关闭
仅当设置了 `CoverEntityFeature.CLOSE` 标志时,才实现此 method。
```python
class MyCover(CoverEntity):
# 实现以下方法之一。
def close_cover(self, **kwargs: Any) -> None:
"""Close cover."""
async def async_close_cover(self, **kwargs: Any) -> None:
"""Close cover."""
```
### 设置位置
仅当设置了 `CoverEntityFeature.SET_POSITION` 标志时,才实现此 method。
```python
class MyCover(CoverEntity):
# 实现以下方法之一。
def set_cover_position(self, **kwargs: Any) -> None:
"""Move the cover to a specific position."""
async def async_set_cover_position(self, **kwargs: Any) -> None:
"""Move the cover to a specific position."""
```
### 停止
仅当设置了 `CoverEntityFeature.STOP` 标志时,才实现此 method。
```python
class MyCover(CoverEntity):
# 实现以下方法之一。
def stop_cover(self, **kwargs: Any) -> None:
"""Stop the cover."""
async def async_stop_cover(self, **kwargs: Any) -> None:
"""Stop the cover."""
```
### 打开 tilt
仅当设置了 `CoverEntityFeature.OPEN_TILT` 标志时,才实现此 method。
```python
class MyCover(CoverEntity):
# 实现以下方法之一。
def open_cover_tilt(self, **kwargs: Any) -> None:
"""Open the cover tilt."""
async def async_open_cover_tilt(self, **kwargs: Any) -> None:
"""Open the cover tilt."""
```
### 关闭 tilt
仅当设置了 `CoverEntityFeature.CLOSE_TILT` 标志时,才实现此 method。
```python
class MyCover(CoverEntity):
# 实现以下方法之一。
def close_cover_tilt(self, **kwargs: Any) -> None:
"""Close the cover tilt."""
async def async_close_cover_tilt(self, **kwargs: Any) -> None:
"""Close the cover tilt."""
```
### 设置 tilt 位置
仅当设置了 `CoverEntityFeature.SET_TILT_POSITION` 标志时,才实现此 method。
```python
class MyCover(CoverEntity):
# 实现以下方法之一。
def set_cover_tilt_position(self, **kwargs: Any) -> None:
"""Move the cover tilt to a specific position."""
async def async_set_cover_tilt_position(self, **kwargs: Any) -> None:
"""Move the cover tilt to a specific position."""
```
### 停止 tilt
仅当设置了 `CoverEntityFeature.STOP_TILT` 标志时,才实现此 method。
```python
class MyCover(CoverEntity):
# 实现以下方法之一。
def stop_cover_tilt(self, **kwargs: Any) -> None:
"""Stop the cover."""
async def async_stop_cover_tilt(self, **kwargs: Any) -> None:
"""Stop the cover."""
```
---
url: /developers/core/entity/date.md
---
`date` 是一种允许用户向集成输入日期的实体。从 [`homeassistant.components.date.DateEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/date/__init__.py) 派生实体平台。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| 名称 | 类型 | 默认值 | 描述
| ---- | ---- | ------- | -----------
| native\_value | `datetime.date \| None` | **必填** | 日期的值。
所有实体共有的其他属性(如 `icon`、`name` 等)也适用。
## 方法
### 设置值
当用户或自动化想要更新值时调用。
```python
class MyDate(DateEntity):
# 实现以下方法之一。
def set_value(self, value: date) -> None:
"""Update the current value."""
async def async_set_value(self, value: date) -> None:
"""Update the current value."""
```
---
url: /developers/core/entity/datetime.md
---
`datetime` 是一种允许用户向集成输入时间戳的实体。从 [`homeassistant.components.datetime.DateTimeEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/datetime/__init__.py) 派生实体平台。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| 名称 | 类型 | 默认值 | 描述
| ---- | ---- | ------- | -----------
| native\_value | `datetime.datetime \| None` | **必填** | datetime 的值。必须包含时区信息。
所有实体共有的其他属性(如 `icon`、`name` 等)也适用。
## 方法
### 设置值
当用户或自动化想要更新值时调用。提供的 datetime 始终包含时区信息;无时区的 datetime 将被视为处于 Home Assistant 配置的时区。
```python
class MyDateTime(DateTimeEntity):
# 实现以下方法之一。
def set_value(self, value: datetime) -> None:
"""Update the current value."""
async def async_set_value(self, value: datetime) -> None:
"""Update the current value."""
```
---
url: /developers/core/entity/device-tracker.md
---
Device tracker 是一种只读实体,提供存在信息。有三种类型的 device tracker 实体可供集成长使用:`BaseScannerEntity`、`ScannerEntity` 和 `TrackerEntity`。
## BaseScannerEntity
BaseScannerEntity 报告设备的连接状态,例如与蓝牙 beacons 的连接。如果设备已连接,BaseScannerEntity 的状态将是关联 zone 的名称,例如与 home zone 关联时为 `home`,如果设备未连接,状态将为 `not_home`。
基类设置了 `in_zones` 状态属性。当设备连接时,它将被填充为关联 zone 及其包含的 zone 的 `entity_id`,按大小排序;未连接时则为空。
基类还将 `tracking_type` 能力属性设置为 `TrackingType.CONNECTION`。
从 [`homeassistant.components.device_tracker.BaseScannerEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/device_tracker/__init__.py) 派生平台实体。
### 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| 名称 | 类型 | 默认值 | 描述 |
| ------------- | ---------------| ------------------- | ----------------------------------- |
| is\_connected | `bool \| None` | **必填** | 设备的连接状态。 |
| source\_type | `SourceType` | **必填** | 设备的 source 类型。 |
## ScannerEntity
ScannerEntity 报告设备在本地网络上的连接状态。如果设备已连接,ScannerEntity 的状态将是关联 zone 的名称,例如与 home zone 关联时为 `home`;如果设备未连接,状态将为 `not_home`。
基类设置了 `in_zones` 状态属性。当设备连接时,它将被填充为关联 zone 及其包含的 zone 的 `entity_id`,按大小排序;未连接时则为空。
基类还将 `tracking_type` 能力属性设置为 `TrackingType.CONNECTION`。
ScannerEntity 基于 BaseScannerEntity,旨在跟踪连接到 IP 网络且可通过 MAC 地址识别的设备。
从 [`homeassistant.components.device_tracker.ScannerEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/device_tracker/__init__.py) 派生平台实体。
### 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| 名称 | 类型 | 默认值 | 描述 |
| ------------- | ---------------| ------------------- | ----------------------------------- |
| hostname | `str \| None` | `None` | 设备的主机名。 |
| ip\_address | `str \| None` | `None` | 设备的 IP 地址。 |
| is\_connected | `bool \| None` | **必填** | 设备的连接状态。 |
| mac\_address | `str \| None` | `None` | 设备的 MAC 地址。 |
| source\_type | `SourceType` | `SourceType.ROUTER` | 设备的 source 类型。 |
### DHCP 发现
如果 device tracker 的 `source_type` 为 `router`,并且已设置 `ip_address`、`mac_address` 和 `hostname` 属性,数据将
加速 `DHCP discovery`,因为系统不必等待
DHCP discover 数据包来查找现有设备。
## TrackerEntity
TrackerEntity 跟踪设备的位置,并以 zone 名称或 `home` / `not_home` 状态报告。TrackerEntity 可使用 GPS 坐标或 `Zone` entity\_id 列表来确定其状态。应设置 `in_zones` 或 `latitude` 和 `longitude` 来报告状态。如果 `in_zones` 和 `latitude` + `longitude` 同时存在,则 `in_zones` 优先。如果未提供 `in_zones`,基类将计算一个 `in_zones` 列表,包含设备当前所处的 active 和 passive zone。实体的状态为 `in_zones` 列表中的第一个 active zone,如果第一个 active zone 是 home zone 则为 `home`,如果没有 active zone 则为 `not_home`。
基类将 `tracking_type` 能力属性设置为 `TrackingType.POSITION`。
从 [`homeassistant.components.device_tracker.TrackerEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/device_tracker/__init__.py) 派生平台实体。
### 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| 名称 | 类型 | 默认值 | 描述 |
| ----------------- | --------------------| ---------------- | ---------------------------------------- |
| in\_zones | `list[str] \| None` | `None` | 设备所在的 zones,包括 passive zones。此列表应按 zone 大小排序,然后按到 zone 中心的距离排序。 |
| latitude | `float \| None` | `None` | 设备的纬度坐标,如果 `in_zones` 不为 `None` 则忽略。 |
| location\_accuracy | `float` | `0` | 设备的位置精度(米)。 |
| longitude | `float \| None` | `None` | 设备的经度坐标,如果 `in_zones` 不为 `None` 则忽略。 |
| source\_type | SourceType | `SourceType.GPS` | 设备的 source 类型。 |
---
url: /developers/core/entity/event.md
---
Event 是在发生某事时发出的信号,例如用户按下门铃等物理按钮或按下遥控器上的按钮时。event 实体捕获物理世界中的这些事件,并将它们作为实体在 Home Assistant 中可用。
Event 实体派生自 [`homeassistant.components.event.EventEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/event/__init__.py)。
## 状态
Event 实体是无状态的,这意味着你无需维护状态。相反,当物理世界中发生某事时,你可以触发一个事件。Home Assistant 会跟踪最后发出的事件,并将其显示为实体的当前状态。
实体的主要状态是最后发出事件的时间戳,此外,还会跟踪事件类型以及随事件提供的可选额外状态数据。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| 名称 | 类型 | 默认值 | 描述 |
| ----------- | --------------- | ------------ | ---------------------------------------------------- |
| event\_types | `list[str]` | **必填** | 此实体可以发出的可能事件类型列表。 |
所有实体共有的其他属性(如 `device_class`、`icon`、`name` 等)也适用。
## 触发事件
Event 实体与其他实体略有不同。Home Assistant 管理状态,但集成
负责触发事件。这是通过调用 event 实体上的 `_trigger_event` 方法来实现的。
此方法以事件类型作为第一个参数,以额外的状态数据作为可选的第二个参数。
```python
class MyEvent(EventEntity):
_attr_device_class = EventDeviceClass.BUTTON
_attr_event_types = ["single_press", "double_press"]
@callback
def _async_handle_event(self, event: str) -> None:
"""Handle the demo button event."""
self._trigger_event(event, {"extra_data": 123})
self.async_write_ha_state()
async def async_added_to_hass(self) -> None:
"""Register callbacks with your device API/library."""
my_device_api.listen(self._async_handle_event)
```
只能发出在 `event_types` 属性中定义的事件类型。如果发出未定义在 `event_types` 属性中的事件类型,将引发 `ValueError`。
:::tip
务必在实体从 Home Assistant 中移除时注销任何回调。
:::
### 可用的 device class
可选指定实体是什么类型。
| 常量 | 描述 |
| --------------------------- | ----------------------------------------------------- |
| `EventDeviceClass.BUTTON` | 按下了遥控器上的按钮。 |
| `EventDeviceClass.DOORBELL` | 专门用于用作门铃的按钮。 |
| `EventDeviceClass.MOTION` | 用于运动传感器检测到的运动事件。 |
### 标准事件类型
某些 device class 定义了标准事件类型,以确保集成的了一致性,从而启用与底层集成无关的通用自动化。
#### 门铃
* `DoorbellEventType.RING`:表示标准的"门铃被按下"事件。**此事件类型是必需的**。
#### 按钮
* `ButtonEventType.PRESS_START`:按钮被按下。
* `ButtonEventType.PRESS_END`:按钮在短暂按下后释放(标准的"点击")。
* `ButtonEventType.LONG_PRESS_START`:按钮被按住超过某个阈值。
* `ButtonEventType.LONG_PRESS_END`:按钮在长按后释放。
* `ButtonEventType.MULTI_PRESS_ONGOING`:检测到多按序列中的中间一次按下。
* `ButtonEventType.MULTI_PRESS_END`:多按序列完成。
`MULTI_PRESS_ONGOING` 和 `MULTI_PRESS_END` 在 `event_data` 中包含 `multi_press_count` 属性,表示按下的次数。
如果设备每次交互仅发出一个事件,没有单独的按下和释放,则将其映射到对应的 `_end` 类型(短按为 `PRESS_END`,长按为 `LONG_PRESS_END`,以此类推)。
对于从单个来源报告方向的按钮(例如上/下滚轮),优先按方向暴露独立的实体。当无法拆分为多个实体时,使用非标准的 `direction` 属性是可接受的替代方案。
也允许其他非标准事件类型。
---
url: /developers/core/entity/fan.md
---
Fan entity 是控制 fan 的各个向量(如 speed、direction 和 oscillation)的 device。从 [`homeassistant.components.fan.FanEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/fan/__init__.py) 派生 entity platforms。
## 属性
:::tip
Properties 应始终只从内存返回信息,而不要执行 I/O(如网络请求)。请实现 `update()` 或 `async_update()` 来获取数据。
:::
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
| current\_direction | `str \| None` | `None` | Fan 当前的 direction。 |
| is\_on | `bool \| None` | `None` | 如果 fan 已开启则为 `True`。 |
| oscillating | `bool \| None` | `None` | 如果 fan 正在 oscillating 则为 `True`。 |
| percentage | `int \| None` | `0` | 当前的 speed percentage。必须是 0(off)到 100 之间的值。 |
| preset\_mode | `str \| None` | `None` | 当前的 preset\_mode。为 `preset_modes` 中的值之一,或如果没有活跃的 preset 则为 `None`。 |
| preset\_modes | `list[str] \| None` | `None` | 支持的 preset\_modes 列表。这是一个任意 str 列表,不应包含任何 speed。 |
| speed\_count | `int` | 100 | Fan 支持的 speed 数量。 |
### 预设模式
Fan 可能有 preset modes,自动控制 percentage speed 或其他功能。常见示例包括 `auto`、`smart`、`whoosh`、`eco` 和 `breeze`。如果没有设置 preset mode,`preset_mode` property 必须设为 `None`。
Preset modes 不应包含命名的(手动)speed settings,因为这些应表示为 percentage。
手动设置 speed 必须禁用任何已设置的 preset mode。如果可以在不禁用 preset mode 的情况下手动设置 percentage speed,请创建一个 switch 或 service action 来代表该 mode。
## 支持的功能
Supported features 通过使用 `FanEntityFeature` 枚举中的值来定义,
并使用按位或(`|`)运算符组合。
| Value | Description |
| ------------- | ------------------------------------------------------------------------ |
| `DIRECTION` | Fan 支持更改 direction。 |
| `OSCILLATE` | Fan 支持 oscillation。 |
| `PRESET_MODE` | Fan 支持 preset modes。 |
| `SET_SPEED` | Fan 支持设置 speed percentage 和可选 preset modes。 |
| `TURN_OFF` | Fan 支持关闭。 |
| `TURN_ON` | Fan 支持开启。 |
## 方法
### 设置方向
仅当设置了 `FanEntityFeature.DIRECTION` 标志时,才实现此 method。
```python
class FanEntity(ToggleEntity):
# 实现以下方法之一。
def set_direction(self, direction: str) -> None:
"""Set the direction of the fan."""
async def async_set_direction(self, direction: str) -> None:
"""Set the direction of the fan."""
```
### 设置预设模式
仅当设置了 `FanEntityFeature.PRESET_MODE` 标志时,才实现此 method。
```python
class FanEntity(ToggleEntity):
# 实现以下方法之一。
def set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode of the fan."""
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set the preset mode of the fan."""
```
### 设置速度百分比
仅当设置了 `FanEntityFeature.SET_SPEED` 标志时,才实现此 method。
```python
class FanEntity(ToggleEntity):
# 实现以下方法之一。
def set_percentage(self, percentage: int) -> None:
"""Set the speed percentage of the fan."""
async def async_set_percentage(self, percentage: int) -> None:
"""Set the speed percentage of the fan."""
```
:::tip Converting speeds
Home Assistant 包含用于转换 speed 的 utility。
如果 device 有命名的 speed 列表:
```python
from homeassistant.util.percentage import ordered_list_item_to_percentage, percentage_to_ordered_list_item
ORDERED_NAMED_FAN_SPEEDS = ["one", "two", "three", "four", "five", "six"] # 不包含 off
percentage = ordered_list_item_to_percentage(ORDERED_NAMED_FAN_SPEEDS, "three")
named_speed = percentage_to_ordered_list_item(ORDERED_NAMED_FAN_SPEEDS, 23)
...
@property
def percentage(self) -> Optional[int]:
"""Return the current speed percentage."""
return ordered_list_item_to_percentage(ORDERED_NAMED_FAN_SPEEDS, current_speed)
@property
def speed_count(self) -> int:
"""Return the number of speeds the fan supports."""
return len(ORDERED_NAMED_FAN_SPEEDS)
```
如果 device 有数值的 speed range:
```python
from homeassistant.util.percentage import ranged_value_to_percentage, percentage_to_ranged_value
from homeassistant.util.scaling import int_states_in_range
SPEED_RANGE = (1, 255) # 不包含 off
percentage = ranged_value_to_percentage(SPEED_RANGE, 127)
value_in_range = math.ceil(percentage_to_ranged_value(SPEED_RANGE, 50))
...
@property
def percentage(self) -> Optional[int]:
"""Return the current speed percentage."""
return ranged_value_to_percentage(SPEED_RANGE, current_speed)
@property
def speed_count(self) -> int:
"""Return the number of speeds the fan supports."""
return int_states_in_range(SPEED_RANGE)
```
:::
### 开启
仅当设置了 `FanEntityFeature.TURN_ON` 标志时,才实现此 method。
```python
class FanEntity(ToggleEntity):
# 实现以下方法之一。
def turn_on(self, percentage: Optional[int] = None, preset_mode: Optional[str] = None, **kwargs: Any) -> None:
"""Turn on the fan."""
async def async_turn_on(self, percentage: Optional[int] = None, preset_mode: Optional[str] = None, **kwargs: Any) -> None:
"""Turn on the fan."""
```
:::tip `speed` 已弃用。
对于新集成,不应实现 `speed`,而应仅使用 `percentage` 和 `preset_mode`。
:::
### 关闭
仅当设置了 `FanEntityFeature.TURN_OFF` 标志时,才实现此 method。
```python
class FanEntity(ToggleEntity):
# 实现以下方法之一。
def turn_off(self, **kwargs: Any) -> None:
"""Turn the fan off."""
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the fan off."""
```
### 切换
可选。如果未实现,默认会通过检查 `is_on` property 来确定调用哪个 method。
仅当设置了 `FanEntityFeature.TURN_ON` 和 `FanEntityFeature.TURN_OFF` 标志时,才实现此 method。
```python
class FanEntity(ToggleEntity):
# 实现以下方法之一。
def toggle(self, **kwargs: Any) -> None:
"""Toggle the fan."""
async def async_toggle(self, **kwargs: Any) -> None:
"""Toggle the fan."""
```
### 摆头
仅当设置了 `FanEntityFeature.OSCILLATE` 标志时,才实现此 method。
```python
class FanEntity(ToggleEntity):
# 实现以下方法之一。
def oscillate(self, oscillating: bool) -> None:
"""Oscillate the fan."""
async def async_oscillate(self, oscillating: bool) -> None:
"""Oscillate the fan."""
```
---
url: /developers/core/entity/geo-location.md
---
Geolocation 实体表示一个与位置相关联的外部事件,例如地震、野火或外部 feed 发布的其他兴趣点。每个实体都携带坐标,通常显示为地图上的标记。
Geolocation 实体派生自 [`homeassistant.components.geo_location.GeolocationEvent`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/geo_location/__init__.py)。
## 状态
Geolocation 实体的状态是事件位置到 Home Assistant 实例配置的 home location 的距离,四舍五入到一位小数。状态由基类实体管理,无法覆盖;应设置 `distance` 属性。距离的单位通过标准的 `unit_of_measurement` 属性声明。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| 名称 | 类型 | 默认值 | 描述 |
| --------- | --------------- | ------------ | ------------------------------------------------------------------------------ |
| source | `str` | **必填** | 事件的来源,通常是提供该事件集成的 domain。 |
| distance | `float \| None` | `None` | 从 home location 到事件的距离,单位为 `unit_of_measurement`。 |
| latitude | `float \| None` | `None` | 事件的纬度。 |
| longitude | `float \| None` | `None` | 事件的经度。 |
所有实体共有的其他属性(如 `icon`、`name` 等)也适用。
`latitude`、`longitude` 和 `source` 值会自动作为状态属性暴露;坐标四舍五入到五位小数。
## 生命周期
与大多数实体不同,geolocation 实体通常表示短期外部事件,而不是设备:集成在新事件出现在外部 feed 中添加实体,并在事件不再被报告时将其移除。常见的模式是一个管理器订阅 feed 更新,并在每次更新时为新事件添加实体、更新现有实体,并通过调用其 `async_remove(force_remove=True)` 方法来移除已消失事件的实体——`force_remove` 标志确保已注册的实体从状态机中完全移除,而不是作为不可用而滞留。使用唯一 ID 创建的实体也应移除其 entity registry 条目。
[GDACS integration](https://github.com/home-assistant/core/blob/dev/homeassistant/components/gdacs/geo_location.py) 是此模式的示例,[demo platform](https://github.com/home-assistant/core/blob/dev/homeassistant/components/demo/geo_location.py) 提供了最小实现。
---
url: /developers/core/entity/humidifier.md
---
Humidifier 实体是主要用途为控制湿度的设备,即加湿器或除湿器。从 [`homeassistant.components.humidifier.HumidifierEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/humidifier/__init__.py) 派生实体平台。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| 名称 | 类型 | 默认值 | 描述 |
| ----------------------- | ---------------------------------------------- | ------------------------------------- | -------------------------------------------------- |
| action | `HumidifierAction \| None` | `None` | 返回设备的当前状态。 |
| available\_modes | `list[str] \| None` | **MODES 必填** | 可用的模式。需要 `HumidifierEntityFeature.MODES`。 |
| current\_humidity | `float \| None` | `None` | 设备测量的当前湿度。 |
| device\_class | `HumidifierDeviceClass \| None` | `None` | hygrostat 的类型 |
| is\_on | `bool \| None` | `None` | 设备是开启还是关闭。 |
| max\_humidity | `float` | `DEFAULT_MAX_HUMIDITY`(值 == 100) | 最大湿度。 |
| min\_humidity | `float` | `DEFAULT_MIN_HUMIDITY`(值 == 0) | 最小湿度。 |
| mode | `str \| None` | **必填** | 当前活动的模式。需要 `HumidifierEntityFeature.MODES`。 |
| target\_humidity | `float \| None` | `None` | 设备试图达到的目标湿度。 |
| target\_humidity\_step | `float \| None` | `None` | 目标湿度可增加或减少的步长。 |
### 可用的 device class
| 常量 | 描述 |
| ------------------------------------ | ------------------------------------------ |
| `HumidifierDeviceClass.DEHUMIDIFIER` | 除湿器 |
| `HumidifierDeviceClass.HUMIDIFIER` | 加湿器 |
### 模式
设备可以有不同的操作模式,并希望向用户展示。它们可以被视为预设或某些设备状态,针对特殊条件具有缩减或增强的功能,即"auto"或"baby"。有几个内置模式会提供翻译,但如果自定义模式能更好地表示设备,也允许添加自定义模式。
| 名称 | 描述 |
| -------------- | --------------------------------------- |
| `MODE_NORMAL` | 无预设激活,正常运行 |
| `MODE_ECO` | 设备正在运行节能模式 |
| `MODE_AWAY` | 设备处于 away 模式 |
| `MODE_BOOST` | 设备将所有阀门完全打开 |
| `MODE_COMFORT` | 设备处于舒适模式 |
| `MODE_HOME` | 设备处于 home 模式 |
| `MODE_SLEEP` | 设备为睡眠做好了准备 |
| `MODE_AUTO` | 设备自动控制湿度 |
| `MODE_BABY` | 设备正在为婴儿优化 |
## 支持的功能
支持的功能通过使用 `HumidifierEntityFeature` 枚举中的值来定义,并使用按位或(`|`)运算符进行组合。
| 值 | 描述 |
| ------- | ------------------------------------ |
| `MODES` | 设备支持不同的模式。 |
## 动作
`action` 属性可以返回设备的当前操作状态,即正在加湿还是空闲。这是一个信息性属性。请注意,当设备关闭时,`action` 属性(如果存在)将自动替换为"off"。另请注意,将 `action` 设置为 `off` 不会替代 `is_on` 属性。
`HumidifierAction` 的当前值:
| 名称 | 描述 |
| ------------- | ------------------------------------------ |
| `HUMIDIFYING` | 设备当前正在加湿。 |
| `DRYING` | 设备当前正在除湿。 |
| `IDLE` | 设备已开启但目前未活动。 |
| `OFF` | 设备已关闭。 |
## 方法
### 设置模式
```python
class MyHumidifierEntity(HumidifierEntity):
# 实现以下方法之一。
def set_mode(self, mode: str) -> None:
"""Set new target preset mode."""
async def async_set_mode(self, mode: str) -> None:
"""Set new target preset mode."""
```
### 设置湿度
如果当前模式不允许调整目标湿度,设备应在此调用时自动更改其模式,以使其成为可能。
```python
class MyHumidifierEntity(HumidifierEntity):
# 实现以下方法之一。
def set_humidity(self, humidity: int) -> None:
"""Set new target humidity."""
async def async_set_humidity(self, humidity: int) -> None:
"""Set new target humidity."""
```
### 开启
```python
class MyHumidifierEntity(HumidifierEntity):
# 实现以下方法之一。
def turn_on(self, **kwargs: Any) -> None:
"""Turn the device on."""
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the device on."""
```
### 关闭
```python
class MyHumidifierEntity(HumidifierEntity):
# 实现以下方法之一。
def turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""
```
---
url: /developers/core/entity/image.md
---
Image 实体可以显示静态图像。从 [`homeassistant.components.image.ImageEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/image/__init__.py) 派生平台实体。
Image 实体是 [`camera`](/developers/core/entity/camera.md) 实体的简化版本,支持提供静态图像或可获取的图像 URL。
实现可以提供 URL,图像将从该 URL 自动获取,也可以提供 `bytes` 类型的图像数据。提供 URL 时,获取的图像将缓存在 `self._cached_image` 中;将 `self._cached_image` 设置为 `None` 以使缓存失效。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| 名称 | 类型 | 默认值 | 描述 |
| -------------------| --------------------------------- | ------------ | -------------------------------------------------------------------------------------------------------- |
| content\_type | str | `image/jpeg` | 图像的 content-type,如果 image 实体提供了 URL 则自动设置。 |
| image\_last\_updated | `datetime.datetime \| None` | `None` | 图像上次更新的时间戳。用于确定 `state`。前端在此值变更后会调用 `image` 或 `async_image`。 |
| image\_url | `str \| None` | `UNDEFINED` | 可选的 URL,应从该 URL 获取图像。 |
## 方法
### 图像
如果你的实体返回图像的 bytes 而不是提供 URL,请实现 `async_image` 或 `image`。前端将调用 `async_image` 或 `image` 来获取图像。如果图像是从远程获取的,应缓存图像数据,并在 `image_last_updated` 更改时使缓存失效。
注意:
* image 实体的 `async_image` 或 `image` 方法仅在前端获取图像时调用。
* 前端将会:
* 当加载包含 image 实体的页面时获取一次图像
* 当 image 实体通过更改 `image_last_updated` 改变状态时重新获取图像
这意味着在 `async def async_image` 中递增 `image_last_updated` 属性是不正确的。相反,image 实体应在图像更新可用时或定期(如果图像需要过一段时间后重新获取)更新 `image_last_updated` 时间戳。例如,这可以发生在 entity coordinator 更新的过程中。
```python
class MyImage(ImageEntity):
# 实现以下方法之一。
def image(self) -> bytes | None:
"""Return bytes of image."""
async def async_image(self) -> bytes | None:
"""Return bytes of image."""
```
---
url: /developers/core/entity/infrared.md
---
Infrared 域提供了 IR 硬件(如 ESPHome、Broadlink 或 ZHA 设备)与需要发送或接收 IR 命令的设备特定集成(如 LG 或 Samsung 电视控制)之间的抽象层。它定义了两类虚拟实体——一个发送 IR 命令的**发射器**(emitter)和一个捕获传入 IR 信号的**接收器**(receiver),其他集成可以使用它们来控制或对 IR 设备做出反应。
发射器和接收器基类位于 [`homeassistant.components.infrared`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/infrared/entity.py)。
## 架构概览
Infrared 集成在以下方面创建分离:
1. **Emitter 集成**(如 ESPHome、Broadlink):这些实现 `InfraredEmitterEntity`,以提供硬件特定的 IR 传输。
2. **Receiver 集成**(如 ESPHome、Broadlink):这些实现 `InfraredReceiverEntity`,以提供硬件特定的 IR 接收。
3. **Consumer 集成**(如 LG、Samsung):这些使用 infrared 辅助函数——或提供的 consumer 基类——通过发射器发送设备特定的 IR 命令和/或对来自接收器的信号做出反应。
```mermaid
flowchart TD
subgraph consumers["Consumer integrations"]
direction LR
LG["LG"] ~~~ Samsung["Samsung"] ~~~ Daikin["Daikin"] ~~~ more1["..."]
end
subgraph infrared["infrared domain"]
direction LR
subgraph bases["Base classes"]
direction TB
InfraredEmitterEntity ~~~ InfraredReceiverEntity
end
subgraph helpers["Helper functions / consumer bases"]
direction TB
async_get_emitters ~~~ async_send_command ~~~ InfraredEmitterConsumerEntity
async_get_receivers ~~~ async_subscribe_receiver ~~~ InfraredReceiverConsumerEntity
end
end
subgraph emitters["Emitter / receiver integrations"]
direction LR
ESPHome["ESPHome"] ~~~ Broadlink["Broadlink"] ~~~ ZHA["ZHA"] ~~~ more2["..."]
end
consumers -->|"Use helpers / consumer bases"| helpers
bases -->|"Implemented by"| emitters
```
## 设备类型
Infrared 实体公开一个 [`InfraredDeviceClass`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/infrared/entity.py),反映其角色:
| 值 | 含义 |
| ---------- | ---------------------------------------------------- |
| `emitter` | 发送 IR 命令。由 `InfraredEmitterEntity` 自动设置。 |
| `receiver` | 接收 IR 信号。由 `InfraredReceiverEntity` 自动设置。 |
Emitter 和 receiver 集成无需自行设置 device class——基类会自动分配。
## InfraredEmitterEntity
Emitter 实体封装了一个 IR 发射器硬件。基类是 `InfraredEmitterEntity`,由 `InfraredEmitterEntityDescription` 描述。
### 状态
Infrared emitter 实体状态表示最后发送 IR 命令的时间戳。这在基类 InfraredEmitterEntity 中实现,集成不应更改。
### 发送命令方法
`InfraredEmitterEntity.async_send_command` 方法必须由 emitter 集成实现,以处理实际的 IR 传输。
```python
class MyInfraredEmitter(InfraredEmitterEntity):
"""My infrared emitter."""
async def async_send_command(self, command: infrared_protocols.commands.Command) -> None:
"""Send an IR command.
Args:
command: The IR command to send.
Raises:
HomeAssistantError: If transmission fails.
"""
```
:::important
Consumer 集成不得直接调用 `InfraredEmitterEntity.async_send_command`。请使用 [`async_send_command`](#send-command) 辅助函数(或 [`InfraredEmitterConsumerEntity`](#emitter-consumer-base-class) 基类),它会自动处理状态更新和 context 传播。
:::
## InfraredReceiverEntity
Receiver 实体封装了一个 IR 接收器硬件。基类是 `InfraredReceiverEntity`,由 `InfraredReceiverEntityDescription` 描述。
### 状态
Infrared receiver 实体状态表示最后接收 IR 信号的时间戳。这在基类 InfraredReceiverEntity 中实现,集成不应更改。
### 报告接收到的信号
Receiver 集成在硬件上观察到 IR 信号时,从基类调用 `_handle_received_signal`。基类更新状态并通知订阅者。
```python
class MyInfraredReceiver(InfraredReceiverEntity):
"""My infrared receiver."""
def _on_hardware_signal(self, timings: list[int], modulation: int | None) -> None:
self._handle_received_signal(
InfraredReceivedSignal(timings=timings, modulation=modulation)
)
```
## 辅助函数
Infrared 域公开了辅助函数,使 consumer 集成能够发现硬件并与之交互,而无需持有对实体实例的直接引用。
### 获取 emitters
返回所有可用的 infrared emitter 实体的实体 IDs。
```python
from homeassistant.components import infrared
emitters = infrared.async_get_emitters(hass)
```
### 获取 receivers
返回所有可用的 infrared receiver 实体的实体 IDs。
```python
from homeassistant.components import infrared
receivers = infrared.async_get_receivers(hass)
```
### 发送命令
通过特定的 emitter 实体发送 IR 命令。
```python
from infrared_protocols.commands.nec import NECCommand
from homeassistant.components import infrared
command = NECCommand(
address=0x04,
command=0x08,
modulation=38000, # 38 kHz carrier frequency
)
await infrared.async_send_command(
hass,
emitter_entity_id,
command,
context=context, # 用于 logbook 跟踪的可选 context
)
```
### 订阅接收器
订阅来自特定 receiver 实体的 IR 信号。返回一个取消订阅的回调。
```python
from homeassistant.components import infrared
from homeassistant.components.infrared import InfraredReceivedSignal
@callback
def handle_signal(signal: InfraredReceivedSignal) -> None:
...
unsubscribe = infrared.async_subscribe_receiver(
hass, receiver_entity_id, handle_signal
)
```
## Consumer 基类
Consumer 集成通常不需要直接调用发送/接收辅助函数。Infrared 集成提供了两个基类,负责跟踪底层硬件的可用性,以及(对于 receivers)管理订阅生命周期。
### Emitter consumer 基类
`InfraredEmitterConsumerEntity` 跟踪所配置 emitter 的可用性,并暴露一个 `_send_command` 方法,通过 `async_send_command` 转发,包括实体的当前 context。
```python
from homeassistant.components.infrared import InfraredEmitterConsumerEntity
class MyButton(InfraredEmitterConsumerEntity, ButtonEntity):
"""A button that emits an IR command."""
def __init__(self, emitter_entity_id: str) -> None:
self._infrared_emitter_entity_id = emitter_entity_id
async def async_press(self) -> None:
await self._send_command(SOME_COMMAND)
```
基类根据 emitter 的状态设置 `self._attr_available`,并在 emitter 变为可用或不可用时进行更新。
### Receiver consumer 基类
`InfraredReceiverConsumerEntity` 跟踪所配置 receiver 的可用性,在可用时订阅其信号,并在 receiver 消失时自动取消订阅。子类实现 `_handle_signal`。
```python
from homeassistant.components.infrared import (
InfraredReceivedSignal,
InfraredReceiverConsumerEntity,
)
class MyRemote(InfraredReceiverConsumerEntity, EventEntity):
"""An event entity that fires when an IR signal is received."""
def __init__(self, receiver_entity_id: str) -> None:
self._infrared_receiver_entity_id = receiver_entity_id
@override
@callback
def _handle_signal(self, signal: InfraredReceivedSignal) -> None:
...
```
## IR 命令
[`infrared-protocols`](https://github.com/home-assistant-libs/infrared-protocols) 库提供了命令类,将协议特定的数据(NEC、RC-5、Kaseikyo 等)转换为原始 timings。
所有 IR 命令继承自 `infrared_protocols.commands.Command` 并实现 `get_raw_timings()`。
---
url: /developers/core/entity/lawn-mower.md
---
从 [`homeassistant.components.lawn_mower.LawnMowerEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/lawn_mower/__init__.py) 派生实体平台。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| 名称 | 类型 | 默认值 | 描述
| -------- | ------------------------------------------ | ------- | -----------------
| activity | `LawnMowerActivity \| None` | `None` | 当前活动。
## 活动
| 活动 | 描述
| -------- | -----------
| `MOWING` | 割草机当前正在割草。
| `DOCKED` | 割草机已完成割草,当前已停靠。
| `PAUSED` | 割草机处于活动状态,现在已暂停。
| `RETURNING` | 割草机正在返回 dock。
| `ERROR` | 割草机在活动过程中遇到错误,需要协助。
## 支持的功能
支持的功能通过使用 `LawnMowerEntityFeature` 枚举中的值来定义,并使用按位或(`|`)运算符进行组合。
| 值 | 描述 |
| -------------- | ---------------------------------------------------- |
| `START_MOWING` | 割草机支持 start mowing 命令。 |
| `PAUSE` | 割草机支持暂停当前任务。 |
| `DOCK` | 割草机支持返回 dock 命令。 |
## 方法
### `start_mowing` 或 `async_start_mowing`
开始或恢复割草任务。
### `dock` 或 `async_dock`
停止割草机,返回 dock。
### `pause` 或 `async_pause`
在当前操作过程中暂停割草机。
---
url: /developers/core/entity/light.md
---
Light entity 控制 light source 的 brightness、hue 和 saturation color value、white value、color temperature 和 effect。从 [`homeassistant.components.light.LightEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/light/__init__.py) 派生 platform entities。
## 属性
| Name | Type | Default | Description
| ---- | ---- | ---- | ----
| brightness | `int \| None` | `None` | 该 light 的 brightness,范围在 1..255 之间
| color\_mode | `ColorMode \| None` | `None` | Light 的 color mode。返回的 color mode 必须存在于 `supported_color_modes` property 中,除非 light 正在渲染 effect。
| color\_temp\_kelvin | `int \| None` | `None` | CT color value(单位为 K)。当 light 的 color mode 设为 `ColorMode.COLOR_TEMP` 时,此 property 会复制到 light 的 state attribute 中,否则忽略。
| effect | `str \| None` | `None` | 当前的 effect。如果 light 支持 effect 且当前未渲染任何 effect,则应为 `EFFECT_OFF`。
| effect\_list | `list[str] \| None` | `None` | 支持的 effect 列表。
| hs\_color | `tuple[float, float] \| None` | `None` | Hue 和 saturation color value(float, float)。当 light 的 color mode 设为 `ColorMode.HS` 时,此 property 会复制到 light 的 state attribute 中,否则忽略。
| is\_on | `bool \| None` | `None` | Light entity 是否开启。
| max\_color\_temp\_kelvin | `int \| None` | `None` | 该 light 支持的最冷 color\_temp\_kelvin。
| min\_color\_temp\_kelvin | `int \| None` | `None` | 该 light 支持的最暖 color\_temp\_kelvin。
| rgb\_color | `tuple[int, int, int] \| None` | `None` | RGB color value(int, int, int)。当 light 的 color mode 设为 `ColorMode.RGB` 时,此 property 会复制到 light 的 state attribute 中,否则忽略。
| rgbw\_color | `tuple[int, int, int, int] \| None` | `None` | RGBW color value(int, int, int, int)。当 light 的 color mode 设为 `ColorMode.RGBW` 时,此 property 会复制到 light 的 state attribute 中,否则忽略。
| rgbww\_color | `tuple[int, int, int, int, int] \| None` | `None` | RGBWW color value(int, int, int, int, int)。当 light 的 color mode 设为 `ColorMode.RGBWW` 时,此 property 会复制到 light 的 state attribute 中,否则忽略。
| supported\_color\_modes | `set[ColorMode] \| None` | `None` | 标志支持的 color modes。
| xy\_color | `tuple[float, float] \| None` | `None` | XY color value(float, float)。当 light 的 color mode 设为 `ColorMode.XY` 时,此 property 会复制到 light 的 state attribute 中,否则忽略。
## 颜色模式
Light 必须同时实现 `color_mode` 和 `supported_color_modes`。Supported color modes 通过使用 `ColorMode` 枚举中的值来定义,`color_mode` 必须设为 `supported_color_modes` 中列出的 modes 之一。
设置 `supported_color_modes` 是必需的。未设置它的 light 在写入 state 时会引发 error。
可用的 color modes 有:
| Value | Description
|----------|-----------------------
| `ColorMode.UNKNOWN` | Light 的 color mode 未知。
| `ColorMode.ONOFF` | Light 可以开启或关闭。如果 light 支持此 mode,它必须是唯一支持的 mode。
| `ColorMode.BRIGHTNESS` | Light 可以 dim。如果 light 支持此 mode,它必须是唯一支持的 mode。
| `ColorMode.COLOR_TEMP` | Light 可以 dim,且其 color temperature 出现在 state 中。
| `ColorMode.HS` | Light 可以 dim,且其 color 可以调整。Light 的 brightness 可以通过 `brightness` 参数设置,通过 `brightness` property 读取。Light 的 color 可以通过 `hs_color` 参数设置,通过 `hs_color` property 读取。`hs_color` 是 (h, s) tuple(不含 brightness)。
| `ColorMode.RGB` | Light 可以 dim,且其 color 可以调整。Light 的 brightness 可以通过 `brightness` 参数设置,通过 `brightness` property 读取。Light 的 color 可以通过 `rgb_color` 参数设置,通过 `rgb_color` property 读取。`rgb_color` 是 (r, g, b) tuple(未按 brightness 归一化)。
| `ColorMode.RGBW` | Light 可以 dim,且其 color 可以调整。Light 的 brightness 可以通过 `brightness` 参数设置,通过 `brightness` property 读取。Light 的 color 可以通过 `rgbw_color` 参数设置,通过 `rgbw_color` property 读取。`rgbw_color` 是 (r, g, b, w) tuple(未按 brightness 归一化)。
| `ColorMode.RGBWW` | Light 可以 dim,且其 color 可以调整。Light 的 brightness 可以通过 `brightness` 参数设置,通过 `brightness` property 读取。Light 的 color 可以通过 `rgbww_color` 参数设置,通过 `rgbww_color` property 读取。`rgbww_color` 是 (r, g, b, cw, ww) tuple(未按 brightness 归一化)。
| `ColorMode.WHITE` | Light 可以 dim,且其 color 可以调整。此外,Light 可以设为 white mode。Light 的 brightness 可以通过 `brightness` 参数设置,通过 `brightness` property 读取。Light 可以通过使用 `white` 参数(值为期望的 brightness)设为 white mode。注意,没有 `white` property。如果 `brightness` 和 `white` 都出现在 service action call 中,`white` 参数将更新为 `brightness` 的值。如果支持此 mode,light *必须*同时支持 `ColorMode.HS`、`ColorMode.RGB`、`ColorMode.RGBW`、`ColorMode.RGBWW` 或 `ColorMode.XY` 中的至少一种,且*不能*支持 `ColorMode.COLOR_TEMP`。
| `ColorMode.XY` | Light 可以 dim,且其 color 可以调整。Light 的 brightness 可以通过 `brightness` 参数设置,通过 `brightness` property 读取。Light 的 color 可以通过 `xy_color` 参数设置,通过 `xy_color` property 读取。`xy_color` 是 (x, y) tuple。
注意,在 color modes `ColorMode.RGB`、`ColorMode.RGBW` 和 `ColorMode.RGBWW` 中,`brightness` property 和 color 中都包含 brightness 信息。例如,如果 light 的 brightness 是 128 且 light 的 color 是 (192, 64, 32),light 的整体 brightness 为:128/255 \* max(192, 64, 32)/255 = 38%。
如果 light 处于 `ColorMode.HS`、`ColorMode.RGB` 或 `ColorMode.XY` mode,light 的 state attribute 将包含用 `hs`、`rgb` 和 `xy` color 格式表示的 light color。注意,当 light 处于 `ColorMode.RGB` mode 时,`hs` 和 `xy` state attributes 仅保留 `rgb` color 的 chromaticity,因为 `hs` 和 `xy` pair 不包含 brightness 信息。
如果 light 处于 `ColorMode.RGBW` 或 `ColorMode.RGBWW` mode,light 的 state attribute 将包含用 `hs`、`rgb` 和 `xy` color 格式表示的 light color。Color 转换是通过将 white channel 加到 color 上来近似完成的。
### 白色颜色模式
有两种 white color modes:`ColorMode.COLOR_TEMP` 和 `ColorMode.WHITE`。两种 mode 的区别在于 `ColorMode.WHITE` 不允许调整 color temperature,而 `ColorMode.COLOR_TEMP` 允许调整 color temperature。
具有可调整 color temperature 的 lamp 通常由至少两组 LED 实现,具有不同的 color temperature,通常是一组 warm-white LED 和一组 cold-white LED。
具有不可调整 color temperature 的 light 通常只有一组 white LED。
### 渲染 effect 时的 color mode
在渲染 effect 时,应根据 effect 支持的调整来设置 `color_mode`。如果 effect 不支持任何调整,`color_mode` 应设为 `ColorMode.ONOFF`。
如果 effect 允许调整 brightness,`color_mode` 应设为 `ColorMode.BRIGHTNESS`。
在渲染 effect 时,允许将 `color_mode` 设为比 `supported_color_mode` property 指示的更 restrictive 的 mode:
* 支持 color 的 light 在由 effect 控制时可以将其 color\_mode 设为 `ColorMode.ONOFF` 或 `ColorMode.BRIGHTNESS`
* 支持 brightness 的 light 在由 effect 控制时可以将其 color\_mode 设为 `ColorMode.ONOFF`
## 支持的功能
Supported features 通过使用 `LightEntityFeature` 枚举中的值来定义,
并使用按位或(`|`)运算符组合。
| Value | Description |
| ------------ | -------------------------------------------------------------- |
| `EFFECT` | 控制 light source 显示的 effect。 |
| `FLASH` | 控制 light source 显示的 flash 持续时间。 |
| `TRANSITION` | 控制 color 和 effect 之间 transition 的持续时间。 |
## 方法
### 开启 light device
```python
class MyLightEntity(LightEntity):
def turn_on(self, **kwargs: Any) -> None:
"""Turn the device on."""
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn device on."""
```
注意,传递给 `async_turn_on` method 时没有 `color_mode`,而是只允许单个 color attribute。
保证 integration 在 `turn_on` call 中只会收到单个 color attribute,并且根据 light 的 `supported_color_modes` property,保证它被 light 支持。为确保这一点,如果 light 不支持相应的 color mode,service action call 中的 color 会在调用 entity 的 `async_turn_on` method 之前进行转换:
| Color type | Translation
|--------------|-----------------------
| color\_temp | 如果不支持则从 service action call 中移除,如果 light 支持则转换为 `hs_color`、`rgb_color`、`rgbw_color`、`rgbww_color` 或 `xy_color`。
| hs\_color | 如果不支持则从 service action call 中移除,如果 light 支持则转换为 `rgb_color`、`rgbw_color`、`rgbww_color` 或 `xy_color`。
| rgb\_color | 如果不支持则从 service action call 中移除,如果 light 支持则转换为 `rgbw_color`、`rgbww_color`、`hs_color` 或 `xy_color`。
| rgbw\_color | 如果不支持则从 service action call 中移除。
| rgbww\_color | 如果不支持则从 service action call 中移除。
| xy\_color | 如果不支持则从 service action call 中移除,如果 light 支持则转换为 `hs_color`、`rgb_color`、`rgbw_color` 或 `rgbww_color`。
:::tip Scaling brightness
Home Assistant 包含用于 scale brightness 的 utility。
如果 light 支持 brightness,有时 brightness value 需要 scaling:
```python
from homeassistant.util.color import value_to_brightness
BRIGHTNESS_SCALE = (1, 1023)
...
@property
def brightness(self) -> int | None:
"""Return the current brightness."""
return value_to_brightness(BRIGHTNESS_SCALE, self._device.brightness)
```
将 brightness scale 到 device range:
````python
from homeassistant.util.color import brightness_to_value
BRIGHTNESS_SCALE = (1, 1023)
...
class MyLightEntity(LightEntity):
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn device on."""
...
value_in_range = math.ceil(brightness_to_value(BRIGHTNESS_SCALE, kwargs[ATTR_BRIGHTNESS]))
:::
### 关闭 light device
```python
class MyLightEntity(LightEntity):
def turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn device off."""
````
---
url: /developers/core/entity/lock.md
---
Lock 实体可以被锁定和解锁。锁定和解锁可以选择用用户代码来保护。一些锁还允许打开 latch,这也可能用用户代码来保护。从 [`homeassistant.components.lock.LockEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/lock/__init__.py) 派生平台实体。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| 名称 | 类型 | 默认值 | 描述
| ---- | ---- | ------- | -----------
| changed\_by | string | None | 描述上次更改是由什么触发的。
| code\_format | string | None | 用于验证传递给 lock、unlock 和 open 操作的代码的 Regex,如果不需代码则为 None。
| is\_locked | bool | None | 锁是否已锁定。用于确定 `state`。
| is\_locking | bool | None | 锁是否正在锁定过程中。用于确定 `state`。
| is\_unlocking | bool | None | 锁是否正在解锁过程中。用于确定 `state`。
| is\_open | bool | None | 锁是否已解锁并释放了 latch。仅对支持 `LockEntityFeature.OPEN` 的锁有意义。用于确定 `state`。
| is\_opening | bool | None | 锁是否正在释放 latch 的过程中。用于确定 `state`。
| is\_jammed | bool | None | 锁尝试移动但在完成前卡住。用于确定 `state`。
### 状态
状态从上述属性派生,是以下 `LockState` 枚举成员之一:
| 值 | 描述 |
|-------------|--------------------------------------------------------------------|
| `LOCKED` | 锁已锁定。 |
| `LOCKING` | 锁正在锁定过程中。 |
| `UNLOCKED` | 锁未锁定。 |
| `UNLOCKING` | 锁正在解锁过程中。 |
| `OPEN` | 锁未锁定并已释放 latch。 |
| `OPENING` | 锁正在释放 latch 的过程中。 |
| `JAMMED` | 锁尝试移动但在完成前卡住。 |
:::note
`OPEN` 状态和 `is_open` 属性要求锁既未锁定**又**已释放 latch。在锁仍锁定时释放 latch 并不是 open 状态。
:::
当多个状态属性被设置时,它们按固定的优先级顺序评估,第一个匹配项决定状态:
1. `is_jammed` → `JAMMED`
2. `is_opening` → `OPENING`
3. `is_locking` → `LOCKING`
4. `is_open` → `OPEN`
5. `is_unlocking` → `UNLOCKING`
6. `is_locked` → `LOCKED`(当为 `True` 时),`UNLOCKED`(当为 `False` 时)
如果 `is_locked` 为 `None`,且没有其他状态属性为真值,则状态为 `unknown`。
## 支持的功能
支持的功能通过使用 `LockEntityFeature` 枚举中的值来定义,并使用按位或(`|`)运算符进行组合。
| 值 | 描述 |
| ------ | ------------------------------------------ |
| `OPEN` | 此锁支持打开门 latch。 |
## 方法
### 锁定
```python
class MyLock(LockEntity):
def lock(self, **kwargs: Any) -> None:
"""Lock all or specified locks. A code to lock the lock with may optionally be specified."""
async def async_lock(self, **kwargs: Any) -> None:
"""Lock all or specified locks. A code to lock the lock with may optionally be specified."""
```
### 解锁
```python
class MyLock(LockEntity):
def unlock(self, **kwargs: Any) -> None:
"""Unlock all or specified locks. A code to unlock the lock with may optionally be specified."""
async def async_unlock(self, **kwargs: Any) -> None:
"""Unlock all or specified locks. A code to unlock the lock with may optionally be specified."""
```
### 打开
仅当设置了 `LockEntityFeature.OPEN` 标志时实现此方法。
```python
class MyLock(LockEntity):
def open(self, **kwargs: Any) -> None:
"""Open (unlatch) all or specified locks. A code to open the lock with may optionally be specified."""
async def async_open(self, **kwargs: Any) -> None:
"""Open (unlatch) all or specified locks. A code to open the lock with may optionally be specified."""
```
---
url: /developers/core/entity/media-player.md
---
:::info 未完成
此条目尚不完整。欢迎贡献。
:::
Media player entity 控制 media player。从 [`homeassistant.components.media_player.MediaPlayerEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/media_player/__init__.py) 派生 platform entity。
## 属性
:::tip
Properties 应该只从内存返回信息,而不要执行 I/O(如网络请求)。请实现 `update()` 或 `async_update()` 来获取数据。
:::
| Name | Type | Default | Description
| ------------------------------- | ----------------------------------------------- | ------- | -----------
| app\_id | `str \| None` | `None` | 当前运行的 app 的 ID。
| app\_name | `str \| None` | `None` | 当前运行的 app 的名称。
| device\_class | `MediaPlayerDeviceClass \| None` | `None` | Media player 的类型。
| group\_members | `list[str] \| None` | `None` | 当前分组进行同步播放的 player entities 动态列表。如果 platform 有定义 group leader 的概念,leader 应是该列表的第一个元素。
| is\_volume\_muted | `bool \| None` | `None` | 如果 volume 当前已 muted,则为 `True`。
| media\_album\_artist | `str \| None` | `None` | 当前播放 media 的 album artist,仅限 music track。
| media\_album\_name | `str \| None` | `None` | 当前播放 media 的 album name,仅限 music track。
| media\_artist | `str \| None` | `None` | 当前播放 media 的 artist,仅限 music track。
| media\_channel | `str \| None` | `None` | 当前播放的 channel。
| media\_content\_id | `str \| None` | `None` | 当前播放 media 的 content ID。
| media\_content\_type | `MediaType \| str \| None` | `None` | 当前播放 media 的 content type。
| media\_duration | `int \| None` | `None` | 当前播放 media 的 duration(秒)。
| media\_episode | `str \| None` | `None` | 当前播放 media 的 episode,仅限 TV show。
| media\_image\_hash | `str \| None` | `None` | Media image 的 hash,如果 `media_image_url` 不为 `None`,默认为 `media_image_url` 的 SHA256。
| media\_image\_remotely\_accessible | `bool \| None` | `False` | 如果 property `media_image_url` 可以在 home network 外部访问,则为 `True`。
| media\_image\_url | `str \| None` | `None` | 当前播放 media 的 image URL。
| media\_playlist | `str \| None` | `None` | 当前播放的 Playlist 的 title。
| media\_position | `int \| None` | `None` | 当前播放 media 的 position(秒)。
| media\_position\_updated\_at | `datetime \| None` | `None` | `_attr_media_position` 上次更新的时间戳。时间戳应通过调用 `homeassistant.util.dt.utcnow()` 设置。
| media\_season | `str \| None` | `None` | 当前播放 media 的 season,仅限 TV show。
| media\_series\_title | `str \| None` | `None` | 当前播放 media 的 series title,仅限 TV show。
| media\_title | `str \| None` | `None` | 当前播放 media 的 title。
| media\_track | `int \| None` | `None` | 当前播放 media 的 track number,仅限 music track。
| repeat | `RepeatMode \| str \| None` | `None` | 当前的 repeat mode。
| shuffle | `bool \| None` | `None` | 如果 shuffle 已启用,则为 `True`。
| sound\_mode | `str \| None` | `None` | Media player 当前的 sound mode。
| sound\_mode\_list | `list[str] \| None` | `None` | 可用 sound modes 的动态列表。
| source | `str \| None` | `None` | Media player 当前选择的 input source。
| source\_list | `list[str] \| None` | `None` | Media player 可能的 input sources 列表。(该列表应包含适合 frontend 显示的 human readable names)。
| state | `MediaPlayerState \| None` | `None` | Media player 的 state。
| volume\_level | `float \| None` | `None` | Media player 的 volume level,范围(0..1)。
| volume\_step | `float \| None` | 0.1 | 用于 `volume_up` 和 `volume_down` service actions 的 volume step。
## 支持的功能
Supported features 通过使用 `MediaPlayerEntityFeature` enum 中的值来定义,
并使用按位或(`|`)运算符组合。
| Value | Description |
| ------------------- | ------------------------------------------------------------------ |
| `BROWSE_MEDIA` | Entity 允许浏览 media。 |
| `CLEAR_PLAYLIST` | Entity 允许清除 active playlist。 |
| `GROUPING` | Entity 可以与其他 players 分组进行同步播放。 |
| `MEDIA_ANNOUNCE` | Entity 支持 `play_media` action 的 announce 参数。 |
| `MEDIA_ENQUEUE` | Entity 支持 `play_media` action 的 enqueue 参数。 |
| `NEXT_TRACK` | Entity 允许跳过到下一个 media track。 |
| `PAUSE` | Entity 允许暂停 media 的播放。 |
| `PLAY` | Entity 允许播放/恢复 media 的播放。 |
| `PLAY_MEDIA` | Entity 允许播放 media sources。 |
| `PREVIOUS_TRACK` | Entity 允许返回到上一个 media track。 |
| `REPEAT_SET` | Entity 允许设置 repeat。 |
| `SEARCH_MEDIA` | Entity 允许搜索 media。 |
| `SEEK` | Entity 允许在 media 播放期间 seek position。 |
| `SELECT_SOUND_MODE` | Entity 允许选择 sound mode。 |
| `SELECT_SOURCE` | Entity 允许选择 source/input。 |
| `SHUFFLE_SET` | Entity 允许 shuffle active playlist。 |
| `STOP` | Entity 允许停止 media 的播放。 |
| `TURN_OFF` | Entity 可以关闭。 |
| `TURN_ON` | Entity 可以开启。 |
| `VOLUME_MUTE` | Entity 的 volume 可以 muted。 |
| `VOLUME_SET` | Entity 的 volume 可以设为特定 levels。 |
| `VOLUME_STEP` | Entity 的 volume 可以上下调整。 |
## 状态
设置 state 应在 `state` property 中返回一个 `MediaPlayerState` 枚举值。Resulting state 值是 enum 成员名称的小写版本(例如,`MediaPlayerState.PLAYING` 结果为 state `playing`)。
| Value | Description |
|-------------|---------------------------------------------------------------------------------------------------------------------|
| `OFF` | Entity 已关闭,在开启之前不接受 commands。 |
| `ON` | Entity 已开启,但当前未知其 state 的详细信息。 |
| `IDLE` | Entity 已开启并接收 commands,但当前未播放任何 media。可能处于某个 idle home screen。 |
| `PLAYING` | Entity 当前正在播放 media。 |
| `PAUSED` | Entity 有 active media,且当前已 paused |
| `BUFFERING` | Entity 正在准备开始播放某些 media |
:::note
Media players 在 standby state 时通常无法控制。如果 Home Assistant 可以使用其他 protocol 或 method 开启 device,即使用于控制 device 的主要 channel 当前不可用,也应显示为 `off`。如果 Home Assistant 没有方法开启 device,应显示为 `unavailable`。更多详情请见 [entity-unavailable Exceptions](/developers/core/integration-quality-scale/rules/entity-unavailable.md#exceptions)。
:::
## 方法
### 播放媒体
通知 media player 播放 media。使用以下方法实现:
```python
class MyMediaPlayer(MediaPlayerEntity):
def play_media(
self,
media_type: str,
media_id: str,
enqueue: MediaPlayerEnqueue | None = None,
announce: bool | None = None, **kwargs: Any
) -> None:
"""Play a piece of media."""
async def async_play_media(
self,
media_type: str,
media_id: str,
enqueue: MediaPlayerEnqueue | None = None,
announce: bool | None = None, **kwargs: Any
) -> None:
"""Play a piece of media."""
```
`enqueue` attribute 是字符串 enum `MediaPlayerEnqueue`:
* `add`:将给定 media item 添加到队列末尾
* `next`:接下来播放给定的 media item,保留队列
* `play`:立即播放给定的 media item,保留队列
* `replace`:立即播放给定的 media item,清除队列
当 `announce` 布尔 attribute 设为 `true` 时,media player 应尝试暂停当前 music,向用户 announce media,然后恢复 music。
### 浏览媒体
如果 media player 支持浏览 media,应实现以下 method:
```python
class MyMediaPlayer(MediaPlayerEntity):
async def async_browse_media(
self, media_content_type: str | None = None, media_content_id: str | None = None
) -> BrowseMedia:
"""Implement the websocket media browsing helper."""
return await media_source.async_browse_media(
self.hass,
media_content_id,
content_filter=lambda item: item.media_content_type.startswith("audio/"),
)
```
如果 media player 也允许从 URL 播放 media,还可以添加对浏览
Home Assistant media sources 的支持。这些 sources 可以由任何集成提供。示例包括
text-to-speech 和 local media。
```python
from homeassistant.components import media_source
from homeassistant.components.media_player.browse_media import (
async_process_play_media_url,
)
class MyMediaPlayer(MediaPlayerEntity):
async def async_browse_media(
self, media_content_type: str | None = None, media_content_id: str | None = None
) -> BrowseMedia:
"""Implement the websocket media browsing helper."""
# 如果 media player 没有自己的 media sources 可供浏览,将所有 browse commands
# 路由到 media source 集成。
return await media_source.async_browse_media(
self.hass,
media_content_id,
# 这允许过滤 content。在本例中,它只显示 audio sources。
content_filter=lambda item: item.media_content_type.startswith("audio/"),
)
async def async_play_media(
self,
media_type: str,
media_id: str,
enqueue: MediaPlayerEnqueue | None = None,
announce: bool | None = None, **kwargs: Any
) -> None:
"""Play a piece of media."""
if media_source.is_media_source_id(media_id):
media_type = MediaType.MUSIC
play_item = await media_source.async_resolve_media(self.hass, media_id, self.entity_id)
# play_item 在需要在 Home Assistant host 上解析时返回 relative URL
# 此调用会将其转换为完整 URL
media_id = async_process_play_media_url(self.hass, play_item.url)
# 用调用 media player 播放 media 函数替换此处。
await self._media_player.play_url(media_id)
```
### 搜索媒体
如果 media player 支持搜索 media,应实现以下 method:
```python
class MyMediaPlayer(MediaPlayerEntity):
async def async_search_media(
self,
query: SearchMediaQuery,
) -> SearchMedia:
"""Search the media player."""
# 在 library client 上搜索请求的 media。
result = await my_client.search(query=query.search_query)
return SearchMedia(result=result)
```
SearchMediaQuery 是一个具有以下 properties 的 dataclass:
| Attribute | Type | Default | Description |
|-----------------------|---------------------------------------|-------------|------------------------------------|
| `search_query` | `str` | *必需* | 搜索字符串或 query。 |
| `media_content_type` | `MediaType \| str \| None` | `None` | 要搜索的 content type。 |
| `media_content_id` | `str \| None` | `None` | 要搜索的 content ID。 |
| `media_filter_classes`| `list[MediaClass] \| None` | `None` | 要过滤的 media classes 列表。 |
### 选择 sound mode
可选。切换 media player 的 sound mode。
```python
class MyMediaPlayer(MediaPlayerEntity):
# 实现以下方法之一。
def select_sound_mode(self, sound_mode: str) -> None:
"""Switch the sound mode of the entity."""
async def async_select_sound_mode(self, sound_mode: str) -> None:
"""Switch the sound mode of the entity."""
```
### 选择来源
可选。切换 media player 选择的 input source。
```python
class MyMediaPlayer(MediaPlayerEntity):
# 实现以下方法之一。
def select_source(self, source: str) -> None:
"""Select input source."""
async def async_select_source(self, source: str) -> None:
"""Select input source."""
```
### 媒体类型
必需。返回与 mediatype 匹配的 MediaType enum 值之一。
| CONST |
|-------|
|MediaType.MUSIC|
|MediaType.TVSHOW|
|MediaType.MOVIE|
|MediaType.VIDEO|
|MediaType.EPISODE|
|MediaType.CHANNEL|
|MediaType.PLAYLIST|
|MediaType.IMAGE|
|MediaType.URL|
|MediaType.GAME|
|MediaType.APP|
```python
class MyMediaPlayer(MediaPlayerEntity):
# 实现以下方法。
@property
def media_content_type(self) -> MediaType | str | None:
"""Content type of current playing media."""
```
:::info
在 `play_media` service action 中,使用集成名称作为 `media_content_type` 也是可接受的,前提是集成提供了不映射到已定义常量的处理。
:::
### 可用的设备类型
可选。这是什么类型的 media device。它可能会映射到 Google device types。
| Value | Description
| ----- | -----------
| projector | Device 是 projector 类型的 device。
| receiver | Device 是 audio video receiver 类型的 device,接收 audio 并输出到 speakers,video 输出到某个 display。
| speaker | Device 是 speakers 或 stereo 类型的 device。
| tv | Device 是 television 类型的 device。
### 为 media browser 代理 album art
可选。如果 media player 只能从 internal network 访问,则需要通过 Home Assistant 代理 album art,以便在离开 home 或通过 mobile app 时能够正常工作。
要通过 Home Assistant 代理 image,请将 `BrowseMedia` item 的 `thumbnail` property 设为由 `self.get_browse_image_url(media_content_type, media_content_id, media_image_id=None)` method 生成的 URL。然后浏览器将获取此 URL,从而导致调用 `async_get_browse_image(media_content_type, media_content_id, media_image_id=None)`。
:::info
仅当 web request 源自网络外部时才使用代理 thumbnail。可以使用从 `homeassistant.helpers.network` 导入的 `is_local_request(hass)` 进行测试。
:::
在 `async_get_browse_image` 中,使用 `self._async_fetch_image(url)` 从 local network 获取 image。不要使用 `self._async_fetch_image_from_cache(url)`,它只应用于当前播放的 artwork。
:::info
不要将 URL 作为 `media_image_id` 传入。这可能允许攻击者从 local network 获取任何数据。
:::
```python
class MyMediaPlayer(MediaPlayerEntity):
# 实现以下方法。
async def async_get_browse_image(
self,
media_content_type: str,
media_content_id: str,
media_image_id: str | None = None,
) -> tuple[bytes | None, str | None]:
"""Serve album art. Returns (content, content_type)."""
image_url = ...
return await self._async_fetch_image(image_url)
```
### 将 player entities 分组在一起
可选。如果 player 支持将 player entities 分组进行同步播放(由 `MediaPlayerEntityFeature.GROUPING` 指示),则需要定义一个 join method 和一个 unjoin method。
```python
class MyMediaPlayer(MediaPlayerEntity):
# 实现以下 join methods 之一:
def join_players(self, group_members: list[str]) -> None:
"""Join `group_members` as a player group with the current player."""
async def async_join_players(self, group_members: list[str]) -> None:
"""Join `group_members` as a player group with the current player."""
# 实现以下 unjoin methods 之一:
def unjoin_player(self) -> None:
"""Remove this player from any group."""
async def async_unjoin_player(self) -> None:
"""Remove this player from any group."""
```
---
url: /developers/core/entity/notify.md
---
Notify 实体是一种可以向设备或服务发送消息的实体,但从 Home Assistant 的角度来看是无状态的。
Notify 实体派生自 [`homeassistant.components.notify.NotifyEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/notify/__init__.py),
在发送通知消息方面可能有所帮助(但不限于):
* 短信 (SMS)
* 电子邮件
* 直接消息或聊天
* 设备 LCD 显示屏上的屏幕消息
## 状态
Notify 实体的状态是一个时间戳,代表最后发送消息的日期和时间。
与 `text` 实体不同,`notify` 实体没有可以设置的状态。
如果你想表示一个可以更改的文本值(从而有实际状态),应使用 `text` 实体。
## 属性
由于此集成是无状态的,它不为其自身提供任何特定属性。
所有实体共有的其他属性(如 `icon`、`name` 等)仍然适用。
## 方法
### 发送消息
发送消息方法用于向设备或服务发送消息。
```python
class MyNotifier(NotifyEntity):
# 实现以下方法之一。
def send_message(self, message: str, title: str | None = None) -> None:
"""Send a message."""
async def async_send_message(self, message: str, title: str | None = None) -> None:
"""Send a message."""
```
### 记录通知
一些集成为发送通知提供带有扩展的、集成特定功能的自定义操作,或以其他方式在 Home Assistant 内部触发通知。为了跟踪通知发送的时间,集成可以调用 `_async_record_notification` 或 `_record_notification`。
:::important
仅应记录源自 Home Assistant 内部的通知。外部生成的通知不得记录。对此应改用 event 实体。
:::
```python
class MyNotifier(NotifyEntity):
# 默认通过 notify.send_message 操作发送通知的方法
async def async_send_message(self, message: str, title: str | None = None) -> None:
"""Send a message."""
await self._publish(message=message, title=title)
# 集成为发送通知实现了自定义实体操作
async def publish(self, message: str, title: str | None = None, priority: int | None = None) -> None:
"""Send a message with priority."""
await self._publish(message=message, title=title, priority=priority)
# 记录已发送通知
self._async_record_notification()
```
---
url: /developers/core/entity/number.md
---
`number` 是一种允许用户向集成输入任意值的 entity。从 [`homeassistant.components.number.NumberEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/number/__init__.py) 派生 entity platforms。
## 属性
:::tip
Properties 应该只从内存返回信息,而不要执行 I/O(如网络请求)。请实现 `update()` 或 `async_update()` 来获取数据。
:::
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
| device\_class | string | `None` | Number 的类型。
| mode | string | `auto` | 定义 number 在 UI 中的显示方式。建议使用默认值 `auto`。可以是 `box` 或 `slider` 以强制显示模式。
| native\_max\_value | float | 100 | Number 的 `native_unit_of_measurement` 中接受的最大值(inclusive)
| native\_min\_value | float | 0 | Number 的 `native_unit_of_measurement` 中接受的最小值(inclusive)
| native\_step | float | **见下文** | 定义 values 的 resolution,即 number 的 `native_unit_of_measurement` 中最小的增量或减量。
| native\_value | float | **必需** | Number 的 `native_unit_of_measurement` 中的 value。
| native\_unit\_of\_measurement | string | `None` | Number 的 value 所表示的计量单位。如果 `native_unit_of_measurement` 是 °C 或 °F,且其 `device_class` 是 temperature,则 number 的 `unit_of_measurement` 将为用户配置的 preferred temperature unit,number 的 `state` 将是可选单位转换后的 `native_value`。如果提供了 [unit translation](/developers/internationalization/core.md#unit-of-measurement-of-entities),则不应定义 `native_unit_of_measurement`。
其他在所有 entity 中通用的 properties(如 `icon`、`name` 等)也适用。
默认 step 值根据 range(max - min)值动态选择。如果 max\_value 和 min\_value 之间的差值大于 1.0,则默认 step 为 1.0。如果 range 较小,则 step 会迭代除以 10,直到小于 range。
### 可用的设备类型
如果指定 device class,你的 number entity 还需要返回正确的计量单位。
| Constant | Supported units | Description
| ---- | ---- | -----------
| `NumberDeviceClass.ABSOLUTE_HUMIDITY` | g/m³, mg/m³ | Absolute humidity
| `NumberDeviceClass.APPARENT_POWER` | mVA, VA, kVA | Apparent power
| `NumberDeviceClass.AQI` | None | Air Quality Index
| `NumberDeviceClass.AREA` | m², cm², km², mm², in², ft², yd², mi², ac, ha | Area
| `NumberDeviceClass.ATMOSPHERIC_PRESSURE` | cbar, bar, hPa, mmHG, inHg, inH₂O, kPa, mbar, Pa, psi | Atmospheric pressure
| `NumberDeviceClass.BATTERY` | % | 剩余 battery 百分比
| `NumberDeviceClass.BLOOD_GLUCOSE_CONCENTRATION` | mg/dL, mmol/L | Blood glucose concentration\`\`\`
| `NumberDeviceClass.CO2` | ppm | 二氧化碳浓度。
| `NumberDeviceClass.CO` | ppb, ppm, µg/m³, mg/m³ | 一氧化碳浓度。
| `NumberDeviceClass.CONDUCTIVITY` | S/cm, mS/cm, µS/cm | Conductivity
| `NumberDeviceClass.CURRENT` | A, mA, µA | Current
| `NumberDeviceClass.DATA_RATE` | bit/s, kbit/s, Mbit/s, Gbit/s, B/s, kB/s, MB/s, GB/s, KiB/s, MiB/s, GiB/s | Data rate
| `NumberDeviceClass.DATA_SIZE` | bit, kbit, Mbit, Gbit, B, kB, MB, GB, TB, PB, EB, ZB, YB, KiB, MiB, GiB, TiB, PiB, EiB, ZiB, YiB | Data size
| `NumberDeviceClass.DISTANCE` | km, m, cm, mm, mi, nmi, yd, in | Generic distance
| `NumberDeviceClass.DURATION` | d, h, min, s, ms, µs | Time period。不应仅因时间流逝而更新。Device 或 service 需要给出新的数据点才能更新。
| `NumberDeviceClass.ENERGY` | J, kJ, MJ, GJ, mWh, Wh, kWh, MWh, GWh, TWh, cal, kcal, Mcal, Gcal | Energy,此 device class 应用于表示 energy consumption,例如 electricity meter。表示 *power* over *time*。不要与 `power` 混淆。
| `NumberDeviceClass.ENERGY_DISTANCE` | kWh/100km, Wh/km, mi/kWh, km/kWh | Energy per distance,此 device class 应用于表示按距离的 energy consumption,例如 electric car 消耗的 electric energy 量。
| `NumberDeviceClass.ENERGY_STORAGE` | J, kJ, MJ, GJ, mWh, Wh, kWh, MWh, GWh, TWh, cal, kcal, Mcal, Gcal | Stored energy,此 device class 应用于表示 stored energy,例如 battery 中当前存储的 electric energy 量或 battery 的 capacity。表示 *power* over *time*。不要与 `power` 混淆。
| `NumberDeviceClass.FREQUENCY` | mHz, Hz, kHz, MHz, GHz | Frequency
| `NumberDeviceClass.GAS` | L, m³, ft³, CCF, MCF | Gas 的体积。以 kWh 的 energy 测量的 gas consumption 而不是体积,应归类为 energy。
| `NumberDeviceClass.HUMIDITY` | % | Relative humidity
| `NumberDeviceClass.ILLUMINANCE` | lx | Light level
| `NumberDeviceClass.IRRADIANCE` | W/m², BTU/(h⋅ft²) | Irradiance
| `NumberDeviceClass.MOISTURE` | % | Moisture
| `NumberDeviceClass.MONETARY` | [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217#Active_codes) | 带有 currency 的 Monetary value。
| `NumberDeviceClass.NITROGEN_DIOXIDE` | ppb, ppm, µg/m³ | 二氧化氮浓度
| `NumberDeviceClass.NITROGEN_MONOXIDE` | ppb, µg/m³ | 一氧化氮浓度
| `NumberDeviceClass.NITROUS_OXIDE` | µg/m³ | 一氧化二氮浓度
| `NumberDeviceClass.OZONE` | ppb, ppm, µg/m³ | 臭氧浓度
| `NumberDeviceClass.PH` | None | 水溶液的 Potential hydrogen(pH)
| `NumberDeviceClass.PM1` | µg/m³ | 小于 1 微米的 particulate matter 浓度
| `NumberDeviceClass.PM25` | µg/m³ | 小于 2.5 微米的 particulate matter 浓度
| `NumberDeviceClass.PM4` | µg/m³ | 小于 4 微米的 particulate matter 浓度
| `NumberDeviceClass.PM10` | µg/m³ | 小于 10 微米的 particulate matter 浓度
| `NumberDeviceClass.POWER` | mW, W, kW, MW, GW, TW | Power。
| `NumberDeviceClass.POWER_FACTOR` | %, None | Power Factor
| `NumberDeviceClass.PRECIPITATION` | cm, in, mm | Accumulated precipitation
| `NumberDeviceClass.PRECIPITATION_INTENSITY` | in/d, in/h, mm/d, mm/h | Precipitation intensity
| `NumberDeviceClass.PRESSURE` | cbar, bar, hPa, mmHg, inHg, kPa, mbar, Pa, psi, mPa | Pressure。
| `NumberDeviceClass.RADON` | Bq/m³, pCi/L | Radon 浓度
| `NumberDeviceClass.REACTIVE_ENERGY` | varh, kvarh | Reactive energy
| `NumberDeviceClass.REACTIVE_POWER` | mvar, var, kvar | Reactive power
| `NumberDeviceClass.SIGNAL_STRENGTH` | dB, dBm | Signal strength
| `NumberDeviceClass.SOUND_PRESSURE` | dB, dBA | Sound pressure
| `NumberDeviceClass.SPEED` | ft/s, in/d, in/h, in/s, km/h, kn, m/s, mph, mm/d, mm/s | Generic speed
| `NumberDeviceClass.SULPHUR_DIOXIDE` | ppb, µg/m³ | 二氧化硫浓度
| `NumberDeviceClass.TEMPERATURE` | °C, °F, K | Temperature。
| `NumberDeviceClass.TEMPERATURE_DELTA` | °C, °F, K | 此 device class 表示 temperature interval(delta),即两个 temperature values 之间的差。
| `NumberDeviceClass.VOLATILE_ORGANIC_COMPOUNDS` | µg/m³, mg/m³ | Volatile organic compounds 浓度
| `NumberDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS` | ppm, ppb | Volatile organic compounds 比率
| `NumberDeviceClass.VOLTAGE` | V, mV, µV, kV, MV | Voltage
| `NumberDeviceClass.VOLUME` | L, mL, gal, fl. oz., m³, ft³, CCF, MCF | Generic volume,此 device class 应用于表示 consumption,例如 vehicle 消耗的 fuel 量。
| `NumberDeviceClass.VOLUME_FLOW_RATE` | m³/h, m³/min, m³/s, ft³/min, L/h, L/min, L/s, gal/d, gal/h, gal/min, mL/s | Volume flow rate,此 device class 应用于表示某些 volume 的 flow,例如瞬间消耗的水量。
| `NumberDeviceClass.VOLUME_STORAGE` | L, mL, gal, fl. oz., m³, ft³, CCF, MCF | Generic stored volume,此 device class 应用于表示 stored volume,例如 fuel tank 中的 fuel 量。
| `NumberDeviceClass.WATER` | L, gal, m³, ft³, CCF, MCF | Water consumption
| `NumberDeviceClass.WEIGHT` | kg, g, mg, µg, oz, lb, st | Generic mass;`weight` 用于代替 `mass` 以符合日常语言。
| `NumberDeviceClass.WIND_DIRECTION` | ° | Wind direction
| `NumberDeviceClass.WIND_SPEED` | ft/s, km/h, kn, m/s, mph | Wind speed
## 恢复 number 状态
在 restart 或 reload 后恢复 state 的 numbers 不应扩展 `RestoreEntity`,因为它不存储 `native_value`,而是存储可能被 number base entity 修改的 `state`。恢复 state 的 numbers 应扩展 `RestoreNumber`,并从 `async_added_to_hass` 调用 `await self.async_get_last_number_data`,以访问存储的 `native_min_value`、`native_max_value`、`native_step`、`native_unit_of_measurement` 和 `native_value`。
## 方法
### 设置值
当用户或 automation 想要更新 value 时调用。
```python
class MyNumber(NumberEntity):
# 实现以下方法之一。
def set_native_value(self, value: float) -> None:
"""Update the current value."""
async def async_set_native_value(self, value: float) -> None:
"""Update the current value."""
```
---
url: /developers/core/entity/radio-frequency.md
---
Radio frequency 实体在 RF 收发器硬件(如 ESPHome、Broadlink 或基于 SDR 的设备)与需要发送 RF 命令的设备特定集成(如车库门开启器、RF 遥控器或无线开关)之间提供了一个抽象层。它充当一个虚拟 RF 发射器,可被其他集成用于控制 RF 设备。
Radio frequency 发射器实体派生自 [`homeassistant.components.radio_frequency.RadioFrequencyTransmitterEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/radio_frequency/__init__.py)。
## 架构概览
Radio frequency 实体集成在以下方面创建分离:
1. **Transmitter 集成**(如 ESPHome、Broadlink):这些实现 `RadioFrequencyTransmitterEntity`,以提供硬件特定的 RF 传输能力。
2. **Consumer 集成**(如车库门或 RF 遥控器集成):这些使用 `radio_frequency` 辅助函数,通过可用的 transmitter 发送设备特定的 RF 命令。
```mermaid
flowchart TD
subgraph consumers["Consumer integrations"]
direction LR
Garage["Garage door"] ~~~ Remote["RF remote"] ~~~ Switch["RF switch"] ~~~ more1["..."]
end
subgraph rf["radio_frequency domain"]
direction LR
base["RadioFrequencyTransmitterEntity base class"]
subgraph helpers["Helper functions"]
direction TB
async_get_transmitters ~~~ async_send_command
end
end
subgraph transmitters["Transmitter integrations"]
direction LR
ESPHome["ESPHome"] ~~~ Broadlink["Broadlink"] ~~~ SDR["SDR"] ~~~ more2["..."]
end
consumers -->|"Use helper functions"| helpers
base -->|"Implemented by"| transmitters
```
## RadioFrequencyTransmitterEntity 类
### 状态
Radio frequency 实体状态表示最后发送 RF 命令的时间戳。这在基类 `RadioFrequencyTransmitterEntity` 中实现,集成不应更改。
### 支持的频率范围
Transmitter 集成必须通过实现 `supported_frequency_ranges` 属性来声明其硬件可以在哪些频率范围内传输。每个范围是一个 `(min_hz, max_hz)` 元组。Consumer 集成使用这些范围来选择兼容的 transmitter。
```python
class MyRadioFrequencyTransmitterEntity(RadioFrequencyTransmitterEntity):
"""My RF transmitter entity."""
@property
def supported_frequency_ranges(self) -> list[tuple[int, int]]:
"""Return list of (min_hz, max_hz) tuples."""
return [(300_000_000, 348_000_000), (433_050_000, 434_790_000)]
```
### 发送命令方法
`RadioFrequencyTransmitterEntity.async_send_command` 方法必须由 transmitter 集成实现,以处理实际的 RF 传输。
```python
from rf_protocols import RadioFrequencyCommand
class MyRadioFrequencyTransmitterEntity(RadioFrequencyTransmitterEntity):
"""My RF transmitter entity."""
async def async_send_command(self, command: RadioFrequencyCommand) -> None:
"""Send an RF command.
Args:
command: The RF command to send.
Raises:
HomeAssistantError: If transmission fails.
"""
```
:::important
不要从 consumer 集成中直接调用 `RadioFrequencyTransmitterEntity.async_send_command`。请使用 [`radio_frequency.async_send_command`](#send-command),它会自动处理状态更新和 context 管理。
:::
## 辅助函数
`radio_frequency` 域为 consumer 集成提供了发现 transmitter 和发送 RF 命令的辅助函数。
### 获取 transmitters
返回所有支持给定频率和调制方式的 RF transmitter 的实体 IDs。
```python
from rf_protocols import ModulationType
from homeassistant.components import radio_frequency
transmitters = radio_frequency.async_get_transmitters(
hass,
frequency=433_920_000, # 433.92 MHz
modulation=ModulationType.OOK,
)
```
空列表表示没有可用的兼容 transmitter。
:::note
目前仅支持 `ModulationType.OOK`(开关键控,on-off keying)。未来版本可以添加其他调制类型。
:::
### 发送命令
向特定的 radio frequency 实体发送 RF 命令。可以通过实体的 entity ID 或 entity registry UUID 来引用该实体。
```python
from rf_protocols.codes.garage import make_garage_command
from homeassistant.components import radio_frequency
command = make_garage_command(...)
await radio_frequency.async_send_command(
hass,
rf_entity_id,
command,
context=context, # 用于 logbook 跟踪的可选 context
)
```
## RF 命令
[rf-protocols 库](https://github.com/home-assistant-libs/rf-protocols) 提供了 RF 命令的基类,将协议特定的数据转换为原始 timings 和载波配置。
所有 RF 命令必须继承自 `rf_protocols.RadioFrequencyCommand`。
---
url: /developers/core/entity/remote.md
---
Remote 实体可以表示两种不同类型的设备:
1. 发送命令的物理设备。
2. Home Assistant 中的虚拟设备,向另一个物理设备(如电视)发送命令。
从 [`homeassistant.components.remote.RemoteEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/remote/__init__.py) 派生实体平台。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| 名称 | 类型 | 默认值 | 描述 |
| ---- | ---- | ------- | ----------- |
| is\_on | boolean | `None` | 设备当前是开启还是关闭。 |
| current\_activity | str | `None` | 返回当前活动的 activity。 |
| activity\_list | list | `None` | 返回可用的 activity 列表。 |
### 活动
Activity 是一个预定义的 activity 或宏,将 remote 置于特定状态。例如,"Watch TV" activity 可能会打开多个设备并将频道更改为特定频道。
## 支持的功能
支持的功能通过使用 `RemoteEntityFeature` 枚举中的值来定义,并使用按位或(`|`)运算符进行组合。
| 值 | 描述 |
| ---------------- | --------------------------------------------- |
| `LEARN_COMMAND` | 实体允许从设备学习命令。 |
| `DELETE_COMMAND` | 实体允许从设备删除命令。 |
| `ACTIVITY` | 实体支持 activity。 |
## 方法
### 开启命令
```python
class MyRemote(RemoteEntity):
def turn_on(self, activity: str | None = None, **kwargs: Any) -> None:
"""Send the power on command."""
async def async_turn_on(self, activity: str | None = None, **kwargs: Any) -> None:
"""Send the power on command."""
```
### 关闭命令
```python
class MyRemote(RemoteEntity):
def turn_off(self, activity: str | None = None, **kwargs: Any) -> None:
"""Send the power off command."""
async def async_turn_off(self, activity: str | None = None, **kwargs: Any) -> None:
"""Send the power off command."""
```
### 切换命令
```python
class MyRemote(RemoteEntity):
def toggle(self, activity: str | None = None, **kwargs: Any) -> None:
"""Toggle a device."""
async def async_toggle(self, activity: str | None = None, **kwargs: Any) -> None:
"""Toggle a device."""
```
### 发送命令
```python
class MyRemote(RemoteEntity):
def send_command(self, command: Iterable[str], **kwargs: Any) -> None:
"""Send commands to a device."""
async def async_send_command(self, command: Iterable[str], **kwargs: Any) -> None:
"""Send commands to a device."""
```
### 学习命令
仅当设置了 `RemoteEntityFeature.LEARN_COMMAND` 标志时实现此方法。
```python
class MyRemote(RemoteEntity):
def learn_command(self, **kwargs: Any) -> None:
"""Learn a command from a device."""
async def async_learn_command(self, **kwargs: Any) -> None:
"""Learn a command from a device."""
```
### 删除命令
仅当设置了 `RemoteEntityFeature.DELETE_COMMAND` 标志时实现此方法。
```python
class MyRemote(RemoteEntity):
def delete_command(self, **kwargs: Any) -> None:
"""Delete a command from a device."""
async def async_delete_command(self, **kwargs: Any) -> None:
"""Delete a command from a device."""
```
---
url: /developers/core/entity/scene.md
---
Scene 实体是一种 [可以为一组实体重现所需状态](/developers/core/platform/reproduce_state.md) 的实体。Scene 实体可以向一组设备激活 scene,但从 Home Assistant 的角度来看是无状态的。
Scene 实体派生自 [`homeassistant.components.scene.Scene`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/scene/__init__.py)。
如果你想表示一个可以开启和关闭的东西(从而有实际状态),应使用 `switch` 实体。
Scene 实体也可以 [通过 Scene 编辑器或 YAML 由用户创建](https://www.home-assistant.io/integrations/scene)。
## 属性
由于此集成是无状态的,它不为其自身提供任何特定属性。
所有实体共有的其他属性(如 `icon`、`name` 等)仍然适用。
## 方法
### 激活
激活 scene。
```python
class MyScene(Scene):
# 实现以下方法之一。
def activate(self, **kwargs: Any) -> None:
"""Activate scene. Try to get entities into requested state."""
async def async_activate(self, **kwargs: Any) -> None:
"""Activate scene. Try to get entities into requested state."""
```
Activate 方法可用于向设备或服务激活 scene。
当用户按下 scene 的 `activate` 按钮或调用 `scene.turn_on` 操作来激活 scene 时,Home Assistant 会调用此方法。
一些集成可以接收外部事件,在 Home Assistant 外部激活 scene。这些激活不源自 Home Assistant UI 或服务调用,而是来自物理按钮等外部来源。
为了支持此场景,集成应从 `BaseScene` 而不是 `Scene` 派生,覆盖 `_async_activate()` 以处理来自 Home Assistant 侧的 scene 激活,并在发生外部 scene 激活时调用 `_async_record_activation()`。
此外,由于这些 scene 在 Home Assistant 外部激活,集成可能希望延迟更新 scene 状态时间戳,直到外部 scene 报告为活动状态,即使在 Home Assistant UI 中激活也是如此。
```python
# 继承自 BaseScene
class MyScene(BaseScene):
# 注意前面的下划线
async def _async_activate(self, **kwargs: Any) -> None:
"""Activate scene."""
# 调用服务以激活 scene
await mqtt.async_publish(self.hass, self._topic, self._payload)
# 在服务回调中记录激活
async def _state_received(self, msg: ReceiveMessage) -> None:
self._async_record_activation()
self.async_write_ha_state()
```
### 可用的 device class
没有特定的 device class。`device_class` 属性未在 scene 实体上设置。
---
url: /developers/core/entity/select.md
---
`select` 是一种允许用户从集成提供的有限 options 列表中选择 option 的 entity。从 [`homeassistant.components.select.SelectEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/select/__init__.py) 派生 entity platforms。
此 entity 应仅在没有更适合的 option 可用的情况下使用。
例如,bulb 可以有用户可选择的 light effects。虽然这可以使用此 `select` entity 实现,但它应该真正成为 `light` entity 的一部分,后者已经支持 light effects。
## 属性
:::tip
Properties 应该只从内存返回信息,而不要执行 I/O(如网络请求)。请实现 `update()` 或 `async_update()` 来获取数据。
:::
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
| current\_option | str | None | 当前的 select option
| options | list | **必需** | 可用 options 的列表(字符串形式)
其他在所有 entity 中通用的 properties(如 `icon`、`unit_of_measurement`、`name` 等)也适用。
## 方法
### 选择选项
当用户或 automation 想要更改当前选择的 option 时调用。
```python
class MySelect(SelectEntity):
# 实现以下方法之一。
def select_option(self, option: str) -> None:
"""Change the selected option."""
async def async_select_option(self, option: str) -> None:
"""Change the selected option."""
```
---
url: /developers/core/entity/sensor.md
---
Sensor 是一种提供某些信息的 read-only entity。信息具有一个 value,以及选项性的计量单位。从 [`homeassistant.components.sensor.SensorEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/sensor/__init__.py) 派生 entity platforms。
## 属性
:::tip
Properties 应该只从内存返回信息,而不要执行 I/O(如网络请求)。请实现 `update()` 或 `async_update()` 来获取数据。
:::
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
| device\_class | `SensorDeviceClass \| None` | `None` | Sensor 的类型。
| last\_reset | `datetime.datetime \| None` | `None` | 累积型 sensor(如 electricity usage meter、gas meter、water meter 等)被初始化的时间。如果初始化时间未知,设为 `None`。注意,`last_reset` property 返回的 `datetime.datetime` 在更新 entity 的 state attributes 时将被转换为 ISO 8601 格式字符串。更改 `last_reset` 时,`state` 必须是有效的 number。
| native\_unit\_of\_measurement | `str \| None` | `None` | Sensor 的 value 所表示的计量单位。如果 `native_unit_of_measurement` 是 °C 或 °F,且其 `device_class` 是 temperature,则 sensor 的 `unit_of_measurement` 将为用户配置的 preferred temperature unit,sensor 的 `state` 将是可选单位转换后的 `native_value`。如果提供了 [unit translation](/developers/internationalization/core.md#unit-of-measurement-of-entities),则不应定义 `native_unit_of_measurement`。
| native\_value | `str \| int \| float \| date \| datetime \| Decimal \| None` | **必需** | Sensor 的 `native_unit_of_measurement` 中的 value。使用 `device_class` 可能限制此 property 可返回的 types。
| options | `list[str] \| None` | `None` | 如果此 sensor 提供 text state,此 property 可用于提供可能的 states 列表。要求设置 `enum` device class。不能与 `state_class` 或 `native_unit_of_measurement` 组合使用。
| state\_class | `SensorStateClass \| str \| None` | `None` | State 的类型。如果非 `None`,则假设 sensor 为数值型,前端将显示为 line-chart 而不是离散 values。
| suggested\_display\_precision | `int \| None` | `None` | 显示 sensor 的 state 时应使用的小数位数。
| suggested\_unit\_of\_measurement | `str \| None` | `None` | 用于 sensor state 的计量单位。对于具有 `unique_id` 的 sensors,这将用作初始计量单位,用户随后可以覆盖。对于没有 `unique_id` 的 sensors,这将是 sensor state 的计量单位。此 property 旨在供集成覆盖自动单位转换规则,例如使 temperature sensor 无论配置的 unit system 首选 `°C` 还是 `°F` 都始终显示为 `°C`,或使 distance sensor 即使配置的 unit system 是 metric 也始终显示为 miles。
:::tip
对于 sensor entity,不要添加 `extra_state_attributes`,而应创建额外的 sensor entity。不改变的 attributes 只保存一次到 database 中。如果 `extra_state_attributes` 和 sensor value 都频繁变化,会迅速增加 database 的大小。
:::
### 可用的设备类型
如果指定 device class,你的 sensor entity 还需要返回正确的计量单位。
| Constant | Supported units | Description
| ---- | ---- | -----------
| `SensorDeviceClass.ABSOLUTE_HUMIDITY` | g/m³, mg/m³ | Absolute humidity
| `SensorDeviceClass.APPARENT_POWER` | mVA, VA, kVA | Apparent power
| `SensorDeviceClass.AQI` | None | Air Quality Index
| `SensorDeviceClass.AREA` | m², cm², km², mm², in², ft², yd², mi², ac, ha | Area
| `SensorDeviceClass.ATMOSPHERIC_PRESSURE` | cbar, bar, hPa, mmHG, inHg, inH₂O, kPa, mbar, Pa, psi | Atmospheric pressure
| `SensorDeviceClass.BATTERY` | % | 剩余 battery 百分比
| `SensorDeviceClass.BLOOD_GLUCOSE_CONCENTRATION` | mg/dL, mmol/L | Blood glucose concentration
| `SensorDeviceClass.CO2` | ppm | 二氧化碳浓度。
| `SensorDeviceClass.CO` | ppb, ppm, µg/m³, mg/m³ | 一氧化碳浓度。
| `SensorDeviceClass.CONDUCTIVITY` | S/cm, mS/cm, µS/cm | Conductivity
| `SensorDeviceClass.CURRENT` | A, mA, µA | Current
| `SensorDeviceClass.DATA_RATE` | bit/s, kbit/s, Mbit/s, Gbit/s, B/s, kB/s, MB/s, GB/s, KiB/s, MiB/s, GiB/s | Data rate
| `SensorDeviceClass.DATA_SIZE` | bit, kbit, Mbit, Gbit, B, kB, MB, GB, TB, PB, EB, ZB, YB, KiB, MiB, GiB, TiB, PiB, EiB, ZiB, YiB | Data size
| `SensorDeviceClass.DATE` | | Date。要求 `native_value` 是 Python `datetime.date` 对象,或 `None`。
| `SensorDeviceClass.DISTANCE` | km, m, cm, mm, mi, nmi, yd, in | Generic distance
| `SensorDeviceClass.DURATION` | d, h, min, s, ms, µs | Time period。不应仅因时间流逝而更新。Device 或 service 需要给出新的数据点才能更新。
| `SensorDeviceClass.ENERGY` | J, kJ, MJ, GJ, mWh, Wh, kWh, MWh, GWh, TWh, cal, kcal, Mcal, Gcal | Energy,此 device class 应用于表示 energy consumption 的 sensors,例如 electricity meter。表示 *power* over *time*。不要与 `power` 混淆。
| `SensorDeviceClass.ENERGY_DISTANCE` | kWh/100km, Wh/km, mi/kWh, km/kWh | Energy per distance,此 device class 应用于表示按距离的 energy consumption,例如 electric car 消耗的 electric energy 量。
| `SensorDeviceClass.ENERGY_STORAGE` | J, kJ, MJ, GJ, mWh, Wh, kWh, MWh, GWh, TWh, cal, kcal, Mcal, Gcal | Stored energy,此 device class 应用于表示 stored energy 的 sensors,例如 battery 中当前存储的 electric energy 量或 battery 的 capacity。表示 *power* over *time*。不要与 `power` 混淆。
| `SensorDeviceClass.ENUM` | | Sensor 具有有限的(非 numeric)states 集。使用此 device class 时,`options` property 必须设为可能的 states 列表。
| `SensorDeviceClass.FREQUENCY` | mHz, Hz, kHz, MHz, GHz | Frequency
| `SensorDeviceClass.GAS` | L, m³, ft³, CCF, MCF | Gas 的体积。以 kWh 的 energy 测量的 gas consumption 而不是体积,应归类为 energy。
| `SensorDeviceClass.HUMIDITY` | % | Relative humidity
| `SensorDeviceClass.ILLUMINANCE` | lx | Light level
| `SensorDeviceClass.IRRADIANCE` | W/m², BTU/(h⋅ft²) | Irradiance
| `SensorDeviceClass.MOISTURE` | % | Moisture
| `SensorDeviceClass.MONETARY` | [ISO 4217](https://en.wikipedia.org/wiki/ISO_4217#Active_codes) | 带有 currency 的 Monetary value。
| `SensorDeviceClass.NITROGEN_DIOXIDE` | ppb, ppm, µg/m³ | 二氧化氮浓度
| `SensorDeviceClass.NITROGEN_MONOXIDE` | ppb, µg/m³ | 一氧化氮浓度
| `SensorDeviceClass.NITROUS_OXIDE` | µg/m³ | 一氧化二氮浓度
| `SensorDeviceClass.OZONE` | ppb, ppm, µg/m³ | 臭氧浓度
| `SensorDeviceClass.PH` | None | 水溶液的 Potential hydrogen(pH)
| `SensorDeviceClass.PM1` | µg/m³ | 小于 1 微米的 particulate matter 浓度
| `SensorDeviceClass.PM25` | µg/m³ | 小于 2.5 微米的 particulate matter 浓度
| `SensorDeviceClass.PM4` | µg/m³ | 小于 4 微米的 particulate matter 浓度
| `SensorDeviceClass.PM10` | µg/m³ | 小于 10 微米的 particulate matter 浓度
| `SensorDeviceClass.POWER` | mW, W, kW, MW, GW, TW | Power。
| `SensorDeviceClass.POWER_FACTOR` | %, None | Power Factor
| `SensorDeviceClass.PRECIPITATION` | cm, in, mm | Accumulated precipitation
| `SensorDeviceClass.PRECIPITATION_INTENSITY` | in/d, in/h, mm/d, mm/h | Precipitation intensity
| `SensorDeviceClass.PRESSURE` | cbar, bar, hPa, mmHg, inHg, kPa, mbar, Pa, psi, mPa | Pressure。
| `SensorDeviceClass.RADON` | Bq/m³, pCi/L | Radon 浓度
| `SensorDeviceClass.REACTIVE_ENERGY` | varh, kvarh | Reactive energy
| `SensorDeviceClass.REACTIVE_POWER` | mvar, var, kvar | Reactive power
| `SensorDeviceClass.SIGNAL_STRENGTH` | dB, dBm | Signal strength
| `SensorDeviceClass.SOUND_PRESSURE` | dB, dBA | Sound pressure
| `SensorDeviceClass.SPEED` | ft/s, in/d, in/h, in/s, km/h, kn, m/s, mph, mm/d, mm/s | Generic speed
| `SensorDeviceClass.SULPHUR_DIOXIDE` | ppb, µg/m³ | 二氧化硫浓度
| `SensorDeviceClass.TEMPERATURE` | °C, °F, K | Temperature。
| `SensorDeviceClass.TEMPERATURE_DELTA` | °C, °F, K | 此 device class 表示 temperature interval(delta),即两个 temperature values 之间的差。
| `SensorDeviceClass.TIMESTAMP` | | Timestamp。要求 `native_value` 返回 Python `datetime.datetime` 对象,带 timezone 信息,或 `None`。
| `SensorDeviceClass.UPTIME` | | Timestamp。表示 device 上次 boot 的 datetime。要求 `native_value` 返回 Python `datetime.datetime` 对象,带 timezone 信息,或 `None`。
| `SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS` | µg/m³, mg/m³ | Volatile organic compounds 浓度
| `SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS` | ppm, ppb | Volatile organic compounds 比率
| `SensorDeviceClass.VOLTAGE` | V, mV, µV, kV, MV | Voltage
| `SensorDeviceClass.VOLUME` | L, mL, gal, fl. oz., m³, ft³, CCF, MCF | Generic volume,此 device class 应用于表示 consumption 的 sensors,例如 vehicle 消耗的 fuel 量。
| `SensorDeviceClass.VOLUME_FLOW_RATE` | m³/h, m³/min, m³/s, ft³/min, L/h, L/min, L/s, gal/d, gal/h, gal/min, mL/s | Volume flow rate,此 device class 应用于表示某些 volume 的 flow 的 sensors,例如瞬间消耗的水量。
| `SensorDeviceClass.VOLUME_STORAGE` | L, mL, gal, fl. oz., m³, ft³, CCF, MCF | Generic stored volume,此 device class 应用于表示 stored volume 的 sensors,例如 fuel tank 中的 fuel 量。
| `SensorDeviceClass.WATER` | L, gal, m³, ft³, CCF, MCF | Water consumption
| `SensorDeviceClass.WEIGHT` | kg, g, mg, µg, oz, lb, st | Generic mass;`weight` 用于代替 `mass` 以符合日常语言。
| `SensorDeviceClass.WIND_DIRECTION` | ° | Wind direction,如果 wind speed 为 0 或太低而无法准确测量 wind direction,则应设为 `None`。
| `SensorDeviceClass.WIND_SPEED` | ft/s, km/h, kn, m/s, mph | Wind speed
### 可用的 state classes
:::caution
选择 sensor 的 state class 时要谨慎。在大多数情况下,应选择 state class `SensorStateClass.MEASUREMENT` 或没有 `last_reset` 的 state class `SensorStateClass.TOTAL`,下面 [How to choose `state_class` and `last_reset`](#how-to-choose-state_class-and-last_reset) 中进一步解释了这一点。
:::
| Type | Description
| ---- | -----------
| `SensorStateClass.MEASUREMENT` | State 表示 *present time 的 measurement*,而不是历史聚合(如统计)或未来预测。应归类为 `SensorStateClass.MEASUREMENT` 的示例有:current temperature、humidity 或 electric power。不应归类为 `SensorStateClass.MEASUREMENT` 的示例有:明天的 forecasted temperature、昨天的 energy consumption 或任何不包含 *current* measurement 的内容。对于支持的 sensors,每小时 min、max 和平均 sensor readings 的 statistics 每 5 分钟更新一次。
| `SensorStateClass.MEASUREMENT_ANGLE` | 与上述 `SensorStateClass.MEASUREMENT` 类似,state 表示以 degrees(`°`)测量的 angles 在 *present time 的 measurement*。应归类为 `SensorStateClass.MEASUREMENT_ANGLE` 的示例有:current wind direction
| `SensorStateClass.TOTAL` | State 表示既可以增加又可以减少的 total amount,例如 net energy meter。从 sensor 首次添加以来其 value 的 accumulated growth 或 decline 的 statistics 每 5 分钟更新一次。此 state class 不应用于对 absolute value 感兴趣而不是 accumulated growth 或 decline 的 sensors,例如剩余 battery capacity 或 CPU load;在这种情况下应改用 state class `SensorStateClass.MEASUREMENT`。
| `SensorStateClass.TOTAL_INCREASING` | 类似于 `SensorStateClass.TOTAL`,但限制为 state 表示单调递增的 positive total,并定期从 0 重新开始计数,例如每日消耗的 gas 量、每周 water consumption 或 lifetime energy consumption。从 sensor 首次添加以来其 value 的 accumulated growth 的 statistics 每 5 分钟更新一次。Decreasing value 被解释为新 meter cycle 的开始或 meter 的更换。
### entity 选项
Sensors 可以由用户配置,这是通过在其 entity registry entry 中存储 `sensor` entity options 来实现的。
| Option | Description
| ------ | -----------
| `unit_of_measurement` | 对于 device class 为 `SensorDeviceClass.PRESSURE` 或 `SensorDeviceClass.TEMPERATURE` 的 sensors,可以覆盖 sensor 的计量单位。
## 恢复 sensor 状态
在 restart 或 reload 后恢复 state 的 sensors 不应扩展 `RestoreEntity`,因为它不存储 `native_value`,而是存储可能被 sensor base entity 修改的 `state`。恢复 state 的 sensors 应扩展 `RestoreSensor`,并从 `async_added_to_hass` 调用 `await self.async_get_last_sensor_data`,以访问存储的 `native_value` 和 `native_unit_of_measurement`。
## 长期统计
Home Assistant 支持将 sensors 存储为 long-term statistics,前提是 entity 具有
正确的 properties。要 opt-in 进入 statistics,sensor 必须将
`state_class` 设为有效的 state class 之一:`SensorStateClass.MEASUREMENT`、`SensorStateClass.TOTAL` 或
`SensorStateClass.TOTAL_INCREASING`。
对于某些 device classes,statistics 的 unit 会被 normalized,例如使
能够在单个 graph 中绘制多个 sensors。
### 不表示总量的 entities
Home Assistant 在 statistics 期间跟踪 min、max 和 mean value。
`state_class` property 必须设为 `SensorStateClass.MEASUREMENT`,且 `device_class` 不能
是 `SensorDeviceClass.DATE`、`SensorDeviceClass.ENUM`、`SensorDeviceClass.ENERGY`、`SensorDeviceClass.GAS`、`SensorDeviceClass.MONETARY`、
`SensorDeviceClass.TIMESTAMP`、`SensorDeviceClass.VOLUME` 或 `SensorDeviceClass.WATER` 中的任何一种。
### 表示总量的 entities
跟踪 total amount 的 entities 具有一个可能定期重置的 value,
例如本月 energy consumption、今天的 energy production、过去一周用于供暖的 pellets 重量,或 stock portfolio 的年度增长。首次编译 statistics 时 sensor 的 value 用作初始零基准点。
#### 如何选择 `state_class` 和 `last_reset`
建议尽可能使用没有 `last_reset` 的 state class `SensorStateClass.TOTAL`,只有在没有 `last_reset` 的 state class `SensorStateClass.TOTAL` 不适用于 sensor 时,才应使用 state class `SensorStateClass.TOTAL_INCREASING` 或带有 `last_reset` 的 `SensorStateClass.TOTAL`。
示例:
* Sensor 的 value 从不重置,例如 lifetime total energy consumption 或 production:state\_class `SensorStateClass.TOTAL`,`last_reset` 未设置或设为 `None`
* Sensor 的 value 可能重置为 0,且其 value 只能增加:state class `SensorStateClass.TOTAL_INCREASING`。示例:与 billing cycle 对齐的 energy consumption(例如每月),每次断开连接时重置为 0 的 energy meter
* Sensor 的 value 可能重置为 0,且其 value 既可以增加又可以减少:state class `SensorStateClass.TOTAL`,在 value 重置时更新 `last_reset`。示例:与 billing cycle 对齐的 net energy consumption(例如每月)。
* Sensor 的 state 每次 state update 时重置,例如每分钟更新一次、显示过去一分钟 energy consumption 的 sensor:state class `SensorStateClass.TOTAL`,每次 state change 时更新 `last_reset`。
#### 状态类 `SensorStateClass.TOTAL`
对于 state class 为 `SensorStateClass.TOTAL` 的 sensors,`last_reset` attribute 可以
选项性地设置,以获取对 meter cycles 的 manual control。
Sensor 首次添加到 Home Assistant 时的 state 用作初始
零基准点。当 `last_reset` 改变时,零基准点将设为 0。
如果未设置 last\_reset,则计算 `sum` statistics 时使用 sensor 首次添加时的 value 作为
零基准点。
换句话说:更新 statistics 时的逻辑是用当前 state 与前一个 state 之间的差来更新
sum 列,除非 `last_reset` 已改变,此时不添加任何内容。
没有 last\_reset 的 state class `SensorStateClass.TOTAL` 示例:
| t | state | sum | sum\_increase | sum\_decrease
| :--------------------- | -----: | -----: | -----------: | -----------:
| 2021-08-01T13:00:00 | 1000 | 0 | 0 | 0
| 2021-08-01T14:00:00 | 1010 | 10 | 10 | 0
| 2021-08-01T15:00:00 | 0 | -1000 | 10 | 1010
| 2021-08-01T16:00:00 | 5 | -995 | 15 | 1010
带有 last\_reset 的 state class `SensorStateClass.TOTAL` 示例:
| t | state | last\_reset | sum | sum\_increase | sum\_decrease
| :--------------------- | -----: | ------------------- | -----: | -----------: | -----------:
| 2021-08-01T13:00:00 | 1000 | 2021-08-01T13:00:00 | 0 | 0 | 0
| 2021-08-01T14:00:00 | 1010 | 2021-08-01T13:00:00 | 10 | 10 | 0
| 2021-08-01T15:00:00 | 1005 | 2021-08-01T13:00:00 | 5 | 10 | 5
| 2021-08-01T16:00:00 | 0 | 2021-09-01T16:00:00 | 5 | 10 | 5
| 2021-08-01T17:00:00 | 5 | 2021-09-01T16:00:00 | 10 | 15 | 5
新 meter cycle 开始时初始 state 不为 0、但将 0 用作零基准点的 state class `SensorStateClass.TOTAL` 示例:
| t | state | last\_reset | sum | sum\_increase | sum\_decrease
| :--------------------- | -----: | ------------------- | -----: | -----------: | -----------:
| 2021-08-01T13:00:00 | 1000 | 2021-08-01T13:00:00 | 0 | 0 | 0
| 2021-08-01T14:00:00 | 1010 | 2021-08-01T13:00:00 | 10 | 10 | 0
| 2021-08-01T15:00:00 | 1005 | 2021-08-01T13:00:00 | 5 | 10 | 5
| 2021-08-01T16:00:00 | 5 | 2021-09-01T16:00:00 | 10 | 15 | 5
| 2021-08-01T17:00:00 | 10 | 2021-09-01T16:00:00 | 15 | 20 | 5
#### 状态类 `SensorStateClass.TOTAL_INCREASING`
对于 state\_class 为 `SensorStateClass.TOTAL_INCREASING` 的 sensors,decreasing value 被
解释为新 meter cycle 的开始或 meter 的更换。重要的是,集成要确保从带有 measurement noise 的 sensor 计算 value 时,value 不会错误地减少。
有一定的容差,state change 之间减少 < 10% 不会触发新 meter cycle。此 state class 适用于 gas meters、electricity meters、water meters 等。
计算 `sum` statistics 时,sensor reading 减少时的 value 不会用作零基准点,而是将零基准点设为 0。
换句话说:更新 statistics 时的逻辑是用当前 state 与前一个 state 之间的差来更新
sum 列,除非该差为负,此时不添加任何内容。
State class `SensorStateClass.TOTAL_INCREASING` 示例:
| t | state | sum
| :--------------------- | -----: | ---:
| 2021-08-01T13:00:00 | 1000 | 0
| 2021-08-01T14:00:00 | 1010 | 10
| 2021-08-01T15:00:00 | 0 | 10
| 2021-08-01T16:00:00 | 5 | 15
Sensor 不重置为 0 的 state class `SensorStateClass.TOTAL_INCREASING` 示例:
| t | state | sum
| :--------------------- | -----: | ---:
| 2021-08-01T13:00:00 | 1000 | 0
| 2021-08-01T14:00:00 | 1010 | 10
| 2021-08-01T15:00:00 | 5 | 15
| 2021-08-01T16:00:00 | 10 | 20
### 处理从不支持到支持计量单位的迁移
集成可能有具有自定义计量单位的 sensors,即它们不使用 Home Assistant 常量来设置 units。
在将这样的 sensor 迁移到 Home Assistant unit system 支持的 unit 时,旧的自定义 unit 必须与 Home Assistant 常量的值完全匹配,否则 Home Assistant 会将其视为 unit change。
例如,集成可能已将 energy sensor 的 unit 设为 `KWh`,这与 `UnitOfEnergy.KILO_WATT_HOUR`(kWh)的值不同。
在编译 long-term statistics 时会检测到这种 unit change。在没有旧 unit 和新 unit 之间关系的知识的情况下,
会抑制 statistics 的收集并生成关于 unit 不稳定的 warning。
为了便于这些 migration 情况,集成可以提供自定义 unit mapping 来声明任何不支持的 unit
等同于一个支持的 unit。这通过在集成的目录中创建 recorder platform `recorder.py`
并实现函数 `async_custom_equivalent_units`,为任何相关的 `entity_id` 返回一个 equivalent units 字典来完成。
此 mapping 将在 statistics 编译期间收集,并使集成能够平稳过渡到 Home Assistant 的 unit system。
示例实现:
```python
@callback
def async_custom_equivalent_units(hass: HomeAssistant) -> dict[str, dict[str | None, str]]:
"""Return custom equivalent units per entity id."""
return {
"sensor.example_sensor_1": {
"b/s": UnitOfDataRate.BYTES_PER_SECOND, # B/s
},
"sensor.example_sensor_2": {
"KWh": UnitOfEnergy.KILO_WATT_HOUR, # kWh
},
}
```
---
url: /developers/core/entity/siren.md
---
Siren 实体是主要用途为控制 siren 设备(如门铃或铃声)的设备。从 [`homeassistant.components.siren.SirenEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/siren/__init__.py) 派生实体平台。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据,或构建一种机制将状态更新推送到实体类实例。
:::
| 名称 | 类型 | 默认值 | 描述 |
| ----------------------- | ------ | ------------------------------------- | --------------------------------------------------------------------------------------- |
| is\_on | bool | `None` | 设备是开启还是关闭。 |
| available\_tones | list 或 dict | `NotImplementedError()` | 设备上可用的 tones 列表或字典,传递给 `turn_on` 服务操作。如果提供字典,当用户使用 tone 的 dict 值时,它在传递给集成平台之前会转换为对应的 dict 键。需要 `SirenEntityFeature.TONES` 功能。 |
### 音调
设备可以有不同的 tones。集成在支持时负责提供可用的 tones。
### 支持的功能
支持的功能常量使用按位或(`|`)运算符进行组合。
| 名称 | 描述 |
| ------------------------- | ---------------------------------------------------------------------------------------------------------------- |
| `SirenEntityFeature.TURN_ON` | 设备可以通过 `turn_on` 服务操作开启。 |
| `SirenEntityFeature.TURN_OFF` | 设备可以通过 `turn_off` 服务操作关闭。 |
| `SirenEntityFeature.TONES` | 设备支持不同的 tones(tone 可以传入 `turn_on` 服务操作)。 |
| `SirenEntityFeature.DURATION` | 设备支持为 tone 设置持续时间(duration 可以传入 `turn_on` 服务操作)。 |
| `SirenEntityFeature.VOLUME_SET` | 设备支持设置设备的音量级别(volume level 可以传入 `turn_on` 服务操作)。 |
## 方法
### 开启
`turn_on` 服务操作需要 `SirenEntityFeature.TURN_ON` 功能。
有三个可选输入参数可以传入服务操作调用,每个由支持的功能标志门控。如果在服务操作调用中提供某个输入参数时相应的标志未设置,基础平台将在传递给集成之前从调用中过滤掉它。
| 参数名称 | 数据验证 | 支持的功能标志 |
|---------------- |--------------------------------------- |------------------------------- |
| `tone` | `vol.Any(vol.Coerce(int), cv.string)` | `SirenEntityFeature.TONES` |
| `duration` | `cv.positive_int` | `SirenEntityFeature.DURATION` |
| `volume_level` | `cv.small_float` | `SirenEntityFeature.VOLUME_SET` |
```python
class MySirenEntity(SirenEntity):
# 实现以下方法之一。
def turn_on(self, **kwargs: Any) -> None:
"""Turn the device on."""
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the device on."""
```
### 关闭
`turn_off` 服务操作需要 `SirenEntityFeature.TURN_OFF` 功能。
```python
class MySirenEntity(SirenEntity):
# 实现以下方法之一。
def turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the device off."""
```
---
url: /developers/core/entity/stt.md
---
Speech-to-text (STT) 实体允许其他集成或应用程序向 STT API 流式传输语音数据并获取文本。
Speech-to-text 实体派生自 [`homeassistant.components.stt.SpeechToTextEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/stt/__init__.py)。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。
:::
| 名称 | 类型 | 默认值 | 描述
| ---- | ---- | ------- | -----------
| supported\_languages | list\[str] | **必填** | STT 服务支持的语言。
| supported\_formats | list\[AudioFormats] | **必填** | STT 服务支持的音频格式,wav 或 ogg。
| supported\_codecs | list\[AudioCodecs] | **必填** | STT 服务支持的音频编解码器,pcm 或 opus。
| supported\_bit\_rates | list\[AudioBitRates] | **必填** | STT 服务支持的音频比特率,8、16、24 或 32。
| supported\_sample\_rates | list\[AudioSampleRates] | **必填** | STT 服务支持的音频采样率。
| supported\_channels | list\[AudioChannels] | **必填** | STT 服务支持的音频声道,1 或 2。
## 方法
### 处理音频流
处理音频流方法用于向 STT 服务发送音频并获取文本。
```python
class MySpeechToTextEntity(SpeechToTextEntity):
"""Represent a Speech To Text entity."""
async def async_process_audio_stream(
self, metadata: SpeechMetadata, stream: AsyncIterable[bytes]
) -> SpeechResult:
"""Process an audio stream to STT service.
Only streaming content is allowed!
"""
```
---
url: /developers/core/entity/switch.md
---
Switch entity 开启或关闭某物,例如 relay。从 [`homeassistant.components.switch.SwitchEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/switch/__init__.py) 派生 platform entity。
要表示具有 on 或 off state 但无法控制的事物(例如只传输其 state 但无法从 Home Assistant 开启或关闭的 wall switch),使用 Binary Sensor 更好。
要表示没有 state 的事物(例如 door bell push button),使用自定义 event 或 Device Trigger 更好。
## 属性
:::tip
Properties 应该只从内存返回信息,而不要执行 I/O(如网络请求)。请实现 `update()` 或 `async_update()` 来获取数据。
:::
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
| is\_on | boolean | `None` | Switch 当前是否开启或关闭。
## 方法
### 开启
开启 switch。
```python
class MySwitch(SwitchEntity):
# 实现以下方法之一。
def turn_on(self, **kwargs: Any) -> None:
"""Turn the entity on."""
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the entity on."""
```
### 关闭
关闭 switch。
```python
class MySwitch(SwitchEntity):
# 实现以下方法之一。
def turn_off(self, **kwargs: Any) -> None:
"""Turn the entity off."""
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the entity off."""
```
### 切换
可选。如果未实现,默认会通过检查 `is_on` property 来确定调用哪个 method。
```python
class MySwitch(SwitchEntity):
# 实现以下方法之一。
def toggle(self, **kwargs: Any) -> None:
"""Toggle the entity."""
async def async_toggle(self, **kwargs: Any) -> None:
"""Toggle the entity."""
```
### 可用的设备类型
可选。这是什么类型的 device。它可能会映射到 Google device types。
| Constant | Description
| ----- | -----------
| `SwitchDeviceClass.OUTLET` | Device 是 power 的 outlet。
| `SwitchDeviceClass.SWITCH` | Device 是某种类型 entity 的 switch。
---
url: /developers/core/entity/text.md
---
Text 实体是一种允许用户向集成输入文本值的实体。从 [`homeassistant.components.text.TextEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/text/__init__.py) 派生实体平台。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据,或构建一种机制将状态更新推送到实体类实例。
:::
| 名称 | 类型 | 默认值 | 描述
| ---- | ---- | ------- | -----------
| mode | string | `text` | 定义文本在 UI 中应如何显示。可以是 `text` 或 `password`。
| native\_max | int | 255 | 文本值中字符的最大数量(含)。
| native\_min | int | 0 | 文本值中字符的最小数量(含)。
| pattern | str | `None` | 文本值必须匹配的 regex 模式。
| native\_value | str | **必填** | 文本的值。
所有实体共有的其他属性(如 `icon`、`name` 等)也适用。
## 方法
### 设置值
```python
class MyTextEntity(TextEntity):
# 实现以下方法之一。
def set_value(self, value: str) -> None:
"""Set the text value."""
async def async_set_value(self, value: str) -> None:
"""Set the text value."""
```
---
url: /developers/core/entity/time.md
---
`time` 是一种允许用户向集成输入时间的实体。从 [`homeassistant.components.time.TimeEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/time/__init__.py) 派生实体平台。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| 名称 | 类型 | 默认值 | 描述
| ---- | ---- | ------- | -----------
| native\_value | time | None | `None` | 时间的值。
所有实体共有的其他属性(如 `icon`、`name` 等)也适用。
## 方法
### 设置值
当用户或自动化想要更新值时调用。
```python
class MyTime(TimeEntity):
# 实现以下方法之一。
def set_value(self, value: time) -> None:
"""Update the current value."""
async def async_set_value(self, value: time) -> None:
"""Update the current value."""
```
---
url: /developers/core/entity/todo.md
---
待办事项实体(To-do list entity)是一种代表待办事项列表的实体。待办事项列表包含按顺序排列的待办事项(To-do items),每个事项有状态(完成或进行中)。待办事项实体派生自 [`homeassistant.components.todo.TodoListEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/todo/__init__.py)。
## 属性
:::tip
Properties 应只从内存中返回信息,不要执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| 名称 | 类型 | 默认值 | 描述 |
| ----- | ------------- | --------------------- | ------------------------------------------------------- |
| todo\_items | `list[TodoItem] \| None` | `None` | **必填。** 待办事项列表的有序内容。 |
### 状态
`TodoListEntity` 的 state 是待办事项列表中未完成事项的数量。
## 支持的功能
Supported features 通过使用 `TodoListEntityFeature` enum 中的值定义,并使用按位或(`|`)运算符组合。
| 值 | 描述 |
| -------------------------- | ------------------------------------------------------------------ |
| `CREATE_TODO_ITEM` | 实体实现了允许创建待办事项的方法。 |
| `DELETE_TODO_ITEM` | 实体实现了允许删除待办事项的方法。 |
| `UPDATE_TODO_ITEM` | 实体实现了允许更新待办事项的方法。 |
| `MOVE_TODO_ITEM` | 实体实现了允许重新排列待办事项的方法。 |
| `SET_DUE_DATE_ON_ITEM` | 实体在创建或更新待办事项时,实现了将事项的 `due` 字段设置为 `datetime.date`。 |
| `SET_DUE_DATETIME_ON_ITEM` | 实体在创建或更新待办事项时,实现了将事项的 `due` 字段设置为 `datetime.datetime`。 |
| `SET_DESCRIPTION_ON_ITEM` | 实体在创建或更新待办事项时,实现了设置事项的 `description` 字段。 |
## 方法
### 创建待办事项
待办事项实体可通过指定 `CREATE_TODO_ITEM` supported feature 来支持创建待办事项。
```python
from homeassistant.components.todo import TodoListEntity
class MyTodoListEntity(TodoListEntity):
async def async_create_todo_item(self, item: TodoItem) -> None:
"""Add an item to the To-do list."""
```
### 删除待办事项
待办事项实体可通过指定 `DELETE_TODO_ITEM` supported feature 来支持删除待办事项。集成必须支持删除多个事项。
```python
from homeassistant.components.todo import TodoListEntity
class MyTodoListEntity(TodoListEntity):
async def async_delete_todo_items(self, uids: list[str]) -> None:
"""Delete an item from the to-do list."""
```
### 更新待办事项
待办事项实体可通过指定 `UPDATE_TODO_ITEM` supported feature 来支持更新待办事项。`TodoItem` 字段 `uid` 始终存在,指示要更新哪个事项。传递的更新事项是原始事项的副本,部分字段已更新或清除。
```python
from homeassistant.components.todo import TodoListEntity
class MyTodoListEntity(TodoListEntity):
async def async_update_todo_item(self, item: TodoItem) -> None:
"""Add an item to the To-do list."""
```
### 移动待办事项
待办事项实体可通过指定 `MOVE_TODO_ITEM` supported feature 来支持在列表中重新排列待办事项。具有指定 `uid` 的待办事项应移动到列表中 `previous_uid` 指定事项之后(`None` 表示移动到列表第一位)。
```python
from homeassistant.components.todo import TodoListEntity
class MyTodoListEntity(TodoListEntity):
async def async_move_todo_item(
self,
uid: str,
previous_uid: str | None = None
) -> None:
"""Move an item in the To-do list."""
```
## TodoItem
`TodoItem` 表示待办事项列表中的单个事项。上述方法描述了在创建或更新时哪些字段是可选的。
| 名称 | 类型 | 默认值 | 描述 |
| ----------- | ---------------- | ------------ | ----------------------------------------------------------------------------------------------------------------------------------------------- |
| uid | `str \| None` | `None` | 待办事项的唯一标识符。此字段在更新和实体状态中必填。
| summary | `str \| None` | `None` | 待办事项的标题或摘要。此字段在实体状态中必填。
| status | `TodoItemStatus \| None` | `None` | 定义待办事项的整体状态,为 `NEEDS_ACTION` 或 `COMPLETED`。此字段在实体状态中必填。
| due | `datetime.date \| datetime.datetime \| None` | `None` | 待办事项预计完成的日期和时间。作为 datetime 时,必须有时区。
| description | `str \| None` | `None` | 比 summary 提供的更完整的待办事项描述。
| completed | `datetime.datetime \| None` | `None` | 指示此事项最后被标记为 `COMPLETED` 的时间戳。
---
url: /developers/core/entity/tts.md
---
Text-to-speech (TTS) 实体使 Home Assistant 能够对你说话。
Text-to-speech 实体派生自 [`homeassistant.components.tts.TextToSpeechEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/tts/__init__.py)。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。
:::
| 名称 | 类型 | 默认值 | 描述
| ---- | ---- | ------- | -----------
| supported\_languages | list\[str] | **必填** | TTS 服务支持的语言。
| default\_language | str | **必填** | TTS 服务的默认语言。
| supported\_options | list\[str] | None | TTS 服务支持的选项,如 voice、emotions。
| default\_options | Mapping\[str, Any] | None | TTS 服务的默认选项。
## 方法
### 获取支持的语音
此方法用于返回 TTS 服务某个语言支持的语音列表。
```python
class MyTextToSpeechEntity(TextToSpeechEntity):
"""Represent a Text To Speech entity."""
@callback
def async_get_supported_voices(self, language: str) -> list[Voice] | None:
"""Return a list of supported voices for a language."""
```
### 一次性生成 TTS 音频
此方法以消息和语言作为输入,返回 TTS 音频。可以作为同步或异步实现,必须实现。
```python
class MyTextToSpeechEntity(TextToSpeechEntity):
"""Represent a Text To Speech entity."""
def get_tts_audio(
self, message: str, language: str, options: dict[str, Any]
) -> TtsAudioType:
"""Load tts audio file from the engine."""
async def async_get_tts_audio(
self, message: str, language: str, options: dict[str, Any]
) -> TtsAudioType:
"""Load tts audio file from the engine."""
```
### 以消息流式方式生成 TTS 音频
大型语言模型以块的形式生成文本。TTS 服务可以接收文本消息流,并以块的形式返回音频。
此方法是可选项。当未实现时,TTS 服务将使用最终消息调用一次性方法。
```python
class MyTextToSpeechEntity(TextToSpeechEntity):
"""Represent a Text To Speech entity."""
async def async_stream_tts_audio(
self, request: TTSAudioRequest
) -> TTSAudioResponse:
"""Generate speech from an incoming message."""
```
`TTSAudioRequest` 和 `TTSAudioResponse` 对象的定义如下:
```python
@dataclass
class TTSAudioRequest:
"""Request to get TTS audio."""
language: str
options: dict[str, Any]
message_gen: AsyncGenerator[str]
@dataclass
class TTSAudioResponse:
"""Response containing TTS audio stream."""
extension: str
data_gen: AsyncGenerator[bytes]
```
---
url: /developers/core/entity/update.md
---
Update entity 是一种指示 device 或 service 是否有 update 可用的 entity。
它可以是任何 update,包括 device(如 light bulb 或 router)的 firmware
update,或 apps(以前称为 add-ons)或 containers 的 software updates。
它可用于:
* 提供 device 或 service 是否有 update 可用的指示器。
* 提供一种 install method,允许安装 update 或 software 的特定 version。
* 允许在安装新 update 之前提供 backup。
## 属性
:::tip
Properties 应该只从内存返回信息,而不要执行 I/O(如网络请求)。请实现 `update()` 或 `async_update()` 来获取数据。
:::
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
| auto\_update | bool | `False` | Entity 所代表的 device 或 service 具有 auto update logic。当此值设为 `True` 时,不能跳过 updates。
| display\_precision | int | `0` | 显示 update progress 的小数位数。
| in\_progress | bool | `False` | Update installation progress。应返回布尔值(进行中标记为 True,未进行标记为 False)。
| installed\_version | str | `None` | 当前已安装并使用的 software version。
| latest\_version | str | `None` | 可用 software 的最新 version。
| release\_summary | str | `None` | Release notes 或 changelog 的摘要。不适合长 changelogs,仅适合最多 255 字符的简短 update description 摘录。
| release\_url | str | `None` | 可用最新 version 的完整 release notes 的 URL。
| title | str | `None` | Software 的 title。这有助于区分 device 或 entity name 与已安装 software 的 title。
| update\_percentage | int, float | `None` | Update installation progress。可以返回一个 0 到 100% 的 progress 数字,或 None。
其他在所有 entity 中通用的 properties(如 `device_class`、`entity_category`、`icon`、`name` 等)仍然适用。
## 支持的功能
Supported features 通过使用 `UpdateEntityFeature` enum 中的值来定义。
| Value | Description |
|----------|--------------------------------------|
| `BACKUP` | 可以在安装 update 之前自动创建 backup。
| `INSTALL` | 可以从 Home Assistant 安装 update。
| `PROGRESS` | 此集成能够提供 progress information。如果省略,Home Assistant 会尝试提供 progress status;尽管最好是从 device 或 service API 中提取 progress。
| `SPECIFIC_VERSION` | 可以使用 `update.install` service action 安装 update 的特定 version。
| `RELEASE_NOTES` | Entity 提供获取完整 changelog 的 methods。
## 方法
### 比较版本
在需要 override 默认 version comparison logic 时应实现此 method。
以下是一个示例:
```python
def version_is_newer(self, latest_version: str, installed_version: str) -> bool:
"""Return True if latest_version is newer than installed_version."""
return AwesomeVersion(
latest_version,
find_first_match=True,
ensure_strategy=[AwesomeVersionStrategy.SEMVER],
) > AwesomeVersion(
installed_version,
find_first_match=True,
ensure_strategy=[AwesomeVersionStrategy.SEMVER],
)
```
它允许 developers 指定自定义逻辑来确定一个 version 是否比另一个更新。首次尝试应基于 [AwesomeVersion library](https://github.com/ludeeus/awesomeversion?tab=readme-ov-file#awesomeversion-class) 提供的 strategies。
### 安装
实现此 method 使用户可以直接从 Home Assistant 内安装提供的 update。
此 method 要求设置 `UpdateEntityFeature.INSTALL`。此外,如果此集成支持安装特定 version 或能够在开始 update installation process 之前创建 backup,则可以分别设置 `UpdateEntityFeature.SPECIFIC_VERSION` 和
`UpdateEntityFeature.BACKUP`。
```python
class MyUpdate(UpdateEntity):
# 实现以下方法之一。
def install(
self, version: str | None, backup: bool, **kwargs: Any
) -> None:
"""Install an update."""
async def async_install(
self, version: str | None, backup: bool, **kwargs: Any
) -> None:
"""Install an update.
Version 可以指定为安装特定 version。当为 `None` 时,
需要安装最新 version。
Backup 参数表示应在安装 update 之前创建 backup。
"""
```
### 发布说明
实现此 method 使用户可以在安装 update 之前,在 Home Assistant Frontend 的 more-info dialog 中查看完整的 release notes。
返回的字符串可以包含 markdown,frontend 会正确格式化它。
此 method 要求设置 `UpdateEntityFeature.RELEASE_NOTES`。
```python
class MyUpdate(UpdateEntity):
# 实现以下方法之一。
def release_notes(self) -> str | None:
"""Return the release notes."""
return "Lorem ipsum"
async def async_release_notes(self) -> str | None:
"""Return the release notes."""
return "Lorem ipsum"
```
### 可用的设备类型
选项性地指定 entity 的类型。
| Constant | Description
| ----- | -----------
| `UpdateDeviceClass.FIRMWARE` | Update 是 device 的 firmware update。
---
url: /developers/core/entity/vacuum.md
---
从 [`homeassistant.components.vacuum.StateVacuumEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/vacuum/__init__.py) 派生 entity 平台。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| 名称 | 类型 | 默认值 | 描述
| ---- | ---- | ------- | -----------
| fan\_speed | string | `none` | 当前的 fan speed。
| fan\_speed\_list | list | `NotImplementedError()`| 可用的 fan speeds 列表。
| name | string | **必填** | Entity 的名称。
| activity | VacuumActivity | **必填** | 返回下面"状态"部分所列状态之一。
## 状态
设置 state 时应返回 `VacuumActivity` 枚举中的一个值,通过 `activity` 属性返回。
| 值 | 描述
| ----- | -----------
| `CLEANING` | 吸尘器当前正在清洁。
| `DOCKED` | 吸尘器当前已停放在 dock 上,假定 docked 也可能表示充电中。
| `IDLE` | 吸尘器既未暂停、未停放在 dock 上,也没有任何错误。
| `PAUSED` | 吸尘器曾正在清洁,但在未返回 dock 的情况下被暂停。
| `RETURNING` | 吸尘器已完成清洁,当前正在返回 dock,但尚未停放在 dock 上。
| `ERROR` | 吸尘器在清洁过程中遇到错误。
## 支持的功能
支持的功能通过使用 `VacuumEntityFeature` 枚举中的值来定义,
并使用按位或(`|`)运算符组合。
注意,所有从 `homeassistant.components.vacuum.StateVacuumEntity` 派生的 vacuum entity 平台
都必须设置 `VacuumEntityFeature.STATE` 标志。
| 值 | 描述 |
| -------------- | ---------------------------------------------------- |
| `CLEAN_AREA` | 吸尘器支持清洁特定区域。 |
| `CLEAN_SPOT` | 吸尘器支持点清洁。 |
| `FAN_SPEED` | 吸尘器支持设置 fan speed。 |
| `LOCATE` | 吸尘器支持定位。 |
| `MAP` | 吸尘器支持获取其地图。 |
| `PAUSE` | 吸尘器支持 pause 命令。 |
| `RETURN_HOME` | 吸尘器支持返回 dock 的命令。 |
| `SEND_COMMAND` | 吸尘器支持向吸尘器发送命令。 |
| `START` | 吸尘器支持 start 命令。 |
| `STATE` | 吸尘器支持返回其 state。 |
| `STOP` | 吸尘器支持 stop 命令。 |
## 方法
### `async_get_segments`
返回一个 `Segment` 对象列表,表示吸尘器报告的可清洁 segment。当支持 `CLEAN_AREA` 时,集成平台必须实现此方法。它在配置 area mapping 时被调用,因此应返回最新的信息。
```python
async def async_get_segments(self) -> list[Segment]:
"""Get the segments that can be cleaned."""
```
`Segment` dataclass 定义如下:
```python
@dataclass(slots=True)
class Segment:
"""Represents a cleanable segment reported by a vacuum."""
id: str
name: str
group: str | None = None
```
`id` 必须对于给定 vacuum entity 的所有 segment 全局唯一,无论属于哪个 group。`group` 字段仅用于在 mapping UI 中对 segment 进行分组。
### `clean_segments` 或 `async_clean_segments`
按 ID 清洁指定的 segment。当支持 `CLEAN_AREA` 时,集成平台必须实现此方法。它在 `clean_area` 服务将目标 area 解析为 segment 后被内部调用。
```python
async def async_clean_segments(self, segment_ids: list[str], **kwargs: Any) -> None:
"""Perform an area clean."""
```
### `last_seen_segments`
一个属性,返回吸尘器报告的、在上次配置 area mapping 时可用的 segment。集成可以在其更新周期中将其与当前设备 segment 进行比较,以检测变化,并在适当时调用 `async_create_segments_issue`。如果尚未保存任何 mapping,则返回 `None`,此时不应触发 issue。
```python
@property
def last_seen_segments(self) -> list[Segment] | None:
"""Return segments as seen by the user, when last mapping the areas."""
```
### `async_create_segments_issue`
一个辅助方法,当吸尘器报告的 segment 与上次配置 area mapping 时可用的 segment 不同时创建 repair issue。当 segment 变化需要调整 area mapping 时,集成应调用此方法。产生的 repair issue 会提示用户重新配置 area mapping,从而相应地更新 `last_seen_segments`。
```python
@callback
def async_create_segments_issue(self) -> None:
"""Create a repair issue when vacuum segments have changed."""
```
### `clean_spot` 或 `async_clean_spot`
执行点清洁。
### `locate` 或 `async_locate`
定位吸尘器。
### `pause` 或 `async_pause`
暂停清洁任务。
### `return_to_base` 或 `async_return_to_base`
设置吸尘器返回 dock。
### `send_command` 或 `async_send_command`
向吸尘器发送命令。
### `set_fan_speed` 或 `async_set_fan_speed`
设置 fan speed。
### `start` 或 `async_start`
启动或恢复清洁任务。
### `stop` 或 `async_stop`
停止吸尘器,不返回 dock。
---
url: /developers/core/entity/valve.md
---
Valve 实体控制如水阀或气阀等阀门设备。从 [`homeassistant.components.valve.ValveEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/valve/__init__.py) 派生平台实体。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| 名称 | 类型 | 默认值 | 描述
| ----------------------- | ---- | ------- | -----------
| current\_valve\_position | `int \| None` | `None` | 阀门的当前位置,其中 0 表示关闭,100 表示完全打开。此属性在 `reports_position = True` 的阀门上必填,用于确定状态。
| is\_closed | `bool \| None` | `None` | 阀门是否已关闭。用于确定不报告位置的阀门的 `state`。
| is\_closing | `bool \| None` | `None` | 阀门是否正在关闭。用于确定 `state`。
| is\_opening | `bool \| None` | `None` | 阀门是否正在打开。用于确定 `state`。
| reports\_position | bool | **必填** | 阀门是否知道其位置。
### 设备类型
| 常量 | 描述
|----------|-----------------------|
| `ValveDeviceClass.WATER` | 水阀的控制。
| `ValveDeviceClass.GAS` | 气阀的控制。
### 状态
状态通过设置其属性来定义。结果状态使用 `ValveState` 枚举返回以下成员之一。
| 值 | 描述 |
|----------|--------------------------------------------------------------------|
| `OPENING`| 阀门正在打开以达到设定的位置。 |
| `OPEN` | 阀门已到达打开位置。 |
| `CLOSING`| 阀门正在关闭以达到设定的位置。 |
| `CLOSED` | 阀门已到达关闭位置。 |
## 支持的功能
支持的功能通过使用 `ValveEntityFeature` 枚举中的值来定义,并使用按位或(`|`)运算符进行组合。
| 值 | 描述 |
| ------------------- | -------------------------------------------------------------------------------- |
| `OPEN` | 阀门支持打开。 |
| `CLOSE` | 阀门支持关闭。 |
| `SET_POSITION` | 阀门支持移动到打开和关闭之间的特定位置。 |
| `STOP` | 阀门支持停止当前操作(打开、关闭、设置位置) |
## 方法
### 打开阀门
仅当设置了 `ValveEntityFeature.OPEN` 标志时实现此方法。对于可以设置位置的阀门,此方法应留空实现,仅需要 `set_valve_position`。
```python
class MyValve(ValveEntity):
# 实现以下方法之一。
def open_valve(self) -> None:
"""Open the valve."""
async def async_open_valve(self) -> None:
"""Open the valve."""
```
### 关闭阀门
仅当设置了 `ValveEntityFeature.CLOSE` 标志时实现此方法。对于可以设置位置的阀门,此方法应留空实现,仅需要 `set_valve_position`。
```python
class MyValve(ValveEntity):
# 实现以下方法之一。
def close_valve(self) -> None:
"""Close valve."""
async def async_close_valve(self) -> None:
"""Close valve."""
```
### 设置阀门位置
仅当设置了 `ValveEntityFeature.SET_POSITION` 标志时实现此方法。能够设置位置的阀门必须实现此方法。
```python
class MyValve(ValveEntity):
# 实现以下方法之一。
def set_valve_position(self, position: int) -> None:
"""Move the valve to a specific position."""
async def async_set_valve_position(self, position: int) -> None:
"""Move the valve to a specific position."""
```
### 停止阀门
仅当设置了 `ValveEntityFeature.STOP` 标志时实现此方法。
```python
class MyValve(ValveEntity):
# 实现以下方法之一。
def stop_valve(self) -> None:
"""Stop the valve."""
async def async_stop_valve(self) -> None:
"""Stop the valve."""
```
---
url: /developers/core/entity/wake_word.md
---
Wake word detection 实体允许其他集成或应用程序在音频流中检测唤醒词(也称为 hotwords)。
Wake word detection 实体派生自 [`homeassistant.components.wake_word.WakeWordDetectionEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/wake_word/__init__.py)。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。
:::
所有实体共有的属性(如 `icon`、`name` 等)适用。
## 方法
### 获取支持的唤醒词
返回实体支持的唤醒词。每个 `WakeWord` 都有一个 `id`(唯一标识符)、一个 `name`(人类可读的名称)和一个可选的 `phrase`。
```python
class MyWakeWordDetectionEntity(WakeWordDetectionEntity):
"""Represent a Wake Word Detection entity."""
async def get_supported_wake_words(self) -> list[WakeWord]:
"""Return a list of supported wake words."""
```
### 处理音频流
处理音频流方法用于检测唤醒词。它必须返回一个 `DetectionResult`,或者如果音频流在没有检测到的情况下结束则返回 `None`。
```python
class MyWakeWordDetectionEntity(WakeWordDetectionEntity):
"""Represent a Wake Word Detection entity."""
async def _async_process_audio_stream(
self, stream: AsyncIterable[tuple[bytes, int]], wake_word_id: str | None
) -> DetectionResult | None:
"""Try to detect wake word(s) in an audio stream with timestamps.
Audio must be 16Khz sample rate with 16-bit mono PCM samples.
"""
```
音频流由形如 `(audio_chunk, timestamp)` 的元组组成,其中:
* `audio_chunk` 是一块 16 位有符号单声道 PCM 采样,采样率为 16Khz
* `timestamp` 是自音频流开始以来的毫秒数
如果检测到唤醒词,将返回一个 `DetectionResult`,包含:
* `wake_word_id` - 检测到的唤醒词的唯一标识符
* `timestamp` - 检测发生时音频块的 timestamp
* `queued_audio` - 将转发到 speech-to-text 的可选音频块(见下文)
在 [Assist pipeline](/developers/voice/pipelines.md) 中,音频流在 wake word detection 和 speech-to-text 之间共享。这意味着在 wake word detection 期间移除的任何音频块**无法**由 speech-to-text 处理,除非通过 `DetectionResult` 的 `queued_audio` 传回。
---
url: /developers/core/entity/water-heater.md
---
从 [`homeassistant.components.water_heater.WaterHeaterEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/water_heater/__init__.py) 派生实体平台。
## 属性
:::tip
属性应始终仅返回内存中的信息,而不是执行 I/O(如网络请求)。实现 `update()` 或 `async_update()` 来获取数据。
:::
| 名称 | 类型 | 默认值 | 描述
| --------------------- | ----------- | --------- | -----------
| `min_temp` | `float` | 110°F | 可以设置的最低温度。
| `max_temp` | `float` | 140°F | 可以设置的最高温度。
| `current_temperature` | `float` | `None` | 当前温度。
| `target_temperature` | `float` | `None` | 我们试图达到的温度。
| `target_temperature_high` | `float` | `None` | 我们试图达到的温度的上界。
| `target_temperature_low` | `float` | `None` | 我们试图达到的温度的下界。
| `target_temperature_step` | `float` | `None` | 目标温度可增加或减少的步长。
| `temperature_unit` | `str` | `NotImplementedError` | `UnitOfTemperature.CELSIUS`、`UnitOfTemperature.FAHRENHEIT` 或 `UnitOfTemperature.KELVIN` 之一。
| `current_operation` | `string` | `None` | 当前的 operation mode。
| `operation_list` | `List[str]` | `None` | 可能的 operation mode 列表。
| `supported_features` | `WaterHeaterEntityFeature` | `WaterHeaterEntityFeature(0)`(无功能) | 支持的功能列表。
| `is_away_mode_on` | `bool` | `None` | away mode 的当前状态。
允许的 operation mode 是基础组件中指定的状态,water\_heater 组件的实现不能有所不同。
属性必须遵循 `temperature_unit` 中定义的单位。
## 状态
| 状态 | 描述
| ----- | -----------
| `STATE_ECO` | 节能模式,提供节能和快速加热。
| `STATE_ELECTRIC` | 仅电动模式,能耗最高。
| `STATE_PERFORMANCE` | 高性能模式。
| `STATE_HIGH_DEMAND` | 当热水器功率不足时满足高需求。
| `STATE_HEAT_PUMP` | 加热最慢,但能耗较少。
| `STATE_GAS` | 仅燃气模式,能耗最高。
| `STATE_OFF` | 热水器已关闭。
## 支持的功能
支持的功能通过使用 `WaterHeaterEntityFeature` 枚举中的值来定义,并使用按位或(`|`)运算符进行组合。
| 值 | 描述 |
| -------------------- | ------------------------- |
| `TARGET_TEMPERATURE` | 可以设置温度 |
| `OPERATION_MODE` | 可以设置 operation mode |
| `AWAY_MODE` | 可以设置 away mode |
| `ON_OFF` | 可以开启或关闭 |
## 方法
### `set_temperature` 或 `async_set_temperature`
设置热水器应将水加热到的温度。
### `set_operation_mode` 或 `async_set_operation_mode`
设置热水器的 operation mode。必须在 operation\_list 中。
### `turn_away_mode_on` 或 `async_turn_away_mode_on`
将热水器设置为 away mode。
### `turn_away_mode_off` 或 `async_turn_away_mode_off`
将热水器恢复到上一个 operation mode。关闭 away mode。
### `turn_on` 或 `async_turn_on`
开启热水器。
### `turn_off` 或 `async_turn_off`
关闭热水器。
---
url: /developers/core/entity/weather.md
---
从 [`homeassistant.components.weather.WeatherEntity`](https://github.com/home-assistant/core/blob/dev/homeassistant/components/weather/__init__.py) 派生 entity platforms。
## 属性
:::tip
Properties 应始终只从内存返回信息,而不要执行 I/O(如网络请求)。请实现 `update()` 或 `async_update()` 来获取数据。
:::
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
| cloud\_coverage | int | `None` | 当前的 cloud coverage(%)。
| condition | string | **必需** | 当前的 weather condition。
| humidity | float | `None` | 当前的 humidity(%)。
| native\_apparent\_temperature | float | `None` | 当前的 apparent(体感)temperature,单位为 °C 或 °F。
| native\_dew\_point | float | `None` | 当前的 dew point temperature,单位为 °C 或 °F。
| native\_precipitation\_unit | string | `None` | 降水计量单位;mm 或 in。
| native\_pressure | float | `None` | 当前的 air pressure,单位为 hPa、mbar、inHg 或 mmHg。
| native\_pressure\_unit | string | `None` | Air pressure 单位;hPa、mbar、inHg 或 mmHg。如果设置了 native\_pressure,则为必需。
| native\_temperature | float | **必需** | 当前的 temperature,单位为 °C 或 °F。
| native\_temperature\_unit | string | **必需** | Temperature 单位;°C 或 °F。
| native\_visibility | float | `None` | 当前的 visibility,单位为 km 或 mi。
| native\_visibility\_unit | string | `None` | Visibility 单位;km 或 mi。如果设置了 native\_visibility,则为必需。
| native\_wind\_gust\_speed | float | `None` | 当前的 wind gust speed,单位为 Beaufort、m/s、km/h、mi/h、ft/s 或 kn。
| native\_wind\_speed | float | `None` | 当前的 wind speed,单位为 Beaufort、m/s、km/h、mi/h、ft/s 或 kn。
| native\_wind\_speed\_unit | string | `None` | Wind speed 单位;Beaufort、m/s、km/h、mi/h、ft/s 或 kn。如果设置了 native\_wind\_speed,则为必需。
| ozone | float | `None` | 当前的 ozone 浓度。
| uv\_index | float | `None` | 当前的 [UV index](https://en.wikipedia.org/wiki/Ultraviolet_index)。
| wind\_bearing | float or string | `None` | 当前的 wind bearing,表示为 azimuth angle(度数)或 1-3 字母的 cardinal direction。
### 单位转换
Properties 必须遵循表中各自计量单位所提及的单位。
对于用户,properties 将根据 unit system 进行呈现。这是通过创建 state objects 时自动转换单位来实现的。
对于每个 weather entity,用户还可以选择覆盖 presentation units,即 state objects 中使用的单位。
### state 和 condition 的推荐值
以下 weather conditions 已包含在我们的 translation files 中,并会显示相应的 icon。
| Condition | Description
| --------- | -----------
| clear-night | 晴朗的夜空
| cloudy | 多云
| exceptional | 极端天气
| fog | 雾
| hail | 冰雹
| lightning | 雷电 / 雷暴
| lightning-rainy | 雷电 / 雷暴并伴随降雨
| partlycloudy | 少云
| pouring | 倾盆大雨
| rainy | 下雨
| snowy | 下雪
| snowy-rainy | 雨夹雪
| sunny | 晴天
| windy | 有风
| windy-variant | 有风并伴随云层
这意味着 `weather` platforms 不需要自己支持各种语言。
## 支持的功能
Supported features 通过使用 `WeatherEntityFeature` enum 中的值来定义,
并使用按位或(`|`)运算符组合。
| Value | Description |
| -------------------------- | ------------------------------------------------------------------------------------------- |
| `FORECAST_DAILY` | Device 支持 daily forecast。 |
| `FORECAST_HOURLY` | Device 支持 hourly forecast。 |
| `FORECAST_TWICE_DAILY` | Device 支持 twice-daily forecast。 |
## 天气预报
Weather platform 可以选项性地提供 weather forecasts。对 weather forecasts 的支持通过设置正确的 [supported feature](#supported-features) 来指示。Weather forecasts 不属于 entity 的 state,而是通过单独的 API 提供。Consumer(例如 frontend)可以订阅 weather forecast updates。
### 预报数据
Forecast data 可以是 daily、hourly 或 twice\_daily。集成可以提供其中任意一种或全部。
集成应实现下面文档中说明的一个或多个异步方法 `async_forecast_daily`、`async_forecast_hourly` 和 `async_forecast_twice_daily` 来获取 forecast data。
| Name | Type | Default | Description
| ---- | ---- | ------- | -----------
| datetime | string | **必需** | UTC datetime,格式为 RFC 3339。
| is\_daytime | bool | `None` | `async_forecast_twice_daily` 返回的 forecast data 中必需,用于指示是白天还是夜晚。
| cloud\_coverage | int | `None` | Cloud coverage(%)。
| condition | string | `None` | 该 time point 的 weather condition。
| humidity | float | `None` | Humidity(%)。
| native\_apparent\_temperature | float | `None` | Apparent(体感)temperature,单位为 °C 或 °F。
| native\_dew\_point | float | `None` | Dew point temperature,单位为 °C 或 °F。
| native\_precipitation | float | `None` | 降水量,单位为 mm 或 in。
| native\_pressure | float | `None` | Air pressure,单位为 hPa、mbar、inHg 或 mmHg。
| native\_temperature | float | **必需** | 较高 temperature,单位为 °C 或 °F。
| native\_templow | float | `None` | 日间较低 temperature,单位为 °C 或 °F。
| native\_wind\_gust\_speed | int | `None` | Wind gust speed,单位为 Beaufort、m/s、km/h、mi/h、ft/s 或 kn。
| native\_wind\_speed | int | `None` | Wind speed,单位为 Beaufort、m/s、km/h、mi/h、ft/s 或 kn。
| precipitation\_probability | int | `None` | 降水概率(%)。
| uv\_index | float | `None` | UV index。
| wind\_bearing | float or string | `None` | Wind bearing,表示为 azimuth angle(度数)或 1-3 字母的 cardinal direction。
Forecast data 在适用的情况下,需要遵循与 properties 定义相同的计量单位。
### 获取天气预报的方法
调用这些 methods 以从 api 获取 forecasts。
```python
class MyWeatherEntity(WeatherEntity):
"""Represent a Weather entity."""
async def async_forecast_daily(self) -> list[Forecast] | None:
"""返回 daily forecast,使用 native units。
仅在设置了 `WeatherEntityFeature.FORECAST_DAILY` 时实现此方法
"""
async def async_forecast_twice_daily(self) -> list[Forecast] | None:
"""返回 twice daily forecast,使用 native units。
仅在设置了 `WeatherEntityFeature.FORECAST_TWICE_DAILY` 时实现此方法
"""
async def async_forecast_hourly(self) -> list[Forecast] | None:
"""返回 hourly forecast,使用 native units。
仅在设置了 `WeatherEntityFeature.FORECAST_HOURLY` 时实现此方法
"""
```
### 更新天气预报
强烈建议 weather entity 缓存获取到的 weather forecasts,以避免不必要的 API 访问。
当有新的 weather forecast 可用时,应使 weather forecast cache 失效,并 `await` `WeatherEntity.async_update_listeners` 方法,以触发将更新后的 weather forecast 推送到所有 active subscribers。如果有 active listeners,`WeatherEntity.async_update_listeners` 将调用相应的 `async_forecast_xxx` methods。如果没有 active listeners,`WeatherEntity.async_update_listeners` 不会调用任何 `async_forecast_xxx` methods。
---
url: /developers/core/integration-quality-scale/checklist.md
---
import Checklist from './\_includes/checklist.jsx'
在更改集成的质量等级时,请确保在提交 PR 更改质量等级之前已完成所有规则。
在 PR 描述中,请附上本检查清单的副本,并标记已完成的规则。
请确保添加相关代码的链接,以加快评定流程。
---
url: /developers/core/integration-quality-scale/index.md
---
import RuleOverview from './\_includes/rule\_overview.jsx'
Integration quality scale 是 Home Assistant 根据用户体验、功能、代码质量和开发者体验对集成进行评级的框架。
为了进行评级,项目提出了一系列 tier,每个 tier 都有其特定含义。
## 等级分级
共有 4 个 scaled tiers:bronze、silver、gold 和 platinum。
要达到某个 tier,集成必须满足该 tier 及其以下所有 tier 的全部规则。
这些 tier 定义如下。
### 🥉 Bronze
Bronze tier 是基准标准,也是所有新集成的要求。它满足代码质量、功能和用户体验方面的最低要求。它符合基本期望,为用户提供与设备和服务交互的可靠基础。
文档提供直接从 Home Assistant 用户界面设置集成的指南。
从技术角度来看,此集成已审查以符合所有基准标准,这是我们要求所有新集成遵守的标准,包括设置集成的自动化测试。
Bronze tier 具有以下特征:
* 可以通过 UI 轻松设置。
* 源代码符合基本编码标准和开发准则。
* 保护此集成的自动化测试可以正确配置。
* 提供基本的最终用户文档,足以让用户轻松分步入门。
### 🥈 Silver
Silver tier 在 "Bronze" 级别的基础上提升了集成的可靠性和稳健性,确保稳定的运行时体验。它确保集成正确处理错误,例如当对设备或服务的认证失败时,处理离线设备以及其他错误。
这些集成的文档提供使用此集成时 Home Assistant 中可用的功能信息,以及出现问题时的故障排除信息。
此集成有一个或多个活跃的 code owners,帮助维护它以确保此级别上的体验现在和未来持续存在。
Silver tier 具有以下特征:
* 提供 "Bronze" 的所有功能。
* 在各种条件下提供稳定的用户体验。
* 有一个或多个活跃的 code owners 帮助维护集成。
* 正确且自动地从连接错误或离线设备中恢复,不会填满日志文件,也不会产生不必要的消息。
* 如果与设备或服务的认证失败,自动触发重新认证。
* 提供有关集成功能的详细文档和问题故障排除说明。
### 🥇 Gold
集成用户体验的金标准,为集成的设备和服务提供广泛而全面的支持。Gold-tier 集成旨在用户友好、功能齐全,并面向更广泛的受众。
在可能的情况下,设备会自动发现以实现轻松无缝的设置,并且它们的 firmware/software 可以直接从 Home Assistant 更新。
所有提供的设备和 entity 都有逻辑化的名称且完全可翻译,并且已正确分类并启用于长期统计使用。
这些集成的文档非常详细,主要面向最终用户且易于理解。除提供集成的一般信息外,文档还提供可能的示例用例、兼容设备列表、集成提供的已描述 entity 列表,以及集成提供的可用 actions 的广泛描述和使用示例。强烈建议结合示例 automations、dashboards、可用 Blueprints 以及指向额外外部资源的链接。
集成提供调试问题的手段,包括下载 diagnostic 信息和文档化的故障排除说明。如有需要,可以通过 UI 重新配置集成。
从技术角度来看,集成需要对其代码库具有完整的自动化测试覆盖,以确保当前和未来的集成质量得以保持。
所有在 Works with Home Assistant 项目中提供设备的集成至少需要达到此 tier。
Gold tier 具有以下特征:
* 提供 "Silver" 的所有功能。
* 提供集成能够提供的最佳最终用户体验;流畅且直观。
* 可以被自动发现,简化集成设置。
* 集成可以被重新配置和调整。
* 支持 translations。
* 面向最终用户的详细文档。
* 在可能时支持通过 Home Assistant 更新设备的 software/firmware。
* 集成具有覆盖整个集成的自动化测试。
* Works with Home Assistant 项目中提供设备的集成的必需级别。
### 🏆 Platinum
Platinum 是集成可以达到的最高 tier,是 Home Assistant 中质量的典范。它不仅提供最佳的用户体验,还通过遵循最高标准、卓越的代码质量以及优化的性能和效率来实现技术卓越。
Platinum tier 具有以下特征:
* 提供 "Gold" 的所有功能。
* 所有源代码遵循所有编码和 Home Assistant 集成标准及最佳实践,并通过类型注释和清晰的代码注释完全类型化,以提高代码清晰度和可维护性。
* 完全异步的集成代码库确保高效运行。
* 实现高效的数据处理,减少网络和 CPU 使用。
### 跟踪已实现的规则
致力于达到更高 tier 或已有 tier 的集成,必须向其集成中添加 `quality_scale.yaml` 文件。
此文件的目的是跟踪已实现规则的进度,并跟踪被豁免的规则及豁免原因。
此文件的示例如下:
```yaml
rules:
config_flow: done
docs_high_level_description:
status: exempt
comment: This integration does not connect to any device or service.
```
### 调整集成的等级
Home Assistant 鼓励我们的贡献者将其集成提升到尽可能高的 tier,为我们的贡献者提供出色的编码体验,为用户带来最佳体验。
当集成达到某个 tier 的最低要求时,贡献者可以打开 pull request 来调整该集成的级别。
此请求需要附带每个 scale rule 的完整清单(包括所有较低 tier 的规则),证明其已满足这些要求。
清单可以在[此处](/developers/core/integration-quality-scale/checklist.md)找到。
Home Assistant core team 审查并批准后,集成将在 Home Assistant 的下一个 major release 中显示新的 tier。
除了将集成升级到更高的 tier 之外,集成也可能被降级到更低的 tier。
例如,当不再存在活跃的集成 code owner 时就会发生这种情况。
在此特定示例中,即使集成在其他方面完全符合 "Platinum" tier,它也将被降级为 "Bronze"。
无需创建文档 pull request。在集成的 tier 已在代码库中更新后,quality scale 将在 Home Assistant 的下一个 minor release 的文档中自动更新。
### 对各等级中包含的规则的调整
IoT 世界以及 Home Assistant 使用的所有技术都在快速发展;不仅体现在 Home Assistant 可以支持或做的事情上,也体现在 Home Assistant 所基于的软件上。Home Assistant 正在以快速的速度在行业中引领技术。
这也意味着随着时间的推移会出现新的见解和新开发并采用的最佳实践,从而导致集成 quality scale 个别规则的新增和改进。
如果某个 tier 被调整,该 tier 中的所有集成都需要重新评估并相应调整。
:::info
一个例外是拥有属于 Works with Home Assistant 项目设备的集成。这些集成将被标记为沿用其现有 tier。
:::
## 特殊等级
还有 4 个 special tiers,用于无法放入 scaled tier 列表的集成。
这是因为它们要么是 core 的内部组成部分,要么根本不在 core 中,要么不符合与 scaled tiers 相比评级的最低要求。
Special tiers 定义如下。
### ❓ No score
这些集成可以通过 Home Assistant 用户界面设置。"No score" 并不表示它们差或有 bug,而是表示它们尚未根据 quality scale 进行评估,或者需要一些维护才能达到现在被视为最低的 "Bronze" 标准。
"No score" tier 不能分配给新集成,因为它们引入时至少需要达到 "Bronze" 级别。Home Assistant 项目鼓励社区帮助更新这些没有分数的集成,使其至少满足 "Bronze" 级别要求。
特征:
* 尚未评分或缺乏足够的评分信息。
* 可以通过 UI 设置,但可能需要增强以获得更好的体验。
* 可能正常工作,但尚未根据当前标准进行验证。
* 文档通常只提供基本设置步骤。
### 🏠 Internal
此 tier 分配给 Home Assistant 内部使用的集成。这些集成为 Home Assistant 的核心程序或其他集成在其之上构建提供基本组件和构建块。
Internal 集成由 Home Assistant 项目维护,并受严格的架构设计程序约束。
特征:
* Home Assistant 核心程序的内部、内置构建块。
* 提供其他集成使用和在其之上构建的构建块。
* 由 Home Assistant 项目维护。
### 💾 Legacy
Legacy 集成是较旧的集成,已在 Home Assistant 中存留多年,可能从 inception 起就存在。它们只能通过 YAML 文件配置,并且往往缺乏活跃的维护者(code owners)。这些集成可能设置复杂,在使用和功能上不符合当前/现代最终用户的期望。
Home Assistant 项目鼓励社区帮助将这些集成迁移到 UI 并更新它们以符合现代标准,使这些集成对所有人可访问。
特征:
* 设置过程复杂;只能通过 YAML 配置,没有基于 UI 的设置。
* 可能缺乏活跃的 code ownership 和维护。
* 可能缺少近期更新或 bug 修复。
* 文档可能仍面向开发者。
### 📦 Custom
Custom 集成由社区开发并发布,为 Home Assistant 提供额外的功能和设备与服务支持。这些集成不包含在官方的 Home Assistant 发布中,可以手动安装或通过第三方工具(如 HACS(Home Assistant Community Store))安装。
Home Assistant 项目不审查、安全审计、维护或支持第三方 custom 集成。鼓励用户在安装前谨慎行事,并审查 custom 集成的源代码和社区反馈。
鼓励并邀请开发者通过使 custom 集成与 integration quality scale 对齐并提交纳入请求,将其贡献给 Home Assistant 项目。
特征:
* 不包含在官方的 Home Assistant 发布中。
* 可手动安装或通过社区工具(如 HACS)安装。
* 由个人开发者或社区成员维护。
* 用户体验可能差异很大。
* 功能、安全性和稳定性可能差异很大。
* 文档可能有限。
---
url: /developers/core/integration-quality-scale/rules.md
---
import RuleOverview from './\_includes/rule\_overview.jsx'
每个 tier 的规则如下所述,并配有各自的页面,包含示例和更多信息。
### 🥉 Bronze
### 🥈 Silver
### 🥇 Gold
### 🏆 Platinum
---
url: /developers/core/integration-quality-scale/rules/action-exceptions.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 理由
执行 service action 时可能会出现意外情况。
当这种情况发生时,集成应引发异常以指示出了问题。
异常消息将显示在 UI 中给用户,可用于帮助诊断问题。
消息将从附加的 translation string 或异常参数中生成。
## 示例实现
当问题由使用不当引起时(例如输入错误或引用不存在的内容),应引发 `ServiceValidationError`。
当问题由 service action 本身的错误引起时(例如网络错误或服务中的 bug),应引发 `HomeAssistantError`。
:::note
此规则不仅限于集成注册自定义 service actions。
平台操作(例如切换 `switch` 或打开 `light`)也适用于此规则,并在遇到失败时应引发异常。
:::
在此示例中,我们展示了一个注册为 Home Assistant 中 service action 的函数。
如果输入不正确(当结束日期早于开始日期时),将引发 `ServiceValidationError`,如果无法连接到服务,则引发 `HomeAssistantError`。
```python {8,12} showLineNumbers
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
async def async_set_schedule(call: ServiceCall) -> ServiceResponse:
"""Set the schedule for a day."""
start_date = call.data[ATTR_START_DATE]
end_date = call.data[ATTR_END_DATE]
if end_date < start_date:
raise ServiceValidationError("End date must be after start date")
try:
await client.set_schedule(start_date, end_date)
except MyConnectionError as err:
raise HomeAssistantError("Could not connect to the schedule") from err
```
## 附加资源
有关引发异常的更多信息,请参阅[文档](/developers/core/platform/raising_exceptions.md)。
## 例外
此规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/action-setup.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 理由
集成可以向 Home Assistant 添加自己的 service actions。
过去,它们经常在 `async_setup_entry` 方法中注册,并在 `async_unload_entry` 方法中移除。
这导致 service actions 仅在存在已加载的 entry 时才可用。
这并不理想,因为这样我们无法验证用户使用这些 service actions 创建的 automations,因为可能存在 configuration entry 无法加载的情况。
我们更倾向于集成在 `async_setup` 方法中设置它们的 service actions。
这样,如果目标 configuration entry 未加载,我们可以让用户知道 service action 为什么不工作。
验证应在 service action 内部进行,如果输入无效应引发 `ServiceValidationError`。
## 示例实现
以下是 service action 在 `async_setup` 方法中注册的代码片段。
在此示例中,service call 需要 configuration entry id 作为参数。
这用于首先获取 configuration entry,然后检查它是否已加载。
如果 configuration entry 不存在或找到的 configuration entry 未加载,我们将引发相关错误并显示给用户。
提供 description placeholders 以启用 service 参数的翻译,例如,引用需要独立于 service 描述进行本地化或更新的外部资源(如文档 URL)。
`__init__py`:
```python {13-20} showLineNumbers
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up my integration."""
async def async_get_schedule(call: ServiceCall) -> ServiceResponse:
"""Get the schedule for a specific range."""
if not (entry := hass.config_entries.async_get_entry(call.data[ATTR_CONFIG_ENTRY_ID])):
raise ServiceValidationError("Entry not found")
if entry.state is not ConfigEntryState.LOADED:
raise ServiceValidationError("Entry not loaded")
client = cast(MyConfigEntry, entry).runtime_data
...
hass.services.async_register(
DOMAIN,
SERVICE_GET_SCHEDULE,
async_get_schedule,
schema=SERVICE_GET_SCHEDULE_SCHEMA,
supports_response=SupportsResponse.ONLY,
description_placeholders={"example_url": "https://schedule.example.com"}
)
```
## 附加资源
有关如何设置 service actions 的更多信息,请参阅[service 文档](/developers/dev_101_services.md)。
## 例外
此规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/appropriate-polling.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 理由
在理想世界中,所有集成都会使用基于推送的数据接口,即设备或服务会在有新数据时通知我们。
这样可以减少 Home Assistant 发出的请求数量。
然而,在现实世界中,许多设备和服务无法进行基于推送的通信,因此我们必须使用轮询。
为了负责任地进行轮询,我们应该设置一个适用于大多数用户的合适轮询间隔。
实际上并没有明确的定义来界定什么是合适的轮询间隔,因为它取决于被轮询的设备或服务。
例如,我们不应该每 5 秒轮询一次空气质量传感器,因为数据不会变化那么频繁。
在这些情况下,超过 99% 的用户都会接受一分钟或更长的轮询间隔。
再举一个例子,如果我们轮询一个云服务的太阳能板数据,而数据每小时更新一次。
每分钟轮询一次是没有意义的,因为两次轮询之间数据不会改变。
对于确实希望更频繁更新的用户,他们可以[定义自定义轮询间隔](https://www.home-assistant.io/common-tasks/general/#defining-a-custom-polling-interval)
## 示例实现
有两种方式设置轮询间隔。
使用哪种方式取决于集成如何轮询数据。
当使用 update coordinator 时,可以通过在 coordinator 中设置 `update_interval` 参数或属性来设置轮询间隔。
当使用内置的 entity update 方法时,通过设置 `should_poll` entity 属性为 `True`,可以在 platform 模块中设置 `SCAN_INTERVAL` 常量来设置轮询间隔。
`coordinator.py`:
```python {10} showLineNumbers
class MyCoordinator(DataUpdateCoordinator[MyData]):
"""Class to manage fetching data."""
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize coordinator."""
super().__init__(
hass,
logger=LOGGER,
name=DOMAIN,
update_interval=timedelta(minutes=1),
)
```
`sensor.py`:
```python {1} showLineNumbers
SCAN_INTERVAL = timedelta(minutes=1)
class MySensor(SensorEntity):
"""Representation of a Sensor."""
_attr_should_poll = True
```
## 更多资源
关于轮询的更多信息,请参见[文档](/developers/integration_fetching_data.md)。
## 例外
此规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/async-dependency.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 理由
Home Assistant 使用 asyncio 来高效处理任务。
为避免在 asyncio event loop 和其他线程之间切换上下文(这在性能上代价很高),理想情况下你的库也应使用 asyncio。
这不仅使系统更高效,代码也更加整洁。
## 附加资源
有关如何创建库的更多信息,请参阅[文档](/developers/api_lib_index.md)。
## 例外
此规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/brands.md
---
## 理由
品牌资源对于集成易于识别以及在所有集成中提供一致的外观和感觉非常重要。
项目在[brands 仓库](https://github.com/home-assistant/brands)中跟踪这些资源。
所需资源的要求可在 brands 仓库的 [readme](https://github.com/home-assistant/brands/blob/master/README.md) 中找到。
## 例外
此规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/common-modules.md
---
## 理由
Home Assistant 的代码库中有一些随时间积累而来的通用模式。
例如,大多数新集成都使用一个 coordinator 来集中处理数据获取。
coordinator 应放置在 `coordinator.py` 中。
这提高了集成之间的一致性,并更容易找到某个特定集成的 coordinator。
第二个通用模式是 base entity。
由于很多集成会提供多种类型的 entity,使用 base entity 可以有效减少代码重复。
base entity 应放置在 `entity.py` 中。
为提高集成之间一致性所做的努力,对代码库的质量和开发者体验都有积极影响。
## 示例实现
在本示例中,我们有一个存储在 `coordinator.py` 中的 coordinator,以及一个存储在 `entity.py` 中的 base entity。
`coordinator.py`
```python showLineNumbers
class MyCoordinator(DataUpdateCoordinator[MyData]):
"""Class to manage fetching data."""
def __init__(self, hass: HomeAssistant, client: MyClient) -> None:
"""Initialize coordinator."""
super().__init__(
hass,
logger=LOGGER,
name=DOMAIN,
update_interval=timedelta(minutes=1),
)
self.client = client
```
`entity.py`
```python showLineNumbers
class MyEntity(CoordinatorEntity[MyCoordinator]):
"""Base entity for MyIntegration."""
_attr_has_entity_name = True
def __init__(self, coordinator: MyCoordinator) -> None:
"""Initialize the entity."""
super().__init__(coordinator)
self._attr_device_info = ...
```
## 例外
本规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/config-entry-unloading.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原因
集成应支持配置条目卸载。
这允许 Home Assistant 在运行时卸载集成,使用户能够在无需重启 Home Assistant 的情况下移除集成或重新加载它。
由于用户无需重启 Home Assistant 即可执行更多操作,这改善了用户体验。
## 示例实现
在 `async_unload_entry` 接口函数中,集成应清理所有订阅,并关闭在集成设置期间打开的所有连接。
在此示例中,我们有一个监听器,存储在配置条目的 `runtime_data` 中,我们希望对其进行清理以避免内存泄漏。
`__init__.py`:
```python showLineNumbers
async def async_unload_entry(hass: HomeAssistant, entry: MyConfigEntry) -> bool:
"""Unload a config entry."""
if (unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS))
entry.runtime_data.listener()
return unload_ok
```
:::info
集成可以使用 `entry.async_on_unload` 注册回调函数,这些回调函数将在配置条目卸载或设置失败时被调用。
这对于无需自己跟踪清理方法来清理资源非常有用。
注册的回调函数将在以下情况下被调用:
* `async_setup_entry` 抛出 `ConfigEntryError`、`ConfigEntryAuthFailed` 或 `ConfigEntryNotReady`
* `async_unload_entry` 成功,即返回 True 且未抛出异常。
请注意,集成始终需要实现 `async_unload_entry` 以支持配置条目卸载,仅调用 `entry.async_on_unload` 是不够的。
:::
## 更多资源
关于配置条目及其生命周期的更多信息,请参见[config entry 文档](/developers/config_entries_index.md)。
## 例外
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/config-flow-test-coverage.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 理由
config flow 是用户与你的集成进行的第一次交互。
确保 config flow 按预期工作,使用户能够毫无问题地(或与 config flow 相关的错误地)完成集成设置,这一点非常重要。
这意味着我们希望对 config flow 达到 **100%** 的测试覆盖率。
在这些测试中,我们要求验证 flow 能够从错误中恢复,以确认即使用户遇到某些问题时也能顺利完成 flow。
为了让用户在使用其他集成 flow 时获得顺畅体验,本规则也适用于 reconfigure、reauthentication 和 options flow。
为集成编写测试的额外好处是,它可以引导开发者接触测试,使其更容易为集成的其他部分编写测试。
:::warning
尽管用于检查 config entry 唯一性的代码很可能会被 happy flow 测试触及,但仍请确保测试 flow 不允许添加超过一个唯一配置条目,以达到完整覆盖率。
:::
## 示例实现
我们需要针对 config flow 可以被触发的每种方式(由用户触发、通过 discovery 触发或通过 import flow 触发)测试以下场景。
下面的示例展示了由用户发起的基本 happy flow。
`test_config_flow.py`:
```python showLineNumbers
async def test_full_flow(
hass: HomeAssistant,
mock_my_client: AsyncMock,
mock_setup_entry: AsyncMock,
) -> None:
"""Test full flow."""
result = await hass.config_entries.flow.async_init(
DOMAIN,
context={"source": SOURCE_USER},
)
assert result["type"] is FlowResultType.FORM
assert result["step_id"] == "user"
result = await hass.config_entries.flow.async_configure(
result["flow_id"],
{CONF_HOST: "10.0.0.131"},
)
assert result["type"] is FlowResultType.CREATE_ENTRY
assert result["title"] == "My integration"
assert result["data"] == {
CONF_HOST: "10.0.0.131",
}
```
## 附加资源
关于 config flow 的更多信息,请参阅 [config flow 文档](/developers/core/integration/config_flow.md)。
关于集成测试的更多信息,请参阅 [测试文档](/developers/development_testing.md)。
## 例外
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/config-flow.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 理由
自 2018 年推出以来,config flow 已成为在 Home Assistant 中设置集成的标准方式。
它们在各个集成中提供一致的用户体验,并提供一种引导用户完成设置过程的方式。
由于更好的用户体验,我们希望确保所有集成都能通过 config flow 设置。
由于这是用户开始使用集成的入口点,我们应该确保 config flow 非常用户友好且易于理解。
这意味着我们应该在合适的位置使用合适的 selectors,在需要的地方验证输入,并在 `strings.json` 中使用 `data_description` 来提供有关输入字段的上下文。
集成应将所有配置存储在 `ConfigEntry.data` 字段中,而所有连接不需要的设置应存储在 `ConfigEntry.options` 字段中。
## 示例实现
要在集成中使用 config flow,你需要在集成文件夹中创建一个 `config_flow.py` 文件,并在 `manifest.json` 中将 `config_flow` 设置为 `true`。
config flow 中显示的文本在 `strings.json` 文件中定义。
`config_flow.py`:
```python
class MyConfigFlow(ConfigFlow, domain=DOMAIN):
"""My config flow."""
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}
if user_input:
return self.async_create_entry(
title="MyIntegration",
data=user_input,
)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema({vol.Required(CONF_HOST): str}),
errors=errors,
)
```
`strings.json`:
```json
{
"config": {
"step": {
"user": {
"data": {
"host": "Host"
},
"data_description": {
"host": "The hostname or IP address of the MyIntegration device."
}
}
}
}
}
```
## 附加资源
有关 config flows 的更多信息,请参阅[config flow 文档](/developers/core/integration/config_flow.md)。
有关 config flows 架构决策的更多信息,请参阅 [ADR-0010](https://github.com/home-assistant/architecture/blob/master/adr/0010-integration-configuration.md)
## 例外
在 [ADR-0010](https://github.com/home-assistant/architecture/blob/master/adr/0010-integration-configuration.md) 中豁免的集成不适用于此规则。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/dependency-transparency.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原理说明
Home Assistant 的运行依赖大量第三方依赖项。
这些依赖项将随 Home Assistant 的新版本一起发布。
为了让项目能够信任这些依赖项,我们制定了一套依赖项应满足的要求。
* 该依赖项的源代码必须采用 OSI 批准的许可证发布。
* 该依赖项必须在 PyPI 上可用。
* 发布到 PyPI 的包应当由公开 CI 流水线构建并从中发布。
* PyPI 上发布的依赖项版本应对应一个开放在线仓库中的带标签(tagged)版本。
## 例外情况
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/devices.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原理说明
在 Home Assistant 中,设备(Devices)用于将实体(entities)分组,以表示单个物理设备或服务。
这非常有用,因为用户通常认为自己是在系统中添加一个设备或服务,而不是单个实体。
Home Assistant 将设备信息存储在设备注册表(device registry)中。
为了让用户获得最佳体验,设备的信息应尽可能完整。
## 示例实现
在这个示例中,有一个传感器实体,它定义了自己在设备注册表中应被添加到哪个设备,同时还提供了该设备的一些元数据。
这将提供一个丰富的设备信息页面,用户可以根据设备名称、序列号及其他字段来识别设备。
`sensor.py`:
```python {8-18} showLineNumbers
class MySensor(SensorEntity):
"""Representation of a sensor."""
_attr_has_entity_name = True
def __init__(self, device: MyDevice) -> None:
"""Initialize the sensor."""
self._attr_device_info = DeviceInfo(
connections={(CONNECTION_NETWORK_MAC, device.mac)},
name=device.name,
serial_number=device.serial,
hw_version=device.rev,
sw_version=device.version,
manufacturer="My Company",
model="My Sensor",
model_id="ABC-123",
via_device=(DOMAIN, device.hub_id),
)
```
:::info
如果设备表示的是一个服务,请务必在 `DeviceInfo` 对象中添加 `entry_type=DeviceEntryType.SERVICE`,以将该设备标记为服务。
:::
## 补充资料
更多有关设备的详细信息,请参阅 [device](/developers/device_registry_index.md) 文档。
## 例外情况
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/diagnostics.md
---
## 理由
Diagnostics 是用户收集集成数据的便捷方式,在调试集成时可能很有用。
我们认为实现 diagnostics 是一项良好的实践。
需要注意的是,diagnostics 不应暴露任何敏感信息,例如密码、tokens 或坐标。
## 示例实现
在以下示例中,我们提供了包含来自各种来源数据的 diagnostics,例如配置和集成的当前状态。
由于配置可能包含敏感信息,我们在返回 diagnostics 之前隐藏了敏感信息。
`diagnostics.py`:
```python showLineNumbers
TO_REDACT = [
CONF_API_KEY,
CONF_LATITUDE,
CONF_LONGITUDE,
]
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: MyConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
return {
"entry_data": async_redact_data(entry.data, TO_REDACT),
"data": entry.runtime_data.data,
}
```
## 附加资源
要了解有关 diagnostics 的更多信息,请参阅[diagnostics 文档](/developers/core/integration/diagnostics.md)。
## 例外
此规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/discovery-update-info.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原因
大多数终端用户的网络使用动态 IP 地址。
这意味着设备和服务可能会获得与首次设置时不同的 IP 地址。
为了避免需要用户将设备设置为静态 IP 地址(这并不总是可行的),集成应使用发现信息来更新设备或服务的网络信息。
只有在集成确定设备或服务与之前设置的是同一个时,才应更新设备或服务的 IP 地址。
## 示例实现
在下面的示例中,我们有一个使用 mDNS 发现设备的集成。
每次启动 zeroconf 发现流程时,集成都会将流程的唯一 ID 设置为设备的序列号。
如果唯一 ID 已存在,当设备 IP 地址发生变化时将对其进行更新,然后流程将中止。
`manifest.json`:
```json
{
"zeroconf": ["_mydevice._tcp.local."]
}
```
`config_flow.py`:
```python {14-15} showLineNumbers
class MyConfigFlow(ConfigFlow, domain=DOMAIN):
"""My config flow."""
def __init__(self) -> None:
"""Initialize the config flow."""
self.data: dict[str, Any] = {}
async def async_step_zeroconf(
self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> ConfigFlowResult:
"""Handle zeroconf discovery."""
self.data[CONF_HOST] = host = discovery_info.host
await self.async_set_unique_id(discovery_info.properties["serialno"])
self._abort_if_unique_id_configured(updates={CONF_HOST: host})
client = MyClient(host)
try:
await client.get_data()
except MyClientError:
return self.async_abort(reason="cannot_connect")
return await self.async_step_discovery_confirm()
```
:::info
如果你使用 DHCP 发现,并希望针对更新的 IP 地址接收发现流程,请务必在 device info 中注册 MAC 地址,并在 manifest 中将 `registered_devices` 设置为 `true`。
这将为这些设备创建设置流程。
:::
## 更多资源
要了解有关配置流程的更多信息,请查阅[config flow 文档](/developers/core/integration/config_flow.md)。
要了解有关网络协议和发现的信息,请查阅[Networking and discovery 文档](/developers/network_discovery.md)。
## 例外
本规则的例外情况是并非每个设备都可以被发现。
设备无法被发现的集成免于此规则。
---
url: /developers/core/integration-quality-scale/rules/discovery.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原因
许多设备都具备被发现的能力。
这可以通过以下方法之一实现:
* App(以前称为 add-ons)
* [Bluetooth](/developers/creating_integration_manifest.md#bluetooth)
* [DHCP](/developers/creating_integration_manifest.md#dhcp)
* [HomeKit](/developers/creating_integration_manifest.md#homekit)
* [mDNS](/developers/creating_integration_manifest.md#zeroconf)
* [MQTT](/developers/creating_integration_manifest.md#mqtt)
* [SSDP](/developers/creating_integration_manifest.md#ssdp)
* [USB](/developers/creating_integration_manifest.md#usb)
这是一种让用户更容易找到和设置设备的好方法,因为用户无需手动查找应使用哪个集成,然后再输入 host。
这大大减少了设置设备所需的工作量,从而改善了用户体验。
使用基于网络的设置方式,还允许在设备获得新 IP 地址时更新集成的配置。
## 示例实现
在下面的示例中,集成可以通过 mDNS 被发现。
设备通过提供 `_mydevice._tcp.local.` 服务来使自己可被发现。
Home Assistant 将捕获此信息并为用户启动发现流程。
用户随后可以确认发现结果并设置集成。
`manifest.json`:
```json {2} showLineNumbers
{
"zeroconf": ["_mydevice._tcp.local."]
}
```
`config_flow.py`:
```python {8-23,25-36} showLineNumbers
class MyConfigFlow(ConfigFlow, domain=DOMAIN):
"""My config flow."""
def __init__(self) -> None:
"""Initialize the config flow."""
self.data: dict[str, Any] = {}
async def async_step_zeroconf(
self, discovery_info: zeroconf.ZeroconfServiceInfo
) -> ConfigFlowResult:
"""Handle zeroconf discovery."""
self.data[CONF_HOST] = host = discovery_info.host
await self.async_set_unique_id(discovery_info.properties["serialno"])
self._abort_if_unique_id_configured(updates={CONF_HOST: host})
client = MyClient(host)
try:
await client.get_data()
except MyClientError:
return self.async_abort(reason="cannot_connect")
return await self.async_step_discovery_confirm()
async def async_step_discovery_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm discovery."""
if user_input is not None:
return self.async_create_entry(
title="MyIntegration",
data={CONF_HOST: self.data[CONF_HOST]},
)
self._set_confirm_only()
return self.async_show_form(step_id="discovery_confirm")
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}
if user_input:
client = MyClient(user_input[CONF_HOST])
try:
serial_number = await client.check_connection()
except MyException as exception:
errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(
serial_number, raise_on_progress=False
)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title="MyIntegration",
data=user_input,
)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_HOST): TextSelector(),
}
),
errors=errors,
)
```
## 更多资源
要了解有关配置流程的更多信息,请查阅[config flow 文档](/developers/core/integration/config_flow.md)。
要了解有关网络协议发现的信息,请查阅[Networking and discovery 文档](/developers/network_discovery.md)。
要了解有关蓝牙设备发现的信息,请查阅[Bluetooth 文档](/developers/bluetooth.md)。
要了解有关在 manifest 中指定不同发现方法的信息,请查阅[Integration Manifest 文档](/developers/creating_integration_manifest.md)。
## 例外
本规则的例外情况是并非每个设备都可以被发现。
设备无法被发现的集成免于此规则。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/docs-actions.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 理由
集成可以注册 service action,以提供标准 entity 无法实现的功能。
这些 service action 可能比标准 service action 更难以使用,因此我们希望确保文档描述了它们的作用以及相关参数。
## 示例实现
```markdown showLineNumbers
## 操作
该集成提供以下 action。
### Action:获取日程
`my_integration.get_schedule` service 用于从集成获取日程。
- **Data 属性**:`config_entry_id`
- **说明**:要获取日程的 config entry 的 ID。
- **可选**:否
```
## 例外
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/docs-conditions.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 理由
集成可以注册 condition,以提供触发自动化(automation)的额外方式。
这些 condition 可能比标准 condition 更难以使用,因此我们希望确保文档描述了它们的作用以及相关参数。
为避免集成文档显得杂乱,我们希望为每个 condition 设置单独的页面,并从集成文档中链接到该页面。
每个 condition 页面应包含该 condition 的作用说明、可用参数列表,以及通过 UI 和 YAML 使用该 condition 的概述。
## 示例实现
```markdown showLineNumbers
---
title: "检测到一氧化碳"
condition: air_quality.is_co_detected
domain: air_quality
description: "测试是否有一个或多个一氧化碳传感器正在检测一氧化碳。"
related_conditions:
- air_quality.is_co_cleared
- air_quality.is_co_value
---
**检测到一氧化碳** 条件在一个或多个一氧化碳传感器主动检测到一氧化碳(CO)时通过。由于一氧化碳无色无味,传感器是确认其存在的唯一途径。将本条件添加到自动化中,可确保安全措施(如触发警报、启动通风或发送紧急通知)仅在危险被确认时才执行。这能避免传感器短暂闪烁引发的误报,并将响应聚焦于真实威胁。
{% include conditions/ui_header.md %}
要在自动化中使用此条件:
1. 前往 {% my automations title="**设置** > **自动化与场景**" %}。
2. 打开现有自动化,或选择 **创建自动化** > **创建新自动化**。
3. 在 **And if** 部分,选择 **添加条件**。
4. 选择你要检查的内容。在 **按目标**(见 [Targets](#targets))下,选择一氧化碳传感器所在的区域(如厨房或车库)。你也可以选择楼层、设备、具体 entity 或 label。
5. 在该目标显示的条件中,选择 **检测到一氧化碳**。
6. 在 **条件通过条件**(见 [Behavior](#behavior-with-multiple-targets))下,选择 **任一** 或 **全部**,以控制在针对多个传感器时检查的行为。
7. 选择 **保存**。
### UI 中的选项
{% options_ui %}
条件通过条件:
description: 当针对多个传感器时,控制结果如何组合。选择 **任一** 在至少一个目标传感器检测到大一氧化碳时通过,或选择 **全部** 仅在每个目标传感器都检测到大一氧化碳时通过。
{% endoptions_ui %}
{% include conditions/yaml_header.md %}
在 YAML 中,将此条件引用为 `air_quality.is_co_detected`。一个基本示例如下:
{% example %}
condition: |
condition: air_quality.is_co_detected
target:
entity_id: binary_sensor.hallway_co
{% endexample %}
当走廊一氧化碳传感器当前正在检测 CO 时,此条件通过。
### YAML 中的选项
YAML 有时为更复杂的用例提供 UI 中不可用的额外选项。
{% options_yaml %}
behavior:
description: >
当针对多个传感器时,控制结果如何组合。接受 `all` 或 `any`。
required: true
type: string
default: any
{% endoptions_yaml %}
{% include conditions/targets.md %}
{% include conditions/behavior.md %}
## 小贴士
- 不可用(`unavailable`)或状态未知(`unknown`)的传感器不算作检测到。使用 **任一** 行为时,它们会被跳过。使用 **全部** 行为时,如果每个目标传感器都不可用,则该条件失败。
- 要检查是否不再检测到大一氧化碳,请使用 [大一氧化碳已清除](/conditions/air_quality.is_co_cleared/)。
- 要检查实际 CO 浓度而非仅仅二元检测结果,请使用 [大一氧化碳数值](/conditions/air_quality.is_co_value/)。
{% include conditions/try_it.md %}
{% include conditions/more_examples.md %}
### 自动化:在 CO 事件期间有人到家时提醒家人
如果家人外出期间室内 CO 积聚,第一个回家的人需要在进门之前收到警告。此自动化在有人进入家 zone 时触发,并检查走廊 CO 传感器是否仍在检测大一氧化碳。如果是,则发送紧急通知提醒他们待在室外并拨打紧急电话。
- **触发器**:进入 Zone
- **目标**:Frenck(`person.frenck`)
- **Zone**:Home(`zone.home`)
- **条件**:检测到一氧化碳
- **目标**:走廊 CO 传感器
- **条件通过条件**:任一
- **动作**:发送通知消息
- **目标**:My Device(`notify.my_device`)
{% details "到家时 CO 警告的 YAML 示例" %}
{% example %}
automation: |
alias: "CO warning on arrival home"
triggers:
- trigger: zone.entered
target:
entity_id: person.frenck
options:
zone: zone.home
conditions:
- condition: air_quality.is_co_detected
target:
entity_id: binary_sensor.hallway_co
options:
behavior: any
actions:
- action: notify.send_message
target:
entity_id: notify.my_device
data:
title: "Carbon monoxide detected at home"
message: >
The hallway CO sensor is detecting carbon
monoxide. Stay outside and call emergency services.
{% endexample %}
{% enddetails %}
{% include conditions/stuck.md %}
{% include conditions/related.md %}
```
## 例外
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/docs-configuration-parameters.md
---
## 原理说明
集成可以提供 options flow,允许用户更改集成的配置。
本规则确保所有配置选项都被文档化,以便用户了解每个选项的作用以及如何使用它。
## 示例实现
以下示例针对一个具有多个配置选项、使用 `configuration_basic` 标签的集成。
```markdown showLineNumbers
{% include integrations/option_flow.md %}
{% configuration_basic %}
国家代码:
description: 您可以指定显示在摄像头中的国家代码(NL 或 BE)。
时间范围:
description: 降水预测传感器向前查看的分钟数(最小 5,最大 120)。
{% endconfiguration_basic %}
```
## 例外情况
本规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/docs-data-update.md
---
## 原理说明
为了让用户了解集成的工作原理,我们应描述集成的数据是如何更新的。
这将帮助用户对集成在其使用场景下的表现建立预期。
例如,一个每 5 分钟才轮询一次的运动传感器,其可用性低于一个主动推送更新(push)的运动传感器。
由于用户可以为轮询型集成定义自己的轮询间隔,我们应说明当前的轮询速率,并描述任何限制。
例如,如果我们要连接的设备在处理过多请求时存在已知问题,我们应在文档中加以说明。
## 示例实现
```markdown showLineNumbers
## 数据更新
我的集成默认每 5 分钟从设备获取一次数据。
较新的设备(运行 MyOS 的设备)支持推送数据。
在集成启动时,我们会尝试启用该功能;如果失败,则回退到 {% term polling %}。
```
## 例外情况
本规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/docs-examples.md
---
## 原理说明
为了展示集成如何使用,请提供一组有限的 blueprint,其中包含常见或实用的示例。
这将帮助用户更快、更容易地开始使用集成。
请将 blueprint 上传到 [`https://github.com/home-assistant/home-assistant.io/tree/current/source/blueprints/integrations`](https://github.com/home-assistant/home-assistant.io/tree/current/source/blueprints/integrations) 下的 blueprints 文件夹,或上传到 [论坛的 blueprint exchange](https://community.home-assistant.io/c/blueprints-exchange)。在集成页面上添加指向该 blueprint 的链接。
请不要将文档页面作为 blueprint 文件夹或 blueprint exchange 的集合或替代品。
## 示例实现
```markdown showLineNumbers
## 示例
### 夜间关闭 LED
设备上的状态 LED 可能比较亮。
为了解决这一问题,您可以使用这个 blueprint 轻松实现日落后自动关闭 LED。
link to blueprint
```
## 例外情况
本规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/docs-high-level-description.md
---
## 原理说明
集成的用户文档应提供对集成品牌、产品或服务的高级描述。
这些信息可能有助于用户判断集成是否适合他们及其使用场景。
如果可能的话,文档还应包含指向品牌、产品或服务网站的链接,以便用户获取更多详细信息。
## 示例实现
```markdown showLineNumbers
**my integration** {% term integration %} 用于与 [MyCompany](https://www.mycompany.com) 的设备进行集成。
该公司生产各种智能家居电器和设备,以其 MyProduct 而闻名。
```
## 例外情况
不与设备或服务集成的集成(如内部集成)无法包含对设备或服务的描述,因此不受此规则约束。
---
url: /developers/core/integration-quality-scale/rules/docs-installation-instructions.md
---
## 原理说明
我们希望用户在设置集成时获得流畅的体验。
这意味着文档应提供清晰简洁的安装说明。
这包括安装集成所需的所有先决条件。
## 示例实现
```markdown showLineNumbers
## 先决条件
1. 打开应用商店并安装 **MyProduct** 应用。
2. 创建账号。
3. 在应用中添加一个设备。
4. 打开应用并进入 **Settings** 页面。
5. 选择 **Expose API**。
{% include integrations/config_flow.md %}
```
## 例外情况
本规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/docs-installation-parameters.md
---
## 原理说明
在设置集成时,没有什么比不知道需要填写哪些信息更令人沮丧了。
为了提升用户体验,文档应描述安装过程中所需的所有参数。
这将帮助用户在开始安装过程之前收集好所有必要的信息。
## 示例实现
如果集成通过 config flow 使用:
```markdown showLineNumbers
{% configuration_basic %}
Host:
description: "您的 bridge 的 IP 地址。您可以在路由器中找到,或在集成应用的 **Bridge Settings** > **Local API** 下找到。"
Local access token:
description: "您的 bridge 的本地访问令牌。您可以在集成应用的 **Bridge Settings** > **Local API** 下找到。"
{% endconfiguration_basic %}
```
如果集成通过 `configuration.yaml` 中的 YAML 设置:
```markdown showLineNumbers
{% configuration %}
Host:
description: "您的 bridge 的 IP 地址。您可以在路由器中找到,或在集成应用的 **Bridge Settings** -> **Local API** 下找到。"
required: false
type: string
Local access token:
description: "您的 bridge 的本地访问令牌。您可以在集成应用的 **Bridge Settings** -> **Local API** 下找到。"
required: false
type: string
{% endconfiguration %}
```
## 例外情况
本规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/docs-known-limitations.md
---
## 理由
在文档中描述集成的已知限制,可以让用户了解对集成的预期表现。
我们应避免记录 bug,因为我们使用 GitHub 的 issue tracker 来处理它们,并且不希望重复信息。
## 示例实现
```markdown showLineNumbers
## 已知限制
该集成不提供重启功能,此操作可通过制造商的应用来完成。
```
## 例外
本规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/docs-removal-instructions.md
---
## 理由
从 Home Assistant 中移除设备或服务并不总是很简单。
文档应提供有关如何移除设备或服务的清晰说明。
## 示例实现
```markdown showLineNumbers
## 移除集成
此集成遵循标准的集成移除方式。无需额外步骤。
{% include integrations/remove_device_service.md %}
删除集成后,请前往制造商的应用,并从中移除 Home Assistant 集成。
```
## 例外
本规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/docs-supported-devices.md
---
## 原理说明
许多 Home Assistant 用户会根据 Home Assistant 是否支持来决定购买哪些设备。
为了让用户更容易确认某设备是否受支持,文档应描述已知支持或不支持的设备。
这将减少用户尝试设置设备时才得知设备不受支持所带来的糟糕体验。
## 示例实现
```markdown showLineNumbers
## 支持的设备
以下设备已知受该集成支持:
- Device 1
- Device 2
- 所有运行 MyOS 的设备
## 不支持的设备
以下设备不受该集成支持:
- Device 3
- 2010 年之前生产的设备
```
## 例外情况
本规则不适用于不连接设备或服务的集成。
本规则也不适用于不集成物理设备的集成。
---
url: /developers/core/integration-quality-scale/rules/docs-supported-functions.md
---
## 原理说明
用户应能够了解该集成将为他们(计划购买的)设备带来什么价值。
这将有助于建立用户的预期。
例如,如果用户正在寻找一款新冰箱,我们应清楚地说明他们能从该集成中获得什么。
如果集成仅支持查看门是开还是关,而用户却期望能看到冰箱温度,那他们将会失望。
## 示例实现
按实体类型排序的示例:
```markdown showLineNumbers
## 支持的功能
### 实体
XY 集成提供以下实体。
#### 按钮
- **Start backflush**
- **描述**:启动机器的回冲过程。激活后您有 15 秒时间拨动手柄。
- **适用机器**:所有
#### 数字
- **Dose**
- **描述**:每个按键的剂量(以刻度为单位)
- **适用机器**:GS3 AV、Linea Mini
- **备注**:GS3 会多次出现此项,每个物理按键(1-4)各一次,且这些实体默认为禁用状态。
#### 传感器
- **Current coffee temperature**
- **描述**:咖啡锅炉的当前温度。
- **适用机器**:所有
- **备注**:当机器达到温度时,由于测量点不同,该值将比 `Coffee target temperature` 高约 3 度。
- **Current steam temperature**
- **描述**:蒸汽锅炉的当前温度。
- **适用机器**:Linea Micra、GS3 AV、GS3 MP
- **备注**:-
#### 更新
- **Gateway firmware**
- **描述**:网关的固件状态。
- **适用机器**:所有
#### 选择器
- **Prebrew/-infusion mode**
- **描述**:是否使用 prebrew、preinfusion,或都不使用。
- **选项**:Disabled、Prebrew、Preinfusion
- **适用机器**:Linea Micra、Linea Mini、GS3 AV
- **Steam level**
- **描述**:蒸汽锅炉应运行的档位。
- **选项**:1、2、3
- **适用机器**:Linea Micra
```
按设备排序的示例:
```markdown
## 支持的功能
### XYZ 产品名空气净化器、空气加湿器和落地扇
#### 传感器
- **Filter lifetime remaining**:滤网剩余寿命(以年为单位)。默认为启用状态。
- **Purify volume**:已净化空气的体积(以立方米为单位)。默认为禁用状态。
#### 数字
- **Favorite level**:设置偏好档位。可能的取值为 0 到 10。`0` 表示关闭。
- **Volume**:设置音量。以百分比表示。`0%` 表示关闭。
```
## 例外情况
本规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/docs-triggers.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 理由
集成可以注册 trigger,以提供触发自动化(automation)的额外方式。
这些 trigger 可能比标准 trigger 更难以使用,因此我们希望确保文档描述了它们的作用以及相关参数。
为避免集成文档显得杂乱,我们希望为每个 trigger 设置单独的页面,并从集成文档中链接到该页面。
每个 trigger 页面应包含该 trigger 的作用说明、可用参数列表,以及通过 UI 和 YAML 使用该 trigger 的概述。
## 示例实现
```markdown showLineNumbers
---
title: "二氧化碳水平发生变化"
trigger: air_quality.co2_changed
domain: air_quality
description: "当一个或多个二氧化碳水平发生变化时触发。"
related_triggers:
- air_quality.co2_crossed_threshold
---
**二氧化碳水平发生变化** trigger 在一个或多个空气质量传感器的二氧化碳(CO2)读数发生显著变化后触发。二氧化碳会在有人居住的房间中由于呼吸、烹饪和取暖而自然积聚。晚上关着门的卧室或午餐后人员密集的会议室,都是 CO2 悄悄上升而不被察觉的典型场所。CO2 升高是最明显的室内空气需要新鲜的信号之一。
想象一下:卧室通风风扇在深夜自动启动,因为 CO2 在你睡觉时升高了,所以你醒来时神清气爽而不是昏沉。使用此 trigger 来自动通风、记录室内空气质量趋势,或在 CO2 发生明显变化时提醒家庭成员打开窗户。
{% include triggers/ui_header.md %}
要在自动化中使用此 trigger:
1. 前往 {% my automations title="**设置** > **自动化与场景**" %}。
2. 打开现有自动化,或选择 **创建自动化** > **创建新自动化**。
3. 在 **When** 部分,选择 **添加触发器**。
4. 选择你要监控的内容。在 **按目标**(见 [Targets](#targets))下,选择 CO2 传感器所在的区域(如客厅或卧室)。你也可以选择楼层、设备、具体 entity 或 label。
5. 在该目标显示的 trigger 中,选择 **二氧化碳水平发生变化**。
6. 在 **阈值类型** 下,设置水平变化多少才触发该 trigger。
7. 选择 **保存**。
### UI 中的选项
{% options_ui %}
阈值类型:
description: 二氧化碳水平需要变化多少才会触发该 trigger。可以是固定数值,也可以引用提供该值的 helper entity。
{% endoptions_ui %}
{% include triggers/yaml_header.md %}
在 YAML 中,将此 trigger 引用为 `air_quality.co2_changed`。一个基本示例如下:
{% example %}
trigger: |
trigger: air_quality.co2_changed
target:
entity_id: sensor.bedroom_co2
options:
threshold: 50
{% endexample %}
每当卧室 CO2 传感器读数变化至少 50 ppm 时,就会触发此 trigger。
### YAML 中的选项
YAML 有时为更复杂的用例提供 UI 中不可用的额外选项。
{% options_yaml %}
threshold:
description: >
二氧化碳水平触发前必须变化的最小量。接受一个数值,或对 `input_number`、`number` 或 `sensor` entity 的引用。
required: true
type: any
{% endoptions_yaml %}
{% include triggers/targets.md %}
## 小贴士
- 室内 CO2 水平通常在约 400 ppm(通风良好)到超过 1,000 ppm(闷热的房间)之间。50 到 100 ppm 的阈值适用于大多数通风自动化。
- 只要变化达到阈值,无论水平上升还是下降,都会触发该 trigger。
- 如果只想在 CO2 朝一个方向越过特定水平时作出响应,请使用 [二氧化碳水平越过阈值](/triggers/air_quality.co2_crossed_threshold/)。
{% include triggers/try_it.md %}
{% include triggers/more_examples.md %}
### 自动化:卧室 CO2 升高时启动通风
在密闭的卧室中睡觉会令 CO2 在夜间积聚,让你早上醒来时昏沉。此自动化在 CO2 水平发生显著变化时启动卧室通风风扇,保持空气新鲜,让你醒来时精力充沛。
- **Trigger**:二氧化碳水平发生变化
- **目标**:卧室 CO2 传感器
- **阈值类型**:100
- **动作**:打开风扇
{% details "基于 CO2 的卧室通风 YAML 示例" %}
{% example %}
automation: |
alias: "Ventilate bedroom on CO2 change"
triggers:
- trigger: air_quality.co2_changed
target:
entity_id: sensor.bedroom_co2
options:
threshold: 100
actions:
- action: fan.turn_on
target:
entity_id: fan.bedroom_ventilation
{% endexample %}
{% enddetails %}
{% include triggers/stuck.md %}
{% include triggers/related.md %}
```
## 例外
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/docs-troubleshooting.md
---
## 理由
我们应提供解决常见问题的说明。
在可能的情况下,故障排除主题应包含症状描述以及解决问题所需的步骤。
这能减少支持请求的数量,并改善用户体验。
## 示例实现
```markdown showLineNumbers
## 故障排除
### 无法设置设备
#### 症状:"无法访问此设备"
在尝试设置集成时,表单显示消息"无法访问此设备"。
##### 说明
这意味着设备的设置不正确,因为设备需要启用本地通信功能。
##### 解决方法
要解决此问题,请尝试以下步骤:
1. 确保设备已上电(LED 亮起)。
2. 确保设备已连接到互联网:
- 确保制造商的应用能够看到该设备。
3. 确保设备已启用本地通信:
- 检查设备的设置。
- 检查设备的用户手册。
...
### 我看不到我的设备
请确保设备可通过制造商的应用看到并控制。
如果不能,请检查设备的电源和网络连接。
### 设备一天后变为不可用
请确保你已关闭设备的省电模式。
```
## 例外
本规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/docs-use-cases.md
---
## 原理说明
有时候,看到某个设备或服务被集成到 Home Assistant 中,你会不禁想:"我为什么要集成这个?"
对于一些集成,其预期用途和好处比其他集成更为明显。
文档中的使用场景示例可以展示集成的价值。
## 示例实现
```markdown showLineNumbers
MyCompany 的运动检测设备便宜且实用。
将它与他们的其他设备结合使用时,您可以实现 x。
```
## 例外情况
本规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/dynamic-devices.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 理由
正如规则 [stale-devices](/developers/core/integration-quality-scale/rules/stale-devices.md) 所解释的,当我们可以确定设备不再连接时,设备应被自动移除。
本规则解释的是另一面:一旦新设备连接,我们应该自动为该设备创建相应的 entity。
这能改善用户体验,因为用户只需将设备添加到集成中,它就会自动出现在 Home Assistant 中。
## 示例实现
在下面的示例中,我们使用 coordinator 从服务获取所有数据。
每次更新时,`_check_device` 都会检查是否有需要创建 entity 的新设备,并将它们添加到 Home Assistant。
`coordinator.py`
```python showLineNumbers
class MyCoordinator(DataUpdateCoordinator[dict[str, MyDevice]]):
"""Class to manage fetching data."""
def __init__(self, hass: HomeAssistant, client: MyClient) -> None:
"""Initialize coordinator."""
super().__init__(
hass,
logger=LOGGER,
name=DOMAIN,
update_interval=timedelta(minutes=1),
)
self.client = client
async def _async_update_data(self) -> dict[str, MyDevice]:
try:
return await self.client.get_data()
except MyException as ex:
raise UpdateFailed(f"The service is unavailable: {ex}")
```
`sensor.py`
```python {9,11-16,18-21} showLineNumbers
async def async_setup_entry(
hass: HomeAssistant,
entry: MyConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up My integration from a config entry."""
coordinator = entry.runtime_data
known_devices: set[str] = set()
def _check_device() -> None:
current_devices = set(coordinator.data)
new_devices = current_devices - known_devices
if new_devices:
known_devices.update(new_devices)
async_add_entities([MySensor(coordinator, device_id) for device_id in new_devices])
_check_device()
entry.async_on_unload(
coordinator.async_add_listener(_check_device)
)
```
## 例外
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/entity-category.md
---
## 理由
entity 应分配适当的 EntityCategory,以确保在默认 category 不适当时,它们能被正确分类并容易被识别。
entity category 用于例如自动生成的 dashboard。
## 示例实现
在本示例中,我们有一个返回诊断值的 sensor。
`sensor.py`
```python {4} showLineNumbers
class MySensor(SensorEntity):
"""Representation of a sensor."""
_attr_entity_category = EntityCategory.DIAGNOSTIC
def __init__(self, ...) -> None:
```
## 附加资源
要了解有关 registry properties 的更多信息,请参阅[文档](/developers/core/entity.md#registry-properties)。
## 例外
本规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/entity-device-class.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原理说明
Device class 是一种为实体提供上下文的方式。
Home Assistant 出于多种目的使用 device class:
* 允许用户切换到设备提供的测量单位以外的其他单位。
* 用于语音控制,以提出诸如"客厅温度是多少?"之类的问题。
* 用于将实体暴露给 Google Assistant 和 Amazon Alexa 等基于云的系统生态。
* 用于调整 Home Assistant UI 中的显示方式。
* 可用于设置实体的默认名称,以减轻译者的负担。
出于这些原因,尽可能使用 device class 非常重要。
## 示例实现
在下面的示例中,我们有一个温度传感器,它使用了 device class `temperature`。
该实体的名称将是 `My device temperature`。
`sensor.py`
```python {5} showLineNumbers
class MyTemperatureSensor(SensorEntity):
"""Representation of a sensor."""
_attr_has_entity_name = True
_attr_device_class = SensorDeviceClass.TEMPERATURE
def __init__(self, device: Device) -> None:
"""Initialize the sensor."""
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, device.id)},
name="My device",
)
```
## 补充资料
可用 device class 的列表可在 [entity](/developers/core/entity.md) 页面下的实体页面中找到。
更多有关实体命名的信息可在 [entity](/developers/core/entity.md#has_entity_name-true-mandatory-for-new-integrations) 文档中找到。
## 例外情况
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/entity-disabled-by-default.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 理由
Home Assistant 会跟踪 entity 状态的变化。
这样做是为了能够在 UI 中显示 entity 的历史记录。
每一个被跟踪的状态都会占用一定的资源。
频繁改变状态的 entity(嘈杂的 entity)比较少改变状态的 entity 更频繁地进行此操作。
我们认为,默认禁用不常用或嘈杂的 entity 是一种良好实践。
如果用户对此类 entity 有使用场景,他们可以自行启用。
这样,对于没有使用场景的用户来说,就不必承担跟踪 entity 状态的成本。
关于什么算作常用 entity 并没有硬性规则,因为这取决于集成和设备。
例如,一个蓝牙温度传感器可以有一个表示设备信号强度的 entity。
对于大多数用户来说,这个 entity 并不是很有用,因此应该默认禁用。
而如果有一个提供设备来测量信号强度的集成,那么这个 entity 对大多数用户是有用的,应该默认启用。
## 示例实现
在下面的示例中,entity 默认被禁用。
`sensor.py`
```python {8} showLineNumbers
class MySignalStrengthSensor(SensorEntity):
"""Representation of a sensor."""
_attr_has_entity_name = True
_attr_entity_category = EntityCategory.DIAGNOSTIC
_attr_device_class = SensorDeviceClass.SIGNAL_STRENGTH
_attr_native_unit_of_measurement = SIGNAL_STRENGTH_DECIBELS_MILLIWATT
_attr_entity_registry_enabled_default = False
def __init__(self, device: Device) -> None:
"""Initialize the sensor."""
...
```
## 附加资源
要了解有关 entity registry properties 的更多信息,请参阅[文档](/developers/core/entity.md#registry-properties)。
## 例外
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/entity-event-setup.md
---
## 理由
entity 可能需要订阅事件,例如来自集成库的事件,并在新事件到来时更新状态。
为了正确地做到这一点,entity 应该在 entity 方法 `async_added_to_hass` 中订阅并注册 update callback。
此 entity 方法在 entity 被 entity platform helper 注册后调用,此时 entity 将拥有所有可调用接口,如 `self.hass` 和 `self.async_write_ha_state`。
在此阶段之前注册 update callback 会导致错误,例如 callback 尝试访问 `self.hass` 或写入状态更新时。
为避免内存泄漏,entity 应该在 entity 方法 `async_will_remove_from_hass` 中取消订阅事件,即注销 update callback。
## 示例实现
在下面的示例中,`self.client.events.subscribe` 返回一个函数,调用该函数即可取消实体对事件的订阅。
因此,我们在 `async_added_to_hass` 中订阅事件,在 `async_will_remove_from_hass` 中取消订阅。
`sensor.py`
```python {10-13,15-19} showLineNumbers
class MySensor(SensorEntity):
"""Representation of a sensor."""
unsubscribe: Callable[[], None] | None = None
def __init__(self, client: MyClient) -> None:
"""Initialize the sensor."""
self.client = client
async def async_added_to_hass(self) -> None:
"""Subscribe to the events."""
await super().async_added_to_hass()
self.unsubscribe = self.client.events.subscribe("my_event", self._handle_event)
async def async_will_remove_from_hass(self) -> None:
"""Unsubscribe from the events."""
if self.unsubscribe:
self.unsubscribe()
await super().async_will_remove_from_hass()
async def _handle_event(self, event: Event) -> None:
"""Handle the event."""
...
self.async_write_ha_state()
```
:::info
上述示例可以使用 lifecycle 函数进行简化。
这样就不需要在 entity 中存储 callback 函数。
```python showLineNumbers
async def async_added_to_hass(self) -> None:
"""Subscribe to the events."""
await super().async_added_to_hass()
self.async_on_remove(
self.client.events.subscribe("my_event", self._handle_event)
)
```
:::
## 例外
本规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/entity-translations.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 理由
Home Assistant 被世界各地的人们使用。
为了让非英语用户也能更轻松地使用 Home Assistant,entity 具有翻译后的名称非常重要。
这能帮助用户更容易理解 entity 的含义。
## 示例实现
在本示例中,sensor 的英文名称为 "Phase voltage"。
结合设备名称,此 entity 会将自己命名为 "My device Phase voltage"。
`sensor.py`:
```python {5} showLineNumbers
class MySensor(SensorEntity):
"""Representation of a sensor."""
_attr_has_entity_name = True
_attr_translation_key = "phase_voltage"
def __init__(self, device_id: str) -> None:
"""Initialize the sensor."""
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, device_id)},
name="My device",
)
```
`strings.json`:
```json {5} showLineNumbers
{
"entity": {
"sensor": {
"phase_voltage": {
"name": "Phase voltage"
}
}
}
}
```
:::info
如果 entity 的 platform 是 `binary_sensor`、`number`、`sensor` 或 `update`,并且设置了 device class,而你希望 entity 使用与 device class 相同的名称,则可以省略 translation key,因为此时 entity 将自动使用 device class 名称。
:::
## 附加资源
关于翻译流程的更多信息,请参阅 [翻译文档](/developers/internationalization/core.md),其中还包含有关 [entity 翻译](/developers/internationalization/core.md#name-of-entities)的信息。
关于 entity 命名的更多信息,请参阅 [entity 文档](/developers/core/entity.md#has_entity_name-true-mandatory-for-new-integrations)。
## 例外
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/entity-unavailable.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 理由
如果我们无法从设备或服务获取数据,应将其标记为不可用。
这样做的目的是反映更准确的状态,而不是仅仅显示上一次已知状态。
如果我们能成功获取数据,但暂时缺少部分数据,则应将 entity 状态标记为 unknown。
## 示例实现
由于实现方式多种多样,我们只针对使用 coordinator 的集成以及通过 `async_update` 更新状态的 entity 提供示例。
### 使用 coordinator 的集成示例
在本示例中,我们有一个使用 coordinator 获取数据的集成。
coordinator 与 `CoordinatorEntity` 结合使用时,具备内置的可用性逻辑。
如果需要额外的可用性逻辑,请务必纳入 `super().available` 的值。
在示例的 sensor 中,当更新失败或该设备的数据缺失时,我们将 entity 标记为不可用。
`coordinator.py`
```python {18} showLineNumbers
class MyCoordinator(DataUpdateCoordinator[dict[str, MyDevice]]):
"""Class to manage fetching data."""
def __init__(self, hass: HomeAssistant, client: MyClient) -> None:
"""Initialize coordinator."""
super().__init__(
hass,
logger=LOGGER,
name=DOMAIN,
update_interval=timedelta(minutes=1),
)
self.client = client
async def _async_update_data(self) -> dict[str, MyDevice]:
try:
return await self.client.get_data()
except MyException as ex:
raise UpdateFailed(f"The service is unavailable: {ex}")
```
`sensor.py`
```python {6} showLineNumbers
class MySensor(SensorEntity, CoordinatorEntity[MyCoordinator]):
@property
def available(self) -> bool:
"""Return True if entity is available."""
return super().available and self.identifier in self.coordinator.data
```
### 通过 `async_update` 更新的 entity 示例
在本示例中,我们有一个通过 `async_update` 更新值的 sensor。
如果无法获取数据,我们通过简写方式将 entity 设置为不可用。
如果能获取数据,则将 entity 设置为可用并更新值。
`sensor.py`
```python {7,9} showLineNumbers
class MySensor(SensorEntity):
async def async_update(self) -> None:
try:
data = await self.client.get_data()
except MyException as ex:
self._attr_available = False
else:
self._attr_available = True
self._attr_native_value = data.value
```
## 附加资源
有关管理集成状态的更多信息,请参阅[文档](/developers/integration_fetching_data.md)。
## 例外
如果集成可以打开设备(无论是通过用户定义的自动化 trigger,还是通过自动创建辅助控制通道,例如使用 Wake-on-LAN 或红外发射器),则当设备处于待机状态且对主通道(例如 TCP)无响应时,应报告为 `off`。如果不存在此类方法,并且设备在当前状态下无法被控制,则应报告为 `unavailable`。
以下是一个示例场景:一个进入待机模式且只能通过外部设备(例如红外发射器)打开的媒体播放器:
* 当首次添加到 Home Assistant 且没有活动连接时,设备显示为 `unavailable`。
* 如果用户配置自动化(例如使用红外发射器)来打开它,设备在待机时将显示为 `off`。
* 一旦通过外部方法打开且主连接建立,状态将更新为 `on`。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/entity-unique-id.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原理说明
在过去,实体(entities)是未被持久化的。
Home Assistant 不跟踪过去已知哪些实体、又不知道哪些实体。
为了允许对实体进行自定义(如重命名实体或更改测量单位),Home Assistant 需要一种在重启之间跟踪每个实体的方式。
为了解决这个问题,Home Assistant 引入了 entity registry(实体注册表)。
Entity registry 是 Home Assistant 跟踪其所知所有实体的中心位置。
Entity registry 中的每个实体都有一个 unique ID(唯一 ID),该 ID 在每个集成 domain 和每个平台 domain 下是唯一的。
如果实体没有 unique ID,用户对实体的控制就会减少。
因此,确保实体具有 unique ID 可以改善用户体验。
## 示例实现
在这个示例中,有一个温度传感器使用简写表示法设置其 unique ID。
`sensor.py`:
```python {6} showLineNumbers
class MySensor(SensorEntity):
"""Representation of a sensor."""
def __init__(self, device_id: str) -> None:
"""Initialize the sensor."""
self._attr_unique_id = f"{device_id}_temperature"
```
## 补充资料
更多有关 unique ID 要求的信息可在 [documentation](/developers/entity_registry_index.md#unique-id-requirements) 中找到。
## 例外情况
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/exception-translations.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原理说明
有时会出现问题,我们想向用户显示错误消息。
由于 Home Assistant 被世界各地的人使用,确保这些错误消息可被翻译非常重要。
这提高了不使用英语的用户的 Home Assistant 可用性。
Home Assistant 内置了对来自 `HomeAssistantError` 异常的翻译消息的支持。
## 示例实现
在这个示例中,我们展示了一个注册为 Home Assistant 服务 action 的函数。
在抛出异常时,会传入集成 domain 和翻译的 key。
异常应继承自 `HomeAssistantError` 以支持翻译。
错误消息随后在集成的 `strings.json` 文件中定义。
```python {6-9,13-16} showLineNumbers
async def async_set_schedule(call: ServiceCall) -> ServiceResponse:
"""Set the schedule for a day."""
start_date = call.data[ATTR_START_DATE]
end_date = call.data[ATTR_END_DATE]
if end_date < start_date:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="end_date_before_start_date",
)
try:
await client.set_schedule(start_date, end_date)
except MyConnectionError as err:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="cannot_connect_to_schedule",
) from err
```
`strings.json`:
```json
{
"exceptions": {
"end_date_before_start_date": {
"message": "The end date cannot be before the start date."
},
"cannot_connect_to_schedule": {
"message": "Cannot connect to the schedule."
}
}
}
```
## 补充资料
有关抛出异常的更多信息,请参阅 [documentation](/developers/core/platform/raising_exceptions.md)。
## 例外情况
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/has-entity-name.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 理由
`has_entity_name` 是一个 entity 属性,用于改善 Home Assistant 中 entity 的命名。
它的引入旨在根据名称显示的上下文,向用户展示更好的 entity 名称。
我们认为这是一种良好实践,因为它允许不同集成之间的命名保持一致性。
## 示例实现
在下面的示例中,如果设备名称为 "My device",字段为 "temperature",则 entity 名称将显示为 "My device temperature"。
`sensor.py`
```python {4} showLineNumbers
class MySensor(SensorEntity):
"""Representation of a sensor."""
_attr_has_entity_name = True
def __init__(self, device: Device, field: str) -> None:
"""Initialize the sensor."""
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, device.id)},
name=device.name,
)
self._attr_name = field
```
但是,当 entity 名称设置为 `None` 时,将使用设备名称作为 entity 名称。
在这种情况下,lock entity 将直接称为 "My device"。
这应用于设备的主要功能。
`lock.py`
```python {4-5,11} showLineNumbers
class MyLock(LockEntity):
"""Representation of a lock."""
_attr_has_entity_name = True
_attr_name = None
def __init__(self, device: Device) -> None:
"""Initialize the lock."""
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, device.id)},
name=device.name,
)
```
## 附加资源
关于 entity 命名的更多信息,请参阅 [entity 文档](/developers/core/entity.md#has_entity_name-true-mandatory-for-new-integrations)。
关于设备的更多信息,请参阅 [device 文档](/developers/device_registry_index.md)。
## 例外
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/icon-translations.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原理说明
在过去,图标(icons)是集成状态(state)的一部分。
这其实并不是必需的,因为它们通常是静态的,或者具有固定的一组状态。
为了减轻状态机(state machine)的负担,引入了 icon translations。
这个功能的名称听起来有些奇怪,因为它并不是要翻译图标本身,而是通过 translation key 来引用图标。
Icon translations 背后的想法是:由集成在一个文件中定义图标,然后由前端使用该文件来显示图标。
此外,这还为状态属性(state attribute)的不同值添加了图标支持,例如气候实体(climate entity)可能的预设模式(preset modes)。
:::info
请注意,实体也可以从 device class 获取图标。
如果实体的上下文与 device class 完全相同,我们不应覆盖该图标,以保持集成之间的一致性。
例如,PM2.5 传感器实体将不会获得自定义图标,因为 device class 已经在相同的上下文中提供了图标。
:::
## 示例实现
### 基于状态的图标
在这个示例中,我们定义一个具有 translation key 的传感器实体。
在 `icons.json` 文件中,我们为传感器实体定义图标,并为状态 `high` 定义一个状态图标。
因此,当实体状态为 `high` 时,将显示图标 `mdi:tree-outline`;否则将显示 `mdi:tree`。
`sensor.py`
```python {5} showLineNumbers
class MySensor(SensorEntity):
"""Representation of a sensor."""
_attr_has_entity_name = True
_attr_translation_key = "tree_pollen"
```
`icons.json`
```json
{
"entity": {
"sensor": {
"tree_pollen": {
"default": "mdi:tree",
"state": {
"high": "mdi:tree-outline"
}
}
}
}
}
```
### 基于范围的图标
对于数值型实体,您可以定义根据数值范围变化的图标。该功能消除了集成代码中自定义逻辑的需求,并提供了一致的可视化方式来表示不同的传感器值。
基于范围的 icon translations 特别适用于:
* 电池电量指示器
* 信号强度计
* 温度传感器
* 空气质量指示器
* 填充液位传感器
#### 配置
在 `icons.json` 文件中,按升序定义各范围及其对应的图标:
```json
{
"entity": {
"sensor": {
"battery_level": {
"default": "mdi:battery-unknown",
"range": {
"0": "mdi:battery-outline",
"10": "mdi:battery-10",
"20": "mdi:battery-20",
"30": "mdi:battery-30",
"40": "mdi:battery-40",
"50": "mdi:battery-50",
"60": "mdi:battery-60",
"70": "mdi:battery-70",
"80": "mdi:battery-80",
"90": "mdi:battery-90",
"100": "mdi:battery"
}
}
}
}
}
```
系统会选择与小于或等于实体当前数值状态的最高范围值相关联的图标。例如,使用上述配置时:
* 值为 15 将显示 `mdi:battery-10` 图标(15 大于 10 但小于 20)
* 值为 45 将显示 `mdi:battery-40` 图标(45 大于 40 但小于 50)
* 值为 100 将显示 `mdi:battery` 图标(100 等于定义的最高范围值)
* 值为 5 将显示 `mdi:battery-outline` 图标(5 大于 0 但小于 10)
* 值为 -10 将显示 `mdi:battery-unknown` 默认图标(值在定义的范围之外)
* 值为 120 将显示 `mdi:battery` 图标(任何超出最后一个定义的范围条目 100 的值,都将使用该最终范围值所对应的图标)
在实现基于范围的图标时,请注意:
* 范围值必须为数值,并且必须按升序定义
* 支持整数("0"、"100")和小数("0.5"、"99.9")范围值
* 给定状态的图标是从小于或等于实体当前值的最高范围值中选出的
* 在以下情况下使用默认图标:
* 实体的状态值落在所有定义范围之外
* 实体不可用
* 实体的状态无法解析为有效数字
* 如果在同一个 translation key 中同时定义了基于状态的图标和基于范围的图标,则基于状态的图标优先于基于范围的图标
* 定义的范围数量没有限制,但请考虑性能和可读性
## 补充资料
更多有关 icon translations 的信息,请参阅 [entity](/developers/core/entity.md#icon-translations) 文档。
## 例外情况
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/inject-websession.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原理说明
由于许多设备和服务通过 HTTP 连接,活跃 web session 的数量可能会很高。
为提高这些 web session 的效率,建议在集成使用的依赖客户端中支持传入 web session。
Home Assistant 支持 [`aiohttp`](https://docs.aiohttp.org/en/stable/) 和 [`httpx`](https://www.python-httpx.org/) 这两种方式。
这意味着集成的依赖应使用这两个库中的一个。
## 示例实现
在下面的示例中,将一个 `aiohttp` session 传入客户端。
`httpx` 的等效实现为 `get_async_client`。
```python {4} showLineNumbers
async def async_setup_entry(hass: HomeAssistant, entry: MyConfigEntry) -> bool:
"""Set up my integration from a config entry."""
client = MyClient(entry.data[CONF_HOST], async_get_clientsession(hass))
```
:::info
在某些情况下可能不需要共享 session,例如使用 cookies 时。
在这种情况下,可以使用 `async_create_clientsession`(针对 `aiohttp`)或 `create_async_httpx_client`(针对 `httpx`)来创建新 session。
:::
## 例外情况
如果集成不发出任何 HTTP 请求,则此规则不适用。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/integration-owner.md
---
## 原因
Home Assistant 集成了成千上万种不同的设备和服务,大多数集成是由项目核心维护者以外的人员贡献的。
我们鼓励添加和维护集成的贡献者成为"集成所有者"(integration owner)。
这个角色赋予贡献者在 GitHub 上处理该集成相关的 issue 和 pull request 时更大的权限,同时也意味着贡献者承担了该集成维护的责任。
每当有针对其集成的新 issue 或 pull request 时,集成所有者将自动收到通知。
在 GitHub 上,集成所有者被称为"codeowner"。
集成所有者记录在每个集成的 `manifest.json` 文件中。
要成为集成所有者,请提交一个 pull request,将你的 GitHub 用户名添加到 manifest 中的 `"codeowners"` 字段。
一个集成可以有多个所有者。
我们非常看重集成所有者!
我们相信拥有所有者的集成能得到更好的维护。
在代码评审中,我们将集成所有者视为该集成方面的专家,并赋予其意见更高的权重。
## 示例实现
集成所有者在 `manifest.json` 中设置。
```json {4} showLineNumbers
{
"domain": "my_integration",
"name": "My Integration",
"codeowners": ["@me"]
}
```
## 更多资源
关于集成所有者的更多信息,请参见 [ADR-0008](https://github.com/home-assistant/architecture/blob/master/adr/0008-code-owners.md)。
## 例外
本规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/log-when-unavailable.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 理由
当设备或服务无法访问时,entities 通常会变为 unavailable。
为了允许用户了解原因,集成应在此发生时进行记录。
确保总共只记录一次,以避免日志刷屏。
当设备或服务再次可访问时,集成也应记录这一点。
这对于使用日志查找设备或服务何时不可用、何时恢复在线非常有用。
:::info
日志应使用 `info` 级别。
:::
## 示例实现
由于实现方式多种多样,我们只针对使用 coordinator 的集成和通过 `async_update` 更新的 entity 提供示例。
### 使用 coordinator 的集成示例
在此示例中,我们有一个使用 coordinator 获取数据的集成。
coordinator 内置了只记录一次的逻辑。
你在 coordinator 中唯一需要做的是,当设备或服务不可用时抛出 `UpdateFailed`。
`coordinator.py`
```python {18} showLineNumbers
class MyCoordinator(DataUpdateCoordinator[MyData]):
"""Class to manage fetching data."""
def __init__(self, hass: HomeAssistant, client: MyClient) -> None:
"""Initialize coordinator."""
super().__init__(
hass,
logger=LOGGER,
name=DOMAIN,
update_interval=timedelta(minutes=1),
)
self.client = client
async def _async_update_data(self) -> MyData:
try:
return await self.client.get_data()
except MyException as ex:
raise UpdateFailed(f"The device is unavailable: {ex}")
```
### 通过 `async_update` 更新的 entity 示例
在此示例中,我们有一个通过 `async_update` 更新其值的 sensor。
示例将在 sensor 不可用时记录,并在 sensor 重新上线时记录。
注意,使用实例属性来跟踪消息是否已记录,以避免日志刷屏。
`sensor.py`
```python {10-12,16-18} showLineNumbers
class MySensor(SensorEntity):
_unavailable_logged: bool = False
async def async_update(self) -> None:
try:
data = await self.client.get_data()
except MyException as ex:
self._attr_available = False
if not self._unavailable_logged:
_LOGGER.info("The sensor is unavailable: %s", ex)
self._unavailable_logged = True
else:
self._attr_available = True
self._attr_native_value = data.value
if self._unavailable_logged:
_LOGGER.info("The sensor is back online")
self._unavailable_logged = False
```
## 更多资源
有关管理集成状态的更多信息,请参见[文档](/developers/integration_fetching_data.md)
## 例外
此规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/parallel-updates.md
---
## 原因
某些设备或服务不擅长同时接收大量请求。
为了避免这种情况,Home Assistant 内置了一项限制同时发送给某个设备或服务的请求数量的功能。
该限制将同时应用于实体更新和 action 调用。
我们建议显式设置并行更新数量,并将其视为良好实践。
## 示例实现
在下面的示例中,我们将并行更新数量设置为 1。
这意味着如果 sensor platform 上有多个实体,它们将被逐个更新。
如果不需要限制并行更新数量,可以将其设置为 0。
`sensor.py`
```python {1} showLineNumbers
PARALLEL_UPDATES = 1
class MySensor(SensorEntity):
"""Representation of a sensor."""
def __init__(self, device: Device) -> None:
"""Initialize the sensor."""
...
```
### 使用 coordinator 时
使用 coordinator 时,数据更新已经集中处理。
这意味着对于只读 platform(`binary_sensor`、`sensor`、`device_tracker`、`event`),可以设置 `PARALLEL_UPDATES = 0`,此时只需考虑 action 调用是否需要设置合适的并行更新数量。
:::note
coordinator 仅集中处理入站数据更新;它不会限制出站 action 调用。
这意味着即使对于使用 coordinator 的 platform,你仍然需要为包含 action 的 platform 考虑合适的并行更新数量(例如,切换 `switch` 或打开 `light`)。
:::
`sensor.py`
```python {1,2} showLineNumbers
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
class MySensor(CoordinatorEntity, SensorEntity):
"""Representation of a sensor."""
def __init__(self, device: Device) -> None:
"""Initialize the sensor."""
...
```
## 更多资源
关于请求并行性的更多信息,请查阅[相关文档](/developers/integration_fetching_data.md#request-parallelism)。
## 例外
本规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/reauthentication-flow.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原因
用户可能会更改设备或服务密码,却忘记设备或账户仍然与 Home Assistant 关联。
为了避免用户必须删除配置条目并重新添加,我们会启动重新身份验证流程。
在此流程中,用户可以提供新的凭据以供后续使用。
这是一种非常友好的方式,让用户知道他们需要采取行动并更新凭据。
## 示例实现
在下面的示例中,我们展示了一个身份验证流程,允许用户使用新的 API token 重新验证身份。
当我们收到新 token 时,会检查是否能够连接到服务,以避免用户输入无效的 token。
如果连接成功,我们将使用新 token 更新配置条目。
`config_flow.py`:
```python {6-11,13-35} showLineNumbers
class MyConfigFlow(ConfigFlow, domain=DOMAIN):
"""My config flow."""
host: str
async def async_step_reauth(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Perform reauthentication upon an API authentication error."""
self.host = entry_data[CONF_HOST]
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm reauthentication dialog."""
errors: dict[str, str] = {}
if user_input:
client = MyClient(self.host, user_input[CONF_API_TOKEN])
try:
user_id = await client.check_connection()
except MyException as exception:
errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(user_id)
self._abort_if_unique_id_mismatch(reason="wrong_account")
return self.async_update_reload_and_abort(
self._get_reauth_entry(),
data_updates={CONF_API_TOKEN: user_input[CONF_API_TOKEN]},
)
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema({vol.Required(CONF_API_TOKEN): TextSelector()}),
errors=errors,
)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}
if user_input:
client = MyClient(user_input[CONF_HOST], user_input[CONF_API_TOKEN])
try:
user_id = await client.check_connection()
except MyException as exception:
errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(user_id)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title="MyIntegration",
data=user_input,
)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_HOST): TextSelector(),
vol.Required(CONF_API_TOKEN): TextSelector(),
}
),
errors=errors,
)
```
## 更多资源
关于处理过期凭据的更多信息,请查阅[相关文档](/developers/integration_setup_failures.md#handling-expired-credentials)。
## 例外
如果集成不需要任何身份验证,本规则不适用。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/reconfiguration-flow.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原因
用户可能会更改设备或服务的某些设置,例如修改密码或更改 IP 地址。
理想情况下,Home Assistant 能检测到这些事件并通知用户需要进行重新身份验证或关注。
重新配置流程让用户能够主动触发重新配置,从而更新设备或服务的配置,而无需删除并重新添加设备或服务。
这为用户提供了更多尝试修复问题的方式,无需重启软件或触发重新身份验证。
## 示例实现
在 `config_flow.py` 文件中,添加一个名为 `reconfigure` 的新步骤,允许用户重新配置集成。
在以下示例中,我们检查新的 api token 是否有效。
同时,我们会确认用户是否试图用不同的账户重新配置集成,因为集成使用的账户不应改变。
`config_flow.py`:
```python {4-31} showLineNumbers
class MyConfigFlow(ConfigFlow, domain=DOMAIN):
"""My config flow."""
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration of the integration."""
errors: dict[str, str] = {}
if user_input:
client = MyClient(user_input[CONF_HOST], user_input[CONF_API_TOKEN])
try:
user_id = await client.check_connection()
except MyException as exception:
errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(user_id)
self._abort_if_unique_id_mismatch(reason="wrong_account")
return self.async_update_reload_and_abort(
self._get_reconfigure_entry(),
data_updates=user_input,
)
return self.async_show_form(
step_id="reconfigure",
data_schema=vol.Schema(
{
vol.Required(CONF_HOST): TextSelector(),
vol.Required(CONF_API_TOKEN): TextSelector(),
}
),
errors=errors,
)
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}
if user_input:
client = MyClient(user_input[CONF_HOST], user_input[CONF_API_TOKEN])
try:
user_id = await client.check_connection()
except MyException as exception:
errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(user_id)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title="MyIntegration",
data=user_input,
)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_HOST): TextSelector(),
vol.Required(CONF_API_TOKEN): TextSelector(),
}
),
errors=errors,
)
```
## 更多资源
关于重新配置流程的更多信息,请参见[重新配置流程文档](/developers/core/integration/config_flow.md#reconfigure)。
## 例外
配置流程中没有设置项的集成免于此规则。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/repair-issues.md
---
## 原因
Repair issues 和 repair flows 是一种非常友好的方式,让用户知道出现了问题,并且他们可以对此采取行动。
Repair issues 只是告知用户问题可以自行修复,而 repair flows 则可以帮用户自动修复。
Repair issues 和 repair flows 应该是可操作的,并能提供关于问题的详细信息。
因此,我们不应该仅为了告知用户存在问题而提出 repair issue,特别是用户无法自行修复的问题。
## 示例实现
在下面的示例中,我们有一个用于本地托管服务的集成。
启动时,我们会检查是否支持正在运行的服务版本。
如果不支持,我们会提出一个 repair issue,告知用户在能够再次使用集成之前,应该先更新服务。
`__init__.py`
```python {6-14} showLineNumbers
async def async_setup_entry(hass: HomeAssistant, entry: MyConfigEntry) -> None:
"""Set up the integration from a config entry."""
client = MyClient(entry.data[CONF_HOST])
version = await client.get_version()
if version < MINIMUM_VERSION:
ir.async_create_issue(
hass,
DOMAIN,
"outdated_version",
is_fixable=False,
issue_domain=DOMAIN,
severity=ir.IssueSeverity.ERROR,
translation_key="outdated_version",
)
raise ConfigEntryError(
"Version of MyService is %s, which is lower than minimum version %s",
version,
MINIMUM_VERSION,
)
```
## 更多资源
关于 repair issues 和 repair flows 的更多信息,请参见[repairs 文档](/developers/core/platform/repairs.md)。
## 例外
本规则没有例外。
---
url: /developers/core/integration-quality-scale/rules/runtime-data.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原因
`ConfigEntry` 对象有一个 `runtime_data` 属性,可用于存储运行时数据。
这对于存储那些不需要持久化到配置文件存储、但在配置条目生命周期内所需的数据很有用。
通过使用 `runtime_data`,我们为开发者提供了一致且类型安全的方式来存储运行时数据。
由于增加了类型标注,我们可以使用工具来避免类型错误。
## 示例实现
`ConfigEntry` 的类型可以通过放入 `runtime_data` 的数据类型进行扩展。
在下面的示例中,我们使用 `MyClient` 扩展 `ConfigEntry` 类型,这意味着 `runtime_data` 属性将为 `MyClient` 类型。
`__init__.py`:
```python {1,4,9} showLineNumbers
type MyIntegrationConfigEntry = ConfigEntry[MyClient]
async def async_setup_entry(hass: HomeAssistant, entry: MyIntegrationConfigEntry) -> bool:
"""Set up my integration from a config entry."""
client = MyClient(entry.data[CONF_HOST])
entry.runtime_data = client
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
```
:::info
如果集成实现了 `strict-typing`,则必须使用自定义类型标注的 `MyIntegrationConfigEntry`,并在全局范围内使用。
:::
## 更多资源
关于配置条目及其生命周期的更多信息,请参见[config entry 文档](/developers/config_entries_index.md)。
## 例外
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/stale-devices.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原因
当设备从 hub 或账户中移除时,也应从 Home Assistant 中移除。
这样,用户界面就不会再显示已不可用的设备。
我们只应移除那些确定已不可用的设备。
如果无法确定设备是否仍可用,请务必实现 `async_remove_config_entry_device`。
这将允许用户手动从 device registry 中删除设备。
## 示例实现
在此示例中,我们有一个从服务获取数据的 coordinator。
当数据更新时,我们会检查是否有设备被移除。
如果有,我们会将它们从 device registry 中移除。
同时也会导致与该设备关联的所有实体被移除。
`coordinator.py`
```python {13,20-30} showLineNumbers
class MyCoordinator(DataUpdateCoordinator[dict[str, MyDevice]]):
"""Class to manage fetching data."""
def __init__(self, hass: HomeAssistant, client: MyClient) -> None:
"""Initialize coordinator."""
super().__init__(
hass,
logger=LOGGER,
name=DOMAIN,
update_interval=timedelta(minutes=1),
)
self.client = client
self.previous_devices: set[str] = set()
async def _async_update_data(self) -> dict[str, MyDevice]:
try:
data = await self.client.get_data()
except MyException as ex:
raise UpdateFailed(f"The service is unavailable: {ex}")
current_devices = set(data)
if (stale_devices := self.previous_devices - current_devices):
device_registry = dr.async_get(self.hass)
for device_id in stale_devices:
device = device_registry.async_get_device(identifiers={(DOMAIN, device_id)})
if device:
device_registry.async_update_device(
device_id=device.id,
remove_config_entry_id=self.config_entry.entry_id,
)
self.previous_devices = current_devices
return data
```
为了展示一个用户可以手动从 device registry 中删除设备的示例,我们在 `__init__.py` 中实现 `async_remove_config_entry_device`。
定义此函数将在 UI 的设备页面上启用删除按钮。
在此示例中,集成只能获取设备的更新,而无法获取已连接设备的完整列表,因此无法自动删除设备。
在 `async_remove_config_entry_device` 中,我们应该实现一个函数来检查设备是否仍然可用。
如果不可用,我们返回 `True` 以允许用户手动删除设备。
此处,如果一段时间内没有收到设备的任何更新,则假设设备已无法工作。
`__init__.py`
```python showLineNumbers
async def async_remove_config_entry_device(
hass: HomeAssistant, config_entry: MyConfigEntry, device_entry: dr.DeviceEntry
) -> bool:
"""Remove a config entry from a device."""
return not any(
identifier
for identifier in device_entry.identifiers
if identifier[0] == DOMAIN
and identifier[1] in config_entry.runtime_data.data
)
```
## 更多资源
关于设备的更多信息,请查阅[device registry 文档](/developers/device_registry_index.md)。
## 例外
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/strict-typing.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原因
Python 是一种动态类型语言,这可能是许多 bug 的来源。
通过使用类型标注,你可以在早期发现 bug 并避免引入它们。
类型标注由 mypy 进行检查,mypy 是一个 Python 静态类型检查器。
由于 Python 的类型机制以及类型标注在 Python 中是可选的,mypy 只会检查已知已进行类型标注的代码。
为了改进这一点,我们建议完整地为你的库添加类型标注,并使其符合 PEP-561 规范。
这意味着你需要在库中添加一个 `py.typed` 文件。
该文件告诉 mypy 你的库是完全类型标注的,此后 mypy 就可以从你的库中读取类型标注。
在 Home Assistant 代码库中,你可以将你的集成添加到 [`.strict-typing`](https://github.com/home-assistant/core/blob/dev/.strict-typing) 文件中,这将为你集成启用严格的类型检查。
:::warning
如果集成实现了 `runtime-data`,则必须使用自定义类型标注的 `MyIntegrationConfigEntry`,并在全局范围内使用。
:::
## 更多资源
要了解更多关于 `py.typed` 文件的信息,请参见 [PEP-561](https://peps.python.org/pep-0561/)。
## 例外
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/test-before-configure.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原因
配置流程不仅非常易于使用,还是一种让用户在配置完成后立即知道某些设置无法正常工作的有效方式。
它可以捕获以下问题:
* DNS 问题
* 防火墙问题
* 错误的凭据
* 错误的 IP 地址或端口
* 不支持的设备
一旦集成设置完成,这类问题往往很难排查,因此最好尽早捕获,避免用户被一个无法正常工作的集成困住。
由于这改善了用户体验,在配置流程中测试连接是必需的。
## 示例实现
要验证用户输入,你可以像往常一样使用数据调用你的库,并进行一次测试调用。
如果调用失败,你可以向用户返回错误信息。
在下面的示例中,如果 `client.get_data()` 调用抛出 `MyException`,用户将看到一条集成无法连接的错误信息。
`config_flow.py`:
```python {10-17} showLineNumbers
class MyConfigFlow(ConfigFlow, domain=DOMAIN):
"""My config flow."""
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}
if user_input:
client = MyClient(user_input[CONF_HOST])
try:
await client.get_data()
except MyException:
errors["base"] = "cannot_connect"
except Exception: # noqa: BLE001
LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_create_entry(
title="MyIntegration",
data=user_input,
)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema({vol.Required(CONF_HOST): TextSelector()}),
errors=errors,
)
```
## 更多资源
关于配置流程的更多信息,请参见[config flow 文档](/developers/core/integration/config_flow.md)。
## 例外
不与设备或服务建立连接的集成(例如 helpers)无需在配置流程中测试连接,免于此规则。
依赖运行时自动发现的集成(如 Google Cast)也免于此规则。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/test-before-setup.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原因
初始化集成时,我们应该检查是否能够正确设置。
这样可以立即让用户知道集成无法正常工作。
实现这些检查可以提高集成正确工作的信心,并以用户友好的方式展示错误。
这将改善用户体验。
## 示例实现
当失败的原因是临时性的(例如设备暂时离线),我们应该抛出 `ConfigEntryNotReady`,Home Assistant 稍后会重试设置。
如果失败的原因是密码不正确或 api key 无效,我们应该抛出 `ConfigEntryAuthFailed`,Home Assistant 会提示用户重新进行身份验证(如果已实现重新身份验证流程)。
如果我们预计集成在可预见的将来无法工作,我们应该抛出 `ConfigEntryError`。
`__init__.py`:
```python {6-13} showLineNumbers
async def async_setup_entry(hass: HomeAssistant, entry: MyIntegrationConfigEntry) -> bool:
"""Set up my integration from a config entry."""
client = MyClient(entry.data[CONF_HOST])
try:
await client.async_setup()
except OfflineException as ex:
raise ConfigEntryNotReady("Device is offline") from ex
except InvalidAuthException as ex:
raise ConfigEntryAuthFailed("Invalid authentication") from ex
except AccountClosedException as ex:
raise ConfigEntryError("Account closed") from ex
entry.runtime_data = client
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
```
:::info
请注意,使用 data update coordinator 并通过 `await coordinator.async_config_entry_first_refresh()` 时,也可能隐式实现此功能。
:::
## 更多资源
关于配置条目及其生命周期的更多信息,请参见[config entry 文档](/developers/config_entries_index.md)。
## 例外
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/test-coverage.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原因
由于我们支持大量的不同集成,我们并非拥有每一个设备或服务来进行手动测试。
为确保在接受代码更改时不会破坏任何功能,我们需要对所有集成模块有良好的测试覆盖率。
这可以防止 bug 和回归的引入。
这也让新开发者能够理解代码库,并在不破坏任何现有用例的情况下进行更改。
## 更多资源
关于测试以及如何计算测试覆盖率的更多信息,请参见[Testing your code](/developers/development_testing.md)页面。
## 例外
本规则没有例外。
## 相关规则
---
url: /developers/core/integration-quality-scale/rules/unique-config-entry.md
---
import RelatedRules from './\_includes/related\_rules.jsx'
## 原因
由于通过 UI 设置集成非常简便,用户可能会意外地将同一设备或服务设置两次。
这可能导致重复的设备以及具有相同唯一标识符的实体发生冲突,从而产生负面影响。
任何发现流程也必须确保配置条目具有唯一标识性,否则就会发现已经设置过的设备。
为了防止这种情况,我们需要确保用户只能设置一次设备或服务。
## 示例实现
集成检查自己是否已经设置过通常有两种常见方式。
第一种方式是为配置条目分配 `unique_id`。
第二种方式是检查配置条目中的数据片段是否唯一。
以下示例展示了如何在配置流程中实现这些检查。
### 唯一标识符
第一种方式是为配置条目分配 `unique_id`。
该唯一 ID 在每个集成 domain 内是唯一的,因此其他集成可以使用相同的唯一 ID 而不会出现问题。
下面是一个配置流程的示例,它通过 client 获取输入配置对应的 `unique_id`,并检查该 `unique_id` 是否已经存在。
如果已存在,流程将中止并向用户显示错误信息。
`config_flow.py`:
```python {16-17} showLineNumbers
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}
if user_input:
client = MyClient(user_input[CONF_HOST])
try:
identifier = await client.get_identifier()
except MyException:
errors["base"] = "cannot_connect"
except Exception: # noqa: BLE001
LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
await self.async_set_unique_id(identifier)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title="MyIntegration",
data=user_input,
)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema({vol.Required(CONF_HOST): TextSelector()}),
errors=errors,
)
```
### 唯一数据
第二种方式是检查配置条目中的数据片段是否唯一。
在下面的示例中,用户输入 host 和密码。
如果已经存在针对同一 host 的配置条目,流程将中止并向用户显示错误信息。
`config_flow.py`:
```python
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}
if user_input:
self._async_abort_entries_match({CONF_HOST: user_input[CONF_HOST]})
client = MyClient(user_input[CONF_HOST], user_input[CONF_PASSWORD])
try:
await client.get_data()
except MyException:
errors["base"] = "cannot_connect"
except Exception: # noqa: BLE001
LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
return self.async_create_entry(
title="MyIntegration",
data=user_input,
)
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(
{
vol.Required(CONF_HOST): TextSelector(),
vol.Required(CONF_PASSWORD): TextSelector(),
}
),
errors=errors,
)
```
## 更多资源
关于配置流程的更多信息,请参见[config flow 文档](/developers/core/integration/config_flow.md)。
关于唯一标识符要求的更多信息,请参见[相关文档](/developers/entity_registry_index.md#unique-id-requirements)。
## 例外
本规则没有例外。
## 相关规则
---
url: /developers/core/integration/brand_images.md
---
为了帮助用户从视觉上识别集成和硬件,Home Assistant 会为集成和硬件实体显示图标和 logo。
图标是用于在空间有限的 UI 中显示的正方形图片,而 logo 是用于集成配置页面及其他有更大空间的矩形图片。
Home Assistant 支持亮色和暗色模式图片。
## 如何添加品牌图片
添加品牌图片的方式取决于集成的类型。
### Core 集成
对于 core 集成,品牌图片通过向 [brands repository] 创建 pull request 来添加。
图片应添加到 `core_integrations/` 目录中一个以集成 domain 命名的目录下。
例如,Philips Hue 集成的品牌图片应放在 `core_integrations/hue` 中。
:::info
这些图片在服务端进行缓存,因此一旦 pull request 被合并,图片可能不会立即可用。
:::
#### Material design icons
有些集成(如 helpers)没有代表其品牌的标识。
在这种情况下,你可以在 `core_integrations/` 中的对应文件夹中放置一个 `icon.txt` 文件,其内容为要使用的图标名称,前面加上 `mdi:` 前缀,从而使用 Material Design Icons 库中的图标。
### Core 品牌
与 core 集成类似,\[core 品牌] 也是通过向 [brands repository] 创建 pull request 来添加。
图片应添加到 `core_brands/` 目录中一个以品牌 domain 命名的目录下。
例如,Google 品牌的品牌图片应放在 `core_brands/google` 中。
如果品牌的图片与其某个集成的图片相同,可以创建 symlink 以避免重复。
例如,Samsung 品牌的图片与 Samsung TV 集成的图片相同,因此 `core_integrations/samsung_tv` 目录中包含指向 `core_brands/samsung` 中图片的 symlink。
### Custom 集成
在 Home Assistant 2026.3 之前,custom 集成也被要求将品牌图片添加到 [brands repository]。
从 Home Assistant 2026.3 开始,custom 集成可以通过在集成目录内添加 `brand/` 目录来包含自己的品牌图片。
例如,如果你有一个 domain 为 `my_integration` 的 custom 集成,你可以在 `custom_components/my_integration/brand/` 中添加品牌图片。
本地品牌图片优先于来自 [brands repository] 的图片,因此如果 custom 集成有本地 `brand/` 目录,Home Assistant 将使用这些图片而不是来自 [brands repository] 的图片。
:::info
如果 custom 集成将贡献给 Home Assistant Core,请务必移除本地品牌图片,并改为打开 PR 将它们添加到 [brands repository]。
:::
[core brands]: /docs/creating_integration_brand.md
[brands repository]: https://github.com/home-assistant/brands
## 品牌图片如何被提供
品牌图片通过本地 API 提供,以便它们与 frontend 来自同一来源。
可用的 API endpoint 如下:
* `/api/brands/integration/{domain}/{image}` - 集成的图标和 logo
* `/api/brands/hardware/{category}/{image}` - 硬件图片
如果请求的图片不存在,所有 endpoint 默认返回通用占位图片。
若要选择退出并在图片不存在时返回 404,请添加 `?placeholder=no` 查询参数。
这些 endpoint 需要认证。请求可以使用标准已认证会话(Bearer token)或通过在 `token` 查询参数中传递 access token 来认证。
frontend 通过 `brands/access_token` WebSocket 命令获取此 access token,并自动将其附加到所有品牌图片 URL。
---
url: /developers/core/integration/config_flow.md
---
集成可以通过用户界面进行设置,只需为集成添加对 config flow 的支持以创建 config entry。想要支持 config entry 的集成需要定义一个 Config Flow Handler。该 handler 负责管理从用户输入、discovery 或其他来源(如 Home Assistant OS)创建条目。
Config Flow Handler 控制存储在 config entry 中的数据。这意味着 Home Assistant 启动时无需验证配置是否正确。同时也能防止 breaking changes,因为当版本发生变化时,我们可以将配置条目迁移到新格式。
在实例化 handler 时,Home Assistant 会确保加载所有依赖并安装集成的 requirements。
## 更新 manifest
你需要更新集成的 manifest,以告知 Home Assistant 该集成具有 config flow。做法是将 `config_flow: true` 添加到 manifest 中([docs](/developers/creating_integration_manifest.md#config-flow))。
## 定义你的 config flow
Config entries 使用 [data flow entry framework](/developers/data_entry_flow_index.md) 来定义它们的 config flows。config flow 需要在集成文件夹的文件 `config_flow.py` 中定义,继承 `homeassistant.config_entries.ConfigFlow`,并在继承 `ConfigFlow` 时传入一个 `domain` 键。
```python
from homeassistant import config_entries
from .const import DOMAIN
class ExampleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Example config flow."""
```
更新 manifest 并创建 `config_flow.py` 后,需要运行 `python3 -m script.hassfest`(仅一次),Home Assistant 就会为你的集成激活 config entry。
## Config flow 标题
config flow 的标题可以被集成影响,并按以下优先级顺序确定:
1. 如果 config flow 中的 `title_placeholders` 被设置为非空字典,它将用于动态计算 config flow 的标题。Reauth 和 reconfigure flows 会自动将 `title_placeholders` 设置为 `{"name": config_entry_title}`。
1. 如果集成提供了本地化的 `flow_title`,将使用它,并从 `title_placeholders` 替换任何翻译占位符。
2. 如果集成未提供 `flow_title`,但 `title_placeholders` 包含 `name`,则将使用 `name` 作为 flow 的标题。
2. 如果存在,将 flow 标题设置为集成本地化的 `title`。
3. 如果存在,将 flow 标题设置为集成 manifest 的 `name`。
4. 将 flow 标题设置为集成的 domain。
请注意,这个优先级顺序意味着:
* 如果 `title_placeholders` 字典缺失或为空,即使本地化的 `flow_title` 不包含任何占位符,也会被忽略
* 如果 `title_placeholders` 非空,但不存在本地化的 `flow_title` 且 `title_placeholders` 不包含 `name`,则会被忽略
## 定义步骤
你的 config flow 需要定义配置流程的各个步骤。每个步骤由一个唯一的 step name(`step_id`)标识。step 回调方法遵循 `async_step_` 的模式。[Data Entry Flow](/developers/data_entry_flow_index.md) 的文档描述了 step 的不同返回值。以下是一个定义 `user` step 的示例:
```python
import voluptuous as vol
class ExampleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
async def async_step_user(self, info):
if info is not None:
pass # TODO: process info
return self.async_show_form(
step_id="user", data_schema=vol.Schema({vol.Required("password"): str})
)
```
有一些 step name 是预留给系统使用的:
| Step name | Description |
| ----------- |---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| `bluetooth` | 如果集成通过 Bluetooth 被发现,则被调用,如 [使用 manifest 中的 `bluetooth`](/developers/creating_integration_manifest.md#bluetooth) 所指定的。 |
| `discovery` | *已废弃* 如果集成被发现且未定义匹配的 step,则被调用。 |
| `dhcp` | 如果集成通过 DHCP 被发现,则被调用,如 [使用 manifest 中的 `dhcp`](/developers/creating_integration_manifest.md#dhcp) 所指定的。 |
| `hassio` | 如果集成通过 Supervisor add-on 被发现,则被调用。 |
| `homekit` | 如果集成通过 HomeKit 被发现,则被调用,如 [使用 manifest 中的 `homekit`](/developers/creating_integration_manifest.md#homekit) 所指定的。 |
| `mqtt` | 如果集成通过 MQTT 被发现,则被调用,如 [使用 manifest 中的 `mqtt`](/developers/creating_integration_manifest.md#mqtt) 所指定的。 |
| `ssdp` | 如果集成通过 SSDP/uPnP 被发现,则被调用,如 [使用 manifest 中的 `ssdp`](/developers/creating_integration_manifest.md#ssdp) 所指定的。 |
| `usb` | 如果集成通过 USB 被发现,则被调用,如 [使用 manifest 中的 `usb`](/developers/creating_integration_manifest.md#usb) 所指定的。 |
| `user` | 当用户通过用户界面发起 flow,或发现时且未定义匹配的 discovery step 时被调用。 |
| `reconfigure` | 当用户通过用户界面发起 flow 以重新配置现有 config entry 时被调用。 |
| `zeroconf` | 如果集成通过 Zeroconf/mDNS 被发现,则被调用,如 [使用 manifest 中的 `zeroconf`](/developers/creating_integration_manifest.md#zeroconf) 所指定的。 |
| `reauth` | 如果集成指示它[需要重新认证,例如由于凭据过期](#reauthentication),则被调用。 |
| `import` | 预留给从 YAML 配置迁移到 config entries。 |
## 唯一标识符(Unique IDs)
config flow 可以附加一个 unique ID(必须是字符串),以避免同一设备被设置两次。unique ID 不需要全局唯一,只需在集成 domain 内唯一即可。
通过设置 unique ID,用户可以选择忽略你的 config entry 的 discovery。这样,他们就不会再被它打扰。
如果集成使用 Bluetooth、DHCP、HomeKit、Zeroconf/mDNS、USB 或 SSDP/uPnP 来发现,则必须提供 unique ID。
如果没有可用的 unique ID,则可以选择省略 `bluetooth`、`dhcp`、`zeroconf`、`hassio`、`homekit`、`ssdp`、`usb` 和 `discovery` steps,即使它们在集成 manifest 中已配置。在这种情况下,当条目被发现时将调用 `user` step。
或者,如果集成不能总是获取到 unique ID(例如,多个设备,有的有、有的没有),则可以使用一个 helper,它仍然允许发现,只要尚未配置该集成的任何实例。
以下是处理 unique ID 并非总是可用的 discovery 的示例:
```python
if device_unique_id:
await self.async_set_unique_id(device_unique_id)
else:
await self._async_handle_discovery_without_unique_id()
```
### 在 config flows 中管理 unique IDs
设置 unique ID 后,如果已有另一个 flow 正在处理此 unique ID,则当前 flow 将立即中止。你还可以快速中止——如果此 ID 已有现有 config entry。Config entries 将获得创建它们的 flow 的 unique ID。
在 config flow step 内部调用:
```python
# 为 flow 分配 unique ID,如果另一个具有相同 unique ID 的 flow 正在进行,则中止当前 flow
await self.async_set_unique_id(device_unique_id)
# 如果存在具有相同 unique ID 的 config entry,则中止 flow
self._abort_if_unique_id_configured()
```
如果 config flow 随后中止,`strings.json` 中 `abort` 部分的键 `already_configured` 的文本资源将作为中止原因显示给用户。
```json
{
"config": {
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
}
}
}
```
### 唯一标识符要求
unique ID 用于将 config entry 与底层设备或 API 匹配。unique ID 必须稳定,不应允许用户更改,且必须是字符串。
Unique ID 可用于在设备访问信息更改时更新 config entry 数据。例如,对于通过本地网络通信的设备,如果由于新的 DHCP 分配导致 IP 地址更改,集成可以使用 Unique ID 通过以下代码片段更新 host:
```
await self.async_set_unique_id(serial_number)
self._abort_if_unique_id_configured(updates={CONF_HOST: host, CONF_PORT: port})
```
#### unique ID 的可接受来源示例
* 设备的序列号
* MAC 地址:使用 `homeassistant.helpers.device_registry.format_mac` 格式化;仅从设备 API 或 discovery handler 获取 MAC 地址。依赖读取 arp 缓存或本地网络访问的工具(如 `getmac`)并非在所有受支持的網絡环境中都能正常工作,不可接受。
* 表示纬度和经度或其他唯一地理位置的字符串
* 物理印刷在设备上或烧录到 EEPROM 中的唯一标识符
#### 本地设备的 unique ID 有时可接受的来源
* Hostname:如果 hostname 的子集包含其中一个可接受的来源,则可以使用该部分
#### 云服务有时可接受的 unique ID 来源
* Email Address:必须规范化为小写
* Username:如果用户名不区分大小写,必须规范化为小写
* Account ID:不得有冲突
#### unique ID 的不可接受来源
* IP Address
* Device Name
* Hostname(如果可以被用户更改)
* URL
## 发现步骤
当集成被发现时,会调用相应的 discovery step(如 `async_step_dhcp` 或 `async_step_zeroconf`),并传入发现信息。该 step 需要检查以下内容:
* 确保没有其他此 config flow 的实例正在设置所发现的设备。如果有多种方式可以发现某设备在网络上,就可能发生这种情况。
* 在大多数情况下,在 flow 上设置 unique ID 并检查是否已有具有相同 unique ID 的 config entry 就足够了,如[在 config flows 中管理 unique IDs](#managing-unique-ids-in-config-flows)部分所述
* 在某些情况下,无法确定 unique ID,或因为不同发现源可能有不同的计算方式而导致 unique ID 不明确。在这种情况下:
1. 在 flow 上实现方法 `def is_matching(self, other_flow: Self) -> bool`。
2. 调用 `hass.config_entries.flow.async_has_matching_flow(self)`。
3. 然后,你的 flow 的 `is_matching` 方法将对每个其他正在进行的 flow 调用一次。
* 确保设备尚未被设置。
* 调用 discovery step 绝不应导致 flow 完成并产生 config entry。始终需要用户确认。
## 无需身份验证的可发现集成
如果你的集成可以被发现且无需任何身份验证,你将能够使用内置的 Discoverable Flow。此 flow 提供以下功能:
* 在 config flow 完成之前,检测设备/服务是否可以在网络上被发现。
* 支持所有基于 manifest 的 discovery 协议。
* 限制仅 1 个 config entry。发现所有可用设备由 config entry 负责。
要开始使用,运行 `python3 -m script.scaffold config_flow_discovery` 并按照说明操作。这将创建使用 discovery 配置集成所需的所有样板代码。
## 通过 OAuth2 配置
Home Assistant 内置了对提供使用 [OAuth2 authorization framework](https://www.rfc-editor.org/rfc/rfc6749) 进行账户链接的集成的支持。要利用此功能,你需要将 Python API library 组织成允许 Home Assistant 负责刷新 token 的方式。请参阅我们的 [API library guide](/developers/api_lib_index.md) 了解如何操作。
内置的 OAuth2 支持开箱即用地与使用 [Application Credentials platform](/developers/core/platform/application_credentials.md) 配置的本地 client ID / secret 配合使用,并与 Home Assistant Cloud Account Linking 服务配合使用。此服务允许用户将其账户与集中管理的 client ID/secret 链接。如果你希望你的集成成为此服务的一部分,请通过 联系我们。
要开始使用,运行 `python3 -m script.scaffold config_flow_oauth2` 并按照说明操作。这将创建使用 OAuth2 配置集成所需的所有样板代码。
## 翻译
[config flow](/developers/internationalization/core.md#config--options--subentry-flows) handlers 的 translations 在集成翻译文件 `strings.json` 的 `config` 键下定义。以 Hue 集成为例:
```json
{
"title": "Philips Hue Bridge",
"config": {
"step": {
"init": {
"title": "Pick Hue bridge",
"data": {
"host": "Host"
}
},
"link": {
"title": "Link Hub",
"description": "Press the button on the bridge to register Philips Hue with Home Assistant.\n\n"
}
},
"error": {
"register_failed": "Failed to register, please try again",
"linking": "Unknown linking error occurred."
},
"abort": {
"discover_timeout": "Unable to discover Hue bridges",
"no_bridges": "No Philips Hue bridges discovered",
"all_configured": "All Philips Hue bridges are already configured",
"unknown": "Unknown error occurred",
"cannot_connect": "Unable to connect to the bridge",
"already_configured": "Bridge is already configured"
}
}
}
```
当 translations 合并到 Home Assistant 后,它们将自动上传到 [Lokalise](https://lokalise.com/),翻译团队将帮助将其翻译为其他语言。在本地开发时,需要运行 `python3 -m script.translations develop` 以查看对 `strings.json` 所做的更改。[更多信息请参阅翻译 Home Assistant。](/developers/translations.md)
## Config entry 迁移
每个 config entry 都有一个分配给它的 version,由 major 和 minor version 组成。这是为了能够在 config entry schema 更改时将 config entry 数据迁移到新格式。如果未在 config flow 中显式设置,`VERSION` 和 `MINOR_VERSION` 都默认为 `1`,因此集成只有在实现 migration 时才需要设置它们。
migration 可以通过在集成的 `__init__.py` 文件中实现函数 `async_migrate_entry` 来以编程方式处理。如果 migration 成功,该函数应返回 `True`。
如果 minor versions 不同但 major versions 相同,即使集成未实现 `async_migrate_entry`,也允许集成设置继续。这意味着 minor version bump 是向后兼容的,而 major version bump 则不然,如果用户在没有从备份恢复配置的情况下降级 Home Assistant Core,会导致集成设置失败。
要设置新 version,将 `VERSION` 和/或 `MINOR_VERSION` 添加到 config flow 类:
```python
class ExampleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Example config flow."""
VERSION = 2
MINOR_VERSION = 2
```
```python
# Example migration function
async def async_migrate_entry(hass, config_entry: ConfigEntry):
"""Migrate old entry."""
_LOGGER.debug("Migrating configuration from version %s.%s", config_entry.version, config_entry.minor_version)
if config_entry.version == 1:
new_data = {**config_entry.data}
if config_entry.minor_version < 2:
# TODO: modify Config Entry data with changes in version 1.2
pass
if config_entry.minor_version < 3:
# TODO: modify Config Entry data with changes in version 1.3
pass
hass.config_entries.async_update_entry(
config_entry, data=new_data, minor_version=3, version=1
)
_LOGGER.debug("Migration to configuration version %s.%s successful", config_entry.version, config_entry.minor_version)
return True
```
## 重新配置
config entry 可以通过添加 `reconfigure` step 来允许重新配置。这为集成提供了一种方式,使用户能够更改 config entry 数据,而无需为实现不打算作为可选的 setup 数据的 `OptionsFlow`。
这不是用来处理身份验证问题或对其进行重新配置的。为此我们有 [`reauth`](#reauthentication) step,应该在认证出现问题时自动启动。
```python
import voluptuous as vol
class ExampleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for Example integration."""
async def async_step_reconfigure(self, user_input: dict[str, Any] | None = None):
if user_input is not None:
# TODO: process user input
await self.async_set_unique_id(user_id)
self._abort_if_unique_id_mismatch()
return self.async_update_reload_and_abort(
self._get_reconfigure_entry(),
data_updates=data,
)
return self.async_show_form(
step_id="reconfigure",
data_schema=vol.Schema({vol.Required("input_parameter"): str}),
)
```
成功后,reconfiguration flows 预期更新当前条目并中止;它们不应创建新条目。
这通常使用 `return self.async_update_reload_and_abort` helper 完成。
自动化测试应验证 reconfigure flow 更新现有 config entry 且不创建额外条目。
检查是否处于 reconfigure flow 可以使用 `if self.source == SOURCE_RECONFIGURE`。
也可以使用 `self._get_reconfigure_entry()` 访问对应的 config entry。
确保 `unique_id` 未更改应使用 `await self.async_set_unique_id` 后跟 `self._abort_if_unique_id_mismatch()`。
## 重新认证
优雅地处理认证错误(如无效、过期或撤销的 token)是在 [Integration Quality Scale](/developers/core/integration-quality-scale.md) 上进步所必需的。以下是如何为 `script.scaffold` 创建的 OAuth flow 添加 reauth 的示例,遵循 [Building a Python library](/developers/api_lib_auth.md#oauth2) 中的模式。如果你正在查找如何触发 reauthentication flow,请参阅 [handling expired credentials](/developers/integration_setup_failures.md#handling-expired-credentials)。
此示例捕获 `__init__.py` 中 config entry setup 的认证异常,并指示用户访问集成页面以重新配置集成。
要允许用户更改非可选的 config entry 数据(`OptionsFlow`),并且与认证无直接关系的数据(例如更改的 host name),集成应实现 [`reconfigure`](#reconfigure) step。
```python
from homeassistant.config_entries import SOURCE_REAUTH, ConfigEntry
from homeassistant.core import HomeAssistant
from . import api
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry):
"""Set up a config entry."""
# TODO: Replace with actual API setup and exception
auth = api.AsyncConfigEntryAuth(...)
try:
await auth.refresh_tokens()
except TokenExpiredError as err:
raise ConfigEntryAuthFailed(err) from err
# TODO: Proceed with integration setup
```
`config_flow.py` 中的 flow handler 还需要一些额外的步骤来支持 reauth,包括显示确认、启动 reauth flow、更新现有 config entry 以及重新加载以再次调用 setup。
```python
class OAuth2FlowHandler(
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
):
"""Config flow to handle OAuth2 authentication."""
async def async_step_reauth(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Perform reauth upon an API authentication error."""
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Dialog that informs the user that reauth is required."""
if user_input is None:
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema({}),
)
return await self.async_step_user()
async def async_oauth_create_entry(self, data: dict) -> dict:
"""Create an oauth config entry or update existing entry for reauth."""
await self.async_set_unique_id(user_id)
if self.source == SOURCE_REAUTH:
self._abort_if_unique_id_mismatch()
return self.async_update_reload_and_abort(
self._get_reauth_entry(),
data_updates=data,
)
self._abort_if_unique_id_configured()
return await super().async_oauth_create_entry(data)
```
默认情况下,`async_update_reload_and_abort` helper 方法在更新和重新加载后以 `reauth_successful` 中止 flow。默认情况下,entry 将始终被重新加载。如果仅应在 config entry 已更新时重新加载 config entry,请指定 `reload_even_if_entry_is_unchanged=False`。
根据集成的细节,可能还有额外的注意事项,例如确保 reauth 时使用的是同一账户,或处理多个 config entries。
reauth 确认对话框需要在 `strings.json` 中为 reauth 确认和成功对话框添加额外的定义:
```json
{
"config": {
"step": {
"reauth_confirm": {
"title": "[%key:common::config_flow::title::reauth%]",
# TODO: Replace with the name of the integration
"description": "The Example integration needs to re-authenticate your account"
}
},
"abort": {
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
},
}
```
请参阅 [Translations](#translations) 的本地开发说明。
认证失败(如撤销的 oauth token)可能比较难以手动测试。一个建议是复制 `config/.storage/core.config_entries`,并根据你想要测试的场景手动更改 `access_token`、`refresh_token` 和 `expires_at` 的值。然后可以逐步推进 reauth flow,确认值被替换为新的有效 token。
成功后,reauth flows 预期更新当前条目并中止;它们不应创建新条目。
这通常使用 `return self.async_update_reload_and_abort` helper 完成。
自动化测试应验证 reauth flow 更新现有 config entry 且不创建额外条目。
检查是否处于 reauth flow 可以使用 `if self.source == SOURCE_REAUTH`。
也可以使用 `self._get_reauth_entry()` 访问对应的 config entry。
确保 `unique_id` 未更改应使用 `await self.async_set_unique_id` 后跟 `self._abort_if_unique_id_mismatch()`。
## 子条目流
集成可以实现 subentry flows,以允许用户添加(并可选地重新配置)subentries。一个示例是提供天气预报的集成,其中 config entry 存储认证详细信息,而每个需要天气预报的位置则存储为 subentry。
Subentry flows 类似于 config flows,只是 subentry flows 不支持 reauthentication 或 discovery;subentry flow 只能通过 `user` 或 `reconfigure` steps 启动。
```python
class ExampleConfigFlow(config_entries.ConfigFlow, domain=DOMAIN):
"""Config flow for Example integration."""
...
@classmethod
@callback
def async_get_supported_subentry_types(
cls, config_entry: ConfigEntry
) -> dict[str, type[ConfigSubentryFlow]]:
"""Return subentries supported by this integration."""
return {"location": LocationSubentryFlowHandler}
class LocationSubentryFlowHandler(ConfigSubentryFlow):
"""Handle subentry flow for adding and modifying a location."""
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> SubentryFlowResult:
"""User flow to add a new location."""
...
```
### Subentry 唯一标识符
Subentries 可以设置 unique ID。规则与 config entries 的 [unique IDs](#unique-ids) 类似,只是 subentry unique IDs 只需在 config entry 内唯一。
### Subentry 翻译
[subentry flow](/developers/internationalization/core.md#config--options--subentry-flows) handlers 的 translations 在集成翻译文件 `strings.json` 的 `config_subentries` 键下定义,例如:
```json
{
"config_subentries": {
"location": {
"title": "Weather location",
"step": {
"user": {
"title": "Add location",
"description": "Configure the weather location"
},
"reconfigure": {
"title": "Update location",
"description": "..."
}
},
"error": {
},
"abort": {
}
}
}
}
```
### Subentry 重新配置
Subentries 可以被重新配置,类似于 [config entries 可以被重新配置](#reconfigure) 的方式。要为 subentry flow 添加 reconfigure 支持,请实现一个 `reconfigure` step。
```python
class LocationSubentryFlowHandler(ConfigSubentryFlow):
"""Handle subentry flow for adding and modifying a location."""
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> SubentryFlowResult:
"""User flow to add a new location."""
...
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> SubentryFlowResult:
"""User flow to modify an existing location."""
# 检索父 config entry 作为引用
config_entry = self._get_entry()
# 检索要更新的目标 subentry
config_subentry = self._get_reconfigure_subentry()
...
```
## 继续另一个 flow
config flow 可以启动另一个 config flow,并告知 frontend 当第一个 flow 完成后应显示另一个 flow。为此,第一个 flow 需要将 `next_flow` 参数传递给 `async_create_entry` 方法。参数应为形式为 `(flow_type, flow_id)` 的元组。
```python
from homeassistant.config_entries import SOURCE_USER, ConfigFlow, FlowType
class ExampleFlow(ConfigFlow):
"""Example flow."""
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Show create entry with next_flow parameter."""
result = await self.hass.config_entries.flow.async_init(
"another_integration_domain",
context={"source": SOURCE_USER},
)
return self.async_create_entry(
title="Example",
data={},
next_flow=(FlowType.CONFIG_FLOW, result["flow_id"]),
)
```
### 使用 async\_on\_create\_entry
`async_on_create_entry` 提供了一个选项,用于在 config entry 创建并 flow 最终确定后修改最终的 `ConfigFlowResult`。
由于 subentry flows 和 option flows 依赖于 main config entry 存在后才能启动,这些 flow 类型只能与 config flow 中的 `async_on_create_entry()` 方法一起使用:
```python
from homeassistant.config_entries import (
ConfigFlow,
FlowType,
SOURCE_USER,
SubentryFlowContext,
)
class ExampleFlow(ConfigFlow):
"""Example flow."""
async def async_on_create_entry(
self, result: ConfigFlowResult
) -> ConfigFlowResult:
"""Create subentry flow after creating the main entry."""
subentry_result = await self.hass.config_entries.subentries.async_init(
(result["result"].entry_id, "subentry_type"),
context=SubentryFlowContext(source=SOURCE_USER),
)
result["next_flow"] = (
FlowType.CONFIG_SUBENTRIES_FLOW,
subentry_result["flow_id"],
)
return result
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Create entry."""
return self.async_create_entry(
title="Example",
data={},
)
```
## 使用 SchemaConfigFlowHandler 处理简单 flows
对于 helper 和具有简单 config flows 的集成,你可以改用 `SchemaConfigFlowHandler`。
与使用完整的 config flow 相比,`SchemaConfigFlowHandler` 有一些限制需要注意:
* 所有用户输入都保存在结果 config entry 的 `options` 字典中。因此,它不适合在需要连接数据、api key 或其他应存储在 config entry `data` 中的信息的集成中使用。
* 如果你有大量的验证、设置 unique id 或检查重复 config entries,使用普通的 config flow handler 可能更简单。
* 不建议使用 `user` 和 `import` 以外的其他 steps 来启动 flow。
```python
from homeassistant.helpers.schema_config_entry_flow import (
SchemaCommonFlowHandler,
SchemaConfigFlowHandler,
SchemaFlowError,
SchemaFlowFormStep,
)
async def validate_setup(
handler: SchemaCommonFlowHandler, user_input: dict[str, Any]
) -> dict[str, Any]:
"""Validate options."""
if user_input[CONF_SOME_SETTING] == "error":
# 'setup_error' 需要在 string.json 的 config errors 部分中存在
raise SchemaFlowError("setup_error")
return user_input
DATA_SCHEMA_SETUP = vol.Schema(
{
vol.Required(CONF_NAME, default=DEFAULT_NAME): TextSelector()
}
)
DATA_SCHEMA_OPTIONS = vol.Schema(
{
vol.Optional(CONF_SOME_SETTING): TextSelector()
}
)
CONFIG_FLOW = {
"user": SchemaFlowFormStep(
schema=DATA_SCHEMA_SETUP,
next_step="options",
),
"options": SchemaFlowFormStep(
schema=DATA_SCHEMA_OPTIONS,
validate_user_input=validate_setup,
),
}
OPTIONS_FLOW = {
"init": SchemaFlowFormStep(
DATA_SCHEMA_OPTIONS,
validate_user_input=validate_setup,
),
}
class MyConfigFlowHandler(SchemaConfigFlowHandler, domain=DOMAIN):
"""Handle a config flow."""
config_flow = CONFIG_FLOW
options_flow = OPTIONS_FLOW
options_flow_reloads = True # Reload without a config entry listener
def async_config_entry_title(self, options: Mapping[str, Any]) -> str:
"""Return config entry title from input."""
return cast(str, options[CONF_NAME])
```
## 测试你的 config flow
具有 config flow 的集成需要 `config_flow.py` 中所有代码的完整测试覆盖才能被纳入 core。[测试你的代码](/developers/development_testing.md#running-a-limited-test-suite) 包含更多有关如何生成 coverage 报告的细节。
---
url: /developers/core/integration/contributing_to_core.md
---
在此页面上,你将找到有关如何将集成贡献到 Home Assistant Core 的信息。
我们非常乐意接收向 Home Assistant Core 贡献的新集成。
由于这些更改通常比对现有集成的功能更复杂,我们有一组准则,将使所有相关人员的过程更顺利。
## 集成是否适合纳入?
作为流行的项目,Home Assistant 收到了大量贡献。我们希望确保将时间和资源花在审查和维护用户今天和未来都能受益的集成上。
这意味着我们有一些要求,集成必须满足才能被纳入 Home Assistant Core:
* 集成所连接的产品或服务应该可供 Home Assistant 用户购买和使用。这意味着我们不接受尚未发布的产品或处于 private beta 阶段的服务(如 Kickstarter 项目或类似项目)的集成。
* 产品或服务应该是成熟的。我们希望避免公司利用 Home Assistant 作为其新产品的营销平台,因此我们要求产品或服务已广泛可用并拥有一定的用户基础。
如果你的集成不符合这些要求,你可以改为将其作为 custom integration 发布到 GitHub 和 HACS 上。
## 集成需要满足哪些要求?
符合条件的新集成应满足一系列要求才能被接受。
### 集成质量量表
为了衡量集成的质量,我们使用 [integration quality scale]。
该量表定义了一套集成应遵循的规则,以被视为达到特定质量级别。
这些规则涵盖集成的各个方面,如文档、测试、代码质量等。
新集成必须达到集成质量量表的 [bronze] 级别。
不要立即追求更高的级别——正如在 [使你的 pull request 尽可能小](#make-your-pull-request-as-small-as-possible) 部分所述,我们应该保持 pull request 小而集中。
[bronze]: /docs/core/integration-quality-scale/rules#-bronze
[integration quality scale]: /docs/core/integration-quality-scale
### 用于通信的 Python library
一个重要的 \[集成质量量表规则] 是要求使用 Python library 与产品或服务进行通信。
如果我们需要在集成中实现与产品或服务的通信,Home Assistant 代码库将会非常庞大,也会使维护和测试集成变得更加困难。
通过使用 Python library,我们可以将集成代码聚焦在 Home Assistant 特定的部分(如 entity 定义),并将与产品或服务的通信委托给 library。
这也允许该 library 被其他项目和集成重用,从而改善整个生态系统。
要查看 Python library 的要求,请查看集成质量量表中的 [dependency transparency] 规则。
:::tip
有关如何创建 Python library 的更多信息,请查阅我们关于 \[创建 Python library] 的指南。
:::
[integration quality scale rule]: /docs/core/integration-quality-scale/rules/dependency-transparency.md
[dependency transparency]: /docs/core/integration-quality-scale/rules/dependency-transparency.md
[creating a Python library]: /docs/api_lib_index.md
## 如何将集成贡献到 Core
一旦你的集成处于良好状态并准备好贡献到 Home Assistant Core,你需要遵循几个步骤来确保它能够被审查。
### 使你的 pull request 尽可能小
新集成应具有最少的代码和功能,以对用户有用。
范围明确的 pull request 更容易审查和合并,因为这允许审查者审批和合并较小的代码块。
**包含大型代码转 dump 且不遵循这些准则的 pull request 可能会被关闭。**
对于新集成,这意味着:
* 限制为单一 platform。
* 不要添加对初始 platform 工作非必要的功能。这包括以下功能:
* Diagnostics
* Custom service actions
* Reauthentication 和 reconfiguration flows
* 理想情况下,避免复杂的集成质量量表规则(如 `dynamic-devices` 和 `stale-devices`)。
要了解更多有关如何界定 pull request 范围的信息,请查看我们关于 \[创建完美的 PR] 的指南。
[creating the perfect PR]: /docs/review-process.md#creating-the-perfect-pr
### 确保代码检查通过
请确保你已经设置了合适的开发环境,并在打开 pull request 之前已在本地运行了代码检查。
如果 CI checks 因你的更改而失败,在 checks 通过之前我们无法审查和合并你的 pull request。
请注意,某些测试可能不稳定,因此如果你确信失败与你的代码无关,请等待维护者,而不是每次失败时都尝试重新运行 CI。
:::info
我们的 pre-commit checks 可以更新你的集成目录之外的文件。这些更改应包含在你的 pull request 中。
:::
### 在 pull request 描述中提供足够的上下文
当你打开 pull request 以将集成贡献到 Home Assistant Core 时,请确保在 pull request 描述中提供足够的上下文。
这包括:
* 描述集成所连接的产品或服务。
* 指向用于与产品或服务通信的 library 的 git repository 链接。
---
url: /developers/core/integration/diagnostics.md
---
集成可以提供 diagnostics 以帮助用户收集数据,辅助进行故障排除。可以为 config entries 提供 diagnostics,也可以单独为每个 device entry 提供。
用户可以从集成页面上的 config entry options 菜单下载 config entry diagnostics。对于 device diagnostics,用户可以从 device 信息部分下载(或从其菜单下载,具体取决于集成)。请注意,如果集成未实现 device diagnostics,则 device 页面将提供 config entry diagnostics。
:::warning
确保不暴露敏感数据至关重要。包括但不限于:
* 密码和 API keys
* Authentication tokens
* 位置数据
* 个人信息
Home Assistant 提供了 `async_redact_data` 工具函数,你可以使用它安全地从 diagnostics 输出中删除敏感数据。
:::
以下是一个实现 config entry 和 device entry diagnostics 的示例:
```python
TO_REDACT = [
CONF_API_KEY,
APPLIANCE_CODE
]
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: MyConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
return {
"entry_data": async_redact_data(entry.data, TO_REDACT),
"data": entry.runtime_data.data,
}
async def async_get_device_diagnostics(
hass: HomeAssistant, entry: MyConfigEntry, device: DeviceEntry
) -> dict[str, Any]:
"""Return diagnostics for a device."""
appliance = _get_appliance_by_device_id(hass, device.id)
return {
"details": async_redact_data(appliance.raw_data, TO_REDACT),
"data": appliance.data,
}
```
集成可以同时提供这两种类型的 diagnostics,或者只提供其中一种。
---
url: /developers/core/integration/options_flow.md
---
通过 config entry 配置的集成可以向用户公开 options,以允许调整集成的行为,例如应集成哪些设备或位置。
Config Entry Options 使用 [Data Flow Entry framework](data_entry_flow_index.md) 允许用户更新 config entry 的 options。想要支持 config entry options 的组件需要定义一个 Options Flow Handler。
## 选项支持
为了让集成支持 options,它需要在 config flow handler 中有一个 `async_get_options_flow` 方法。调用它将返回组件 options flow handler 的实例。
```python
@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
) -> OptionsFlowHandler:
"""Create the options flow."""
return OptionsFlowHandler()
```
## Flow 处理器
Flow handler 的工作原理与 config flow handler 相同,只是 flow 中的第一个 step 始终是 `async_step_init`。当前 config entry 的详细信息可通过 `self.config_entry` 属性访问。
```python
from homeassistant.config_entries import OptionsFlow
OPTIONS_SCHEMA = vol.Schema(
{
vol.Required("show_things"): bool,
}
)
class OptionsFlowHandler(OptionsFlow):
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Manage the options."""
if user_input is not None:
return self.async_create_entry(data=user_input)
return self.async_show_form(
step_id="init",
data_schema=self.add_suggested_values_to_schema(
OPTIONS_SCHEMA, self.config_entry.options
),
)
```
## 自动重新加载的选项流
如果集成应在 config options 更改后重新加载,它可以继承 `OptionsFlowWithReload` 而不是 `OptionsFlow`。一旦 options 更改,`OptionsFlowWithReload` 将自动重新加载集成。
由于添加 update listener 最常见的原因是当 options 更改时重新加载集成,因此 `OptionsFlowWithReload` 避免了需要该 listener。
```python
from homeassistant.config_entries import OptionsFlowWithReload
OPTIONS_SCHEMA = vol.Schema(
{
vol.Required("show_things"): bool,
}
)
class MyOptionsFlow(OptionsFlowWithReload):
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Manage the options."""
if user_input is not None:
return self.async_create_entry(data=user_input)
return self.async_show_form(
step_id="init",
data_schema=self.add_suggested_values_to_schema(
OPTIONS_SCHEMA, self.config_entry.options
),
)
```
## 信号更新
如果集成应在 options 更新后做出响应,你可以注册一个 update listener 到 config entry,当 entry 被更新时将调用它。通过在集成的 `__init__.py` 中的 `async_setup_entry` 函数中添加以下内容来注册 listener:
```python
entry.async_on_unload(entry.add_update_listener(update_listener))
```
使用上述方法意味着 Listener 在 entry 加载时附加,并在卸载时分离。Listener 应是一个 async 函数,其输入与 async\_setup\_entry 相同。然后可以从 `entry.options` 访问 Options。
```python
async def update_listener(hass: HomeAssistant, config_entry: ConfigEntry):
"""Handle options update."""
```
---
url: /developers/core/integration/system_health.md
---
system health platform 允许集成为用户提供帮助了解集成状态的信息。这可以包括诸如 endpoint 的可用性、集成当前连接到的 server、还剩多少请求配额等详细信息。
用户可以通过前往 **设置** -> **系统** -> **维修**,然后在上部右侧三点菜单中选择 **系统信息** 来查看聚合的系统健康信息。
## Implementing the system health platform
向集成添加 `system_health.py` 文件,并实现 `async_register` 方法,以注册 info 回调:
```python
"""Provide info to system health."""
from homeassistant.components import system_health
from homeassistant.core import HomeAssistant, callback
@callback
def async_register(hass: HomeAssistant, register: system_health.SystemHealthRegistration) -> None:
"""Register system health callbacks."""
register.async_register_info(system_health_info)
```
info 回调应返回一个字典,其值可以是任何类型,包括 coroutine。如果字典条目设置了 coroutine,frontend 将显示等待指示器,并在 coroutine 完成并提供结果后自动更新。
```python
async def system_health_info(hass: HomeAssistant) -> dict[str, Any]:
"""Get info for the info page."""
config_entry: ExampleConfigEntry = hass.config_entries.async_entries(DOMAIN)[0]
quota_info = await config_entry.runtime_data.async_get_quota_info()
return {
"consumed_requests": quota_info.consumed_requests,
"remaining_requests": quota_info.requests_remaining,
# checking the url can take a while, so set the coroutine in the info dict
"can_reach_server": system_health.async_check_can_reach_url(hass, ENDPOINT),
}
```
:::tip
system\_health 组件提供 `async_check_can_reach_url` helper 作为一种轻松实现检查 URL 可用性的方式。
:::
使用 `strings.json` 文件中的 `system_health` 部分翻译 info 字典中的每个 key,以提供良好的描述:
```json
"system_health": {
"info": {
"can_reach_server": "Reach Example server",
"remaining_requests": "Remaining allowed requests"
}
}
```
---
url: /developers/core/integration/yaml_configuration.md
---
`configuration.yaml` 是由用户定义的配置文件。它由 Home Assistant 在首次启动时自动创建。它定义了要加载哪些组件。
:::info 关于设备和/或服务 YAML 配置的说明
与设备和/或服务通信的集成通过 config flow 进行配置。在极少数情况下,我们可以做例外处理。现有但不应该有 YAML 配置的集成被允许并鼓励实现 config flow 并移除 YAML 支持。对于这些现有集成的 YAML 配置的更改将不再被接受。
更多详细信息请阅读 [ADR-0010](https://github.com/home-assistant/architecture/blob/master/adr/0010-integration-configuration.md#decision)
:::
## 预处理
Home Assistant 将根据指定要加载的组件对配置进行一些预处理。
### CONFIG\_SCHEMA
如果组件定义了变量 `CONFIG_SCHEMA`,传入的 config 对象将是 config 通过 `CONFIG_SCHEMA` 运行后的结果。`CONFIG_SCHEMA` 应是一个 voluptuous schema。
### PLATFORM\_SCHEMA
:::info
下面描述的 platform-key 配置结构(配置列在实体组件 domain 下,如 `switch`)是实体组件的现有机制。新集成不得使用它。根据 [ADR-0007](https://github.com/home-assistant/architecture/blob/master/adr/0007-integration-config-yaml-structure.md),如果存在,集成的所有 YAML 配置必须放在集成自己的 domain key 下,通过 `discovery.async_load_platform` 加载 platforms。现有集成中 platform 部分中的 YAML 配置在该集成被重构前被冻结。
:::
如果组件定义了变量 `PLATFORM_SCHEMA`,该组件将被视为实体组件。实体组件的配置是一个 platform 配置列表。
Home Assistant 将收集该组件的所有 platform 配置。它会通过在组件的 domain(即 `light`)下以及任何 domain + 附加文本的条目下查找配置条目来完成此操作。
在收集 platform 配置时,Home Assistant 会对其进行验证。它会检查 platform 是否存在,如果 platform 定义了 `PLATFORM_SCHEMA`,则验证是否符合该 schema。如果未定义,则验证配置是否符合组件中定义的 `PLATFORM_SCHEMA`。任何引用不存在的 platforms 或包含无效配置的内容都将被移除。
以下 `configuration.yaml`:
```yaml
unrelated_component:
some_key: some_value
switch:
platform: example1
switch living room:
- platform: example2
some_config: true
- platform: invalid_platform
```
将被传递给组件如下:
```python
{
"unrelated_component": {
"some_key": "some_value"
},
"switch": [
{
"platform": "example1"
},
{
"platform": "example2",
"some_config": True
}
],
}
```
---
url: /developers/core/llm/index.md
---
Home Assistant 可以与大型语言模型(LLMs)交互。通过将 Home Assistant API 暴露给 LLM,LLM 可以获取数据或控制 Home Assistant 以更好地协助用户。Home Assistant 自带内置的 LLM API,但 custom integrations 可以注册自己的 API 以提供额外功能。
## 内置 Assist API
Home Assistant 具有内置的 API,可将 Assist API 暴露给 LLMs。此 API 允许 LLMs 通过 [intents](/developers/intent_builtin.md) 与 Home Assistant 交互,并可以通过注册 intents 来扩展。
Assist API 等同于内置 conversation agent 也能访问的 capabilities 和 exposed entities。无法执行任何管理任务。
## 贡献工具
集成可以向 LLM API 贡献 tools,而无需拥有完整的 API。`llm` 集成发现 `/llm.py` platform,该 platform 暴露 `async_get_tools` hook。该 platform 被延迟导入,仅在 LLM 请求需要其 tools 时才被查询。
`async_get_tools` 是一个回调,对每个请求根据请求的 `LLMContext` 和被组装 API 的 `api_id` 进行评估。它返回要暴露的 tools,以及一个与该 tools 一起传输的可选 prompt fragment;如果集成没有为某个 API 贡献内容,则返回 `None`。因为它按请求运行,所以它可以根据上下文返回不同的 tools 集合,例如发起请求的 assistant 或 device,或所选 API。
```python
# example_integration/llm.py
from homeassistant.components import llm
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.llm import LLMContext
@callback
def async_get_tools(
hass: HomeAssistant, llm_context: LLMContext, api_id: str
) -> llm.LLMTools | None:
"""Return the tools to expose to the LLM."""
return llm.LLMTools(
tools=[MyTool()],
prompt="Use MyTool to ...", # Optional prompt fragment
)
```
该 hook 仅在 `llm` 集成设置完成后才被调用,因此 platform 本身不需要依赖于 `llm`。
请参阅 [Tools](#tools) 了解如何实现 platform 返回的 `Tool` 对象。
## 支持 LLM API
LLM API 需要在集成中的两个位置进行集成。用户需要能够配置使用哪些 APIs,并在与 LLM 交互时将 APIs 提供的 tools 传递给 LLM。
### 选项流程
所选的 API 应存储在 config entry options 中。它应持有所选 API ID 的字符串或列表(如有)。如果没有选择 API,则必须省略该 key。
在 options flow 中,应向用户提供 selector 以选择使用哪个 API。
```python
from types import MappingProxyType
from homeassistant.const import CONF_LLM_HASS_API
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import llm
from homeassistant.helpers.selector import (
SelectOptionDict,
SelectSelector,
SelectSelectorConfig,
)
@callback
def async_get_options_schema(
hass: HomeAssistant,
options: MappingProxyType[str, Any],
) -> vol.Schema:
"""Return the options schema."""
apis: list[SelectOptionDict] = [
SelectOptionDict(
label=api.name,
value=api.id,
)
for api in llm.async_get_apis(hass)
]
return vol.Schema(
{
vol.Optional(
CONF_LLM_HASS_API,
description={"suggested_value": options.get(CONF_LLM_HASS_API)},
): SelectSelector(SelectSelectorConfig(options=apis, multiple=True)),
}
)
```
### 获取工具
在与 LLM 交互时,提供的 `ChatLog` 将从所选 API 提供任何 selected tools,conversation entity 应将它们与 API 提供的额外 prompt 一起传递给 LLM。
```python
from homeassistant.const import CONF_LLM_HASS_API, CONF_PROMPT
from homeassistant.core import HomeAssistant, callback
from homeassistant.components import conversation
from homeassistant.helpers import intent, llm
from .const import DOMAIN
class MyConversationEntity(conversation.ConversationEntity):
def __init__(self, entry: ConfigEntry) -> None:
"""Initialize the agent."""
self.entry = entry
...
async def _async_handle_message(
self,
user_input: conversation.ConversationInput,
chat_log: conversation.ChatLog,
) -> conversation.ConversationResult:
"""Call the API."""
try:
await chat_log.async_provide_llm_data(
user_input.as_llm_context(DOMAIN),
self.entry.options.get(CONF_LLM_HASS_API),
self.entry.options.get(CONF_PROMPT),
user_input.extra_system_prompt,
)
except conversation.ConverseError as err:
return err.as_conversation_result()
tools: list[dict[str, Any]] | None = None
if chat_log.llm_api:
tools = [
_format_tool(tool) # TODO format the tools as your LLM expects
for tool in chat_log.llm_api.tools
]
messages = [
m
for content in chat_log.content
for m in _convert_content(content) # TODO format messages
]
# 与 LLM 交互并传递 tools
request = user_input.text
for _iteration in range(10):
response = ... # 向 LLM 发送请求并获取流式响应
messages.extend(
[
_convert_content(content) # TODO format messages
async for content in chat_log.async_add_delta_content_stream(
user_input.agent_id, _transform_stream(response) # TODO call tools and stream responses
)
]
)
if not chat_log.unresponded_tool_results:
break
# 向用户发送最终响应
intent_response = intent.IntentResponse(language=user_input.language)
intent_response.async_set_speech(chat_log.content[-1].content or "")
return conversation.ConversationResult(
response=intent_response,
conversation_id=chat_log.conversation_id,
continue_conversation=chat_log.continue_conversation,
)
```
## 创建你自己的 API
要创建自己的 API,需要创建一个继承自 `API` 的类,并实现 `async_get_api_instance` 方法。它返回一个 `APIInstance`,其中包含代表你想要暴露给 LLM 的功能的 `Tool` 对象列表,以及告诉 LLM 如何使用它们的 prompt。
### 工具
`llm.Tool` 类表示 LLM 可以调用的工具。
```python
from homeassistant.core import HomeAssistant
from homeassistant.helpers import llm
from homeassistant.helpers.llm import LLMContext, ToolInput
from homeassistant.util import dt as dt_util
from homeassistant.util.json import JsonObjectType
class TimeTool(llm.Tool):
"""Tool to get the current time."""
name = "GetTime"
description = "Returns the current time."
# 可选。输入参数的 voluptuous schema。
parameters = vol.Schema({
vol.Optional('timezone'): str,
})
async def async_call(
self, hass: HomeAssistant, tool_input: ToolInput, llm_context: LLMContext
) -> JsonObjectType:
"""Call the tool."""
if "timezone" in tool_input.tool_args:
tzinfo = dt_util.get_time_zone(tool_input.tool_args["timezone"])
else:
tzinfo = dt_util.DEFAULT_TIME_ZONE
return {"time": dt_util.now(tzinfo).isoformat()}
```
`llm.Tool` 类具有以下属性:
| Name | Type | Description |
|---------------------|------------|----------------------------------------------------------------------------------------------------------------|
| `name` | string | 工具的名称。必需。 |
| `description` | string | 工具的描述,帮助 LLM 理解何时以及如何调用它。可选,但推荐。 |
| `parameters` | vol.Schema | 参数的 voluptuous schema。默认为 vol.Schema({}) |
`llm.Tool` 类具有以下方法:
#### `async_call`
当被 LLM 调用时执行工具的实际操作。这必须是一个 async 方法。其参数为 `hass`、`llm.ToolInput` 实例以及请求的 `llm.LLMContext`。
响应数据必须是 dict 且可序列化为 JSON [`homeassistant.util.json.JsonObjectType`](https://github.com/home-assistant/core/blob/dev/homeassistant/util/json.py)。
错误必须作为 `HomeAssistantError` 异常(或其子类)抛出。响应数据不应包含用于错误处理的错误代码。
`ToolInput` 具有以下属性:
| Name | Type | Description |
|-------------------|---------|---------------------------------------------------------------------------------------------------------|
| `tool_name` | string | 正在调用的工具的名称 |
| `tool_args` | dict | LLM 提供的参数。使用 `parameters` schema 进行转换和验证。 |
| `id` | string | 工具调用的唯一标识符。默认为新生成的 ULID。 |
| `external` | bool | 工具调用是否在 Home Assistant 之外执行(例如由 model provider 执行)。External 工具调用不会分发给 API 的 tools。默认为 `False`。 |
在请求的所有 tools 之间共享的上下文(conversation agent、请求的 device 等)作为 `async_call` 的 `llm.LLMContext` 单独提供。
`LLMContext` 具有以下属性:
| Name | Type | Description |
|-------------------|---------|---------------------------------------------------------------------------------------------------------|
| `platform` | string | 处理 LLM 请求的 conversation agent 的 DOMAIN |
| `context` | Context | 请求的 `homeassistant.core.Context` |
| `language` | string | conversation agent 的语言,或 "\*" 表示任意语言 |
| `assistant` | string | 用于控制 exposed entities 的 assistant 名称。目前仅支持 `conversation`。 |
| `device_id` | string | 用户用来发起对话的 device 的 device\_id。 |
### API
API 对象允许创建 API instances。API Instance 代表一组将提供给 LLM 的 tools。
```python
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import llm
from homeassistant.helpers.llm import APIInstance, LLMContext
from homeassistant.util import dt as dt_util
from homeassistant.util.json import JsonObjectType
class MyAPI(llm.API):
"""My own API for LLMs."""
async def async_get_api_instance(self, llm_context: LLMContext) -> APIInstance:
"""Return the instance of the API."""
return APIInstance(
api=self,
api_prompt="Call the tools to fetch data from Home Assistant.",
llm_context=llm_context,
tools=[TimeTool()],
)
async def async_setup_api(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Register the API with Home Assistant."""
# 如果 API 与 Config Entry 关联,则当 config entry 卸载时,必须注销 LLM API。
unreg = llm.async_register_api(
hass,
MyAPI(
hass=hass,
id=f"my_unique_key-{entry.entry_id}",
name=entry.title,
),
)
entry.async_on_unload(unreg)
```
`llm.API` 类具有以下属性:
| Name | Type | Description |
|-------------------|---------------|---------------------------------------------------------------------------------------------------------|
| `hass` | HomeAssistant | Home Assistant 实例。必需。 |
| `id` | string | API 的唯一标识符。必需。 |
| `name` | string | API 的名称。必需。 |
所有字段都是仅关键字的,因此 API 必须使用关键字参数实例化。
`llm.APIInstance` 类具有以下属性:
| Name | Type | Description |
|----------------------|-------------|---------------------------------------------------------------------------------------------------------|
| `api` | API | API 对象。必需。 |
| `api_prompt` | string | 对 LLM 如何使用 LLM tools 的说明。必需。 |
| `llm_context` | LLMContext | 工具调用的上下文。必需。 |
| `tools` | list\[Tool] | 此 API 中可用的 tools。必需。 |
| `custom_serializer` | Callable | 可选函数,用于将 voluptuous schemas(如 selectors)转换为 LLM 期望的 JSON schema。默认为 `None`。 |
## 通过 MCP 暴露 API
你无需执行任何特殊操作即可使 API 通过 [Model Context Protocol (MCP)](https://modelcontextprotocol.io) 可用。一旦用户设置好 [MCP Server 集成](https://www.home-assistant.io/integrations/mcp_server/),每个已注册的 LLM API 都会自动通过 MCP 提供。
每个 API 都可以访问自己的 Streamable HTTP endpoint,通过其 API ID 寻址:
```text
/api/mcp/
```
例如,内置的 Assist API 在 `/api/mcp/assist` 可用,而使用 `llm.async_register_api` 注册的 custom API 在 `/api/mcp/` 可用。
这些 per-API endpoints 需要 admin access token(Assist API 除外)。MCP Server 集成还在 `/api/mcp` 暴露单个配置的 API,供不通过 ID 指向特定 API 的 clients 使用。
### 列出已注册的 API
由于这些 endpoints 是通过 API ID 寻址的,clients 需要一种方式来查找哪些 ID 存在。已注册的 APIs 可以通过 [WebSocket API](/developers/api/websocket.md) 列出:
```json
{
"id": 1,
"type": "llm/api/list"
}
```
server 以注册顺序响应每个已注册 API 的 ID 和名称:
```json
{
"id": 1,
"type": "result",
"success": true,
"result": {
"apis": [
{
"id": "assist",
"name": "Assist"
}
]
}
}
```
API 的 `id` 是 `/api/mcp/` 中使用的值,而 `name` 是向用户显示的名称。此命令需要 admin 用户。
---
url: /developers/core/platform/application_credentials.md
---
集成可以支持 [通过 OAuth2 配置](/developers/core/integration/config_flow.md#configuration-via-oauth2),允许用户链接其账户。集成可以添加 `application_credentials.py` 文件并实现下面描述的功能。
OAuth2 需要在应用程序和 provider 之间共享的凭据。在 Home Assistant 中,集成特定的 OAuth2 凭据通过一种或多种方法提供:
* *本地 OAuth 与 Application Credentials 组件*:用户与云 provider 创建自己的凭据,通常作为 application 开发者操作,然后将凭据注册到 Home Assistant 和集成中。此方法对所有支持 OAuth2 的集成是*必需的*。
* *Cloud Account Linking 与 Cloud 组件*:Nabu Casa 与云 provider 注册凭据,提供无缝的用户体验。此方法提供无缝的用户体验,是*推荐的*([更多信息](/developers/core/integration/config_flow.md#configuration-via-oauth2))。
## 添加支持
集成通过在 `manifest.json` 中添加对 `application_credentials` 组件的依赖来支持 application credentials:
```json
{
...
"dependencies": ["application_credentials"],
...
}
```
然后在集成文件夹中添加名为 `application_credentials.py` 的文件,并实现以下内容:
```python
from homeassistant.core import HomeAssistant
from homeassistant.components.application_credentials import AuthorizationServer
async def async_get_authorization_server(hass: HomeAssistant) -> AuthorizationServer:
"""Return authorization server."""
return AuthorizationServer(
authorize_url="https://example.com/auth",
token_url="https://example.com/oauth2/v4/token"
)
```
### AuthorizationServer
`AuthorizationServer` 表示集成使用的 [OAuth2 授权服务器](https://datatracker.ietf.org/doc/html/rfc6749)。
| Name | Type | | Description |
| ------------- | ---- | -------------------------------------------------------------------------------------------------- | ----------- |
| authorize\_url | str | **必需** | 用户在配置流程中被重定向到的 OAuth 授权 URL。 |
| token\_url | str | **必需** | 用于获取 access token 的 URL。 |
### 自定义 OAuth2 实现
集成也可以在 `application_credentials.py` 中提供自定义的 `AbstractOAuth2Implementation`,如下所示:
```python
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_entry_oauth2_flow
from homeassistant.components.application_credentials import AuthImplementation, AuthorizationServer, ClientCredential
class OAuth2Impl(AuthImplementation):
"""Custom OAuth2 implementation."""
# ... 覆盖 AbstractOAuth2Implementation 细节
async def async_get_auth_implementation(
hass: HomeAssistant, auth_domain: str, credential: ClientCredential
) -> config_entry_oauth2_flow.AbstractOAuth2Implementation:
"""Return auth implementation for a custom auth implementation."""
return OAuth2Impl(
hass,
auth_domain,
credential,
AuthorizationServer(
authorize_url="https://example.com/auth",
token_url="https://example.com/oauth2/v4/token"
)
)
```
### 支持 PKCE 的授权流程
如果你想支持 [PKCE](https://www.rfc-editor.org/rfc/rfc7636),可以在 `application_credentials.py` 中返回 `LocalOAuth2ImplementationWithPkce`,如下所示:
```python
from homeassistant.core import HomeAssistant
from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2Implementation, LocalOAuth2ImplementationWithPkce
from homeassistant.components.application_credentials import AuthImplementation, ClientCredential
async def async_get_auth_implementation(
hass: HomeAssistant, auth_domain: str, credential: ClientCredential
) -> AbstractOAuth2Implementation:
"""Return auth implementation for a custom auth implementation."""
return LocalOAuth2ImplementationWithPkce(
hass,
auth_domain,
credential.client_id,
authorize_url="https://example.com/auth",
token_url="https://example.com/oauth2/v4/token",
client_secret=credential.client_secret, # 可选,默认 `""`
code_verifier_length=128 # 可选
)
```
## 导入 YAML 凭据
以前接受 YAML 凭据的集成可以使用 application credentials 集成提供的 import API `async_import_client_credential` 导入凭据。
以下是来自一个以前接受 YAML 凭据的集成的示例:
```python
from homeassistant.components.application_credentials import (
ClientCredential,
async_import_client_credential,
)
# 集成的示例 configuration.yaml schema
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Required(CONF_CLIENT_ID): cv.string,
vol.Required(CONF_CLIENT_SECRET): cv.string,
}
)
},
)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the component."""
if DOMAIN not in config:
return True
await async_import_client_credential(
hass,
DOMAIN,
ClientCredential(
config[DOMAIN][CONF_CLIENT_ID],
config[DOMAIN][CONF_CLIENT_SECRET],
),
)
```
新集成不应在 configuration.yaml 中接受凭据,因为用户可以在 Application Credentials 用户界面中输入凭据。
### ClientCredential
`ClientCredential` 表示由用户提供的 client 凭据。
| Name | Type | | Description |
| ------------- | ---- | ------------------------------------------------------------------------- | ----------- |
| client\_id | str | **必需** | 用户提供的 OAuth Client ID。 |
| client\_secret | str | **必需** | 用户提供的 OAuth Client Secret。 |
## 翻译
Application Credentials 的 translations 在组件翻译文件 `strings.json` 的 `application_credentials` key 下定义。例如:
```json
{
"application_credentials": {
"description": "导航到 [developer console]({console_url}) 创建凭据,然后在下方输入。",
}
}
```
你还可以可选地添加 description placeholder keys,通过在 `application_credentials.py` 中添加新方法来将占位符添加到消息中,如下所示:
```python
from homeassistant.core import HomeAssistant
async def async_get_description_placeholders(hass: HomeAssistant) -> dict[str, str]:
"""Return description placeholders for the credentials dialog."""
return {
"console_url": "https://example.com/developer/console",
}
```
在本地开发时,你需要运行 `python3 -m script.translations develop` 才能看到对 `strings.json` 所做的更改。[更多关于翻译 Home Assistant 的信息。](translations.md)
---
url: /developers/core/platform/backup.md
---
集成实现备份平台有两个主要目的:
1. 添加一个 backup agent,可以将备份上传到某个本地或远程位置。
2. 在创建备份之前暂停或准备集成操作,和/或在备份后运行某些操作。
## 备份代理
要添加一个或多个 backup agents,请在 `backup.py` 中实现 `async_get_backup_agents` 和 `async_register_backup_agents_listener` 这两个方法。示例:
```python
async def async_get_backup_agents(
hass: HomeAssistant,
) -> list[BackupAgent]:
"""Return a list of backup agents."""
if not hass.config_entries.async_loaded_entries(DOMAIN):
LOGGER.debug("No config entry found or entry is not loaded")
return []
return [ExampleBackupAgent()]
@callback
def async_register_backup_agents_listener(
hass: HomeAssistant,
*,
listener: Callable[[], None],
**kwargs: Any,
) -> Callable[[], None]:
"""Register a listener to be called when agents are added or removed.
:return: A function to unregister the listener.
"""
hass.data.setdefault(DATA_BACKUP_AGENT_LISTENERS, []).append(listener)
@callback
def remove_listener() -> None:
"""Remove the listener."""
hass.data[DATA_BACKUP_AGENT_LISTENERS].remove(listener)
return remove_listener
```
存储在 `async_register_backup_agents_listener` 中的 listener 应在每次需要重新加载 backup agents 时被调用,以移除过期的 agents 并添加新的 agents。这可以通过在 `async_setup_entry` 期间注册 listeners 来完成:
```python
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up config entry."""
# do things to set up your config entry
# 通知 backup listeners
def notify_backup_listeners() -> None:
for listener in hass.data.get(DATA_BACKUP_AGENT_LISTENERS, []):
listener()
entry.async_on_unload(entry.async_on_state_change(notify_backup_listeners))
return True
```
Backup agent 应实现 `BackupAgent` 基类的抽象接口,如下例所示:
```python
from homeassistant.components.backup import BackupAgent, BackupAgentError, OnProgressCallback
from .const import DOMAIN
class ExampleBackupAgent(BackupAgent):
"""Backup agent interface."""
domain = DOMAIN
name = "Example Backup-Agent"
unique_id = "example_stable_id"
async def async_download_backup(
self,
backup_id: str,
**kwargs: Any,
) -> AsyncIterator[bytes]:
"""Download a backup file.
Raises BackupNotFound if the backup does not exist.
:param backup_id: The ID of the backup that was returned in async_list_backups.
:return: An async iterator that yields bytes.
"""
async def async_upload_backup(
self,
*,
open_stream: Callable[[], Coroutine[Any, Any, AsyncIterator[bytes]]],
backup: AgentBackup,
on_progress: OnProgressCallback,
**kwargs: Any,
) -> None:
"""Upload a backup.
:param open_stream: A function returning an async iterator that yields bytes.
:param backup: Metadata about the backup that should be uploaded.
:param on_progress: A callback to report the number of uploaded bytes.
"""
async def async_delete_backup(
self,
backup_id: str,
**kwargs: Any,
) -> None:
"""Delete a backup file.
Raises BackupNotFound if the backup does not exist.
:param backup_id: The ID of the backup that was returned in async_list_backups.
"""
async def async_list_backups(self, **kwargs: Any) -> list[AgentBackup]:
"""List backups."""
async def async_get_backup(
self,
backup_id: str,
**kwargs: Any,
) -> AgentBackup:
"""Return a backup.
Raises BackupNotFound if the backup does not exist.
"""
```
Backup agents 应在出错时抛出 `BackupAgentError`(或 `BackupAgentError` 的子类)异常。其他异常不应从 backup agent 中抛出。
### 报告上传进度
`async_upload_backup` 方法接收一个 `on_progress` callback,agents 可以使用它来报告上传进度。调用 callback 时传入到目前为止上传的总字节数(整数):
```python
on_progress(bytes_uploaded=bytes_sent)
```
备份管理器使用此信息来触发 `UploadBackupEvent` 事件,允许 frontend 向用户显示上传进度。Agents 应在上传过程中定期调用此 callback,例如在每次发送数据块之后。
## 预操作和后操作
当 Home Assistant 正在创建备份时,可能需要暂停集成中的某些操作,或 dump 数据以便正确恢复。
这是通过在 `backup.py` 中添加两个函数(`async_pre_backup` 和 `async_post_backup`)来完成的。
### 添加支持
为新集成添加备份支持的最快方法是使用我们内置的 scaffold 模板。从 Home Assistant 开发环境运行 `python3 -m script.scaffold backup` 并按照说明操作。
如果你更喜欢手动方式,请在集成文件夹中创建一个名为 `backup.py` 的新文件,并实现以下方法:
```python
from homeassistant.core import HomeAssistant
async def async_pre_backup(hass: HomeAssistant) -> None:
"""Perform operations before a backup starts."""
async def async_post_backup(hass: HomeAssistant) -> None:
"""Perform operations after a backup finishes."""
```
---
url: /developers/core/platform/media_source.md
---
media source platform 允许集成为 Home Assistant 暴露可浏览和可播放的媒体。Media sources 出现在 media browser UI 中,让用户可以浏览层级化的媒体库并在 media player 设备上播放内容。
## 实现媒体源
要添加 media source 支持,请在集成目录中创建 `media_source.py` 文件。无需更改 `manifest.json`——Home Assistant 通过 integration platform 机制自动发现 `media_source.py` 模块。
该模块必须定义一个 `async_get_media_source` 函数,返回你的 `MediaSource` 子类实例:
```python
from homeassistant.components.media_player import BrowseError, MediaClass, MediaType
from homeassistant.components.media_source import (
BrowseMediaSource,
MediaSource,
MediaSourceItem,
PlayMedia,
Unresolvable,
)
from homeassistant.core import HomeAssistant
from .const import DOMAIN
async def async_get_media_source(hass: HomeAssistant) -> MyMediaSource:
"""Set up my media source."""
return MyMediaSource(hass)
class MyMediaSource(MediaSource):
"""Provide media from my integration."""
name = "My Service"
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the media source."""
super().__init__(DOMAIN)
self.hass = hass
async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia:
"""Resolve a media item to a playable URL."""
...
async def async_browse_media(self, item: MediaSourceItem) -> BrowseMediaSource:
"""Browse media."""
...
```
## `MediaSource` 基类
你的 media source 必须继承 `MediaSource` 并实现 `async_resolve_media` 和 `async_browse_media`。可以可选地实现 `async_search_media` 以允许用户搜索你的媒体。
| Method | Description |
|---|---|
| `async_resolve_media(item)` | 将 `MediaSourceItem` 解析为包含可播放 URL 和 MIME type 的 `PlayMedia`。如果项目无法解析,抛出 `Unresolvable`。 |
| `async_browse_media(item)` | 返回表示给定项目处可浏览结构的 `BrowseMediaSource`。如果浏览失败,抛出 `BrowseError`。 |
| `async_search_media(item, query)` | 可选。返回给定 `SearchMediaQuery` 的 `SearchMedia` 结果。请参阅 [搜索媒体](#searching-media)。 |
将类属性 `name` 设置为你 source 的人类可读名称。如果未设置,则默认为集成 domain。
## 浏览媒体
`async_browse_media` 方法接收一个 `MediaSourceItem` 并必须返回 `BrowseMediaSource` 树。当 `item.identifier` 为空时,返回媒体层级的根。对于非空 identifiers,返回该项目下的子项。
```python
async def async_browse_media(self, item: MediaSourceItem) -> BrowseMediaSource:
"""Browse media."""
if item.identifier:
raise BrowseError("Unknown item")
children = [
BrowseMediaSource(
domain=DOMAIN,
identifier=entity.entity_id,
media_class=MediaClass.VIDEO,
media_content_type=entity.content_type,
title=entity.name,
thumbnail=f"/api/my_proxy/{entity.entity_id}",
can_play=True,
can_expand=False,
)
for entity in self.hass.data[DATA_COMPONENT].entities
]
return BrowseMediaSource(
domain=DOMAIN,
identifier=None,
media_class=MediaClass.APP,
media_content_type="",
title="My Service",
can_play=False,
can_expand=True,
children_media_class=MediaClass.VIDEO,
children=children,
)
```
### `BrowseMediaSource`
`BrowseMediaSource` 扩展了 media player 的 `BrowseMedia`,并自动从 `domain` 和 `identifier` 构造 `media_content_id`。
| Parameter | Type | Description |
|---|---|---|
| `domain` | `str` | 你的集成 domain。 |
| `identifier` | `str \| None` | 项目特定的标识符。根项目使用 `None`。 |
| `media_class` | `MediaClass` | 媒体类型(例如 `MediaClass.APP`、`MediaClass.DIRECTORY`、`MediaClass.MUSIC`、`MediaClass.VIDEO`、`MediaClass.IMAGE`)。 |
| `media_content_type` | `MediaType \| str` | 内容的 MIME type 或媒体类型(例如 `"audio/mpeg"`)。 |
| `title` | `str` | 向用户显示的标题。 |
| `can_play` | `bool` | 项目是否可以直接播放。 |
| `can_expand` | `bool` | 项目是否可以进一步浏览(有子项)。 |
| `can_search` | `bool` | 项目是否可以被搜索。当为 `True` 时,media browser 会为此项目显示搜索栏。默认为 `False`。请参阅 [搜索媒体](#searching-media)。 |
| `search_media_classes` | `list[MediaClass] \| None` | 可选的 media class 列表,作为此项目的搜索过滤器。设置后,media browser 允许用户将搜索范围缩小到这些 class,它们随后作为 `SearchMediaQuery` 中的 `media_filter_classes` 传回。默认为 `None`。请参阅 [搜索媒体](#searching-media)。 |
| `children` | `list[BrowseMediaSource] \| None` | 子项。仅在浏览的父项目上设置。 |
| `children_media_class` | `MediaClass \| None` | 子项的 media class。未设置时自动计算。 |
| `thumbnail` | `str \| None` | 缩略图片的 URL。 |
| `not_shown` | `int` | 未包含的子项数量(例如,被过滤掉)。默认为 `0`。 |
### 分层浏览
对于深层媒体层级,使用 identifier 编码路径。一种常见的模式是在 identifier 内使用 `/` 作为分隔符:
```python
async def async_browse_media(self, item: MediaSourceItem) -> BrowseMediaSource:
"""Browse media."""
category, _, sub_id = (item.identifier or "").partition("/")
if category == "albums" and sub_id:
return await self._async_browse_album(sub_id)
if category == "albums":
return await self._async_browse_albums()
# 根
return BrowseMediaSource(
domain=DOMAIN,
identifier=None,
media_class=MediaClass.APP,
media_content_type="",
title="My Service",
can_play=False,
can_expand=True,
children=[
BrowseMediaSource(
domain=DOMAIN,
identifier="albums",
media_class=MediaClass.DIRECTORY,
media_content_type=MediaType.MUSIC,
title="Albums",
can_play=False,
can_expand=True,
),
],
)
```
## 搜索媒体
搜索是可选的。实现 `async_search_media` 以允许用户从 media browser 搜索你的 media source。它接收正在搜索的 `MediaSourceItem`(搜索开始的位置)和一个 `SearchMediaQuery`,并返回 `SearchMedia` 结果:
```python
from homeassistant.components.media_player import SearchMedia, SearchMediaQuery
async def async_search_media(
self, item: MediaSourceItem, query: SearchMediaQuery
) -> SearchMedia:
"""Search media."""
tracks = await self.api.search(query.search_query)
results = [
BrowseMediaSource(
domain=DOMAIN,
identifier=track.id,
media_class=MediaClass.MUSIC,
media_content_type=track.mime_type,
title=track.title,
can_play=True,
can_expand=False,
)
for track in tracks
]
return SearchMedia(result=results)
```
`item` 参数告诉你搜索从哪里开始,因此你可以将结果范围限定为该部分层级。当 `query.media_filter_classes` 被设置时,仅返回 `media_class` 在列表中的项目。
### `SearchMediaQuery`
传递给 `async_search_media` 的 `SearchMediaQuery` 具有以下属性:
| Attribute | Type | Default | Description |
|---|---|---|---|
| `search_query` | `str` | *必需* | 用户输入的搜索字符串。 |
| `media_content_type` | `MediaType \| str \| None` | `None` | 要搜索的内容类型。 |
| `media_content_id` | `str \| None` | `None` | 要搜索的内容 ID。 |
| `media_filter_classes` | `list[MediaClass] \| None` | `None` | 设置后,仅返回 `media_class` 在此列表中的项目。 |
### `SearchMedia`
`SearchMedia` 包装匹配项目列表。
| Field | Type | Description |
|---|---|---|
| `result` | `Sequence[BrowseMedia]` | 与查询匹配的项目。 |
### 宣传搜索支持
media browser 仅为宣传了搜索的项目显示搜索栏。将 `can_search=True` 设置在 `async_browse_media()` 返回的 `BrowseMediaSource` 项目上,以在浏览时支持搜索:
```python
async def async_browse_media(self, item: MediaSourceItem) -> BrowseMediaSource:
"""Browse media."""
return BrowseMediaSource(
domain=DOMAIN,
identifier=item.identifier,
media_class=MediaClass.APP,
media_content_type="",
title="My Service",
can_play=False,
can_expand=True,
can_search=True,
children=[BrowseMediaSource(...), BrowseMediaSource(...)],
)
```
如果只想在特定路径启用搜索支持,可以在浏览时基于当前 `MediaSourceItem`(通常是可浏览的目录)将 `can_search=True` 设置在 `async_browse_media()` 返回的 `BrowseMediaSource` 项目上:
```python
async def async_browse_media(self, item: MediaSourceItem) -> BrowseMediaSource:
"""Browse media."""
can_search = False
# 根节点的 item.identifier 将为 None
if item.identifier:
can_search = is_searchable(item.identifier)
return BrowseMediaSource(
domain=DOMAIN,
identifier=item.identifier,
media_class=MediaClass.APP,
media_content_type="",
title="My Service",
can_play=False,
can_expand=True,
can_search=can_search,
children=[BrowseMediaSource(...), BrowseMediaSource(...)],
)
```
### 宣传搜索过滤器
要让用户缩小搜索范围,在可搜索项目上将 `search_media_classes` 设置为该位置有意义的 `MediaClass` 值列表。media browser 将这些作为过滤器选项提供,用户选择的 class 作为 [`SearchMediaQuery`](#searchmediaquery) 中的 `media_filter_classes` 传回。在 `async_search_media` 中遵循该列表,仅返回 `media_class` 被包含的项目:
```python
from homeassistant.components.media_player import MediaClass
async def async_browse_media(self, item: MediaSourceItem) -> BrowseMediaSource:
"""Browse media."""
return BrowseMediaSource(
domain=DOMAIN,
identifier=item.identifier,
media_class=MediaClass.APP,
media_content_type="",
title="My Service",
can_play=False,
can_expand=True,
can_search=True,
search_media_classes=[MediaClass.ALBUM, MediaClass.ARTIST, MediaClass.TRACK],
children=[BrowseMediaSource(...), BrowseMediaSource(...)],
)
```
## 解析媒体
`async_resolve_media` 方法将媒体项目解析为可播放 URL。它接收一个 `MediaSourceItem` 并返回 `PlayMedia` 实例:
```python
async def async_resolve_media(self, item: MediaSourceItem) -> PlayMedia:
"""Resolve a media item to a playable URL."""
track = await self.api.get_track(item.identifier)
if not track:
raise Unresolvable(f"Could not resolve: {item.identifier}")
return PlayMedia(track.stream_url, track.mime_type)
```
### `PlayMedia`
| Field | Type | Description |
|---|---|---|
| `url` | `str` | 要播放的 URL。可以是绝对 URL 或由 Home Assistant 提供的相对路径。 |
| `mime_type` | `str` | 媒体的 MIME type(例如 `"audio/mpeg"`、`"video/mp4"`、`"image/jpeg"`)。 |
| `path` | `Path \| None` | 可选的本地文件系统路径。仅用于本地文件源。 |
### `MediaSourceItem`
传递给这两个方法的 `MediaSourceItem` 具有以下属性:
| Attribute | Type | Description |
|---|---|---|
| `domain` | `str \| None` | 你的集成 domain。 |
| `identifier` | `str` | URI 中的项目标识符。 |
| `target_media_player` | `str \| None` | 将播放媒体的 media player 的 entity ID。可用于自定义解析的 URL。 |
## URI 方案
Media sources 使用 `media-source://` URI scheme:
```
media-source://domain/identifier
```
例如:
* `media-source://radio_browser/popular` — radio\_browser 的 "popular" 类别
* `media-source://tts/message` — TTS 消息
使用 `generate_media_source_id` helper 构造 URI:
```python
from homeassistant.components.media_source import generate_media_source_id
media_id = generate_media_source_id(DOMAIN, "my_track_123")
# 返回: "media-source://my_integration/my_track_123"
```
使用 `is_media_source_id` 检查字符串是否为有效的 media source URI:
```python
from homeassistant.components.media_source import is_media_source_id
if is_media_source_id(media_content_id):
# 作为 media source 处理
...
```
## 错误处理
出错时抛出适当的异常:
| Exception | 何时抛出 |
|---|---|
| `Unresolvable` | 在 `async_resolve_media` 中,当媒体项目无法解析为可播放 URL 时。 |
| `BrowseError` | 在 `async_browse_media` 中,当无法获取媒体结构时,或在 `async_search_media` 中搜索失败时。 |
两个异常都支持 translations:
```python
raise Unresolvable(
translation_domain=DOMAIN,
translation_key="item_not_found",
translation_placeholders={"item": item.identifier},
)
```
## 在其他集成中使用媒体源
其他集成(如 media players)可以使用 media source helpers 浏览和解析媒体:
```python
from homeassistant.components.media_player import SearchMediaQuery
from homeassistant.components.media_source import (
async_browse_media,
async_resolve_media,
async_search_media,
is_media_source_id,
)
# 浏览 media sources
result = await async_browse_media(hass, "media-source://my_domain")
# 搜索 media source
search_result = await async_search_media(
hass,
"media-source://my_domain",
SearchMediaQuery(search_query="jazz"),
)
# 将媒体项目解析为可播放 URL
play_media = await async_resolve_media(
hass,
"media-source://my_domain/track_123",
target_media_player="media_player.living_room",
)
```
---
url: /developers/core/platform/raising_exceptions.md
---
## 在 service action handler 中抛出异常
像 service action 调用和 entity 方法(例如 *Set HVAC Mode*)这样的操作应该正确地抛出异常。
当用户做错了某事时,集成应抛出 `ServiceValidationError`(而不是 `ValueError`)。在这种情况下,堆栈跟踪仅在 debug 级别打印。
对于其他故障,如与设备通信时出现问题,应抛出 `HomeAssistantError`。请注意,在这种情况下,异常堆栈跟踪将被打印到日志中。
## 本地化异常
Home Assistant [支持本地化](/developers/internationalization/core.md#exceptions),适用于 `HomeAssistantError` 及其子类如 `ServiceValidationError`。
---
url: /developers/core/platform/repairs.md
---
Home Assistant 会跟踪需要引起用户注意的问题。这些问题可以由集成或 Home Assistant 本身创建。问题可以通过 RepairsFlow 修复,也可以通过链接到包含用户自行解决方法的网站来修复。
维修问题也是面向用户的集成中 [deprecation](/developers/deprecating.md) 的宣布方式,这是它们最常见的用途之一。Core API 的 deprecation 则通过日志警告报告给开发者。
## 创建问题
```python
from homeassistant.helpers import issue_registry as ir
ir.async_create_issue(
hass,
DOMAIN,
"manual_migration",
breaks_in_ha_version="2022.9.0",
is_fixable=False,
severity=ir.IssueSeverity.ERROR,
translation_key="manual_migration",
)
```
| 属性 | 类型 | 默认值 | 描述 |
| --------- | -------- | ------- | ----------- |
| domain | string | | 引发该问题的 domain |
| issue\_id | string | | 问题的标识符,在 `domain` 内必须唯一 |
| breaks\_in\_ha\_version | string | `None` | 问题成为破坏性的版本 |
| data | dict | `None` | 任意数据,不向用户显示 |
| is\_fixable | boolean | | 如果问题可以自动修复则为 True |
| is\_persistent | boolean | | 如果问题应在 Home Assistant 重启后持续存在则为 True |
| issue\_domain | string | `None` | 由代为其他集成创建问题的集成设置 |
| learn\_more\_url | string | `None` | 用户可以查找更多关于问题详细信息的 URL |
| severity | IssueSeverity | | 问题的严重程度 |
| translation\_key | str | | 包含问题简要说明的 translation key |
| translation\_placeholders | dict | `None` | 将注入到翻译中的占位符 |
### 问题的严重程度
为了更好地理解应选择哪个严重程度级别,请参阅下面的列表。
| IssueSeverity | 描述 |
|---------------|--------------------------------------------------------------------|
| CRITICAL | 保留级别,仅用于真正的紧急情况 |
| ERROR | 当前有某事物已损坏,需要立即关注 |
| WARNING | 将来某事物将损坏(例如 API 关闭),需要关注 |
## 修复问题
如果问题的 `is_fixable` 设置为 `True`,用户将被允许修复该问题。成功修复的问题将从 issue registry 中移除。
如果可以进行自动修复,应使用 RepairsFlow 来实现。
### 提供自动修复
在集成文件夹中创建一个名为 `repairs.py` 的新 platform 文件,并根据下面的模式添加代码。
```python
from __future__ import annotations
import voluptuous as vol
from homeassistant import data_entry_flow
from homeassistant.components.repairs import ConfirmRepairFlow, RepairsFlow
from homeassistant.core import HomeAssistant
class Issue1RepairFlow(RepairsFlow):
"""Handler for an issue fixing flow."""
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the first step of a fix flow."""
return await (self.async_step_confirm())
async def async_step_confirm(
self, user_input: dict[str, str] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the confirm step of a fix flow."""
if user_input is not None:
return self.async_create_entry(title="", data={})
return self.async_show_form(step_id="confirm", data_schema=vol.Schema({}))
async def async_create_fix_flow(
hass: HomeAssistant,
issue_id: str,
data: dict[str, str | int | float | None] | None,
) -> RepairsFlow:
"""Create flow."""
if issue_id == "issue_1":
return Issue1RepairFlow()
```
## 问题生命周期
### 问题持久性
问题将保留在 issue registry 中,直到被创建它的集成或用户通过[修复](#fixing-an-issue)将其移除。
`is_persistent` 标志控制问题是否应在 Home Assistant 重启后向用户显示:
* 如果设置了 `is_persistent` 标志,问题将在重启后再次向用户显示。适用于只能在发生时检测到的问题(更新失败、自动化中未知操作)。
* 如果未设置 `is_persistent` 标志,问题将在重启后不再向用户显示,直到其集成重新创建它。适用于可以检查的问题,如磁盘空间不足。
### 被忽略的问题
用户可以"忽略"问题。被忽略的问题将被忽略,直到被明确删除——由集成删除或用户成功走完其[维修流程](#fixing-an-issue)——并重新创建。忽略问题在 Home Assistant 重启后仍然生效,无论[问题持久性](#issue-persistence)如何。
## 删除问题
集成通常不需要删除问题,但在某些情况下可能有用。
```python
from homeassistant.helpers import issue_registry as ir
ir.async_delete_issue(hass, DOMAIN, "manual_migration")
```
---
url: /developers/core/platform/reproduce_state.md
---
Home Assistant 支持 scenes。Scenes 是一组(部分)entity 状态。当 scene 被激活时,Home Assistant 将尝试调用正确的 service actions 以使指定的 scene 处于其指定的状态。
集成负责向 Home Assistant 添加支持,以便能够调用正确的 service actions 来重现 scene 中的状态。
## 添加支持
为新集成添加 reproduce state 支持的最快方法是使用我们内置的 scaffold 模板。从 Home Assistant 开发环境运行 `python3 -m script.scaffold reproduce_state` 并按照说明操作。
如果你更喜欢手动方式,请在集成文件夹中创建一个名为 `reproduce_state.py` 的新文件,并实现以下方法:
```python
import asyncio
from collections.abc import Iterable
from typing import Any
from homeassistant.core import Context, HomeAssistant, State
async def async_reproduce_states(
hass: HomeAssistant,
states: Iterable[State],
*,
context: Context | None = None,
reproduce_options: dict[str, Any] | None = None,
) -> None:
"""Reproduce component states."""
# TODO reproduce states
```
---
url: /developers/core/platform/significant_change.md
---
Home Assistant 不仅收集数据,还将数据导出到各种服务。并非所有这些服务都对每一次变更感兴趣。为了帮助这些服务过滤不重要的变更,你的 entity 集成可以添加重要变更支持。
此支持通过创建一个包含 `async_check_significant_change` 函数的 `significant_change.py` platform 文件来实现。
```python
from typing import Any, Optional
from homeassistant.core import HomeAssistant, callback
@callback
def async_check_significant_change(
hass: HomeAssistant,
old_state: str,
old_attrs: dict,
new_state: str,
new_attrs: dict,
**kwargs: Any,
) -> bool | None:
```
该函数接收之前被认为是重要的 state 和新的 state。它传递的不仅仅是最近 2 个已知 state。函数应返回布尔值,表示是否重要,或者返回 `None` 表示函数无法判断。
在判断重要性时,务必考虑所有已知 attributes。使用 device classes 来区分 entity 类型。
以下是一些不重要的变更示例:
* 电池电量减少 0.1%
* 温度传感器变化 0.1 摄氏度
* 灯光亮度变化 2
Home Assistant 将自动处理 `unknown` 和 `unavailable` 等情况。
要为 entity 集成添加重要 state 支持,运行 `python3 -m script.scaffold significant_change`。
---
url: /developers/core/pr_review_guide.md
---
# PR 审查指南
审查 `home-assistant/core` 上 pull requests 的实用指南。
***
## 1. 第一遍检查:门禁检查
在阅读任何代码之前,先验证这些内容。如果有任何失败,发表评论并停止。继续审查会浪费你的时间。
* **CLA 已签署。** 检查是否存在 `cla-signed` 标签。如果存在 `cla-needed`,评论要求贡献者签署,然后跳过。在 CLA 解决之前不要审查代码。
* **CI 是绿的。** 所有检查必须通过。如果 CI 因贡献者的更改而变红,转换为 draft 并要求他们修复。不要审查有问题的代码。
* **PR 有单一目的。** 一个 bugfix、一个 feature、一个 integration 或一个 refactor。如果 PR 混合了关注点(例如,依赖更新同时也重构测试),要求作者拆分。
* **PR 类型复选框与内容匹配。** PR 模板有类型复选框:验证只有一个被勾选且与代码实际内容匹配。
* **不依赖于其他打开的 PR。** 检查是否有指向其他未合并 PR 的引用。依赖 PR 不应提交,直到其依赖项合并。
* **描述有用。** "Proposed change" 部分应解释*为什么*,而不只是*什么*。对于 bugfix,应有链接的 issue。对于新集成,应有品牌/设备的上下文。
* **PR 描述没有未填充的占位符。** 检查是否有空的 Breaking Change 部分、未填充的 `fixes #` 占位符。
### 1.1 分类信号:此 PR 是否可能完成?
早期识别高风险 PR 的信号可以节省审查者的时间。
**高风险指标**(在解决之前考虑优先推迟审查):
* **首次贡献者的大型 PR。** 首次贡献者的新集成 PR 废弃率最高。如果 PR 是新集成且作者没有先前的已合并 PR,考虑等待 CI 通过并处理初始 bot 反馈后再投入深度审查时间。
* **AI 生成 PR 信号。** 注意那些解决理论而非实际问题的 PR、格式异常完美的描述但代码表现出根本性误解的 PR、在没有相应行为更改的情况下修改 test fixtures 的 PR,或为不可能发生的条件添加防御性代码的 PR。
***
## 2. 新集成 PR(`new-integration`)
这些审查非常密集。quality scale 的 bronze 规则是基准线:每个新集成都必须满足它们。
### 2.1 范围
* **仅限单一 platform。** 初始 PR 应只暴露一个 entity platform(例如,仅 `sensor` 或仅 `switch`)。检查 `__init__.py` 中的 `PLATFORMS` 列表——如果有多于一个条目,要求作者选择一个并将其余推迟。
* **无 diagnostics。** 初始集成 PR 中不应存在 `diagnostics.py`。这是 gold 级别的 quality scale feature,应放在后续 PR 中。
* **无 reconfigure flow。** `config_flow.py` 中的 reconfigure flow(`async_step_reconfigure`)是 gold 级别。要求作者将其删除并单独提交。
* **无 options flow(通常)。** 除非集成确实从一开始就需要用户可配置的 options,否则 options flow 应放在后续 PR 中。
* **无万能集成。** 注意添加所有可能 entity 的 PR。初始 PR 应最小且功能可用。额外的 platforms、entities 和 features 在后续 PR 中添加。
### 2.2 Manifest (`manifest.json`)
* **`iot_class` 正确。** 必须与集成实际的通信方式匹配:`local_polling`、`cloud_push` 等。
* **Library 是开源的。** PyPI 依赖必须公开可用源代码并带有开源 license。检查 repo 链接。
* **License 与 Apache-2.0 兼容。** HA Core 是 Apache-2.0。依赖必须使用兼容 license(MIT、BSD、Apache-2.0、LGPL、MPL-2.0、ISC、Unlicense 均可)。**GPL-2.0、GPL-3.0 和 AGPL 不兼容。**
* **存在 License 元数据。** library 必须在 PyPI 上声明 license。没有 license 的 library 在法律上等于 "all rights reserved"。
* **License identifier 与 LICENSE 文件匹配。** 验证 `pyproject.toml`/`setup.cfg` 中的 `license` 字段与源代码仓库中实际的 LICENSE 文件文本匹配。不匹配表示错误或误导。
* **传递依赖的 license 兼容。** MIT library 引入 GPL-3.0 传递依赖会产生同样的不兼容性。检查完整的依赖树。
* **Library 未废弃或近期未转移。** 检查提交历史和 PyPI maintainer。一个 2+ 年没有活动的 library 突然出现新的 maintainer 值得审查。
### 2.3 质量等级(`quality_scale.yaml`)
* **所有 bronze 规则都已处理。** 每个 bronze rule 都应标记为 `done` 或 `exempt`(附带原因)。不应有 `todo`。
* **Exempt 原因有效。** 如果任何 rule 被标记为 `exempt`,原因应是合理的(例如,"此集成没有 actions" 适用于 `action-setup`)。
* **声称 `done` 的规则确实完成了。** 抽查几个:quality-scale-rule-verifier agent 在此处可以提供帮助。
### 2.4 Config flow
* **`test-before-configure` 已实现。** config flow 必须尝试连接到设备/服务,如果无法连接则以用户友好的错误优雅失败。
* **`unique-config-entry` 防止重复。** flow 应调用 `self._abort_if_unique_id_configured()` 或等效方法来防止添加同一设备两次。
* **Reconfigure 时有建议值。** 如果存在 reconfigure flow(初始 PR 中不应存在,但对于后续 PR):输入字段应使用 `suggested_value` 预填充当前值。
### 2.5 发现流程(Zeroconf / DHCP / SSDP)
Discovery 配置是反复拉锯的常见来源。
* **Discovery patterns 足够具体。** Zeroconf service types、DHCP hostnames 和 SSDP manufacturers 应只匹配目标设备。过于宽泛的模式(例如,匹配 `duco` 而实际上意味着 `duco [`)会意外声明不相关的设备。问自己:"此模式是否可能匹配它不应匹配的内容?"
* **适当位置使用 `raise_on_progress=False`。** 从多个 steps 调用 `async_set_unique_id` 的 flows 需要在 flow source 应优先于 discovery flow 的 steps 中传递 `raise_on_progress=False` 参数。例如,由用户启动的 flow 应始终优先于同一设备 unique id 的 discovery flow。
* **DHCP 作为后备。** 如果集成支持 Zeroconf,考虑是否也应添加 DHCP discovery 作为后备,用于 mDNS 效果不佳的网络。
* **考虑 `registered_devices` 支持。** 对于设备 hostname/IP 可能更改的集成,`registered_devices` 允许重新发现已配置的设备。
* **Manifest discovery entries 与实现匹配。** 如果 `manifest.json` 声明了 `zeroconf`、`dhcp` 或 `ssdp`,请验证 config flow 实际上实现了相应的 `async_step_*` 方法。
### 2.6 协调器与数据获取
* **使用 `DataUpdateCoordinator` 进行轮询。** 轮询集成不应实现自己的轮询循环。例外情况是集成使用[每个 individual entity 的单独轮询](/developers/integration_fetching_data.md#separate-polling-for-each-individual-entity)。
* **一次性工作用 `_async_setup`,轮询用 `_async_update_data`。** 一次性设置工作(认证、连接设置、初始设备信息获取)属于 `_async_setup`。`_async_update_data` 方法在每次 poll cycle 运行——不要在这里放入设置逻辑。
* **认证错误使用 `ConfigEntryAuthFailed`。** 当 API 在数据更新期间返回 auth 错误时,coordinator 必须抛出 `ConfigEntryAuthFailed`(而不是 `UpdateFailed`)。这将触发 reauth flow 而不是只是记录错误并永远重试。要让 reauth flow 实际启动,集成必须已实现 `async_step_reauth` 方法;没有它,entry 只是保持在失败状态。
* **Coordinator 数据已正确类型化。** coordinator 应使用 generics(`DataUpdateCoordinator[MyDataType]`),以便 entity 代码获得类型检查对 coordinator 数据的访问。
* **轮询频率合理。** 检查 `update_interval`:对设备/服务是否合适?天气 API 不需要 10 秒轮询。本地设备可能需要更快的更新。
### 2.7 Entities
* **稳定的 `unique_id`。** Entity unique IDs 不得使用 IP addresses、hostnames 或任何可能更改的内容。优先使用 MAC addresses(使用 `format_mac()` 规范化)、序列号或设备分配 ID。
* **MAC addresses 已规范化。** 如果 MAC address 用作 unique ID 或在 device info 中,必须通过 `homeassistant.helpers.device_registry` 的 `format_mac()` 处理。
* **使用 Entity description。** 新 entity 应使用基于 dataclass 的 [entity description](/developers/core/entity.md#entity-description)(如 `SensorEntityDescription`)来定义。链接文档中描述的其他模式也被接受,但在新代码中优先使用 entity description 模式。
* **设置 `PARALLEL_UPDATES`。** 对于基于 coordinator 的集成,在每个 platform 模块中设置 `PARALLEL_UPDATES = 0`(因为 coordinator 处理同步)。
* **Entity categories 正确。** 请参阅第 8 节获取详细的 `EntityCategory` 指导:这是最常见的审查模式之一。
* **Debug entities 默认禁用。** 用于调试目的的 entities(信号强度、rate limits、固件版本)应设置 `entity_registry_enabled_default=False`。
### 2.8 Strings & translations
* **所有面向用户的字符串在 `strings.json` 中。** Python 代码中不得有硬编码的英文字符串。
* **Sentence case。** `strings.json` 中所有 string 值必须使用 sentence case(不是 Title Case,不是 ALL CAPS)。
* **适用时使用 Common translation keys。** 如果字符串匹配现有的 common key(如通用名称 "Temperature"、"Humidity"),引用 common key 而不是定义新的。
* **代码样式的字面量用反引号包围。** Translatable strings 渲染为 markdown,因此用反引号包围代码样式的字面量(entity IDs、action 名称、文件名、状态值)。协议和品牌名称不加反引号;按其官方大小写书写(如 MQTT、Z-Wave、Zigbee)。
* **Integration name 不在 log messages 中。** Integration domain 已经是 logger name 的一部分——不要在消息文本中重复。
***
## 3. 新功能 PR(`new-feature`)
向现有集成添加功能。
### 3.1 范围与兼容性
* **范围适当。** feature 应该是单一、连贯的添加。一次添加三个新的 entity platforms 太多了——拆分开。
* **与现有集成风格一致。** 新代码应遵循集成中已建立的约定(命名、结构、模式)。
* **维持 quality scale 级别。** 如果集成有 quality scale 级别,新代码不得使其回退。检查新 entity 是否遵循所有适用规则。
* **新字符串遵循 translation 规则。** 与新集成相同的规则:`strings.json`、sentence case、common keys。
* **Breaking changes 已记录。** 如果 feature 更改了现有行为(重命名 entities、更改状态值格式、修改 service schemas),PR 描述必须包含为最终用户编写的 "Breaking change" 部分。什么算 "breaking" 需要理解用户可见的契约:这是 AI reviewer 持续搞错的人类判断。
### 3.2 Config entry lifecycle
* **Schema 更改时进行 config entry migration。** 如果 feature 更改了存储在 config entry 中的内容,必须有 migration:在 config flow 类中 bump `VERSION`,实现 `async_migrate_entry`,并添加涵盖从旧格式到新格式的迁移的测试。
* **VERSION bump 与 schema 更改匹配。** 如果 config entry 数据字段被添加、删除或重组,且 config flow `VERSION` 没有被 bump,标记它。反之,如果 `VERSION` 被 bump,则应有相应的迁移代码。
* **`async_unload_entry` 清理所有资源。** 当 config entry 被卸载时,所有 event listeners、polling tasks 和 connections 都应被清理。缺少清理会导致资源泄漏和 stale 状态。
* **设置失败后不访问 `runtime_data`。** 如果 `async_setup_entry` 中途失败,错误处理程序中的代码不应尝试访问 `entry.runtime_data`:它可能尚未被设置。
### 3.3 发现与设备注册
* **需要时更新 Discovery patterns。** 如果 feature 添加了对新设备型号的支持,检查 `manifest.json` discovery entries(Zeroconf、DHCP、SSDP)是否需要扩展。
* **Device info 完整。** 新 entity 应包含完整的 `DeviceInfo`,在可用时包含 manufacturer、model、serial number 和 firmware version。
***
## 4. Bugfix PRs (`bugfix`)
Bugfix 虽然概念上比其他更改更简单,但生成的审查评论却很多——因为审查者会仔细审视修复是否真正正确且完整。
* **存在关联的 issue。** Bugfix PR 应引用它们修复的 issue(`fixes #XXXX`)。如果没有 issue,询问原因:应确认 bug 确实存在。
* **修复最小化。** 更改应只修复报告的 bug,不做其他改动。没有顺手重构,没有"顺便修一下"的改进。
* **根本原因已解决。** 修复应解决实际原因,而不是掩盖症状。如果 `TypeError` 崩溃是因为 `value` 为 `None`,修复应在适当层级处理 `None`,而不仅仅是在 `try/except` 中包装所有内容。
* **包含回归测试。** 最好有一个本应捕获此 bug 的测试。该测试在没有修复时应失败,有修复时应通过。并非所有集成都需要测试,但具有测试的现有集成应始终维持测试覆盖。
* **无无关的测试更改。** 测试重构(snapshot 更新、parametrize 重写)不应混入 bugfix PR 中。
### 4.1 Bugfix 中的错误处理
* **`HomeAssistantError` 从 service/action handlers 传播。** 当 service handler 捕获来自底层 library 的异常时,应重新抛出为 `HomeAssistantError`(或像 `ServiceValidationError` 这样的子类),以便错误通过 UI 到达用户。在 service handler 中静默吞没异常是一种常见的 bug 模式。
* **错误具体性。** 添加错误处理的 bugfix 应捕获特定的异常类型,而不是宽泛的 `except Exception`。请参阅第 10 节了解详情。
* **认证相关的 bugfix 使用 `ConfigEntryAuthFailed`。** 如果 bugfix 处理 coordinator 中的认证问题,请验证它抛出 `ConfigEntryAuthFailed`(而不是 `UpdateFailed`)以触发 reauth flow。
***
## 5. 代码质量 PR(`code-quality`)
通常由 quality-scale 改进驱动。
* **改进是真实的。** 更改应使代码可测量地更好(更易读、更快、更易维护),而不仅仅是不同。
* **无行为变更。** Code quality PR 不应更改功能。如果更改了,应以不同方式标记。
* **测试已更新以匹配。** 如果代码结构更改,测试应相应更新——但测试重构应与代码更改匹配,不应超出。
* **Breaking changes 已注明。** 即使 code-quality PR 也可能是 breaking 的(例如,删除已弃用的常量)。如适用,必须填写 "Breaking change" 部分。
* **已验证 quality scale 声明。** 验证所有声称的规则确实满足:quality-scale-rule-verifier agent 可以自动化此操作。
***
## 6. 依赖更新 PR(`dependency`)
与 new-integration PR 相比,Dependency PR 生成的审查评论较少,但出现的模式很具体,更大的担忧是在更新依赖时的安全风险。
### 6.1 范围与结构
* **仅更新依赖。** PR 应触及 `manifest.json`(version bump)以及跟踪依赖的 requirements 文件(如重新生成的 `requirements_all.txt`),以及新版本所需的最少代码更改。仅此而已。
* **无顺手重构。** 如果贡献者还重构了测试、清理了 imports 或做了样式更改——要求他们将其拆分为单独的 PR。
* **`quality_scale` 未被意外移除。** 检查 `manifest.json` 是否丢失了 `quality_scale` 或 `loggers` 字段:这在依赖更新中出人意料地经常发生。
### 6.2 上游更改审查
* **Changelog/release notes 已链接。** PR 描述应包含指向 library changelog 或版本间 diff 的链接,以便审查者能看到上游更改了什么。
* **Breaking library 更改已处理。** 如果上游 library 做了 breaking changes,验证集成代码正确适应。
* **已审查 Upstream diff 的安全隐患。** 检查 library 在旧版本和新版本之间实际更改了什么。查找:新的网络调用、新的 `requests`/`aiohttp`/`socket`/`subprocess`/`os.system` imports 或调用、新的环境变量访问、预期路径之外的新文件 I/O。
* **无新的传递依赖。** 如果 library 版本添加了新的传递依赖,应明确提及。新的传递依赖是最常见的供应链攻击向量。
* **PyPI maintainer 未更改。** 如果 library 的 PyPI maintainer 在版本之间发生了变化,这是一个重要的信任事件(可能的账户接管)。标记以仔细检查。
* **Source repo 未移动或未归档。** 验证 library 仍然具有相同的公共 source repository。版本之间的 repo 转移、归档或删除是危险信号。
### 6.3 许可证合规
* **版本之间 license 未更改。** 版本之间的 license 更改(如 MIT → GPL-3.0)将是关键问题。比较 PyPI 上两个版本的 `license` 字段。
* **License 仍与 Apache-2.0 兼容。** 与新集成相同的规则:GPL-2.0/3.0/AGPL 与 HA Core 的 Apache-2.0 license 不兼容。
* **License identifier 仍与 LICENSE 文件匹配。** 验证声明的 license identifier 与实际 LICENSE 文本匹配:每次更新都应重新验证,而不仅仅是首次引入时。
***
## 7. 测试审查(所有 PR 类型)
### 7.1 结构
* **测试在正确位置。** Integration tests 应放在 `tests/components//` 中。
* **所有测试函数参数都有类型注释。** 使用具体类型(`HomeAssistant`、`MockConfigEntry`),不要使用 `Any`。
* **测试练习 HA 的公共接口。** Integration 行为测试应通过 `async_setup_component()` 或 `hass.config_entries.async_setup()` 设置集成,通过 `hass.states` 断言状态,并通过 `hass.services` 触发 actions。它们不应直接实例化集成类或调用内部方法。针对隔离的 helpers 和 utilities 或纯逻辑的聚焦 unit tests 在该边界确实是预期边界时可以直接调用这些方法。
### 7.2 模式
* **实体 platform 使用 Snapshot testing。** Platform 测试(sensor、switch 等)应使用 `snapshot_platform` 验证 entity 状态,而不是手动断言各个属性。
* **`@pytest.mark.parametrize` 用于重复案例。** 如果多个测试函数使用不同输入测试相同行为,应将它们参数化为一个函数。但不要过度参数化:一个场景的单个测试不需要 parametrize。
* **时间测试使用 `freezer`。** 涉及时间的测试必须使用 `freezer` fixture——绝不能直接 mock `datetime`、`time` 或 `utcnow`。
* **不要在 `sys.modules` 上使用 `mock.patch`。** 这是脆弱的且会破坏其他测试。使用适当的 mocking patterns。
* **Config flow 测试有完整覆盖。** config flow 中的每个分支都应被测试:happy path、错误处理、用户中止、每个 step。测试 step 错误的测试应继续 flow 以创建 entry 结果以显示错误恢复。早期审查 config flow 测试通常是快速发现其余代码中 bug 的好方法。
* **错误/恢复场景已测试。** 测试应覆盖设备/服务不可用、返回错误或需要重新认证时会发生什么。这是最常被请求的缺失测试类型。
* **测试脆弱性最小化。** 避免硬编码依赖上游数据的值(如特定节假日)。使用 mocking 或选择稳定的测试数据,这样依赖更新时不会更改。
### 7.3 Mocking
* **Mock library,而不是 HA 内部。** Mocks 应针对第三方 library 的 API 调用,而不是 Home Assistant 的内部机制。例如,mock 集成调用的 client methods(或在 `conftest.py` 中 patch client class),而不是 patch `DataUpdateCoordinator`、`hass` helpers 或 entity 内部。Mock HA 内部会使测试变脆弱并停止测试真实的集成 wiring。
* **Fixtures 在 `conftest.py` 中。** 共享测试 fixtures(mock client、config entry 设置)应放在集成的 `conftest.py` 中,而不是在测试文件中重复。
***
## 8. 实体模式(所有 PR 类型)
Entity 相关的反馈占审查者注意力的主导地位。本节适用于添加或修改 entity 的所有 PR。
### 8.1 `EntityCategory` 分类
贡献者经常错误分类 entity。规则:
**决策树:**
1. 用户是否希望此 entity 出现在主 dashboard 上?→ **无 category**(默认)
2. 它是否是控制集成行为的设置?(模式、阈值、开关)→ **`EntityCategory.CONFIG`**
3. 它是否是 debug/diagnostic 信息?(固件版本、信号强度、rate limits、IP 地址、MAC 地址)或者是设备类为 IDENTIFY 的特定 button entity → **`EntityCategory.DIAGNOSTIC`**
标记为 `CONFIG` 或 `DIAGNOSTIC` 的 entity 会从默认 dashboards 中隐藏并排除在 voice assistants 之外。
* **Diagnostic entities 标记为 `EntityCategory.DIAGNOSTIC`。** 用于调试的 entity——信号强度、rate limits、固件版本、连接统计、错误计数——必须是 `EntityCategory.DIAGNOSTIC`。
* **Config entities 标记为 `EntityCategory.CONFIG`。** 重置按钮、模式选择器、配置开关、阈值设置——任何控制集成或设备如何运行的都必须是 `EntityCategory.CONFIG`。
* **Debug/diagnostic entities 默认禁用。** 除了 `EntityCategory.DIAGNOSTIC` 外,纯粹用于调试的 entity 应设置 `entity_registry_enabled_default=False`。
* **主要功能 entity 无 category。** 用户交互的主要 entity(thermostat 温度、switch 状态、light 亮度)应**没有** `EntityCategory`。不要过度分类。
### 8.2 可用性与 `None` 处理
这是审查者需要理解的最重要模式之一——也是 AI 最难正确处理的一个。
**核心区别:**
* **`native_value` 返回 `None`** = entity 可用,但当前值未知 → entity 在 UI 中显示 "unknown"
* **`_attr_available = False`** = 设备/服务不可达 → entity 在 UI 中显示 "unavailable"
这是非常不同的用户体验。搞错意味着要么显示错误的数据,要么隐藏真实的故障。
* **`None` 不被类型转换隐藏。** `bool(None)` 返回 `False`,这会隐藏数据缺失的事实。`int(None)` 抛出 `TypeError`。当 API 的值可能为 `None` 时,entity property 应返回 `None`(或 `bool | None`),而不是将其转换为具体值。
* **不可用信号正确。** 当设备无响应或 API 失败时,entity 应变得不可用(`_attr_available = False`),而不是显示 stale 数据。检查 coordinator 错误处理:失败的更新是否将 entity 标记为不可用?
* **`None` 返回是有意为之,而非偶然。** 如果 `native_value` 可以返回 `None`,请验证这是预期行为。有时 `None` 意味着 "API 未包含此字段",此时 entity 应该是不可用的。
### 8.3 额外状态属性与独立实体
HA 项目正在积极迁移 away 从 `extra_state_attributes`。审查者一致推动将 attributes 拆分为单独的 entity。
* **Extra state attributes 是有理由的。** 如果 PR 使用 `extra_state_attributes`,询问每个属性是否应该成为自己的 entity。问题是:"此数据是否独立有用?用户是否希望专门基于它进行自动化?"如果为是,它应该是单独的 entity。
* **不再添加新的 `extra_state_attributes`。** 在新代码中,优先使用单独的 entity 而不是 extra attributes。唯一的例外是真正仅在 parent entity 上下文中才有意义的辅助数据。
### 8.4 乐观状态更新
* **Optimistic 更新是合适的。** 当 entity 发送命令(打开 switch、设置温度)时,它应该立即更新其状态(optimistic)还是等待下一次 poll/push 确认?这取决于:
* 设备是否可靠地回传状态?
* poll cycle 有多快?
* 延迟的 UI 反馈是否可接受?
没有普遍正确的答案。具有快速 push 更新的本地设备通常不需要 optimistic 更新。轮询慢的 cloud-controlled 设备通常需要。
### 8.5 Device class & state class
* **适用的情况下设置 Device class 且正确。** Sensor、binary sensor 和其他 typed entities 在适用时应具有适当的 `device_class`。这决定了 UI 图标、单位处理和图表渲染。`device_class` 是可选的——当没有 class 匹配时省略它。
* **支持统计的数值 sensor 设置 State class。** 其值符合长期统计资格的数值 sensor 应设置 `state_class`(`MEASUREMENT`、`TOTAL`、`TOTAL_INCREASING`)。某些 device classes(如 `date`、`timestamp`、`enum`)不符合资格,不应设置 `state_class`。
***
## 9. 代码风格与模式(所有 PR 类型)
### 9.1 架构
* **常量在 `const.py` 中,从那里导入。** 在多个模块之间共享的集成常量(`CONF_*`、`DOMAIN`、entity keys)应放在 `const.py` 中并从那里导入——而不是从 `__init__.py` 或其他 re-exporting 模块导入。如果常量只在单个模块中使用,它应保留在那里。应该是常量 magic strings 应被提取出来。
* **提前返回 / guard clauses。** 优先使用 `if not condition: return` 而不是将整个函数体包裹在 `if condition:` 中。避免深层嵌套。
* **验证后直接使用 dict 访问。** 当验证保证 key 存在时,使用 `data["key"]` 而不是 `data.get("key")`。契约违规应被暴露,而不是静默掩盖。
* **不在 service actions 上添加不必要的防御性检查。** HA 的 service/action schemas 已经验证输入。不要为 schema 验证过的字段添加冗余检查。
* **集成目录中没有 `.gitignore` 文件。** 这些偶尔会从贡献者的本地仓库中混入。
* **使用 dataclasses 处理结构化数据。** 当传递多个相关值时,使用 dataclass 而不是 tuple、dict 或单独的变量。
### 9.2 错误处理
* **Service/action handlers 中使用 `HomeAssistantError`。** 捕获 library 异常的 service handler 必须重新抛出为 `HomeAssistantError`(或 `ServiceValidationError`),以便错误通过 UI 传播给用户。静默吞没异常意味着用户在 action 失败时看不到任何反馈。
* **Narrow exception handling。** `except Exception:` 捕获包括编程错误在内的一切。只捕获预期的 connection/API 异常。
* **Narrow try blocks。** 只将可能抛出你要捕获的异常的语句保留在 `try` block 中。宽 try blocks 使调试更困难,因为可能捕获到错误的行。
* **`async_unload_entry` 完整。** 当 config entry 被卸载时,所有 event listeners、timers 和 connections 必须被清理。检查在 `async_setup_entry` 中创建的一切是否有相应的 teardown。
* **关机时关闭连接。** 当 Home Assistant 停止时,连接应该被关闭。监听 `EVENT_HOMEASSISTANT_STOP` 事件来调度清理。Home Assistant 进程终止时内存会自动释放。
### 9.3 字符串与日志
* **Log messages 中没有 integration domain。** Logger 已经包含 domain。
* **所有面向用户的字符串使用 sentence case。** 包括 entity names、config flow labels、错误消息。
* **正确的 log levels。** 用户应采取操作的事项用 `_LOGGER.error`,降级但仍能功能运行的状态用 `_LOGGER.warning`,开发者 diagnostics 用 `_LOGGER.debug`。不要用 `error` 处理预期条件,如 "device offline"。
### 9.4 属性与状态
* **`@property` vs attributes 用于值。** 考虑 property 还是 instance attribute 是正确的选择。如果值每次访问时都需要从其他数据计算,使用 `@property` 而不是将其存储为 instance variable。但检查值是否计算昂贵——如果是,考虑缓存它。如果不需要计算,或者我们可以用单个方法计算并且计算的需求是由方法驱动的而不是由我们访问值驱动的,则使用 instance attribute。大多数 `Entity` 状态属性都有 `_attr_` 前缀的 attributes,推荐直接实现 property。不要在同一位置存储相同的值。
* **Entity `native_value` 返回正确的类型。** 当值真正未知时返回 `None`。不要返回掩盖未知状态的默认值(0、`False`、`""`)。请参阅第 8.2 节。
***
## 10. 常见陷阱与令人困惑的 API
这些模式代表了贡献者(和自动化审查者)持续出错的地方。
### 10.1 类型转换陷阱
Python 的 truthiness 规则是 HA 集成中常见的 bug 来源:
| Expression | Result | Problem |
| ---------- | ------ | ------- |
| `bool(None)` | `False` | 隐藏缺失数据:entity 显示 "off" 而不是 "unknown/unavailable" |
| `bool("0")` | `True` | 非空字符串是 truthy:entity 在值为 "0" 时显示 "on" |
| `bool(0)` | `False` | 正确,但如果 API 更改为返回 `"0"` 则脆弱 |
| `int(None)` | `TypeError` | 崩溃而不是返回 `None` |
| `float("nan")` | `nan` | `nan != nan` 为 `True`,静默破坏比较 |
* **不在 value 可能为 `None` 的地方使用 `bool(value)`。** 使用显式比较:`value is not None`、`value == "1"`,或返回 `bool | None` 并让 entity framework 处理它。
* **不在 value 可能为 `None` 的地方使用 `int(value)` 或 `float(value)`。** 改为返回 `None` 或在转换前检查。
* **String-to-bool 转换是显式的。** 如果 API 返回 `"0"`/`"1"` 或 `"true"`/`"false"`,显式转换(`value == "1"`)而不是依赖 Python truthiness。
### 10.2 生产代码中的 `assert`
Python 的 `assert` 语句在 Python 使用 `-O`(optimize)运行时被剥离。对运行时验证使用 `if not condition: raise ValueError(...)` 或类似方式。`assert` 仅用于测试或类型提示。
* **测试文件中的 `assert`。** 测试使用 `assert` 语句验证测试在那一时刻的状态。
* **用于类型提示的 `assert`。** 有时可能需要 `assert` 语句向类型检查器解释类型。只有当调整类型注释以取得相同结果很繁琐或不可能时才使用它。始终将此类语句包裹在 `if TYPE_CHECKING:` 条件中。
### 10.3 协调器生命周期混淆
`DataUpdateCoordinator` 有两个看起来相似但用途不同的方法:
| Method | 何时运行 | 用途 |
| ------ | ------------ | ------- |
| `_async_setup` | **一次**,在 coordinator 初始化时 | 认证、连接设置、获取设备信息、初始状态 |
| `_async_update_data` | **每次 poll cycle**(例如,每 30 秒) | 从设备/API 获取当前状态/数据 |
将设置逻辑放入 `_async_update_data` 意味着它会在每次 poll 时运行——浪费资源并可能导致 auth token 刷新或连接重新建立。
* **一次性设置在 `_async_setup` 中。** 如果你在 `_async_update_data` 中看到连接建立、auth token 获取或设备信息获取,应将其移动到 `_async_setup`。
### 10.4 `ConfigEntryAuthFailed` vs `UpdateFailed`
coordinator 中最常被混淆的错误类型:
| Exception | 效果 | 何时使用 |
|-----------|--------|-------------|
| `ConfigEntryAuthFailed` | 触发 **reauth flow**:用户被提示重新认证 | API 返回 401/403、token 过期、凭据无效 |
| `UpdateFailed` | 记录错误,**下次 poll 时重试** | 临时网络故障、API 超时、rate limiting |
对 auth 错误使用 `UpdateFailed` 意味着集成会永远用错误的凭据重试,而不是提示用户修复它们。
* **Auth 错误抛出 `ConfigEntryAuthFailed`。** 在 `_async_update_data` 中,任何捕获认证相关异常的 `except` block(401、403、`InvalidToken`、`AuthenticationError`)应抛出 `ConfigEntryAuthFailed`,而不是 `UpdateFailed`。要允许用户重新认证,集成还需要实现 `async_step_reauth` 方法。
### 10.5 `HomeAssistantError` propagation
集成中的 service/action handlers 必须将错误传播给用户。如果 service 调用(`turn_on`、`set_temperature`、custom actions)捕获了 library 异常,应重新抛出为 `HomeAssistantError` 以便错误出现在 UI 中。
```python
# 错误 — 用户看不到任何反馈
async def async_turn_on(self, **kwargs):
try:
await self.device.turn_on()
except DeviceError:
_LOGGER.error("Failed to turn on") # 仅在日志中,用户看不到
# 正确 — 错误到达用户
async def async_turn_on(self, **kwargs):
try:
await self.device.turn_on()
except DeviceError as err:
raise HomeAssistantError(f"Failed to turn on: {err}") from err
```
* **Service handlers 传播 `HomeAssistantError`。** 检查 service/action handlers 是否没有静默吞没异常。错误应到达用户。确保避免重复错误日志记录。传递给抛出的 `HomeAssistantError` 的参数应包含错误消息,该消息默认会被记录。不要单独记录此错误消息。
***
## 11. Git 与 PR 卫生
* **Commits 讲述故事。** 每个 commit 应是一个逻辑单元。审查者应能跟踪进展。
* **打开后不 amend/squash/rebase。** 根据 repo 政策:"不要 amend、squash 或 rebase 已经在 PR review 开始后推送到 PR 分支的 commits。" 从 `dev` 拉取最新更改的 merge commits 没问题。
* **从 base branch unclean merge 或 rebase 后没有无关 commits。** 如果 PR 包含来自 `dev` 的无关 commits,git history 已损坏。要求作者创建干净的分支并打开新的 PR。
* **没有注释掉的代码。** Dead code 应被删除,而不是注释。
***
## 12. 审查者工作流程
审查 PR 的建议顺序:
1. **Gate checks**(第 1 节):如果 CLA 缺失、CI 是红的或 PR 显示高风险 triage 信号,在此停止。
2. **Scope 检查**(第 2.1 节 / 特定类型章节):此 PR 是否在做一件事?
3. **Manifest & 元数据**(第 2.2-2.3 节):快速结构检查。
4. **Config flow 测试**(第 7.2 节):早期审查 config flow 测试通常能很快暴露其余代码中的问题。
5. **Entity patterns**(第 8 节):Entity categories 正确?Availability 处理得当?无 type casting traps?
6. **剩余测试审查**(第 7 节):测试是否真的测试了重要内容?使用了 snapshot?覆盖了错误场景?
7. **代码审查**(第 9-10 节):架构、模式、风格、常见陷阱。
8. **Strings & translations**(第 2.8 节):快速浏览 `strings.json`。
9. **整体评估**:批准、请求更改或评论。
**请求更改时:** 具体说明需要更改什么及原因。尽可能链接到文档。如果 PR 需要重大返工,明确说明,而不是留下许多加起来等于"推倒重来"的 inline comments。
***
*此指南基于对 home-assistant/core 中 1,100+ PR 和 8,200+ 审查评论的分析,以及官方开发者文档和仓库约定。数据收集于 2026 年 4 月。在 AI 生成后经过人工策展。*
---
url: /developers/creating_component_code_review.md
---
添加新组件时要做的事情清单。
:::info
并非所有现有代码都遵循本清单中的要求。这不能作为不遵循要求的理由!
:::
### 0. Common
1. 遵循我们的[风格指南](/developers/development_guidelines.md)
2. 使用 [`const.py`](https://github.com/home-assistant/core/blob/dev/homeassistant/const.py) 中现有的常量
* 只有当常量被广泛使用时才添加到 `const.py` 中。否则,请在组件级别保留它们
### 1. External requirements
1. 依赖项已添加到 [`manifest.json`](/developers/creating_integration_manifest.md)。`REQUIREMENTS` 常量已弃用。
2. 依赖项版本必须固定:`"requirements": ['phue==0.8.1']`
3. 每个依赖项都满足[库要求](/developers/api_lib_index.md#basic-library-requirements)。
### 2. Configuration
1. 有用于[配置验证](/developers/development_validation.md) 的 Voluptuous schema
2. 默认参数在 voluptuous schema 中指定,而不是在 `setup(…)` 中
3. 尽可能多地使用来自 `homeassistant.const` 的通用配置键
4. 如果你的组件有平台,请定义 `PLATFORM_SCHEMA` 而不是 `CONFIG_SCHEMA`。
5. 如果使用与 `EntityComponent` 配合的 `PLATFORM_SCHEMA`,请从 `homeassistant.helpers.config_validation` 导入基础 schema
6. 永远不要依赖用户在 `customize` 中添加内容来配置你的组件内部行为。
### 3. Component/platform communication
1. 可以通过 `hass.data[DOMAIN]` 与你的平台共享数据。
2. 如果组件获取的数据导致其相关平台实体更新,可以使用 `homeassistant.helpers.dispatcher` 中的 dispatcher 代码来通知它们。
### 4. Communication with devices/services
1. 所有与 API 相关的代码必须作为 PyPi 上托管的第三方库的一部分。Home Assistant 应该只与对象交互,而不直接调用 API。
```python
# bad
status = requests.get(url("/status"))
# good
from phue import Bridge
bridge = Bridge(...)
status = bridge.status()
```
[关于发布你自己的 PyPI 包的教程](https://towardsdatascience.com/how-to-open-source-your-first-python-package-e717444e1da0)
其他关于发布 Python 包的值得注意的资源:
[Cookiecutter Project](https://cookiecutter.readthedocs.io/)
[flit](https://flit.readthedocs.io/)
[Poetry](https://python-poetry.org/)
### 5. Make your pull request as small as possible
将新集成保持在能让人从中获得价值的最小功能范围。这允许审查者一次一批地签署较小代码块,让我们能更早地获得你的新集成/功能。**包含大量代码倾倒的 pull request 不会成为审查的优先级,可能会被关闭。**
* 限制为单个平台
* 不要添加不支持该单个平台所需的功能(如自定义 service actions)
* 不要在单个 pull request 中混合清理工作与新功能
* 不要在单个 pull request 中解决多个问题
* 不要提交依赖于其他尚未合并工作的 pull request
在"现代化"某个长时间未触及的集成时,可能会倾向于打开一个大型 PR,以利用所有最新可用功能。正确的方法是尽可能将功能拆分为独立的功能性更改,并按顺序提交 PR。
处理顺序 PR 的一个策略是,在 `current` PR 的分支上为 `next` PR 创建一个分支,然后可以开始编写代码。如果你已经将 PR 拆分使得一个依赖于前一个,这种策略是有优势的,因为你是在 PR 合并后会在 `dev` 中的代码基础上工作。如果你因为更改/审查反馈而向 `current` PR 添加额外的提交,你可以对 `next` PR 的分支执行 rebase,并更容易地整合任何 merge conflicts。一旦你的 `current` PR 被合并,在 `next` PR 分支中 squash 来自 `current` PR 分支的提交,然后在 `dev` 上 rebase。然后就可以提交 `next` PR 分支进行审查,并根据需要重复此过程。
### 6. Event names
使用 domain 名称作为组件事件名称的前缀。例如,对于 `netatmo` 组件,使用 `netatmo_person` 而不是 `person`。请注意我们在 [Data Science portal](https://data.home-assistant.io/docs/events/#database-table) 上记录的数据结构。
### 7. Tests
强烈考虑为你的组件添加测试,以尽量减少未来的回归问题。
---
url: /developers/creating_component_generic_discovery.md
---
大多数集成只包含单个平台。在这种情况下,只需定义该平台即可。但是,如果你要添加第二个平台,就需要将连接逻辑集中管理。这需要在组件(`__init__.py`)内部完成。
如果你的集成可以通过 `configuration.yaml` 配置,那么你的配置入口点将会发生变化,因为用户现在需要直接设置你的集成,而平台的设置则由你的集成负责。
## 通过 config entry 配置时加载平台
如果你的集成通过 config entry 设置,你需要将 config entry 转发给相应的集成以设置你的平台。更多信息,请参阅 [config entry 文档](/developers/config_entries_index.md#for-platforms)。
## 通过 configuration.yaml 配置时加载平台
如果你的集成没有使用 config entries,则需要使用我们的 discovery helpers 来设置平台。注意,这种方式不支持卸载。
为此,你需要使用 discovery helper 中的 `load_platform` 和 `async_load_platform` 方法。
* 参见一个[实现此逻辑的完整示例](https://github.com/home-assistant/example-custom-config/tree/master/custom_components/example_load_platform/)
---
url: /developers/creating_component_index.md
---
好了,是时候为你的集成编写第一段代码了。太棒了!不用担心,我们已经尽力让它尽可能简单。在 Home Assistant 开发环境中,输入以下命令并按照提示操作:
```shell
python3 -m script.scaffold integration
```
这将为你准备好通过用户界面设置集成所需的一切。更多的集成示例可从[我们的示例仓库](https://github.com/home-assistant/example-custom-config/tree/master/custom_components/)中获得。
:::tip
这个示例仓库展示了存放在 `/custom_components` 目录中的自定义集成。它们必须在[清单文件](/developers/creating_integration_manifest.md#version)中包含 `version` 键。核心集成存放在 `homeassistant/components` 目录中,不需要 `version` 键。两种情况下的架构是相同的。
:::
## 最低要求
scaffold 集成比最低要求略多一些。最低要求是定义一个包含集成 domain 的 `DOMAIN` 常量。第二个部分是它需要定义一个 setup 方法,如果设置成功则返回一个布尔值。
根据你的需求,创建文件 `homeassistant/components/hello_state/__init__.py` 并使用以下两个代码块之一:
* 同步 component:
```python
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType
DOMAIN = "hello_state"
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
hass.states.set("hello_state.world", "Paulus")
# 返回布尔值表示初始化成功。
return True
```
* 如果你更喜欢异步 component:
```python
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType
DOMAIN = "hello_state"
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
hass.states.async_set("hello_state.world", "Paulus")
# 返回布尔值表示初始化成功。
return True
```
此外,还需要一个 manifest 文件,最低要求包含以下键。创建 `homeassistant/components/hello_state/manifest.json`。
```json
{
"domain": "hello_state",
"name": "Hello, state!"
}
```
要加载它,在 `configuration.yaml` 文件中添加 `hello_state:`。
## scaffold 提供的功能
使用 scaffold 脚本时,它将超过集成的最低要求。它将包含 config flow、config flow 的测试以及基本的翻译基础设施,为你的 config flow 提供国际化支持。
---
url: /developers/creating_integration_brand.md
---
一个商业品牌可能拥有多个集成,分别提供对该品牌下不同产品的支持。此外,一个品牌也可能提供符合某个 IoT 标准(例如 Zigbee 或 Z-Wave)的设备。
第一种情况的示例是,有多个集成分别提供对不同 Google 产品的支持,例如 `google` 集成提供的 Google Calendar,以及 `google_sheets` 集成提供的 Google Sheets。
第二种情况的示例是,Innovelli 提供 Zigbee 和 Z-Wave 设备,并不需要它自己的集成。
为了让用户更容易找到这些集成,应该将它们收集到 `homeassistant/brands` 文件夹中的一个文件中。
示例:
```json
{
"domain": "google",
"name": "Google",
"integrations": ["google", "google_sheets"]
}
```
```json
{
"domain": "inovelli",
"name": "Inovelli",
"iot_standards": ["zigbee", "zwave"]
}
```
或者一个可以直接复制到你的项目中的最小示例:
```json
{
"domain": "your_brand_domain",
"name": "Your Brand",
"integrations": [],
"iot_standards": []
}
```
## 域名
domain 是由字符和下划线组成的短名称。该 domain 必须唯一,且无法更改。Google 品牌的 domain 示例:`google`。domain 键必须与该品牌文件所在的文件名相匹配。如果存在一个与品牌同名的集成,它必须列在该品牌的 `integrations` 中。
## 名称
品牌的名称。
## 集成
实现该品牌产品的集成 domain 列表。
## IoT 标准
该品牌的设备所支持的 IoT 标准列表。可能的值是 `homekit`、`matter`、`zigbee` 和 `zwave`。请注意,某个特定设备可能不支持列出的任何 IoT 标准。
## 品牌图片
品牌图片(图标和徽标)存储于 [brands 仓库](https://github.com/home-assistant/brands)。有关品牌图片及其提供方式的更多详情,请参阅 [品牌图片](/developers/core/integration/brand_images.md)。
---
url: /developers/creating_integration_file_structure.md
---
每个集成都存储在一个以其集成 domain 命名的目录中。domain 是由字符和下划线组成的短名称。该 domain 必须唯一,且无法更改。mobile app 集成的 domain 示例:`mobile_app`。因此,该集成的所有文件都在文件夹 `mobile_app/` 中。
该文件夹的最基本内容如下:
* `manifest.json`:manifest 文件描述该集成及其依赖。[更多信息](/developers/creating_integration_manifest.md)
* `__init__.py`:component 文件。如果该集成只提供了一个 platform,可以将此文件限制为一段介绍该集成的 docstring,例如 `"""The Mobile App integration."""`。
## 集成设备 - `light.py`、`switch.py` 等
如果你的集成需要集成一个或多个设备,你需要通过创建一个与实体集成交互的 platform 来实现。例如,如果你想在 Home Assistant 中表示一个灯光设备,就需要创建 `light.py`,其中将包含用于 light 集成的 light platform。
* 更多有关[可用实体集成](/developers/core/entity.md)的信息。
* 更多有关[创建 platform](/developers/creating_platform_index.md)的信息。
## 集成服务动作 - `services.yaml`
如果你的集成需要注册服务动作(service action),它需要提供可用动作的描述。该描述存储在 `services.yaml` 中。[更多有关 `services.yaml` 的信息。](/developers/dev_101_services.md)
## Data update coordinator - `coordinator.py`
你的集成可以通过多种途径接收数据,包括 push 或 poll。通常,集成会通过一次跨所有实体的协调 poll 来获取数据,这需要用到 `DataUpdateCoordinator`。
如果你打算使用它,并且选择创建一个它的子类,建议在 `coordinator.py` 中定义该 coordinator 类。[更多有关 `DataUpdateCoordinator` 的信息](/developers/integration_fetching_data.md#coordinated-single-api-poll-for-data-for-all-entities)。
## Home Assistant 在哪里查找集成
当 Home Assistant 在配置文件中看到对某个 domain 的引用(即 `mobile_app:`),或者它是另一个集成的依赖时,就会查找该集成。Home Assistant 会查看以下位置:
* `/custom_components/`
* `homeassistant/components/`(内置集成)
你可以在 `/custom_components` 文件夹中放置一个与内置集合同名 domain 的集成,从而覆盖内置集成。[当覆盖核心集成时,`manifest.json` 文件要求包含一个 version 标签](/developers/creating_integration_manifest.md#version)。一个被覆盖的核心集成可以通过概览中集成框右上角的特定图标来识别 [](https://my.home-assistant.io/redirect/integrations/)
注意,不建议覆盖内置集成,因为你不会再收到更新。建议使用一个唯一的名称。
## 品牌图片 - `brand/`
自定义集成可以通过在集成目录中添加 `brand/` 目录来包含其自己的品牌图片。
有关品牌图片及其提供方式的更多信息,请参阅 [品牌图片](/developers/core/integration/brand_images.md)。
---
url: /developers/creating_integration_manifest.md
---
每个集成都有一个 manifest 文件,用于指定其基本信息。该文件以 `manifest.json` 的形式存储在你的集成目录中。添加该文件是必需的。
```json
{
"domain": "hue",
"name": "Philips Hue",
"after_dependencies": ["http"],
"codeowners": ["@balloob"],
"dependencies": ["mqtt"],
"documentation": "https://www.home-assistant.io/components/hue",
"integration_type": "hub",
"iot_class": "local_polling",
"issue_tracker": "https://github.com/balloob/hue/issues",
"loggers": ["aiohue"],
"requirements": ["aiohue==1.9.1"],
"quality_scale": "platinum"
}
```
或者一个可以直接复制到你的项目中的最小示例:
```json
{
"domain": "your_domain_name",
"name": "Your Integration",
"codeowners": [],
"dependencies": [],
"documentation": "https://www.example.com",
"integration_type": "hub",
"iot_class": "cloud_polling",
"requirements": []
}
```
## 域名
domain 是由字符和下划线组成的短名称。该 domain 必须唯一,且无法更改。mobile app 集成的 domain 示例:`mobile_app`。domain 键必须与该文件所在的目录相匹配。
## 名称
集成名称必须遵循以下规则:
* 如果某个产品或服务同时提供本地和云端集成,则云端版本应在名称后附加"Cloud"(例如,**LIFX Cloud**)。
* 本地版本(或同时支持本地和云端通信的版本)应直接使用不带后缀的产品或服务名称(例如,**LIFX**)。不要附加"Local"。
* 对本身即基于云端的产品或服务,其集成应直接使用原名称,不加任何后缀(例如,**iCloud** 而不是 **iCloud Cloud**)。
## 版本
对于核心集成,应省略此项。
自定义集成必须提供集成版本。版本必须是 [AwesomeVersion](https://github.com/ludeeus/awesomeversion) 认可的有效版本,例如 [CalVer](https://calver.org/) 或 [SemVer](https://semver.org/)。
## 集成类型
集成被分为多种类型。每个集成必须在它们的 manifest 中提供一个 `integration_type`,用于描述其主要用途。
:::warning
对于具有 config flow 的核心集成,设置 `integration_type` 是必需的。对于自定义集成和基于 YAML 的集成,如果未设置 `integration_type`,则默认值为 `hub`,但建议显式设置正确的类型。
:::
| 类型 | 描述
| ---- | -----------
| `device` | 提供单个设备,例如 ESPHome。 |
| `entity` | 提供基本的实体 platform,例如 sensor 或 light。通常不应使用。 |
| `hardware` | 提供硬件集成,例如 Raspberry Pi 或 Hardkernel。通常不应使用。 |
| `helper` | 提供实体来帮助用户进行自动化,例如 input boolean、derivative 或 group。 |
| `hub` | 提供 hub 集成,包含多个设备或服务,例如 Philips Hue。 |
| `service` | 提供单个服务,例如 DuckDNS 或 AdGuard。 |
| `system` | 提供系统集成,是预留的,通常不应使用。 |
| `virtual` | 本身并不是一个集成。它指向另一个集成或 IoT 标准。请参阅 [virtual integration](#virtual-integration) 部分。 |
:::info
`hub` 与 `service` 或 `device` 之间的区别由集成的性质决定。`hub` 提供通往多个其他设备或服务的网关。`service` 和 `device` 是每次 config entry 提供一个设备或服务的集成。
:::
## 文档
包含集成使用文档的网站。如果该集成正在提交以纳入 Home Assistant,则应该是 `https://www.home-assistant.io/integrations/`。
## 问题跟踪器
你集成的 issue tracker,用户在此报告问题。如果该集成正在提交以纳入 Home Assistant,则应省略此项。对于内置集成,Home Assistant 会自动生成正确的链接。
## 依赖项
依赖项是指其他 Home Assistant 集成,你需要 Home Assistant 在加载本集成之前成功设置它们。将某个集成添加到 dependencies 中,可以确保被依赖的集成先于本集成加载,但这并不能保证所有依赖的配置条目(config entry)都已设置完成。如果你希望提供来自其他集成的功能(例如 webhooks 或 MQTT 连接),添加依赖项可能是必要的。如果某个依赖项是可选的但不是关键性的,添加一个[后置依赖(after dependency)](#after-dependencies)可能是更好的选择。有关在 MQTT 方面的处理详情,请参阅 [MQTT 部分](#mqtt)。
内置集成在 `dependencies` 中只能指定其他内置集成。自定义集成可以在 `dependencies` 中同时指定内置集成和自定义集成。
## 后置依赖项
此选项用于指定集成可能会使用但不是必需的依赖项。当存在 `after_dependencies` 时,集成的设置会等待 `after_dependencies` 中列出的集成(通过 YAML 或 config entry 配置的)先完成设置,然后再设置本集成。它还会确保安装 `after_dependencies` 的 requirements,以便集成中的方法可以安全地导入,而不论 `after_dependencies` 中列出的集成是否已配置。例如,如果 `camera` 集成在某些配置中可能会使用 `stream` 集成,将 `stream` 添加到 `camera` 的 manifest 的 `after_dependencies` 中,就能确保如果 `stream` 已配置,它会在 `camera` 之前加载,并且 `stream` 的任何依赖都会被安装并可由 `camera` 导入。如果 `stream` 未配置,`camera` 仍然会正常加载。
内置集成在 `after_dependencies` 中只能指定其他内置集成。自定义集成可以在 `after_dependencies` 中同时指定内置集成和自定义集成。
## 代码所有者
负责该集成的 GitHub 用户名或团队名称。你应该至少在此添加自己的 GitHub 用户名,以及所有帮助编写已包含代码的人员。
## 配置流
如果你的集成具有 config flow 以创建 config entry,请指定 `config_flow` 键。当指定时,文件 `config_flow.py` 必须存在于你的集成中。
```json
{
"config_flow": true
}
```
### 仅支持单个 config entry
如果你的集成只支持一个 config entry,请指定 `single_config_entry` 键。当指定时,将不允许用户为该集成添加多于一个的 config entry。
```json
{
"single_config_entry": true
}
```
## 需求
requirements 是你在正常使用 `pip` 为你的 component 安装的 Python 库或模块。如果你没有使用 `venv`,Home Assistant 会尝试将 requirements 安装到 Home Assistant[配置目录](https://www.home-assistant.io/docs/configuration/)的 `deps` 子目录中;如果你运行在虚拟环境中,则会安装到类似 `path/to/venv/lib/python3.6/site-packages` 的位置。这将确保在启动时所有 requirements 都已就位。如果某些步骤失败,例如编译模块所需的包缺失或其他安装错误,该 component 将无法加载。
requirements 是一个字符串数组。每个条目都是一个与 `pip` 兼容的字符串。例如,media player 的 Cast platform 依赖于 Python 包 PyChromecast v3.2.0:`["pychromecast==3.2.0"]`。
### 开发和测试期间的自定义 requirements
在开发 component 期间,测试某个 requirement 的不同版本可能很有用。可以分两步完成,以 `pychromecast` 为例:
```shell
pip install pychromecast==3.2.0 --target ~/.homeassistant/deps
hass --skip-pip-packages pychromecast
```
这将使用指定的版本,并阻止 Home Assistant 尝试用 `requirements` 中指定的版本覆盖它。如果要阻止任何包被自动覆盖,而无需指定依赖,可以使用全局 `--skip-pip` 标志启动 Home Assistant。
如果需要修改某个 requirement 以支持你的 component,也可以使用 `pip install -e` 安装该 requirement 的开发版本:
```shell
git clone https://github.com/balloob/pychromecast.git
pip install -e ./pychromecast
hass --skip-pip-packages pychromecast
```
也可以使用公开的 git 仓库来安装 requirement。例如,在将 requirement 依赖的更改发布到 PyPI 之前,测试这些更改非常有用。语法如下:
```json
{
"requirements": ["@git+https://github.com//.git@"]
}
```
`` 可以是任意 git 引用:分支、标签、commit hash 等。请参阅 [PIP 有关 git 支持的文档](https://pip.pypa.io/en/stable/topics/vcs-support/#git)。
以下示例将直接从 GitHub 安装 `pycoolmaster` 库的 `except_connect` 分支:
```json
{
"requirements": ["pycoolmaster@git+https://github.com/issacg/pycoolmaster.git@except_connect"]
}
```
### 自定义集成的 requirements
自定义集成只应包含 Core [requirements.txt](https://github.com/home-assistant/core/blob/dev/requirements.txt) 中未包含的 requirements。
## 日志记录器
`loggers` 字段是一个名称列表,表示该集成的 requirements 在其 [getLogger](https://docs.python.org/3/library/logging.html?highlight=logging#logging.getLogger) 调用中使用的名称。
## 蓝牙
如果你的集成支持通过蓝牙发现,可以在你的 manifest 中添加一个 matcher。如果用户加载了 `bluetooth` 集成,那么在发现设备时,将加载你的集成的 config flow 的 `bluetooth` 步骤。我们支持通过匹配 `connectable`、`local_name`、`service_uuid`、`service_data_uuid`、`manufacturer_id` 和 `manufacturer_data_start` 来监听蓝牙发现。`manufacturer_data_start` 字段期望一个以 0-255 整数编码的字节列表。manifest 值是一个 matcher 字典列表。如果蓝牙数据中找到了任意指定 matcher 的所有项,你的集成就会被发现。去重则由你的 config flow 负责。
`local_name` 的匹配在前三个(3)字符中不得包含任何模式。
如果设备只需要 advertisement 数据,将 `connectable` 设置为 `false` 将选择加入接收来自那些不支持建立连接的蓝牙控制器的发现。
以下示例将匹配 Nespresso Prodigio 机器:
```json
{
"bluetooth": [
{
"local_name": "Prodigio_*"
}
]
}
```
以下示例将匹配用于 SwitchBot bot 和窗帘设备的 128 位 uuid 的 service data:
```json
{
"bluetooth": [
{
"service_uuid": "cba20d00-224d-11e6-9fb8-0002a5d5c51b"
}
]
}
```
如果你想匹配具有 16 位 uuid 的 service data,需要先将它转换为 128 位 uuid,方法是将 `00000000-0000-1000-8000-00805f9b34fb` 中的第 3 和第 4 个字节替换为 16 位 uuid。例如,对于 Switchbot sensor 设备,16 位 uuid 是 `0xfd3d`,对应的 128 位 uuid 变为 `0000fd3d-0000-1000-8000-00805f9b34fb`。因此,以下示例将匹配用于 SwitchBot sensor 设备的 16 位 uuid 的 service data:
```json
{
"bluetooth": [
{
"service_data_uuid": "0000fd3d-0000-1000-8000-00805f9b34fb"
}
]
}
```
以下示例将匹配 HomeKit 设备:
```json
{
"bluetooth": [
{
"manufacturer_id": 76,
"manufacturer_data_start": [6]
}
]
}
```
## Zeroconf
如果你的集成支持通过 [Zeroconf](https://en.wikipedia.org/wiki/Zero-configuration_networking) 发现,可以在你的 manifest 中添加类型。如果用户加载了 `zeroconf` 集成,那么在发现设备时,将加载你的集成的 config flow 的 `zeroconf` 步骤。
Zeroconf 是一个列表,因此你可以指定多个类型进行匹配。
```json
{
"zeroconf": ["_googlecast._tcp.local."]
}
```
某些 zeroconf 类型非常通用(例如 `_printer._tcp.local.`、`_axis-video._tcp.local.` 或 `_http._tcp.local`)。在这种情况下,你应该包含一个 Name(`name`)或 Properties(`properties`)过滤器:
```json
{
"zeroconf": [
{"type":"_axis-video._tcp.local.","properties":{"macaddress":"00408c*"}},
{"type":"_axis-video._tcp.local.","name":"example*"},
{"type":"_airplay._tcp.local.","properties":{"am":"audioaccessory*"}},
]
}
```
注意,`properties` 过滤器中的所有值必须是小写的,并且可以包含 fnmatch 类型的通配符。
## SSDP
如果你的集成支持通过 [SSDP](https://en.wikipedia.org/wiki/Simple_Service_Discovery_Protocol) 发现,可以在你的 manifest 中添加类型。如果用户加载了 `ssdp` 集成,那么在发现设备时,将加载你的集成的 config flow 的 `ssdp` 步骤。我们支持通过 SSDP 的 ST、USN、EXT 和 Server 头(头名称小写)以及 [UPnP 设备描述](https://openconnectivity.org/developer/specifications/upnp-resources/upnp/basic-device-v1-0/) 中的数据来进行 SSDP 发现。manifest 值是一个 matcher 字典列表,如果 SSDP/UPnP 数据中找到了任意指定 matcher 的所有项,你的集成就会被发现。去重则由你的 config flow 负责。
以下示例包含一个由三项组成的 matcher,必须全部匹配才能通过此配置进行发现。
```json
{
"ssdp": [
{
"st": "roku:ecp",
"manufacturer": "Roku",
"deviceType": "urn:roku-com:device:player:1-0"
}
]
}
```
## HomeKit
如果你的集成支持通过 HomeKit 发现,可以在你的 manifest 中添加受支持的型号名称。如果用户加载了 `zeroconf` 集成,那么在发现设备时,将加载你的集成的 config flow 的 `homekit` 步骤。
HomeKit 发现是通过测试发现的模型名是否以 manifest.json 中指定的任何模型名开头来实现的。
```json
{
"homekit": {
"models": [
"LIFX"
]
}
}
```
通过 HomeKit 发现并不意味着你必须使用 HomeKit 协议来与你的设备通信。你可以以任何你合适的方式与设备通信。
当发现信息因为你在 manifest 中的此条目而被路由到你的集成时,该发现信息不再被路由到监听 HomeKit zeroconf 类型的集成。
## MQTT
如果你的集成支持通过 MQTT 发现,可以在你的 manifest 中添加用于发现的 topic。如果用户加载了 `mqtt` 集成,那么在发现设备时,将加载你的集成的 config flow 的 `mqtt` 步骤。
MQTT 发现是通过订阅在 manifest.json 中指定的 MQTT topic 来实现的。
```json
{
"mqtt": [
"tasmota/discovery/#"
]
}
```
如果你的集成需要 `mqtt`,请确保它被添加到了[依赖项](#dependencies)中。
依赖于 MQTT 的集成应该使用 `await mqtt.async_wait_for_mqtt_client(hass)` 等待 MQTT client 可用后才能订阅。`async_wait_for_mqtt_client` 方法会阻塞,并在 MQTT client 可用时返回 `True`。
## DHCP
如果你的集成支持通过 DHCP 发现,可以在你的 manifest 中添加类型。如果用户加载了 `dhcp` 集成,那么在发现设备时,将加载你的集成的 config flow 的 `dhcp` 步骤。我们支持被动监听 DHCP 发现,基于 `hostname` 和 [OUI](https://en.wikipedia.org/wiki/Organizationally_unique_identifier) 进行匹配,或者在 `registered_devices` 设置为 `true` 时,匹配 device registry 中的 mac address。manifest 值是一个 matcher 字典列表,如果 DHCP 数据中找到了任意指定 matcher 的所有项,你的集成就会被发现。使用[Unix 文件名模式匹配](https://docs.python.org/3/library/fnmatch.html)进行匹配。去重则由你的 config flow 负责。
如果集成希望在设备上线时接收发现流程以更新设备的 IP 地址,但 `hostname` 或 `oui` 匹配过于宽泛,并且它已经使用 `CONNECTION_NETWORK_MAC` 在 device registry 中注册了 mac address,那么它应该添加一个 `registered_devices` 设置为 `true` 的 DHCP 条目。
如果集成支持 `zeroconf` 或 `ssdp`,则应优先于 `dhcp` 使用,因为它通常能提供更好的用户体验。
以下示例包含两个由两项组成的 matcher。任意 matcher 中的所有项都必须匹配,才能通过此配置进行发现。
例如:
* 如果 `hostname` 为 `Rachio-XYZ` 且 `macaddress` 为 `00:9D:6B:55:12:AA`,则会触发发现(第 1 个 matcher)。
* 如果 `hostname` 为 `Dachio-XYZ` 或 `Pachio-XYZ`,且 `macaddress` 为 `00:9D:6B:55:12:AA`,则会触发发现(第 3 个 matcher)。
* 如果 `hostname` 为 `Rachio-XYZ` 且 `macaddress` 为 `00:00:00:55:12:AA`,则不会触发发现(MAC 不匹配)。
* 如果 `hostname` 为 `NotRachio-XYZ` 且 `macaddress` 为 `00:9D:6B:55:12:AA`,则不会触发发现(hostname 不匹配)。
```json
{
"dhcp": [
{
"hostname": "rachio-*",
"macaddress": "009D6B*"
},
{
"hostname": "[dp]achio-*",
"macaddress": "009D6B*"
}
]
}
```
设置 `registered_devices` 为 `true` 的示例:
```json
{
"dhcp": [
{
"hostname": "myintegration-*",
},
{
"registered_devices": true,
}
]
}
```
## USB
如果你的集成支持通过 usb 发现,可以在你的 manifest 中添加类型。如果用户加载了 `usb` 集成,那么在发现设备时,将加载你的集成的 config flow 的 `usb` 步骤。我们通过从 USB 描述符中提取 VID(Vendor ID)、PID(Device ID)、序列号(Serial Number)、制造商(Manufacturer)和描述(Description)来支持发现。有关如何识别这些值的帮助,请参阅[如何识别设备](https://wiki.debian.org/HowToIdentifyADevice/USB)。manifest 值是一个 matcher 字典列表。如果 USB 数据中找到了任意指定 matcher 的所有项,你的集成就会被发现。去重则由你的 config flow 负责。
:::warning
某些 VID 和 PID 组合被许多互不相关的设备使用。例如 VID `10C4` 和 PID `EA60` 可以匹配任何 Silicon Labs CP2102 USB-Serial 桥接芯片。在匹配此类设备时,重要的是要匹配 `description` 或其他标识符,以避免意外的发现。
:::
以下示例包含两个由两项组成的 matcher。任意一个 matcher 中的所有项都必须匹配,才能通过此配置进行发现。
例如:
* 如果 `vid` 为 `AAAA` 且 `pid` 为 `AAAA`,则会触发发现。
* 如果 `vid` 为 `AAAA` 且 `pid` 为 `FFFF`,则不会触发发现。
* 如果 `vid` 为 `CCCC` 且 `pid` 为 `AAAA`,则不会触发发现。
* 如果 `vid` 为 `1234`,`pid` 为 `ABCD`,`serial_number` 为 `12345678`,`manufacturer` 为 `Midway USB`,`description` 为 `Version 12 Zigbee Stick`,则会触发发现。
```json
{
"usb": [
{
"vid": "AAAA",
"pid": "AAAA"
},
{
"vid": "BBBB",
"pid": "BBBB"
},
{
"vid": "1234",
"pid": "ABCD",
"serial_number": "1234*",
"manufacturer": "*midway*",
"description": "*zigbee*"
},
]
}
```
## 集成质量等级
[Integration Quality Scale](/developers/core/integration-quality-scale.md) 根据代码质量和用户体验对集成进行评分。质量等级的每一级都包含一个要求列表。如果集成符合所有要求,则认为它已达到该级别。
新集成至少需要满足 bronze 级别,因此请务必查看[Integration Quality Scale](/developers/core/integration-quality-scale.md)的要求列表。它有助于极大地改进代码和用户体验。
```json
{
"quality_scale": "silver"
}
```
## IoT 类别
[IoT class][iot_class] 描述集成如何与设备或服务进行连接。有关 IoT 类别的更多信息,请阅读关于["分类物联网"](https://www.home-assistant.io/blog/2016/02/12/classifying-the-internet-of-things/#classifiers)的博客。
manifest 中接受的 IoT 类别如下:
* `assumed_state`:我们无法获取设备的状态。我们只能根据最后一次命令来推测状态。
* `cloud_polling`:此设备的集成通过云端进行,需要活跃的互联网连接。轮询状态意味着状态的更新可能会稍后才会被注意到。
* `cloud_push`:此设备的集成通过云端进行,需要活跃的互联网连接。一旦有新状态可用,Home Assistant 就会收到通知。
* `local_polling`:提供与设备的直接通信。轮询状态意味着状态的更新可能会稍后才会被注意到。
* `local_push`:提供与设备的直接通信。一旦有新状态可用,Home Assistant 就会收到通知。
* `calculated`:集成不自己处理通信,而是提供了一个计算结果。
[iot_class]: https://www.home-assistant.io/blog/2016/02/12/classifying-the-internet-of-things/#classifiers
## 虚拟集成
有些产品是由不以该产品命名的集成支持的。例如,Yale Home 门锁是通过 August 集成集成的,而 IKEA SYMFONISK 产品线可以与 Sonos 集成一起使用。
还有一种情况是,某产品线只支持标准的 IoT 标准,例如 Zigbee 或 Z-Wave。例如,U-tec ultraloq 通过 Z-Wave 工作,没有特定的专用集成。
对于最终用户来说,找到如何将那些产品与 Home Assistant 集成起来可能会感到困惑。为了帮助处理上述情况,Home Assistant 提供了"Virtual integrations"。这些集成并不是真正的集成,而是用于帮助用户为其设备找到正确的集成。
virtual integration 是一个只包含单个 manifest 文件的集成,没有任何其他代码。有两种类型的 virtual integration:由另一个集成支持的 virtual integration 和使用现有 IoT 标准的 virtual integration。
:::info
virtual integration 只能由 Home Assistant Core 提供,不能由自定义集成提供。
:::
### 支持来源
"Supported by" virtual integration 是指向另一个集成的集成,由后者提供其实现。例如,Yale Home 门锁是通过 August (`august`) 集成集成的。
示例 manifest:
```json
{
"domain": "yale_home",
"name": "Yale Home",
"integration_type": "virtual",
"supported_by": "august"
}
```
`domain` 和 `name` 与其他任何集成相同,但 `integration_type` 设置为 `virtual`。
此 virtual integration 的 domain 的 logo 必须添加到我们的[brands 仓库](https://github.com/home-assistant/brands/),因此在这种情况下,会使用 Yale Home 的品牌标识。
`supported_by` 是为该产品提供实现的集成的 domain。在上面的示例中,Yale Home 门锁由 August 集成支持,并指向其 domain `august`。
结果:
* Yale Home 会列在我们的用户文档网站的集成部分下,并附带一个自动生成的 stub 页面,引导用户使用该集成。
* Yale Home 会在 Home Assistant 中点击"添加集成"时列出。选中后,我们会向用户解释该产品是通过另一个集成集成的,然后用户继续进入 Xioami Miio 的 config flow。
### IoT 标准
"IoT Standards" virtual integration 是指使用现有 IoT 标准来与设备进行连接的集成。例如,U-tec ultraloq 通过 Z-Wave 工作,没有特定的专用集成。
示例 manifest:
```json
{
"domain": "ultraloq",
"name": "ultraloq",
"integration_type": "virtual",
"iot_standards": ["zwave"],
}
```
`domain` 和 `name` 与其他任何集成相同,但 `integration_type` 设置为 `virtual`。
此 virtual integration 的 domain 的 logo 应添加到我们的[brands 仓库](https://github.com/home-assistant/brands/)。
`iot_standards` 是该产品用于连接的标准。在上面的示例中,U-tech ultraloq 产品使用 Z-Wave 与 Home Assistant 集成。
结果:
* U-tech ultraloq 会列在我们的用户文档网站的集成部分下,并附带一个自动生成的 stub 页面,引导用户使用该集成。
* U-tech ultraloq 会在 Home Assistant 中点击"添加集成"时列出。选中后,我们会引导用户添加此 Z-Wave 设备(如果 Z-Wave 尚未设置,则先设置 Z-Wave)。
:::info
品牌也[支持设置 IoT 标准](/developers/creating_integration_brand.md#iot-standards)。
优先建议在品牌层面设置 IoT 标准,只有在会对最终用户造成混淆的情况下,才使用 virtual integration。
:::
---
url: /developers/creating_integration_tests_file_structure.md
---
每个集成的测试都存储在一个以其集成 domain 命名的目录中。例如,mobile app 集成的测试应存储在 `tests/components/mobile_app` 中。
该文件夹的内容如下:
* `__init__.py`:`pytest` 需要它来找到测试,可以将此文件限制为一段介绍集成测试的 docstring,例如 `"""Tests for the Mobile App integration."""`。
* `conftest.py`:Pytest 测试 fixtures
* `test_xxx.py`:测试集成对应部分的测试文件。`__init__.py` 中功能的测试(例如设置、重新加载和卸载 config entry),应该在名为 `test_init.py` 的文件中。
## 与其他集成共享 test fixtures
如果你的集成是一个实体集成(其他集成在它上面有 platforms),例如 `light` 或 `sensor`,则该集成可以提供其他集成编写测试时可使用的 test fixtures。
例如,`light` 集成可以通过向 `tests/components/conftest.py` 添加 fixture stubs,并在 `tests/components/light/common.py` 中实现这些 fixtures 的实际代码,来提供用于创建 mocked light 实体的 fixtures。
---
url: /developers/creating_platform_code_review.md
---
添加新 platform 时需要完成的事项清单。
:::info
并非所有现有的 platforms 都遵循本清单中的要求。这不能作为不遵循它们的理由!
:::
### 0. Common
1. 遵循我们的 [Style guidelines](/developers/development_guidelines.md)
2. 使用 [`const.py`](https://github.com/home-assistant/core/blob/dev/homeassistant/const.py) 中已有的常量
* 只有当常量被广泛使用时才将其添加到 `const.py` 中。否则请将其保留在 platform 级别
* 使用 `CONF_MONITORED_CONDITIONS` 代替 `CONF_MONITORED_VARIABLES`
### 1. External requirements
1. Requirements 已添加到 [`manifest.json`](/developers/creating_integration_manifest.md)。`REQUIREMENTS` 常量已弃用。
2. Requirement 版本应该被固定:`"requirements": ['phue==0.8.1']`
3. 我们不再希望 requirements 托管在 GitHub 上。请上传到 PyPi。
4. 每个 requirement 都满足 [library requirements](/developers/api_lib_index.md#basic-library-requirements)。
### 2. Configuration
1. 如果 platform 可以直接设置,添加一个 voluptuous schema 用于 [configuration validation](/developers/development_validation.md)
2. Voluptuous schema 扩展自 component 的 schema
(例如,`hue.light.PLATFORM_SCHEMA` 扩展自 `light.PLATFORM_SCHEMA`)
3. 默认参数在 voluptuous schema 中指定,而不是在 `setup_platform(...)` 中
4. 你的 `PLATFORM_SCHEMA` 应尽可能多地使用来自 `homeassistant.const` 的通用 config keys
5. 永远不要依赖用户在 `customize` 中添加内容来配置你 platform 内部的行为。
```python
import voluptuous as vol
from homeassistant.const import CONF_FILENAME, CONF_HOST
from homeassistant.components.light import PLATFORM_SCHEMA
import homeassistant.helpers.config_validation as cv
CONF_ALLOW_UNREACHABLE = "allow_unreachable"
DEFAULT_UNREACHABLE = False
PLATFORM_SCHEMA = PLATFORM_SCHEMA.extend(
{
vol.Required(CONF_HOST): cv.string,
vol.Optional(CONF_ALLOW_UNREACHABLE, default=DEFAULT_UNREACHABLE): cv.boolean,
vol.Optional(CONF_FILENAME): cv.string,
}
)
```
### 3. Setup platform
1. 验证传入的配置(user/pass/host 等)是否有效。
2. 尽可能将你的 `add_entities` 调用分组。
3. 如果 platform 添加了额外的 actions,格式应为 `.`。因此,如果你的 integration domain 是 "awesome\_sauce",并且你正在制作一个 light platform,你会在 `awesome_sauce` domain 下注册 service actions。确保你的 service actions [verify permissions](/developers/auth_permissions.md#checking-permissions)。
### 4. Entity
1. 继承你要为其构建 platform 的 integration 的 entity。
```python
from homeassistant.components.light import Light
class HueLight(Light):
"""Hue light component."""
```
2. 避免将 `hass` 作为参数传递给 entity。`hass` 将在 entity 被添加到 Home Assistant 时被设置到 entity 上。这意味着你可以在 entity 内部通过 `self.hass` 访问 `hass`。
3. 不要在 constructor 中调用 `update()`,而是使用 `add_entities(devices, update_before_add=True)`。
4. 不要在 properties 中进行任何 I/O。改为在 `update()` 内部缓存值。
5. 处理时间时,state 和/或 attributes 不应包含某事发生以来的相对时间。相反,它应该存储 UTC timestamps。
6. 利用 [entity lifecycle callbacks](/developers/core/entity.md#lifecycle-hooks) 来附加 event listeners 或清理 connections。
### 5. 与 devices/services 通信
1. 所有与 API 相关的代码必须托管在 PyPi 上的第三方 library 的一部分。Home Assistant 应该只与 objects 交互,而不直接调用 API。
```python
# bad
status = requests.get(url("/status"))
# good
from phue import Bridge
bridge = Bridge(...)
status = bridge.status()
```
[发布你自己的 PyPI package 的教程](https://towardsdatascience.com/how-to-open-source-your-first-python-package-e717444e1da0)
其他值得注意的发布 python packages 的资源:
[Cookiecutter Project](https://cookiecutter.readthedocs.io/)
[flit](https://flit.readthedocs.io/)
[Poetry](https://python-poetry.org/)
---
url: /developers/creating_platform_index.md
---
Home Assistant 有各种内置的 integrations,它们对 device types 进行了抽象。有 [lights](/developers/core/entity/light.md)、[switches](/developers/core/entity/switch.md)、[covers](/developers/core/entity/cover.md)、[climate devices](/developers/core/entity/climate.md),以及[更多](/developers/core/entity.md)。你的 integration 可以通过创建 platform 来接入这些 integrations。对于你要集成的每一个 integration,你都需要一个 platform。
要创建 platform,你需要创建一个文件,文件名为你为其构建 platform 的 integration 的 domain name。因此,如果你正在构建一个 light,你会在你的 integration folder 中添加一个新文件 `light.py`。
我们创建了两个示例 integrations,可以让你了解其工作原理:
* [Example sensor platform](https://github.com/home-assistant/example-custom-config/tree/master/custom_components/example_sensor/): platform 的 hello world。
* [Example light platform](https://github.com/home-assistant/example-custom-config/tree/master/custom_components/example_light/): 展示最佳实践。
### 与 devices 交互
Home Assistant 的一条规则是 integration 永远不要直接与 devices 交互。相反,它应该与一个第三方 Python 3 library 交互。这样,Home Assistant 可以与 Python 社区共享代码,并保持项目的可维护性。
一旦你的 Python library [准备就绪并发布到 PyPI](/developers/api_lib_index.md),将其添加到 [manifest](/developers/creating_integration_manifest.md) 中。现在,是时候实现你为其创建 platform 的 integration 所提供的 Entity base class 了。
在 [entity index](/developers/core/entity.md) 中找到你的 integration,以查看可以实现的 methods 和 properties。
---
url: /developers/data_entry_flow_index.md
---
Data Entry Flow 是 Home Assistant 的一部分,一个数据录入框架。数据录入通过 data entry flows 完成。一个 flow 可以表示一个简单的登录表单,也可以表示为 component 提供的多步骤 setup wizard。Flow Manager 管理所有进行中的 flows,并处理新 flows 的创建。
Data Entry Flow 在 Home Assistant 中用于登录、创建 config entries、处理 options flow、修复 issues。
## Flow 管理器
这是一个管理进行中 flows 的类。在实例化它时,你需要传入两个 async callbacks:
```python
async def async_create_flow(handler, context=context, data=data):
"""Create flow."""
```
该 manager 通过此 async callback 将 config flow handlers 的实例化委托给调用方。这让 manager 的父级可以定义自己的方式来查找 handlers 并为实例化准备 handler。例如,在 config entry manager 的情况下,它会确保 dependencies 和 requirements 已设置好。
```python
async def async_finish_flow(flow, result):
"""Finish flow."""
```
当一个 flow 完成或被中止时,会调用此 async callback,即 `result['type'] in [FlowResultType.CREATE_ENTRY, FlowResultType.ABORT]`。该 callback 函数可以修改 result 并将其返回,如果 result type 变成了 `FlowResultType.FORM`,flow 会继续运行,显示另一个表单。
如果 result type 是 `FlowResultType.FORM`,result 应如下所示:
```python
{
# The result type of the flow
"type": FlowResultType.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 是 `FlowResultType.CREATE_ENTRY`,result 应如下所示:
```python
{
# Data schema version of the entry
"version": 2,
# The result type of the flow
"type": FlowResultType.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 是 `FlowResultType.ABORT`,result 应如下所示:
```python
{
# The result type of the flow
"type": FlowResultType.ABORT,
# the id of the flow
"flow_id": "abcdfgh1234",
# handler name
"handler": "hue",
# the abort reason
"reason": "already_configured",
}
```
## Flow 处理器
Flow handlers 处理单个 flow。一个 flow 包含一个或多个 steps。当一个 flow 被实例化时,会调用 `FlowHandler.init_step` step。每个 step 都有几种可能的结果:
* [显示表单(Show Form)](#show-form)
* [创建条目(Create Entry)](#create-entry)
* [中止(Abort)](#abort)
* [外部步骤(External Step)](#external-step--external-step-done)
* [显示进度(Show Progress)](#show-progress--show-progress-done)
* [显示菜单(Show Menu)](#show-menu)
每个 flow handler 至少需要定义一个版本号和一个 step。这个 step 不一定是 `init`,因为 `async_create_flow` 可以根据当前工作流分配 `init_step`,例如在 configuration 中,`context.source` 会被用作 `init_step`。
例如,最基本的 config flow 如下:
```python
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."""
```
Data entry flows 依赖 translations 来显示 steps 中的文本。这取决于 data entry flow manager 的父级决定存储在哪里。对于 config 和 option flows,分别存储在 `strings.json` 的 `config` 和 `option` 下。
关于 `strings.json` 的更详细解释,请参阅 [backend translation](/developers/internationalization/core.md) 页面。
### 显示表单(Show form)
这种 result type 会向用户显示一个需要填写的表单。你定义当前 step、数据 schema(使用 voluptuous 和/或 [selectors](https://www.home-assistant.io/docs/blueprint/selectors/) 的混合)以及可选的错误字典。
```python
from homeassistant.data_entry_flow import section
from homeassistant.helpers.selector import selector
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,
# Items can be grouped by collapsible sections
vol.Required("ssl_options"): section(
vol.Schema(
{
vol.Required("ssl", default=True): bool,
vol.Required("verify_ssl", default=True): bool,
}
),
# Whether or not the section is initially collapsed (default = False)
{"collapsed": False},
)
}
if self.show_advanced_options:
data_schema[vol.Optional("allow_groups")] = selector({
"select": {
"options": ["all", "light", "switch"],
}
})
return self.async_show_form(step_id="init", data_schema=vol.Schema(data_schema))
```
#### 输入字段的分组
如上例所示,输入字段可以在 sections 中按视觉进行分组。
每个 section 都有一个 [可翻译的 name 和 description](#labels--descriptions),还可以指定一个 icon。
将输入字段按 section 分组会影响向用户展示输入的方式,也会影响用户输入的 structure。
在上面的例子中,用户输入的 structure 如下:
```python
{
"username": "user",
"password": "hunter2",
"ssl_options": {
"ssl": True,
"verify_ssl": False,
},
}
```
只允许单层 sections;不允许在 section 内嵌套 section。
要为某个 section 指定 icon,请按照下面的示例更新 `icons.json`:
```json
{
"config": {
"step": {
"user": {
"sections": {
"ssl_options": "mdi:lock"
}
}
}
}
}
```
#### 标签与描述
表单的 translations 添加到 `strings.json` 中,key 为对应的 `step_id`。该 object 可以包含以下 keys:
| Key | Value | Notes |
| :----------------: | :-----------------: | :------------------------------------------------------------------------------------------------------------------------------------------- |
| `title` | Form heading | 不要包含你的品牌名称,它会自动从 manifest 中注入。 |
| `description` | Form instructions | 可选。不要链接到文档,因为那会自动链接。不要包含 "可以在这里设置 X" 这类的基本信息。 |
| `data` | Field labels | 保持简洁,并在适当的时候与其他 integrations 保持一致,以提供最佳的用户体验。 |
| `data_description` | Field descriptions | 可选的说明文本,显示在字段下方。 |
| `section` | Section translation | Sections 的 translations,每个 section 可以有该 section 的 `name` 和 `description`,以及其字段的 `data` 和 `data_description`。 |
关于翻译 data entry flows 的更多细节,可以在 [core translations documentation](/developers/internationalization/core.md) 中找到。
字段 labels 和 descriptions 以一个字典形式提供,其 keys 对应你的 schema。下面是一个简单的示例:
```json
{
"config": {
"step": {
"user": {
"title": "Add Group",
"description": "Some description",
"data": {
"entities": "Entities"
},
"data_description": {
"entities": "The entities to add to the group"
},
"sections": {
"additional_options": {
"name": "Additional options",
"description": "A description of the section",
"data": {
"advanced_group_option": "Advanced group option"
},
"data_description": {
"advanced_group_option": "A very complicated option which does abc"
},
}
}
}
}
}
}
```
#### 启用浏览器自动填充
假设你的 integration 正在收集表单数据,而这些数据可以由浏览器或密码管理器自动填充,比如登录凭据或联系信息。你应该尽可能启用自动填充,以提供最佳的用户体验和可访问性。有两种方式可以启用它。
第一种方式是使用 Voluptuous,并使用 frontend 可识别的 data keys。Frontend 会识别 `"username"` 和 `"password"` keys,并分别添加值为 `"username"` 和 `"current-password"` 的 HTML `autocomplete` 属性。Autocomplete 的支持仅限于 `"username"` 和 `"password"` 字段,主要是为了让许多收集这些字段的 integrations 能够快速启用自动填充,而不需要将它们的 schemas 转换为 selectors。
第二种方式是使用 [text selector](https://www.home-assistant.io/docs/blueprint/selectors/#text-selector)。Text selector 可以完全控制 input type,并允许指定 `autocomplete` 的任何允许值。一个收集特定可填充数据的假设 schema 可能如下:
```python
import voluptuous as vol
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers.selector import (
TextSelector,
TextSelectorConfig,
TextSelectorType,
)
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_USERNAME): TextSelector(
TextSelectorConfig(type=TextSelectorType.EMAIL, autocomplete="username")
),
vol.Required(CONF_PASSWORD): TextSelector(
TextSelectorConfig(
type=TextSelectorType.PASSWORD, autocomplete="current-password"
)
),
vol.Required("postal_code"): TextSelector(
TextSelectorConfig(type=TextSelectorType.TEXT, autocomplete="postal-code")
),
vol.Required("mobile_number"): TextSelector(
TextSelectorConfig(type=TextSelectorType.TEL, autocomplete="tel")
),
}
)
```
#### 默认值与建议值
如果你想在表单中预填数据,你有两种选择。第一种是使用 `default` 参数。这既会预填该字段,又会在用户留空该字段时作为默认值。
```python
data_schema = {
vol.Optional("field_name", default="default value"): str,
}
```
另一种选择是使用 suggested value——这也会预填表单字段,但允许用户在需要时将其留空。
```python
data_schema = {
vol.Optional(
"field_name", description={"suggested_value": "suggested value"}
): str,
}
```
你也可以将两者混合使用——通过 `suggested_value` 预填,并在字段留空时使用另一个 `default` 值,但这可能会让用户感到困惑,请谨慎使用。
使用 suggested values 还可以声明一个静态 schema,并从现有输入中合并 suggested values。`add_suggested_values_to_schema` helper 可以做到这一点:
```python
OPTIONS_SCHEMA = vol.Schema(
{
vol.Optional("field_name", default="default value"): str,
}
)
class ExampleOptionsFlow(config_entries.OptionsFlow):
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> FlowResult:
return self.async_show_form(
data_schema = self.add_suggested_values_to_schema(
OPTIONS_SCHEMA, self.entry.options
)
)
```
注意:对于 select 类型的输入(由 `vol.In(...)` schema 创建),如果没有指定 `default`,则 frontend 会默认选择第一个选项。
#### 显示只读信息
某些 integrations 的 options 在初始配置后会被冻结。在显示 options flow 时,你可以以只读方式展示这些信息,以便用户记住在初始配置时选择了哪些 options。为此,像往常一样定义一个可选 selector,但将 `read_only` 标志设置为 `True`。
```python
# Example Config Flow Schema
DATA_SCHEMA_SETUP = vol.Schema(
{
vol.Required(CONF_ENTITY_ID): EntitySelector()
}
)
# Example Options Flow Schema
DATA_SCHEMA_OPTIONS = vol.Schema(
{
vol.Optional(CONF_ENTITY_ID): EntitySelector(
EntitySelectorConfig(read_only=True)
),
vol.Optional(CONF_TEMPLATE): TemplateSelector(),
}
)
```
这会在启动 options flow 时,将初始配置中选中的 entity 作为只读属性显示。
#### 验证
用户填写完表单后,step 方法会被再次调用,并传入用户输入。只有当用户输入通过你的 data schema 时,你的 step 才会被调用。当用户传入 data 时,你需要对 data 进行额外的验证。例如,你可以验证传入的 username 和 password 是否有效。
如果有问题,你可以返回一个包含 errors 的字典。错误字典中的每个 key 都对应包含错误信息的字段名称。如果你想显示一个与特定字段无关的错误,请使用 key `base`。所指定的 errors 需要引用 translation 文件中的一个 key。
```python
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
)
```
#### 多步骤 flows
如果用户输入通过了验证,你可以再次返回其中一种可能的 step types。如果你要引导用户进入下一个 step,请返回该 step 的返回值:
```python
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)
当 result 为 "Create Entry" 时,会创建一个条目并传递给 flow manager 的父级。会向用户显示一条成功消息,flow 即告完成。你通过传入 title、data 以及可选的 options 来创建条目。Title 可以在 UI 中用于向用户指示这是哪个条目。Data 和 options 可以是任何数据类型,只要它们可 JSON 序列化。Options 用于可变 data,例如 radius。而 Data 用于不会在 entry 中更改的不可变 data,例如 location data。
```python
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={
"username": user_input["username"],
"password": user_input["password"]
},
options={
"mobile_number": user_input["mobile_number"]
},
)
```
注意:用户可以更改密码,这在技术上是可变 data,但对于更改 authentication credentials,你需要使用 [reauthentication](/developers/core/integration/config_flow.md#reauthentication),它可以修改 config entry data。
### 中止(Abort)
当一个 flow 无法完成时,你需要中止它。这会结束 flow 并通知用户 flow 已完成。一个 flow 无法完成的原因可能是设备已经被配置,或者与 Home Assistant 不兼容。
```python
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)
有时用户需要通过在外部网站上执行操作来完成 config flow。例如,通过重定向到外部网页来设置一个 integration。这通常由使用 OAuth2 来授权用户的 integrations 使用。
*这个示例是关于 config entries 的,但同样适用于使用 data entry flows 的其他部分。*
流程如下:
1. 用户在 Home Assistant 中启动 config flow。
2. Config flow 提示用户在外部网站上完成 flow。
3. 用户打开外部网站。
4. 外部步骤完成后,用户的浏览器会被重定向到 Home Assistant 的一个 endpoint,以传递响应。
5. Endpoint 验证响应,验证通过后,将外部步骤标记为完成,并返回用于关闭窗口:`` 的 JavaScript 代码。
为了能够将外部步骤的结果路由到 Home Assistant endpoint,你需要确保 config flow ID 包含在内。如果你的外部步骤是一个 OAuth2 flow,你可以利用 oauth2 state 来实现这一点。这是一个不被 authorization 页面解释、而是原样传递给 Home Assistant endpoint 的变量。
6. 窗口关闭,Home Assistant 带有 config flow 的用户界面会再次显示给用户。
7. 当外部步骤被标记为完成时,config flow 会自动推进到下一个 step。用户会被提示进入下一个 step。
包含外部步骤的示例 configuration flow。
```python
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` 之前,不要根据外部步骤的 data 执行工作。相反,应该在标记外部步骤完成时,将其作为 `next_step_id` 引用的那个 step 中执行工作。这会在执行工作期间在 UI 中显示一个 spinner,从而提供更好的用户体验。
如果你把工作在 authorize callback 内部执行,用户会一直盯着空白屏幕,直到数据转发完成,窗口突然关闭。如果你在标记外部步骤完成之前执行工作,用户仍然会看到带有 "Open external website" 按钮的表单,同时后台工作正在进行。这也令人不快。
用于标记外部步骤为完成的示例代码:
```python
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.FlowResultType.EXTERNAL_STEP_DONE:
return "success!"
else:
return "Invalid config flow specified"
```
### 显示进度 & 显示进度完成(Show progress & show progress done)
如果一个 data entry flow step 需要相当长的时间才能完成,我们应该告知用户。
*这个示例是关于 config entries 的,但同样适用于使用 data entry flows 的其他部分。*
流程如下:
1. 用户在 Home Assistant 中启动 config flow。
2. Config flow 创建一个 `asyncio.Task` 来执行长时间运行的任务。
3. Config flow 通过调用 `async_show_progress` 并传入 `asyncio.Task` object 来告知用户一个任务正在进行中,并且需要一些时间才能完成。Flow 应该将一个与任务特定的字符串作为 `progress_action` 参数传递,以表示提示信息的翻译文本。
4. 任务完成后,config flow 会被自动调用,但也可以在任务完成之前被调用,例如当 frontend 重新加载时。
* 如果任务尚未完成,flow 不应再创建另一个任务,而是再次调用 `async_show_progress`。
* 如果任务已完成,flow 必须调用 `async_show_progress_done`,指示下一个 step
5. 每次我们调用 show progress 或 show progress done 时,frontend 都会更新。
6. 当 progress 被标记为完成时,config flow 会自动推进到下一个 step。用户会被提示进入下一个 step。
7. 任务可以选择性地调用 `async_update_progress(progress)`,其中 progress 是一个介于 0 和 1 之间的 float,表示任务的完成程度。
包含两个顺序 progress 任务的示例 configuration flow。
```python
import asyncio
from homeassistant import config_entries
from .const import DOMAIN
class TestFlow(config_entries.ConfigFlow, domain=DOMAIN):
VERSION = 1
task_one: asyncio.Task | None = None
task_two: asyncio.Task | None = None
async def async_step_user(self, user_input=None):
uncompleted_task: asyncio.Task[None] | None = None
if not self.task_one:
coro = asyncio.sleep(10)
self.task_one = self.hass.async_create_task(coro)
if not self.task_one.done():
progress_action = "task_one"
uncompleted_task = self.task_one
if not uncompleted_task:
if not self.task_two:
self.async_update_progress(0.5) # tell frontend we are 50% done
coro = asyncio.sleep(10)
self.task_two = self.hass.async_create_task(coro)
if not self.task_two.done():
progress_action = "task_two"
uncompleted_task = self.task_two
if uncompleted_task:
return self.async_show_progress(
progress_action=progress_action,
progress_task=uncompleted_task,
)
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={})
```
### 显示菜单(Show menu)
这会将一个 navigation menu 显示给用户,方便选择下一个 step。Menu labels 可以通过指定一个 `{step_id: label}` 字典硬编码,或者在指定一个列表时,通过 `strings.json` 中的 "menu\_options" 进行翻译。此外,menu descriptions 可以通过 `strings.json` 中的 "menu\_option\_descriptions" 提供。
```python
class ExampleConfigFlow(data_entry_flow.FlowHandler):
async def async_step_user(self, user_input=None):
return self.async_show_menu(
step_id="user",
menu_options=["discovery", "manual"],
description_placeholders={
"model": "Example model",
}
)
# Example showing the other approach
return self.async_show_menu(
step_id="user",
menu_options={
"option_1": "Option 1",
"option_2": "Option 2",
}
)
```
```json
{
"config": {
"step": {
"user": {
"menu_options": {
"discovery": "Discovery",
"manual": "Manual ({model})",
},
"menu_option_descriptions": {
"discovery": "Description of discovery",
"manual": "Description of manual",
}
}
}
}
}
```
向 `async_show_menu` 传递 `sort=True` 还会按照用户语言对 menu items 的 label 进行排序。
## 从外部源初始化 config flow
你可能希望通过编程方式初始化一个 config flow。例如,当我们在网络上发现一个需要用户交互才能完成 setup 的设备时。为此,在初始化 flow 时传入一个 source 参数以及可选的用户输入:
```python
await flow_mgr.async_init(
"hue", context={"source": data_entry_flow.SOURCE_DISCOVERY}, data=discovery_info
)
```
Config flow handler 不会从 `init` step 开始。相反,它会用一个与 source 相等的 step 名称来实例化。该 step 应该遵循与正常 step 相同的返回值。
```python
class ExampleConfigFlow(data_entry_flow.FlowHandler):
async def async_step_discovery(self, info):
"""Handle discovery info."""
```
Config flow 的 source 在 `FlowHandler` 上以 `self.source` 的形式提供。
---
url: /developers/deprecating.md
---
Deprecation 是预定在未来执行的 breaking change:旧行为仍然有效,所有受影响者都会收到警告,而真正的移除发生在之后的某个 release 中。
任何移除都必须先经过 deprecation。即使你认为只影响少数用户也是如此,因为一个 entity、action 或 constant 可能从我们看不到的 automations、scripts 和 dashboards 中被引用。
| Who is affected | Minimum period |
| --- | --- |
| 用户(YAML configuration、actions、entities、attributes、integrations) | 6 个月 |
| 开发者(constants、helpers、entity properties、custom integrations 使用的 platform APIs) | 12 个月 |
如果生态系统尚未跟上,deprecation 期限可以延长,但绝不会缩短。请使用 [calendar version format](/developers/versioning.md) 格式,将移除发生的 release 声明为 `breaks_in_ha_version`。
在旧行为仍然有效时,使用 `IssueSeverity.WARNING`;只有当它已停止工作(因为 integration 已被移除或 migration 失败,用户的 configuration 已不再生效)时,才使用 `IssueSeverity.ERROR`。对于无法触发 repair issue 的 developer-facing features 移除,log levels 同样遵循这一划分。
## 面向用户的 deprecations
每个需要用户介入的 deprecation 都会对应一个 [repair issue](/developers/core/platform/repairs.md)。当 integration 可以被多次设置时,应按 config entry 范围来设定 `issue_id`,否则 issues 会发生冲突。Deprecation 期限结束后,创建一个后续的 pull request,移除 repair issue 和旧行为。
在你自己的 domain 下注册的每个 issue,都需要在 integration 的 [`strings.json`](/developers/internationalization/core.md) 中有匹配的 `issues.` 条目。注册在 `HOMEASSISTANT_DOMAIN` 下的 issues(如 `deprecated_yaml`)会重用已经存在于 core 中的文本,本地无需额外定义。
### 移除整个 integration 的 YAML 支持
要为整个 integration 移除 YAML 支持,请为 integration 添加一个 [config flow](/developers/core/integration/config_flow.md) 和一个 import flow,并使用 `integration_title` 和 `domain` placeholders 触发 core 的 `deprecated_yaml` repair issue。由于该 issue 文本存在于 `homeassistant` domain 中,请使用 `HOMEASSISTANT_DOMAIN` 注册,并将 `issue_domain` 设置为你的 domain。
```python
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the integration."""
if DOMAIN not in config:
return True
result = await hass.config_entries.flow.async_init(
DOMAIN, context={"source": SOURCE_IMPORT}, data=config[DOMAIN]
)
if (
result.get("type") is FlowResultType.ABORT
and (reason := result["reason"]) != "single_instance_allowed"
):
async_create_import_error_issue(hass, reason)
return True
ir.async_create_issue(
hass,
HOMEASSISTANT_DOMAIN,
f"deprecated_yaml_{DOMAIN}",
breaks_in_ha_version="2027.1.0",
is_fixable=False,
issue_domain=DOMAIN,
severity=ir.IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={"domain": DOMAIN, "integration_title": "Example"},
)
return True
```
只有在 import 成功之后才触发 `deprecated_yaml` issue。如果在提前触发而 import 后来中止,用户会被告知他们的 configuration 已被导入,而实际上并没有。当 import 中止时,他们的 configuration 没有生效,因此应改用 `ERROR` severity 触发你自己的 issue。
一旦 deprecation 期限已过,YAML handling 本身也已移除,请将 `cv.config_entry_only_config_schema(DOMAIN)` 保留为 `CONFIG_SCHEMA`。它会在仍然有人在 `configuration.yaml` 中包含该 integration 时记录错误并触发 repair issue。
### 弃用单个 YAML config keys
如果你只想弃用单个 YAML key 或一组 YAML keys(例如因为一次性弃用整个 YAML config 太复杂),我们会在读取 YAML 的 `async_setup` 中触发一个 repair issue,并且只在 key 实际存在时才触发。该 key 会一直工作到 deprecation 期限结束。
```python
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the integration."""
if CONF_IPV6 in config[DOMAIN]:
ir.async_create_issue(
hass,
DOMAIN,
"deprecated_ipv6",
breaks_in_ha_version="2027.1.0",
is_fixable=False,
severity=ir.IssueSeverity.WARNING,
translation_key="deprecated_ipv6",
)
...
```
一旦期限结束,不要只是从 schema 中移除该 key。请在其位置保留 `cv.removed(CONF_IPV6)`,这样会告诉用户该 option 已消失,而不是用一条泛化的 validation 错误失败。
### 操作
从 handler 中创建 repair issue:
```python
async def render_image(call: ServiceCall) -> ServiceResponse:
ir.async_create_issue(
call.hass,
DOMAIN,
"deprecated_generate_image",
breaks_in_ha_version="2026.9.0",
is_fixable=False,
severity=ir.IssueSeverity.WARNING,
translation_key="deprecated_generate_image",
)
```
弃用 action 的单个参数处理方式相同,但只有在参数实际被传递时才触发 issue。
### Entities 和 attributes
当一个 attribute 变成它自己的 entity、一个 entity 被另一个 platform 上的 entity 替换,或一个 entity 被完全移除时,(请先创建新 entity)在 deprecation 期间保持旧的继续工作。
对每一个仍然存在且启用的已弃用 entity 都触发 issue。使用 `automations_with_entity()` 和 `scripts_with_entity()` 让消息更具体,并警告用户特定的 automations 或 scripts 将被破坏。[禁用 entity](https://www.home-assistant.io/common-tasks/general/#enabling-or-disabling-entities) 是用户确认 deprecation 的方式。一旦它被禁用且没有任何引用,就移除 registry entry,使其不会被重新创建。该 issue 本身不是持久的,因此重启后会自动消失。
```python
def async_deprecate_entity(
hass: HomeAssistant,
entity_registry: er.EntityRegistry,
description: ExampleSwitchEntityDescription,
) -> bool:
"""Return whether the deprecated entity should still be created."""
entity_id = entity_registry.async_get_entity_id(
Platform.SWITCH, DOMAIN, description.unique_id
)
if entity_id is None:
return False
items = automations_with_entity(hass, entity_id) + scripts_with_entity(hass, entity_id)
if entity_registry.async_get(entity_id).disabled and not items:
entity_registry.async_remove(entity_id)
return False
ir.async_create_issue(
hass,
DOMAIN,
f"deprecated_entity_{entity_id}",
breaks_in_ha_version="2027.2.0",
is_fixable=False,
severity=ir.IssueSeverity.WARNING,
translation_key="deprecated_entity_used" if items else "deprecated_entity",
translation_placeholders={
"entity_id": entity_id,
"new_entity_id": description.replacement_entity_id,
}
| ({"items": "\n".join(items)} if items else {}),
)
return True
```
在 platform 的 `async_setup_entry` 中调用它,此处 entities 被构建,其返回值决定哪些会被添加:
```python
async def async_setup_entry(
hass: HomeAssistant,
entry: ConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the switch platform."""
entity_registry = er.async_get(hass)
entities = [ExampleSwitch(coordinator, description) for description in SWITCHES]
entities.extend(
ExampleSwitch(coordinator, description)
for description in DEPRECATED_SWITCHES
if async_deprecate_entity(hass, entity_registry, description)
)
async_add_entities(entities)
```
对一个尚不存在的 entity 返回 `False` 意味着,只有已经拥有该 entity 的用户才会重新创建它,因此新用户永远不会得到它。
消息必须点明该 entity 可能被使用的每一个位置,不仅仅是 automations 和 scripts,还要告诉用户禁用它才是移除它的方式:
```json
"deprecated_entity": {
"title": "Deprecated switch detected",
"description": "The switch `{entity_id}` is deprecated because it has been replaced with `{new_entity_id}`.\n\nUpdate your dashboards, templates, automations and scripts to use the replacement entity, then disable the deprecated switch and restart Home Assistant."
},
"deprecated_entity_used": {
"title": "[%key:component::example::issues::deprecated_entity::title%]",
"description": "The switch `{entity_id}` is deprecated because it has been replaced with `{new_entity_id}`.\n\nThe switch was used in the following automations or scripts:\n{items}\n\nUpdate the above, along with any dashboards and templates that use it, to use the replacement entity, then disable the deprecated switch and restart Home Assistant."
}
```
### 移除一个 integration
当一个 integration 完全停止工作且没有更新计划时,应该将其移除,同时触发一个 repair,让用户知道发生了什么。这是通过将其精简为一个会触发 `ERROR` issue 的 stub 来实现,同时保留一个最基础的 `config_flow.py`,以便现有的 entries 仍然可以加载。既定的 translation key 是 `integration_removed`,应在 `strings.json` 中添加并说明移除原因:
```python
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up the integration from a config entry."""
ir.async_create_issue(
hass,
DOMAIN,
DOMAIN,
is_fixable=False,
severity=ir.IssueSeverity.ERROR,
translation_key="integration_removed",
translation_placeholders={
"entries": f"/config/integrations/integration/{DOMAIN}",
},
)
return True
```
`entries` placeholder 是一个指向用户现有 config entries 的链接:
```json
"integration_removed": {
"title": "The Example integration has been removed",
"description": "The Example integration has been removed from Home Assistant, as the vendor shut down the API it relied on.\n\nTo resolve this issue, remove the (now defunct) integration entries from your Home Assistant setup. [Click here to see your existing Example integration entries]({entries})."
}
```
在 `async_unload_entry` 中再次删除该 issue,但只有在该 domain 的最后一条 config entry 卸载之后才删除。
当 integration 是被后继者替代而不是消失时,应改用 `WARNING` 加 `breaks_in_ha_version`,并以 `deprecated` 原因中止 config flow,以便不会创建新的 entries。
### 文档和测试
Deprecation 通过 repair issue 和 release notes 公告。不要在 [home-assistant.io](/developers/documenting/standards.md) 上留下 deprecation 通知,因为该文档只描述当前有效的内容。
使用 `issue_registry` fixture 测试该 issue 是否被创建,以及在最后一条 config entry 卸载后是否被再次删除。
## 面向开发者的 deprecations
其他 integrations 导入或子类化的任何东西都算作 core API,无论它位于 `homeassistant/helpers/`、`homeassistant/const.py`,还是像 `homeassistant/components/sensor/` 这样的 entity platform 中。弃用其中之一会影响 custom integration 作者,他们需要一个自己的 release cycle 来应对。这些有 12 个月的时间,并且会在本网站的 [blog](/developers/blog.md) 上发布公告,说明替换方案(如有)和移除版本。
这些机制会将警告记录到被指责的 integration。它们 **不会** 创建 repair issue,因为用户无法修复 custom integration 的代码。
### Constants、aliases 和 enum members
以 `_DEPRECATED_` 为前缀的名称注册替换项,并安装三个 module hooks,这样读取旧名称时只警告一次:
```python
from functools import partial
from homeassistant.helpers.deprecation import (
DeprecatedConstantEnum,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
_DEPRECATED_CONCENTRATION_GRAMS_PER_CUBIC_METER = DeprecatedConstantEnum(
UnitOfDensity.GRAMS_PER_CUBIC_METER, "2027.8"
)
# These can be removed if no deprecated constants are in this module anymore
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(dir_with_deprecated_constants, module_globals_keys=[*globals().keys()])
__all__ = all_with_deprecated_constants(globals())
```
对于普通值使用 `DeprecatedConstant`,对于 enum member 使用 `DeprecatedConstantEnum`,当类或函数移动时使用 `DeprecatedAlias`。要弃用你要保留的 enum 中的单个 member,请改用 `EnumWithDeprecatedMembers` metaclass:
```python
class MediaPlayerState(
StrEnum,
metaclass=EnumWithDeprecatedMembers,
deprecated={
"STANDBY": ("MediaPlayerState.OFF or MediaPlayerState.IDLE", "2026.8.0"),
},
):
"""State of media player entities."""
OFF = "off"
IDLE = "idle"
STANDBY = "standby"
```
:::note
Deprecated constants 只有在调用方可以被归因于某个 integration 时才会警告。来自 core 代码或测试的访问是静默的,这就是为什么下面的测试 helpers 会通过一个 custom integration 来访问该 constant。
:::
### Functions、methods 和 classes
```python
@deprecated_function(
"homeassistant.helpers.sun.get_astral_observer",
breaks_in_ha_version="2027.7",
)
@callback
def get_astral_location(hass: HomeAssistant) -> tuple[Location, Elevation]:
```
`@deprecated_class` 会在实例化时警告,是保留已重命名 class 作为 alias 的工具。两者都以字符串形式接受替换项。要弃用一个你不再需要的 leading `hass` 参数,请使用 `@deprecated_hass_argument`,它会警告并剥离该参数。
### 使用模式
当问题不是一个符号,而是一种模式时,例如设置一个你想要移除的 property,或调用一个 platform 现在已经替代的 registration function,请使用 `report_usage()`。它能区分 core 代码和 integrations,这样 core 内部的错误会大声失败,而 integrations 只会收到警告:
```python
report_usage(
"calls system_health.async_register_info, which is deprecated; "
"add a system_health platform instead",
breaks_in_ha_version="2027.1",
core_behavior=ReportBehavior.ERROR,
exclude_integrations={DOMAIN},
)
```
第一个参数 `what` 会被插入到 `Detected that integration 'domain' {what}` 中,因此请将它写为动词短语。
`report_usage()` 在 core 中运行,因此它通过遍历调用栈来识别负责的 integration。当 integration 自己调用了已弃用的 function 时这有效。当 core 只是读取 integration 之前设置的内容(如已弃用的 entity property)时无效,因为那时 integration 已经不在栈上了。这种情况下请传入 `integration_domain`,从一个知道自身来源的 object 中获取,例如 entity 上的 `self.platform.platform_name`。
谁会收到什么由三个独立参数控制,每个都接受一个 `ReportBehavior`,可以是 `IGNORE`(什么都不说)、`LOG`(在 `level` 级别记录,每个 caller 一次)或 `ERROR`(记录并抛出 `RuntimeError`):
| Parameter | Applies to | Default |
| --- | --- | --- |
| `core_behavior` | `homeassistant/` 中栈上没有 integration 的代码 | `ERROR` |
| `core_integration_behavior` | 一个 built-in integration | `LOG` |
| `custom_integration_behavior` | 一个 custom integration | `LOG` |
这些默认值通常是想要的:core 代码在测试中大声失败,而 integrations 只收到警告。在仍在迁移 built-in integrations 时,可以将 `core_integration_behavior` 降为 `IGNORE`,这样警告才能触达 custom integration 作者,而不会被你已经在修复的 offenders 刷屏。
### 测试
`tests/common.py` 为 symbols 提供了 `import_and_test_deprecated_constant`、`import_and_test_deprecated_constant_enum`、`import_and_test_deprecated_alias` 和 `help_test_all`。`mock_integration_frame` fixture 覆盖 `report_usage` 警告。
---
url: /developers/dev_101_config.md
---
在 [`hass` 对象](/developers/dev_101_hass.md) 上存在一个 `Config` 类的实例。`Config` 类包含了用户首选的单位、config 目录路径以及加载了哪些 components。
| Name | Type | Description |
| ---- | ---- | ----------- |
| latitude | float | 实例位置的纬度 |
| longitude | float | 实例位置的经度 |
| elevation | int | 实例的海拔 |
| location\_name | str | 实例名称 |
| time\_zone | str | 时区 |
| units | UnitSystem | 单位系统 |
| internal\_url | str | 可在内部访问实例的 URL |
| external\_url | str | 可在外部访问实例的 URL |
| currency | str | 首选货币 |
| country | str | 实例所在的国家 |
| language | str | 首选语言 |
| config\_source | ConfigSource | 配置是通过 UI 设置还是存储在 YAML 中 |
| skip\_pip | bool | 如果为 `True`,启动时会跳过对 requirements 的 pip install |
| skip\_pip\_packages | list\[str] | 启动时安装 requirements 时要跳过的包列表 |
| components | set\[str] | 已加载的 components 列表 |
| api | ApiConfig | API (HTTP) 服务器配置 |
| config\_dir | str | 存放配置的目录 |
| allowlist\_external\_dirs | set\[str] | 允许访问的外部目录列表 |
| allowlist\_external\_urls | set\[str] | 允许 integrations 使用的外部 URL 列表 |
| media\_dirs | dict\[str, str] | integrations 可使用的媒体文件夹字典 |
| safe\_mode | bool | Home Assistant 是否正在以 safe mode 运行 |
| legacy\_templates | bool | 使用旧的模板行为 |
它还提供了若干 helper 方法。
---
url: /developers/dev_101_events.md
---
Home Assistant 的核心由事件驱动。这意味着,如果你想对某件事做出响应,就必须对事件做出响应。大多数情况下你不会直接与 event system 交互,而是使用其中一个 [event listener helpers][helpers]。
Event system 非常灵活。对 event type 没有限制,只要它是一个字符串。每个 event 都可以包含 data。该 data 是一个字典,可以包含任何数据,只要它是 JSON 可序列化的。这意味着你可以使用数字、字符串、字典和列表。
[Home Assistant 发出的事件列表。][object]
## 发出事件(Firing events)
要发出事件,你需要与 event bus 交互。Event bus 在 Home Assistant 实例上以 `hass.bus` 的形式提供。请注意 [Data Science portal](https://data.home-assistant.io/docs/events/#database-table) 上记录的 data 结构。
以下是一个 component 示例,加载时会发出事件。注意自定义 event 名称以前缀 component 名称开头。
```python
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType
DOMAIN = "example_component"
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up is called when Home Assistant is loading our component."""
# Fire event example_component_my_cool_event with event data answer=42
hass.bus.fire("example_component_my_cool_event", {"answer": 42})
# Return successful setup
return True
```
## 监听事件(Listening to events)
大多数情况下你不会发出事件,而是监听事件。例如,实体的 state 变更会以事件形式广播。
```python
from homeassistant.core import Event, HomeAssistant
from homeassistant.helpers.typing import ConfigType
DOMAIN = "example_component"
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up is called when Home Assistant is loading our component."""
count = 0
# Listener to handle fired events
def handle_event(event: Event) -> None:
nonlocal count
count += 1
print(f"Answer {count} is: {event.data.get('answer')}")
# Listen for when example_component_my_cool_event is fired
hass.bus.listen("example_component_my_cool_event", handle_event)
# Return successful setup
return True
```
### 辅助函数
Home Assistant 附带了许多 bundled helpers 来监听特定类型的事件。有用于跟踪某个时间点的 helper,用于跟踪时间间隔的 helper,跟踪 state 变更的 helper,以及跟踪日落的 helper。[查看可用的 methods。][helpers]
[helpers]: https://developers.home-assistant.io/docs/integration_listen_events#available-event-helpers
[object]: https://www.home-assistant.io/docs/configuration/events/
---
url: /developers/dev_101_hass.md
---
在开发 Home Assistant 时,你会看到一个无处不在的变量:`hass`。这是 Home Assistant 的实例,通过它可以访问系统的各个部分。
### The `hass` object
Home Assistant 实例包含四个对象,帮助你与系统进行交互。
| Object | Description |
| ------ | ----------- |
| `hass` | 这是 Home Assistant 的实例。允许启动、停止以及排队新任务(jobs)。 |
| `hass.config` | 这是 Home Assistant 的核心配置,暴露位置、温度偏好和 config 目录路径。 |
| `hass.states` | 这是 StateMachine。允许你设置 state 并跟踪它们的变更。[了解更多。](https://developers.home-assistant.io/docs/dev_101_states) |
| `hass.bus` | 这是 EventBus。允许你触发事件和监听事件。[了解更多。](https://developers.home-assistant.io/docs/dev_101_events) |
| `hass.services` | 这是 ServiceRegistry。允许你注册 service actions。[了解更多。](https://developers.home-assistant.io/docs/dev_101_services) |
### Where to find `hass`
根据你编写的内容不同,`hass` 对象的提供方式也有所不同。
**Component**
通过 `setup(hass, config)` 或 `async_setup(hass, config)` 传入。
**Platform**
通过 `setup_platform(hass, config, add_entities, discovery_info=None)` 或 `async_setup_platform(hass, config, async_add_entities, discovery_info=None)` 传入。
**Entity**
在 platform 内部通过 `add_entities` 回调添加 entity 后,可作为 `self.hass` 使用。
---
url: /developers/dev_101_services.md
---
Home Assistant 提供了许多现成的 actions,但并非总能覆盖一切。与其试图更改 Home Assistant,更好的做法是先将其作为 service action 添加到你自己的 integration 下。一旦我们在这些 service actions 中看到某种模式,再讨论如何将其通用化。
\[Service actions 应始终注册](/docs/core/integration-quality-scale/rules/action-setup),以确保引用它们的 automations 可以被编辑和验证,并且即使 integration 没有加载的 config entries,在调用 service 时也能给出说明性的错误信息。请在 integration 的 `async_setup` 或 `setup` 函数中注册 services,而不是在 integration 的 `async_setup_entry` 或 platform 的 `async_setup_entry`、`async_setup_platform`、`setup_platform` 中注册。
这是一个简单的 "hello world" 示例,用于展示注册 service action 的基本方法。要使用此示例,创建文件 `/custom_components/hello_action/__init__.py` 并复制下面的示例代码。
Actions 可以从 automations 调用,也可以从前端的 **Tools** 中的 **Actions** 标签页调用。
```python
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.helpers.typing import ConfigType
DOMAIN = "hello_action"
ATTR_NAME = "name"
DEFAULT_NAME = "World"
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up is called when Home Assistant is loading our component."""
@callback
def handle_hello(call: ServiceCall) -> None:
"""Handle the service action call."""
name = call.data.get(ATTR_NAME, DEFAULT_NAME)
hass.states.async_set("hello_action.hello", name)
hass.services.async_register(DOMAIN, "hello", handle_hello)
# Return boolean to indicate that initialization was successful.
return True
```
要在 Home Assistant 中加载该 integration,需要创建一个 `manifest.json`,并在 `configuration.yaml` 中添加一个条目。当 component 加载后,应该会出现一个可调用 service。
```yaml
# configuration.yaml entry
hello_action:
```
`manifest.json` 示例:
```json
{
"domain": "hello_action",
"name": "Hello Action",
"documentation": "https://developers.home-assistant.io/docs/dev_101_services",
"iot_class": "local_push",
"version": "0.1.0"
}
```
打开前端,在侧边栏中点击 developer tool 区域中的第一个图标。这会打开 Actions developer tool。在右侧找到你的 action 并点击它,会自动填入正确的值。
现在按下 "Perform action" 将以不带任何参数的方式调用你的 service action。这会让你的 service action 创建一个名为 'World' 的默认 state。如果你想指定名称,需要通过 service action Data 提供参数。在 YAML 模式下,添加以下内容,然后再次按下 "Perform Service"。
```yaml
service: hello_action.hello
data:
name: Planet
```
Service action 现在会用 "Planet" 覆盖之前的 state。
## Service action 描述
添加 actions 只有在用户知道它们时才有用。在 Home Assistant 中,我们通过将 `services.yaml` 作为 integration 的一部分来描述 service actions。
Actions 发布在 integration 的 domain 名称下,因此在 `services.yaml` 中我们只使用 service action 名称作为 base key。
### Service action description 示例
```yaml
# Example services.yaml entry
# Service ID
set_speed:
# If the service action accepts entity IDs, target allows the user to specify
# entities by entity, device, or area. If `target` is specified, `entity_id`
# should not be defined in the `fields` map. By default it shows only targets
# matching entities from the same domain as the action, but if further
# customization is required, target supports the entity, device, and area
# selectors (https://www.home-assistant.io/docs/blueprint/selectors/).
# Entity selector parameters will automatically be applied to device and area,
# and device selector parameters will automatically be applied to area.
target:
entity:
domain: fan
# If not all entities from the action's domain support an action, entities
# can be further filtered by the `supported_features` state attribute. An
# entity will only be possible to select if it supports at least one of the
# listed supported features.
supported_features:
- fan.FanEntityFeature.SET_SPEED
# If a service action requires more than one supported feature, the item
# should be given as a list of required supported features. For example,
# if the service action requires both SET_SPEED and OSCILLATE it would
# be expressed like this
- - fan.FanEntityFeature.SET_SPEED
- fan.FanEntityFeature.OSCILLATE
# Different fields that your service action accepts
fields:
# Key of the field
speed:
# Whether or not field is required (default = false)
required: true
# Example value that can be passed for this field
example: "low"
# The default field value
default: "high"
# Selector (https://www.home-assistant.io/docs/blueprint/selectors/) to control
# the input UI for this field
selector:
select:
translation_key: "fan_speed"
options:
- "off"
- "low"
- "medium"
- "high"
# Fields can be grouped in collapsible sections, this is useful to initially hide
# less commonly used fields and to group related fields. Note that the collapsible section
# only affect presentation to the user, service action data will not be nested.
additional_fields:
# Whether or not the section is initially collapsed (default = false)
collapsed: true
# Input fields in this section
fields:
speed_pct:
selector:
number:
min: 0
max: 100
```
:::info
Service actions 的名称和描述在我们的 [translations](/developers/internationalization/core.md) 中设置,而不是在 service action description 中。每个 service action 和 service action field 都必须有对应的 translation 定义。Description placeholders 允许你排除 URL 等元素而不翻译。
```python
...
hass.services.async_register(
DOMAIN,
"hello", handle_hello,
description_placeholders={"docs_url": "https://example.com/hello_world"},
)
...
```
:::
### Service action fields 的分组
输入字段可以在 sections 中按视觉进行分组。将输入字段按 section 分组只影响向用户展示输入方式,不影响 service action data 的结构。
在 [service action description 示例](#service-action-description-example) 中,`speed_pct` 输入字段位于一个默认折叠的 section `additional_fields` 内。
示例中该 service 的 service action data 是 `{"speed_pct": 50}`,而不是
`{"additional_fields": {"speed_pct": 50}}`。
### Service action fields 的过滤
在某些情况下,action 的 domain 中的实体可能不支持所有 service action fields。
通过为 field description 提供 `filter`,只有当至少有一个选中的实体按照配置的 filter 支持该字段时,该字段才会显示。
filter 必须指定 `supported_features` 或 `attribute` 其中之一,不能同时使用两者。
`supported_features` filter 通过一个 supported features 列表来指定。只有当至少一个选中的实体支持所列特征之一时,字段才会显示。
`attribute` filter 将一个 attribute 与一个 values 列表组合起来。只有当至少一个选中的实体的 attribute 被设置为所列的 attribute states 之一时,字段才会显示。如果 attribute state 是一个列表,只有当选中的实体的 attribute state 中至少有一个项被设置为所列的 attribute states 之一时,字段才会显示。
以下是一个字段的局部示例,只有当至少一个选中的实体支持 `ClimateEntityFeature.TARGET_TEMPERATURE` 时才会显示该字段:
```yaml
fields:
temperature:
name: Temperature
description: New target temperature for HVAC.
filter:
supported_features:
- climate.ClimateEntityFeature.TARGET_TEMPERATURE
```
以下是一个字段的局部示例,只有当至少一个选中的实体的 `supported_color_modes` attribute 包含 `light.ColorMode.COLOR_TEMP` 或
`light.ColorMode.HS` 时才会显示该字段:
```yaml
color_temp:
name: Color temperature
description: Color temperature for the light in mireds.
filter:
attribute:
supported_color_modes:
- light.ColorMode.COLOR_TEMP
- light.ColorMode.HS
```
## 图标
Actions 也可以有 icons。这些 icons 在 Home Assistant UI 中显示 service action 的地方使用,比如在 automation 和 script 编辑器中。
每个 service action 要使用的 icon 可以在 integration 文件夹的 `icons.json` translation 文件中定义,位于 `services` key 下。key 应该是 service action 名称,value 应该是要使用的 icon。
以下示例展示如何为 integration 的 `turn_on` 和 `turn_off` service actions 提供 icons:
```json
{
"services": {
"turn_on": {"service": "mdi:lightbulb-on"},
"turn_off": {"service": "mdi:lightbulb-off"}
}
}
```
此外,也可以为 collapsible sections 选择性地指定 icon。
以下示例展示如何为 `additional_options` section 提供 icon:
```json
{
"services": {
"start_brewing": {
"service": "mdi:flask",
"sections": {
"additional_options": "mdi:test-tube"
}
}
}
}
```
## 为 service actions 选择合适的 target
注册 service action 时,应将其指向 [data hierarchy](/developers/architecture/devices-and-services.md#entity-data-hierarchy) 中该 action 真正需要运行的层级。不要指向比所需层级更高或更低的层级,即使这些层级可以互相解析。
* **Entity level** — 如果 service action 是对某个特定实体进行操作或需要某个特定实体才能工作,请使用 `entity_id` 作为 target。例如控制一个 light。将这些注册为 [entity service actions](#entity-service-actions)。
* **Device level** — 如果 service action 是对整个设备进行操作,并且需要一个 device entry(而不是某个特定实体)才能工作,请使用 `device_id` 字段作为 target。即使可以将实体解析为其父级设备,也不要使用 `entity_id` 作为替代。例如,重启设备这样的 action 是针对设备本身的,而不是它暴露的任何特定实体,因此应该以设备为 target。
* **Config entry level** — 如果 service action 是对 integration 实例进行操作,并且需要一个 config entry 才能工作,请使用 `config_entry_id` 作为 target。即使可以将它们解析回 config entry,也不要使用 `device_id` 或 `entity_id` 作为替代。例如,在外部 API 中创建一个新资源的 action,该资源对整个 account 或 connection 通用,应该以代表该 account 或 connection 的 config entry 为 target,而不是以它下面的 device 或 entity 为 target。
:::tip
指导原则是:**将 target 指向 action 真正操作的对象**。如果 action 需要 device,就以 device 为 target。如果需要 config entry,就以 config entry 为 target。从较低层级进行解析(例如从 entity 查找 config entry)会引入不必要的间接关系,使 action 接口与 integration 中 data hierarchy 的假设耦合,也会让用户更难理解 action 操作的对象。
:::
:::caution
当 service action 需要一个 target 时,该 target 不应是可选的。不要在没有指定 target 时实现一个默认 target。
将 target 设为可选看起来很方便,但当配置因添加 entities 或 entries 而发生变化时,会让 automations 和 scripts 变得不可预测。要求明确的 target 可以让 action 调用在任何用户当前配置下都保持可预测。
:::
## 实体 service actions
有时你想提供额外的 actions 来控制你的 entities。例如,Sonos integration 提供了用于将 devices 分组和取消分组的 action。Entity service actions 的特殊之处在于用户有多种指定 entities 的方式。它可以是 areas、一个 group 或一个 entities 列表。
使用 `homeassistant.helpers.service.async_register_platform_entity_service` 注册 entity service actions。请在 integration domain 下注册 actions,例如 `sonos`,而不是在 platform domain 下注册,例如 `media_player`。如果 entity service action 有 fields,你可以将一个 schema 传递给 `async_register_platform_entity_service`。该 schema 可以是:
* 一个字典,会被自动传递给 `cv._make_entity_service_schema`
* `cv._make_entity_service_schema` 返回的 validator
* `cv._make_entity_service_schema` 返回的 validator,被包裹在一个 `vol.Schema` 中
* `cv._make_entity_service_schema` 返回的 validator,被包裹在一个 `vol.All` 中
添加到 `homeassistant/components/sonos/__init__.py` 的示例代码:
```python
from homeassistant.components.media_player import DOMAIN as MEDIA_PLAYER_DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType
from homeassistant.helpers import config_validation as cv, service
import voluptuous as vol
DOMAIN = "sonos"
SERVICE_SET_TIMER = "set_sleep_timer"
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Sonos integration."""
# This will call each targeted entity's `set_sleep_timer` method with `sleep_time=VALUE`
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_SET_TIMER,
entity_domain=MEDIA_PLAYER_DOMAIN,
schema={vol.Required("sleep_time"): cv.time_period},
func="set_sleep_timer",
)
return True
```
如果你需要对 service action 调用进行更精细的控制,也可以传递一个 async function,该 function 会被调用以替代 `"set_sleep_timer"`:
```python
async def custom_set_sleep_timer(entity, service_call):
await entity.set_sleep_timer(service_call.data['sleep_time'])
```
## 响应数据
Actions 可以使用 data 对 action 调用做出响应,以支撑更复杂的 automations。有以下额外的实现要求:
* Response data 必须是一个 `dict`,并且可 JSON 序列化为 [`homeassistant.util.json.JsonObjectType`](https://github.com/home-assistant/core/blob/dev/homeassistant/util/json.py),以便与系统的其他部分(如 frontend)互操作。
* Errors 必须像任何其他 service action 调用一样作为 exceptions 抛出,
因为我们不希望终端用户在 scripts 和 automations 中进行复杂的错误处理。
Response data 中不应包含用于错误处理的错误码。
示例代码:
```python
import datetime
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, ServiceCall, ServiceResponse, SupportsResponse
from homeassistant.helpers import config_validation as cv, entity_platform, service
from homeassistant.util.json import JsonObjectType
SEARCH_ITEMS_SERVICE_NAME = "search_items"
SEARCH_ITEMS_SCHEMA = vol.Schema({
vol.Required("start"): datetime.datetime,
vol.Required("end"): datetime.datetime,
})
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the integration."""
async def search_items(call: ServiceCall) -> ServiceResponse:
"""Search in the date range and return the matching items."""
items = await my_client.search(call.data["start"], call.data["end"])
return {
"items": [
{
"summary": item["summary"],
"description": item["description"],
} for item in items
],
}
hass.services.async_register(
DOMAIN,
SEARCH_ITEMS_SERVICE_NAME,
search_items,
schema=SEARCH_ITEMS_SCHEMA,
supports_response=SupportsResponse.ONLY,
)
```
Response data 的用途适用于那些不符合 Home Assistant state 的情况。例如,对象的响应流。相反,对于符合 entity state 的情况,不应使用 response data。例如,温度值应该只是一个 sensor。
### 支持 response data
Action 调用在注册时会带有一个 `SupportsResponse` 值,用于指示是否支持 response data。
| Value | Description |
| ---------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `OPTIONAL` | 执行一个 action,并可以选择性地返回 response data。Service action 应当根据 `ServiceCall` 属性 `return_response` 进行条件判断,以决定是否返回 response data,或返回 `None`。 |
| `ONLY` | 不执行任何 actions,始终返回 response data。 |
---
url: /developers/dev_101_states.md
---
Home Assistant 通过 state machine 跟踪 entities 的 states。State machine 的要求非常少:
* 每个 state 都与一个由 entity id 标识的 entity 相关联。这个 id 由 domain 和 object id 组成。例如 `light.kitchen_ceiling`。你可以任意组合 domain 和 object id,甚至可以覆盖现有的 states。
* 每个 state 都有一个主要属性,用于描述 entity 的 state。对于 light 来说,这可能是 "on" 和 "off"。你可以在 state 中存储任何你想要的内容,只要它是一个字符串(如果不是字符串,会被转换)。
* 你可以通过设置 attributes 来存储更多关于 entity 的信息。Attributes 是一个字典,可以包含你想要的任何数据。唯一的约束是它必须是 JSON 可序列化的,所以你只能使用数字、字符串、字典和列表。
[state 对象的描述。](https://www.home-assistant.io/docs/configuration/state_object/)
## 在 component 中使用 states
这是一个简单的教程/示例,介绍如何创建和设置 states。我们将在一个名为 "hello\_state" 的 component 中进行操作。这个 component 的目的是在 frontend 中显示给定的文本。
要开始,创建文件 `/custom_components/hello_state.py` 并复制下面的示例代码。
```python
"""
Support for showing text in the frontend.
For more details about this component, please refer to the documentation at
https://developers.home-assistant.io/docs/dev_101_states
"""
import logging
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger(__name__)
DOMAIN = "hello_state"
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Hello State component."""
_LOGGER.info("The 'hello state' component is ready!")
return True
```
1. 在文件头部我们决定添加一些细节:一个简短的描述和指向文档的链接。
2. 我们需要做一些 logging。这意味着我们导入 Python logging 模块并创建一个别名。
3. Component 名称与 domain 名称相同。
4. `setup` 函数负责初始化我们的 component。
该 component 只写入一条 log message。请记住以后你有多个严重性级别可选:
* `_LOGGER.info(msg)`
* `_LOGGER.warning(msg)`
* `_LOGGER.error(msg)`
* `_LOGGER.critical(msg)`
* `_LOGGER.exception(msg)`
5. 如果一切正常,我们返回 `True`。
将 component 添加到你的 `configuration.yaml` 文件。
```yaml
hello_state:
```
在 Home Assistant 启动或重启后,该 component 会在 log 中创建一条记录。
```log
16-03-12 14:16:42 INFO (MainThread) [custom_components.hello_state] The 'hello state' component is ready!
```
下一步是引入 configuration options。用户可以通过 `configuration.yaml` 将 configuration options 传递给我们的 component。要使用它们,我们将使用传入 `setup` 方法的 `config` 变量。
```python
import logging
from homeassistant.core import HomeAssistant
from homeassistant.helpers.typing import ConfigType
_LOGGER = logging.getLogger(__name__)
DOMAIN = "hello_state"
CONF_TEXT = "text"
DEFAULT_TEXT = "No text!"
def setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Hello State component. """
# Get the text from the configuration. Use DEFAULT_TEXT if no name is provided.
text = config[DOMAIN].get(CONF_TEXT, DEFAULT_TEXT)
# States are in the format DOMAIN.OBJECT_ID
hass.states.set("hello_state.Hello_State", text)
return True
```
要使用我们 component 的最新特性,请更新 `configuration.yaml` 文件中的条目。
```yaml
hello_state:
text: 'Hello, World!'
```
得益于 `DEFAULT_TEXT` 变量,即使 `configuration.yaml` 文件中没有使用 `text:` 字段,该 component 也能正常启动。很多时候有些变量是必需的。重要的是检查所有必需的 configuration variables 是否都已提供。如果没有,setup 应该失败。我们将使用 `voluptuous` 作为 helper 来实现这一点。下面的列表展示了关键部分。
```python
import voluptuous as vol
import homeassistant.helpers.config_validation as cv
CONFIG_SCHEMA = vol.Schema(
{DOMAIN: vol.Schema({vol.Required(CONF_TEXT): cv.string,})}, extra=vol.ALLOW_EXTRA
)
```
现在,当 config 中缺少 `text:` 时,Home Assistant 会提醒用户,而不会 setup 你的 component。
在 Home Assistant 启动或重启后,如果 `configuration.yaml` 文件已更新,该 component 将在 frontend 中可见。