From 4e1559b070661e26725e8398027bc30225991d84 Mon Sep 17 00:00:00 2001 From: Michael Arthur Date: Thu, 14 Nov 2024 20:34:13 +1300 Subject: [PATCH 01/55] Fix apparent power typo (#2452) --- docs/core/entity/number.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/entity/number.md b/docs/core/entity/number.md index b94a63ccca9..4f1b597fa94 100644 --- a/docs/core/entity/number.md +++ b/docs/core/entity/number.md @@ -31,7 +31,7 @@ If specifying a device class, your number entity will need to also return the co | Constant | Supported units | Description | ---- | ---- | ----------- -| `NumberDeviceClass.APPARANT_POWER` | VA | Apparent power | +| `NumberDeviceClass.APPARENT_POWER` | VA | Apparent power | | `NumberDeviceClass.AQI` | None | Air Quality Index | `NumberDeviceClass.ATMOSPHERIC_PRESSURE` | cbar, bar, hPa, inHg, kPa, mbar, Pa, psi | Atmospheric pressure, statistics will be stored in Pa. | `NumberDeviceClass.BATTERY` | % | Percentage of battery that is left From 2e3b065c12478934bf1039ddd64657a4cfc1e436 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Fri, 15 Nov 2024 15:04:04 +0100 Subject: [PATCH 02/55] Add section about updating dependencies in the Perfect PR (#2447) Co-authored-by: Joost Lekkerkerker Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> Co-authored-by: Franck Nijhof --- docs/review-process.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/docs/review-process.md b/docs/review-process.md index 0bd2f6866c0..bfe980253fd 100644 --- a/docs/review-process.md +++ b/docs/review-process.md @@ -87,6 +87,20 @@ the end-user getting your improvement faster. clear, and concise title, and add an extensive description of your change. Be sure to add a motivation (or use case) to your PR, so the reviewer can understand why you are making this change (or why you make certain decisions). + +7. **Update dependency in a standalone PR.** + When you need to bump a dependency, try to do so in a standalone PR. Only + compatibility code adjustments or small related bug fixes should be included in the + PR. If you have new features that depend on the updated dependency, these can be + added in a follow-up. This will also make CI iterations run a lot faster when reviewing + the new features or larger bug fixes, as it restricts the tests to a single integration. + Ensure that the PR description contains at least one (or multiple) of the following: + - A link to the release notes of this package version, and all versions in between. + - A link to the changelog of this package. + - A link to a Git(Hub) diff/compare view from the current version to the bumped version. + This allows us to review upstream changes, which is needed to decide if this change is + working as intended and/or if we can include it in, for example, a patch release of + Home Assistant. ## Receiving review comments From 343fbacc624d86748f58013d8ce38341c2b97608 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Tue, 19 Nov 2024 08:31:51 +0100 Subject: [PATCH 03/55] Bump cross-spawn from 7.0.3 to 7.0.6 (#2458) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index cf5f2abc75c..242e670ffad 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3702,9 +3702,9 @@ cosmiconfig@^8.1.3, cosmiconfig@^8.2.0: path-type "^4.0.0" cross-spawn@^7.0.0, cross-spawn@^7.0.3: - version "7.0.3" - resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.3.tgz#f73a85b9d5d41d045551c177e2882d4ac85728a6" - integrity sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w== + version "7.0.6" + resolved "https://registry.yarnpkg.com/cross-spawn/-/cross-spawn-7.0.6.tgz#8a58fe78f00dcd70c370451759dfbfaf03e8ee9f" + integrity sha512-uV2QOWP2nWzsy2aMp8aRibhi9dlzF5Hgh5SHaB9OiTGEyDTiJJyx0uy51QXdyWbtAHNua4XJzUKca3OzKUd3vA== dependencies: path-key "^3.1.0" shebang-command "^2.0.0" From a1f286224a8d6b2759a48314e31448b0ab9d7d9e Mon Sep 17 00:00:00 2001 From: Manu <4445816+tr4nt0r@users.noreply.github.com> Date: Tue, 19 Nov 2024 22:38:36 +0100 Subject: [PATCH 04/55] =?UTF-8?q?Add=20=C2=B5V=20unit=20for=20voltage=20(#?= =?UTF-8?q?2456)?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- docs/core/entity/number.md | 2 +- docs/core/entity/sensor.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core/entity/number.md b/docs/core/entity/number.md index 4f1b597fa94..6f09384bca9 100644 --- a/docs/core/entity/number.md +++ b/docs/core/entity/number.md @@ -72,7 +72,7 @@ If specifying a device class, your number entity will need to also return the co | `NumberDeviceClass.SULPHUR_DIOXIDE` | µg/m³ | Concentration of sulphure dioxide | | `NumberDeviceClass.TEMPERATURE` | °C, °F, K | Temperature. | `NumberDeviceClass.VOLATILE_ORGANIC_COMPOUNDS` | µg/m³ | Concentration of volatile organic compounds -| `NumberDeviceClass.VOLTAGE` | V, mV | Voltage +| `NumberDeviceClass.VOLTAGE` | V, mV, µV | Voltage | `NumberDeviceClass.VOLUME` | L, mL, gal, fl. oz., m³, ft³, CCF | Generic volume, this device class should be used to represent a consumption, for example the amount of fuel consumed by a vehicle. | `NumberDeviceClass.VOLUME_FLOW_RATE` | m³/h, ft³/min, L/min, gal/min | Volume flow rate, this device class should be used for sensors representing a flow of some volume, for example the amount of water consumed momentarily. | `NumberDeviceClass.VOLUME_STORAGE` | L, mL, gal, fl. oz., m³, ft³, CCF | Generic stored volume, this device class should be used to represent a stored volume, for example the amount of fuel in a fuel tank. diff --git a/docs/core/entity/sensor.md b/docs/core/entity/sensor.md index 0cdb7a54852..0d5300d128c 100644 --- a/docs/core/entity/sensor.md +++ b/docs/core/entity/sensor.md @@ -78,7 +78,7 @@ If specifying a device class, your sensor entity will need to also return the co | `SensorDeviceClass.TIMESTAMP` | | Timestamp. Requires `native_value` to return a Python `datetime.datetime` object, with time zone information, or `None`. | `SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS` | µg/m³ | Concentration of volatile organic compounds | `SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS` | ppm, ppb | Ratio of volatile organic compounds -| `SensorDeviceClass.VOLTAGE` | V, mV | Voltage +| `SensorDeviceClass.VOLTAGE` | V, mV, µV | Voltage | `SensorDeviceClass.VOLUME` | L, mL, gal, fl. oz., m³, ft³, CCF | Generic volume, this device class should be used for sensors representing a consumption, for example the amount of fuel consumed by a vehicle. | `SensorDeviceClass.VOLUME_FLOW_RATE` | m³/h, ft³/min, L/min, gal/min | Volume flow rate, this device class should be used for sensors representing a flow of some volume, for example the amount of water consumed momentarily. | `SensorDeviceClass.VOLUME_STORAGE` | L, mL, gal, fl. oz., m³, ft³, CCF | Generic stored volume, this device class should be used for sensors representing a stored volume, for example the amount of fuel in a fuel tank. From cf52c30bc0c25eb0ae1b762aca02c4e470a69b6c Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 20 Nov 2024 17:41:19 +0100 Subject: [PATCH 05/55] Add integration quality scale docs (#2457) Co-authored-by: Martin Hjelmare Co-authored-by: Franck Nijhof Co-authored-by: c0ffeeca7 <38767475+c0ffeeca7@users.noreply.github.com> --- docs/config_entries_config_flow_handler.md | 2 +- .../_includes/checklist.jsx | 39 ++++ .../_includes/rule_overview.jsx | 25 +++ .../_includes/tiers.json | 68 +++++++ .../integration-quality-scale/checklist.md | 10 + docs/core/integration-quality-scale/index.md | 186 ++++++++++++++++++ .../rules/_includes/related_rules.jsx | 20 ++ .../rules/action-exceptions.md | 47 +++++ .../rules/action-setup.md | 59 ++++++ .../rules/appropriate-polling.md | 67 +++++++ .../rules/async-dependency.md | 25 +++ .../integration-quality-scale/rules/brands.md | 14 ++ .../rules/common-modules.md | 54 +++++ .../rules/config-entry-unloading.md | 46 +++++ .../rules/config-flow-test-coverage.md | 72 +++++++ .../rules/config-flow.md | 81 ++++++++ .../rules/dependency-transparency.md | 25 +++ .../rules/devices.md | 56 ++++++ .../rules/diagnostics.md | 38 ++++ .../rules/discovery-update-info.md | 68 +++++++ .../rules/discovery.md | 126 ++++++++++++ .../rules/docs-actions.md | 27 +++ .../rules/docs-configuration-parameters.md | 30 +++ .../rules/docs-data-update.md | 24 +++ .../rules/docs-examples.md | 26 +++ .../rules/docs-high-level-description.md | 20 ++ .../rules/docs-installation-instructions.md | 27 +++ .../rules/docs-installation-parameters.md | 28 +++ .../rules/docs-known-limitations.md | 21 ++ .../rules/docs-removal-instructions.md | 19 ++ .../rules/docs-supported-devices.md | 31 +++ .../rules/docs-supported-functions.md | 88 +++++++++ .../rules/docs-troubleshooting.md | 48 +++++ .../rules/docs-use-cases.md | 21 ++ .../rules/dynamic-devices.md | 73 +++++++ .../rules/entity-category.md | 30 +++ .../rules/entity-device-class.md | 54 +++++ .../rules/entity-disabled-by-default.md | 54 +++++ .../rules/entity-event-setup.md | 61 ++++++ .../rules/entity-translations.md | 66 +++++++ .../rules/entity-unavailable.md | 88 +++++++++ .../rules/entity-unique-id.md | 45 +++++ .../rules/exception-translations.md | 63 ++++++ .../rules/has-entity-name.md | 68 +++++++ .../rules/icon-translations.md | 64 ++++++ .../rules/inject-websession.md | 39 ++++ .../rules/integration-owner.md | 39 ++++ .../rules/log-when-unavailable.md | 91 +++++++++ .../rules/parallel-updates.md | 43 ++++ .../rules/reauthentication-flow.md | 104 ++++++++++ .../rules/reconfiguration-flow.md | 100 ++++++++++ .../rules/repair-issues.md | 48 +++++ .../rules/runtime-data.md | 48 +++++ .../rules/stale-devices.md | 90 +++++++++ .../rules/strict-typing.md | 24 +++ .../rules/test-before-configure.md | 76 +++++++ .../rules/test-before-setup.md | 55 ++++++ .../rules/test-coverage.md | 26 +++ .../rules/unique-config-entry.md | 118 +++++++++++ docs/integration_quality_scale_index.md | 68 ------- sidebars.js | 24 ++- static/_redirects | 1 + 62 files changed, 3128 insertions(+), 70 deletions(-) create mode 100644 docs/core/integration-quality-scale/_includes/checklist.jsx create mode 100644 docs/core/integration-quality-scale/_includes/rule_overview.jsx create mode 100644 docs/core/integration-quality-scale/_includes/tiers.json create mode 100644 docs/core/integration-quality-scale/checklist.md create mode 100644 docs/core/integration-quality-scale/index.md create mode 100644 docs/core/integration-quality-scale/rules/_includes/related_rules.jsx create mode 100644 docs/core/integration-quality-scale/rules/action-exceptions.md create mode 100644 docs/core/integration-quality-scale/rules/action-setup.md create mode 100644 docs/core/integration-quality-scale/rules/appropriate-polling.md create mode 100644 docs/core/integration-quality-scale/rules/async-dependency.md create mode 100644 docs/core/integration-quality-scale/rules/brands.md create mode 100644 docs/core/integration-quality-scale/rules/common-modules.md create mode 100644 docs/core/integration-quality-scale/rules/config-entry-unloading.md create mode 100644 docs/core/integration-quality-scale/rules/config-flow-test-coverage.md create mode 100644 docs/core/integration-quality-scale/rules/config-flow.md create mode 100644 docs/core/integration-quality-scale/rules/dependency-transparency.md create mode 100644 docs/core/integration-quality-scale/rules/devices.md create mode 100644 docs/core/integration-quality-scale/rules/diagnostics.md create mode 100644 docs/core/integration-quality-scale/rules/discovery-update-info.md create mode 100644 docs/core/integration-quality-scale/rules/discovery.md create mode 100644 docs/core/integration-quality-scale/rules/docs-actions.md create mode 100644 docs/core/integration-quality-scale/rules/docs-configuration-parameters.md create mode 100644 docs/core/integration-quality-scale/rules/docs-data-update.md create mode 100644 docs/core/integration-quality-scale/rules/docs-examples.md create mode 100644 docs/core/integration-quality-scale/rules/docs-high-level-description.md create mode 100644 docs/core/integration-quality-scale/rules/docs-installation-instructions.md create mode 100644 docs/core/integration-quality-scale/rules/docs-installation-parameters.md create mode 100644 docs/core/integration-quality-scale/rules/docs-known-limitations.md create mode 100644 docs/core/integration-quality-scale/rules/docs-removal-instructions.md create mode 100644 docs/core/integration-quality-scale/rules/docs-supported-devices.md create mode 100644 docs/core/integration-quality-scale/rules/docs-supported-functions.md create mode 100644 docs/core/integration-quality-scale/rules/docs-troubleshooting.md create mode 100644 docs/core/integration-quality-scale/rules/docs-use-cases.md create mode 100644 docs/core/integration-quality-scale/rules/dynamic-devices.md create mode 100644 docs/core/integration-quality-scale/rules/entity-category.md create mode 100644 docs/core/integration-quality-scale/rules/entity-device-class.md create mode 100644 docs/core/integration-quality-scale/rules/entity-disabled-by-default.md create mode 100644 docs/core/integration-quality-scale/rules/entity-event-setup.md create mode 100644 docs/core/integration-quality-scale/rules/entity-translations.md create mode 100644 docs/core/integration-quality-scale/rules/entity-unavailable.md create mode 100644 docs/core/integration-quality-scale/rules/entity-unique-id.md create mode 100644 docs/core/integration-quality-scale/rules/exception-translations.md create mode 100644 docs/core/integration-quality-scale/rules/has-entity-name.md create mode 100644 docs/core/integration-quality-scale/rules/icon-translations.md create mode 100644 docs/core/integration-quality-scale/rules/inject-websession.md create mode 100644 docs/core/integration-quality-scale/rules/integration-owner.md create mode 100644 docs/core/integration-quality-scale/rules/log-when-unavailable.md create mode 100644 docs/core/integration-quality-scale/rules/parallel-updates.md create mode 100644 docs/core/integration-quality-scale/rules/reauthentication-flow.md create mode 100644 docs/core/integration-quality-scale/rules/reconfiguration-flow.md create mode 100644 docs/core/integration-quality-scale/rules/repair-issues.md create mode 100644 docs/core/integration-quality-scale/rules/runtime-data.md create mode 100644 docs/core/integration-quality-scale/rules/stale-devices.md create mode 100644 docs/core/integration-quality-scale/rules/strict-typing.md create mode 100644 docs/core/integration-quality-scale/rules/test-before-configure.md create mode 100644 docs/core/integration-quality-scale/rules/test-before-setup.md create mode 100644 docs/core/integration-quality-scale/rules/test-coverage.md create mode 100644 docs/core/integration-quality-scale/rules/unique-config-entry.md delete mode 100644 docs/integration_quality_scale_index.md diff --git a/docs/config_entries_config_flow_handler.md b/docs/config_entries_config_flow_handler.md index 0a87b9b2e27..09bb503d7e3 100644 --- a/docs/config_entries_config_flow_handler.md +++ b/docs/config_entries_config_flow_handler.md @@ -308,7 +308,7 @@ Ensuring that the `unique_id` is unchanged should be done using `await self.asyn ## Reauthentication -Gracefully handling authentication errors such as invalid, expired, or revoked tokens is needed to advance on the [Integration Quality Scale](integration_quality_scale_index.md). This example of how to add reauth to the OAuth flow created by `script.scaffold` following the pattern in [Building a Python library](api_lib_auth.md#oauth2). +Gracefully handling authentication errors such as invalid, expired, or revoked tokens is needed to advance on the [Integration Quality Scale](core/integration-quality-scale). This example of how to add reauth to the OAuth flow created by `script.scaffold` following the pattern in [Building a Python library](api_lib_auth.md#oauth2). If you are looking for how to trigger the reauthentication flow, see [handling expired credentials](integration_setup_failures.md#handling-expired-credentials). This example catches an authentication exception in config entry setup in `__init__.py` and instructs the user to visit the integrations page in order to reconfigure the integration. diff --git a/docs/core/integration-quality-scale/_includes/checklist.jsx b/docs/core/integration-quality-scale/_includes/checklist.jsx new file mode 100644 index 00000000000..e26c397e70b --- /dev/null +++ b/docs/core/integration-quality-scale/_includes/checklist.jsx @@ -0,0 +1,39 @@ +import React from 'react'; +import {useDocsVersion} from '@docusaurus/plugin-content-docs/client'; +import CodeBlock from '@theme/CodeBlock'; + +const tiers = require("./tiers.json") + +function getRule(ruleId, docs) { + const rule = docs[`core/integration-quality-scale/rules/${ruleId.toLowerCase()}`]; + const [id, text] = rule.title.split(" ("); + return {id, text}; +} + +export default function Checklist() { + const docs = useDocsVersion().docs; + const getRuleWithDocs = (ruleId) => getRule(ruleId, docs); + return ( + + {Object.keys(tiers).map((tier) => { + return ( +
+ {`## ${tier.charAt(0).toUpperCase() + tier.slice(1)}\n`} + {tiers[tier].map((rule) => { + if (typeof rule === "string") { + const text = docs[`core/integration-quality-scale/rules/${rule}`].title; + return `- [ ] \`${rule}\` - ${text}\n`; + } + const text = docs[`core/integration-quality-scale/rules/${rule.id}`].title; + return [ + `- [ ] \`${rule.id}\` - ${text}\n`, + ...rule.subchecks.map(subcheck => ` - [ ] ${subcheck}\n`) + ].join(''); + }).join('')} + {`\n`} +
+ ) + })} +
+ ); +} \ No newline at end of file diff --git a/docs/core/integration-quality-scale/_includes/rule_overview.jsx b/docs/core/integration-quality-scale/_includes/rule_overview.jsx new file mode 100644 index 00000000000..a3cf1d5cacc --- /dev/null +++ b/docs/core/integration-quality-scale/_includes/rule_overview.jsx @@ -0,0 +1,25 @@ +import React from 'react'; +import {useDocsVersion} from '@docusaurus/plugin-content-docs/client'; +import Link from '@docusaurus/Link'; + +const tiers = require("./tiers.json") + +export default function RuleOverview({tier}) { + const docs = useDocsVersion().docs; + return ( +
    + {tiers[tier].map((rule) => { + let id = rule; + if (typeof rule === "object") { + id = rule.id; + } + const relatedRule = docs[`core/integration-quality-scale/rules/${id}`]; + return ( +
  • + {id} - {relatedRule.title} +
  • + ); + })} +
+ ); +} \ No newline at end of file diff --git a/docs/core/integration-quality-scale/_includes/tiers.json b/docs/core/integration-quality-scale/_includes/tiers.json new file mode 100644 index 00000000000..42f6fdd234b --- /dev/null +++ b/docs/core/integration-quality-scale/_includes/tiers.json @@ -0,0 +1,68 @@ +{ + "bronze": [ + { + "id": "config-flow", + "subchecks": [ + "Uses `data-description` to give context to fields", + "Uses `ConfigEntry.data` and `ConfigEntry.options` correctly" + ] + }, + "test-before-configure", + "unique-config-entry", + "config-flow-test-coverage", + "runtime-data", + "test-before-setup", + "appropriate-polling", + "entity-unique-id", + "has-entity-name", + "entity-event-setup", + "dependency-transparency", + "action-setup", + "common-modules", + "docs-high-level-description", + "docs-installation-instructions", + "docs-removal-instructions", + "docs-actions", + "brands" + ], + "silver": [ + "config-entry-unloading", + "log-when-unavailable", + "entity-unavailable", + "action-exceptions", + "reauthentication-flow", + "parallel-updates", + "test-coverage", + "integration-owner", + "docs-installation-parameters", + "docs-configuration-parameters" + ], + "gold": [ + "entity-translations", + "entity-device-class", + "devices", + "entity-category", + "entity-disabled-by-default", + "discovery", + "stale-devices", + "diagnostics", + "exception-translations", + "icon-translations", + "reconfiguration-flow", + "dynamic-devices", + "discovery-update-info", + "repair-issues", + "docs-use-cases", + "docs-supported-devices", + "docs-supported-functions", + "docs-data-update", + "docs-known-limitations", + "docs-troubleshooting", + "docs-examples" + ], + "platinum": [ + "async-dependency", + "inject-websession", + "strict-typing" + ] +} \ No newline at end of file diff --git a/docs/core/integration-quality-scale/checklist.md b/docs/core/integration-quality-scale/checklist.md new file mode 100644 index 00000000000..c36901828f4 --- /dev/null +++ b/docs/core/integration-quality-scale/checklist.md @@ -0,0 +1,10 @@ +--- +title: "Checklist" +--- +import Checklist from './_includes/checklist.jsx' + +When changing the quality scale of an integration, make sure you have completed the rules before opening the PR to change the quality scale. +In the PR description, please deliver a copy of this checklist and mark the rules that have been completed. +Make sure you add links to the relevant code to help a speedy grading process. + + diff --git a/docs/core/integration-quality-scale/index.md b/docs/core/integration-quality-scale/index.md new file mode 100644 index 00000000000..850e5881e79 --- /dev/null +++ b/docs/core/integration-quality-scale/index.md @@ -0,0 +1,186 @@ +--- +title: "Integration quality scale" +--- +import RuleOverview from './_includes/rule_overview.jsx' + +The integration quality scale is a framework for Home Assistant to grade integrations based on user experience, features, code quality and developer experience. +To grade this, the project has come up with a set of tiers, which all have their own meaning. + +## Scaled tiers +There are 4 scaled tiers, bronze, silver, gold, and platinum. +To reach a tier, the integration must fulfill all rules of that tier and the tiers below. + +These tiers are defined as follows. + +### 🥉 Bronze +The bronze tier is the baseline standard and requirement for all new integrations. It meets the minimum requirements in code quality, functionality, and user experience. It complies with the fundamental expectations and provides a reliable foundation for users to interact with their devices and services. + +The documentation provides guidelines for setting up the integration directly from the Home Assistant user interface. + +From a technical perspective, this integration has been reviewed to comply with all baseline standards, which we require for all new integrations, including automated tests for setting up the integration. + +The bronze tier has the following characteristics: +- Can be easily set up through the UI. +- The source code adheres to basic coding standards and development guidelines. +- Automated tests that guard this integration can be configured correctly. +- Offers basic end-user documentation that is enough to get users started step-by-step easily. + +### 🥈 Silver +The silver tier builds upon the “Bronze” level by improving the reliability and robustness of integrations, ensuring a solid runtime experience. It ensures an integration handles errors properly, such as when authentication to a device or service fails, handles offline devices, and other errors. + +The documentation for these integrations provides information on what is available in Home Assistant when this integration is used, as well as troubleshooting information when issues occur. + +This integration has one or more active code owners who help maintain it to ensure the experience on this level lasts now and in the future. + +The silver tier has the following characteristics: +- Provides everything “Bronze” has. +- Provides a stable user experience under various conditions. +- Has one or more active code owners who help maintain the integration. +- Correctly and automatically recover from connection errors or offline devices, without filling log files and without unnecessary messages. +- Automatically triggers re-authentication if authentication with the device or service fails. +- Offers detailed documentation of what the integration provides and instructions for troubleshooting issues. + +### 🥇 Gold +The gold standard in integration user experience, providing extensive and comprehensive support for the integrated devices & services. A gold-tier integration aims to be user-friendly, fully featured, and accessible to a wider audience. + +When possible, devices are automatically discovered for an easy and seamless setup, and their firmware/software can be directly updated from Home Assistant. + +All provided devices and entities are named logically and fully translatable, and they have been properly categorized and enabled for long-term statistical use. + +The documentation for these integrations is extensive, and primarily aimed toward end-users and understandable by non-technical consumers. Besides providing general information on the integration, the documentation provides possible example use cases, a list of compatible devices, a list of described entities the integration provides, and extensive descriptions and usage examples of available actions provided by the integration. The use of example automations, dashboards, available Blueprints, and links to additional external resources, is highly encouraged as well. + +The integration provides means for debugging issues, including downloading diagnostic information and documenting troubleshooting instructions. If needed, the integration can be reconfigured via the UI. + +From a technical perspective, the integration needs to have full automated test coverage of its codebase to ensure the set integration quality is maintained now and in the future. + +All integrations that have devices in the Works with Home Assistant program are at least required to have this tier. + +The gold tier has the following characteristics: +- Provides everything “Silver” has. +- Has the best end-user experience an integration can offer; streamlined and intuitive. +- Can be automatically discovered, simplifying the integration setup. +- Integration can be reconfigured and adjusted. +- Supports translations. +- Extensive documentation, aimed at non-technical users. +- It supports updating the software/firmware of devices through Home Assistant when possible. +- The integration has automated tests covering the entire integration. +- Required level for integrations providing devices in the Works with Home Assistant program. + +### 🏆 Platinum +Platinum is the highest tier an integration can reach, the epitome of quality within Home Assistant. It not only provides the best user experience but also achieves technical excellence by adhering to the highest standards, supreme code quality, and well-optimized performance and efficiency. + +The platinum tier has the following characteristics: +- Provides everything “Gold” has. +- All source code follows all coding and Home Assistant integration standards and best practices and is fully typed with type annotations and clear code comments for better code clarity and maintenance. +- A fully asynchronous integration code base ensures efficient operation. +- Implements efficient data handling, reducing network and CPU usage. + + +### Keeping track of the implemented rules +Integrations that are working towards a higher tier or have a tier, must add a `quality_scale.yaml` file to their integration. +The purpose of this file is to keep track of the progress of the rules that have been implemented and to keep track of exempted rules and the reason for the exemption. +An example of this file looks like this: + +```yaml +rules: + config_flow: done + docs_high_level_description: + status: exempt + comment: This integration does not connect to any device or service. +``` + +### Adjusting the tier of an integration +Home Assistant encourages our contributors to get their integrations to the highest possible tier, to provide an excellent coding experience for our contributors and the best experience for our users. + +When an integration reaches the minimum requirements for a certain tier, a contributor can open a pull request to adjust the scale for the integration. +This request needs to be accompanied by the full checklist for each rule of scale (including all rules of lower tiers), demonstrating that it has met those requirements. +The checklist can be found [here](checklist). + +Once the Home Assistant core team reviews and approves it, the integration will display the new tier as of the next major release of Home Assistant. + +Besides upgrading an integration to a higher tier on the scale, it is also possible for an integration to be downgraded to a lower tier. +This can, for example, happen when there is no longer an active integration code owner. +In this specific example, the integration will be downgraded to “Bronze”, even if it otherwise fully complies with the “Platinum” tier. + +### Adjustments to rules contained in each tier +The world of IoT and all technologies used by Home Assistant are changing at a fast pace; not just in terms of what Home Assistant can support or do, but also in terms of the software on which Home Assistant is built. Home Assistant is pioneering the technology in the industry at a fast pace. + +This also means that new insights and newly developed and adopted best practices will occur over time, resulting in new additions and improvements to the individual integration quality scale rules. + +If a tier is adjusted, all integrations in that tier need to be re-evaluated and adjusted accordingly. One exception to this is integrations that have devices that are part of the Works with Home Assistant program. Those integrations will be flagged as grandfathered into their existing tier. + +:::info +One exception to this is integrations that have devices that are part of the Works with Home Assistant program. Those integrations will be flagged as grandfathered into their existing tier. +::: + +## Integration quality scale rules +The rules for each tier are defined down below and come with its own page with examples and more information. + +### 🥉 Bronze + + + +### 🥈 Silver + + + +### 🥇 Gold + + + +### 🏆 Platinum + + + +## Special tiers +There are also 4 special tiers that are used to integration that don't have a place on the scaled tier list. +This is because they are either an internal part of core, they are not in core at all, or they don't meet the minimum requirements to be graded against the scaled tiers. + +The special tiers are defined as follows. + +### ❓ No score +These integrations can be set up through the Home Assistant user interface. The “No score” designation doesn’t imply that they are bad or buggy, instead, it indicates that they haven’t been assessed according to the quality scale or that they need some maintenance to reach the now-considered minimum “Bronze” standard. + +The “No score” tier cannot be assigned to new integrations, as they are required to have at least a “Bronze” level when introduced. The Home Assistant project encourages the community to help update these integrations without a score to meet at least the “Bronze” level requirements. + +Characteristics: +- Not yet scored or lacks sufficient information for scoring. +- Can be set up via the UI, but may need enhancements for a better experience. +- May function correctly, but hasn’t been verified against current standards. +- Documentation most often provides only basic setup steps. + +### 🏠 Internal +This tier is assigned to integrations used internally by Home Assistant. These integrations provide basic components and building blocks for Home Assistant's core program or for other integrations to build on top of it. + +Internal integrations are maintained by the Home Assistant project and subjected to strict architectural design procedures. + +Characteristics: +- Internal, built-in building blocks of the Home Assistant core program. +- Provides building blocks for other integrations to use and build on top of. +- Maintained by the Home Assistant project. + +### 💾 Legacy +Legacy integrations are older integrations that have been part of Home Assistant for many years, possibly since its inception. They can only be configured through YAML files and often lack active maintainers (code owners). These integrations might be complex to set up and do not adhere to current/modern end-user expectations in their use and features. + +The Home Assistant project encourages the community to help migrate these integrations to the UI and update them to meet modern standards, making these integrations accessible to everyone. + +Characteristics: +- Complex setup process; only configurable via YAML, without UI-based setup. +- May lack active code ownership and maintenance. +- Could be missing recent updates or bug fixes. +- Documentation may still be aimed at developers. + +### 📦 Custom +Custom integrations are developed and distributed by the community, and offer additional functionalities and support for devices and services to Home Assistant. These integrations are not included in the official Home Assistant releases and can be installed manually or via third-party tools like HACS (Home Assistant Community Store). + +The Home Assistant project does not review, security audit, maintain, or support third-party custom integrations. Users are encouraged to exercise caution and review the custom integration’s source and community feedback before installation. + +Developers are encouraged and invited to contribute their custom integration to the Home Assistant project by aligning them with the integration quality scale and submitting them for inclusion. + +Characteristics: +- Not included in the official Home Assistant releases. +- Manually installable or installable via community tools, like HACS. +- Maintained by individual developers or community members. +- User experience may vary widely. +- Functionality, security, and stability can vary widely. +- Documentation may be limited. \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/_includes/related_rules.jsx b/docs/core/integration-quality-scale/rules/_includes/related_rules.jsx new file mode 100644 index 00000000000..79b9710bc8e --- /dev/null +++ b/docs/core/integration-quality-scale/rules/_includes/related_rules.jsx @@ -0,0 +1,20 @@ +import React from 'react'; +import {useDocsVersion} from "@docusaurus/plugin-content-docs/client"; +import Link from '@docusaurus/Link'; + + +export default function RelatedRules({relatedRules}) { + const docs = useDocsVersion().docs; + return ( +
    + {relatedRules.map((rule) => { + const relatedRule = docs[`core/integration-quality-scale/rules/${rule}`].title; + return ( +
  • + {rule}: {relatedRule} +
  • + ); + })} +
+ ); +} \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/action-exceptions.md b/docs/core/integration-quality-scale/rules/action-exceptions.md new file mode 100644 index 00000000000..9f5a7768e5e --- /dev/null +++ b/docs/core/integration-quality-scale/rules/action-exceptions.md @@ -0,0 +1,47 @@ +--- +title: "Service actions raise exceptions when encountering failures" +related_rules: + - exception-translations + - action-setup +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +Things can go wrong when a service action is performed. +When this happens, the integration should raise an exception to indicate that something went wrong. +The exception message will be shown to the user in the UI, and can be used to help diagnose the issue. +The message will either be generated from the attached translation string or from the exception argument. + +## Example implementation + +When the problem is caused by incorrect usage (for example incorrect input or referencing something that does not exist) we should raise a `ServiceValidationError`. +When the problem is caused by an error in the service action itself (for example, a network error or a bug in the service), we should raise a `HomeAssistantError`. + +In this example, we show a function that is registered as a service action in Home Assistant. +If the input is incorrect (when the end date is before the start date), a `ServiceValidationError` is raised, and if we can't reach the service, we raise a `HomeAssistantError`. + +```python {6,10} 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("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 +``` + +## Additional resources + +For more info on raising exceptions, check the [documentation](../../platform/raising_exceptions). + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/action-setup.md b/docs/core/integration-quality-scale/rules/action-setup.md new file mode 100644 index 00000000000..0a68fbde205 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/action-setup.md @@ -0,0 +1,59 @@ +--- +title: "Service actions are registered in async_setup" +related_rules: + - action-exceptions +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +Integrations can add their own service actions to Home Assistant. +In the past, they have been frequently registered in the `async_setup_entry` method and removed in the `async_unload_entry` method. +The result of this is that the service actions are only available when there is a loaded entry. +This is not ideal, since this way we can't validate automations users create that use these service actions, since it is possible that the configuration entry could not be loaded. + +We rather prefer integrations to set up their service actions in the `async_setup` method. +This way we can let the user know why the service action did not work, if the targeted configuration entry is not loaded. +The validation should happen inside the service action, and should raise `ServiceValidationError` if the input is invalid. + +## Example implementation + +The example below is a snippet where the service action is registered in the `async_setup` method. +In this example, the service call requires a configuration entry id as parameter. +This is used to first fetch the configuration entry, and then check if it is loaded. +If the configuration entry does not exist or the configuration entry that we found is not loaded, we raise a relevant error which is shown to the user. + +`__init__py`: +```python {13-19} 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, + ) +``` + +## Additional resources + +For more information on how to set up service actions, see the [service documentation](../../../dev_101_services). + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/appropriate-polling.md b/docs/core/integration-quality-scale/rules/appropriate-polling.md new file mode 100644 index 00000000000..c1687588917 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/appropriate-polling.md @@ -0,0 +1,67 @@ +--- +title: "If it's a polling integration, set an appropriate polling interval" +related_rules: + - parallel-updates +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +In an ideal world, all integrations would have a push-based data interface, where the device or service would let us know when new data is available. +This would decrease the amount of requests Home Assistant would make. + +However, in the real world, many devices and services are not capable of push-based communication, so we have to resort to polling. +To do this responsibly, we should set an appropriate polling interval that will serve the majority of users. + +There is no real definition of what an appropriate polling interval is, as it depends on the device or service being polled. +For example, we should not poll an air quality sensor every 5 seconds, as the data will not change that often. +In those cases, more than 99% of the users will be fine with a polling interval of a minute or more. + +To give another example, if we poll a cloud service for solar panel data where the data is updated every hour. +It would not make sense for us to poll every minute, as the data will not change between the polls. + +For the users that do want to have more frequent updates, they can [define a custom polling interval](https://www.home-assistant.io/common-tasks/general/#defining-a-custom-polling-interval) + +## Example implementation + +There are two ways to set the polling interval. +Which one to use depends on how the integration polls for data. +When using an update coordinator, the polling interval can be set by setting the `update_interval` parameter or attribute in the coordinator. +When using the built-in entity update method, having set the `should_poll` entity attribute to `True`, the polling interval can be set by setting the `SCAN_INTERVAL` constant in the platform module. + +`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 +``` + +## Additional resources + +More information about polling can be found in the [documentation](../../../integration_fetching_data). + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/async-dependency.md b/docs/core/integration-quality-scale/rules/async-dependency.md new file mode 100644 index 00000000000..d9dadf268f7 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/async-dependency.md @@ -0,0 +1,25 @@ +--- +title: "Dependency is async" +related_rules: + - inject-websession +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +Home Assistant works with asyncio to be efficient when handling tasks. +To avoid switching context between the asyncio event loop and other threads, which is costly performance wise, ideally, your library should also use asyncio. + +This results not only in a more efficient system but the code is also more neat. + +## Additional resources + +More information on how to create a library can be found in the [documentation](../../../api_lib_index). + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/brands.md b/docs/core/integration-quality-scale/rules/brands.md new file mode 100644 index 00000000000..c660ed6c8fb --- /dev/null +++ b/docs/core/integration-quality-scale/rules/brands.md @@ -0,0 +1,14 @@ +--- +title: "Has branding assets available for the integration" +--- + +## Reasoning + +Branding assets are important for the integration to be easily recognizable and to provide a consistent look and feel across all integrations. +The project keeps track of them in the [brands repository](https://github.com/home-assistant/brands). + +The requirements on the needed assets can be found in the [readme](https://github.com/home-assistant/brands/blob/master/README.md) of the brands repository. + +## Exceptions + +There are no exceptions to this rule. \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/common-modules.md b/docs/core/integration-quality-scale/rules/common-modules.md new file mode 100644 index 00000000000..71f422f07c3 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/common-modules.md @@ -0,0 +1,54 @@ +--- +title: "Place common patterns in common modules" +--- + +## Reasoning + +The Home Assistant codebase has a few common patterns that have originated over time. +For example, the majority of new integrations use a coordinator to centralize their data fetching. +The coordinator should be placed in `coordinator.py`. +This increases consistency between integrations and makes it easier to find the coordinator for a specific integration. + +The second common pattern is the base entity. +Since a lot of integrations provide more types of entities, a base entity can prove useful to reduce code duplication. +The base entity should be placed in `entity.py`. + +The efforts done to increase consistency between integrations have a positive impact on the quality of the codebase and the developer experience. + +## Example implementation + +In this example we have a coordinator, stored in `coordinator.py`, and a base entity, stored in `entity.py`. + +`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_infp = ... +``` + +## Exceptions + +There are no exceptions to this rule. + diff --git a/docs/core/integration-quality-scale/rules/config-entry-unloading.md b/docs/core/integration-quality-scale/rules/config-entry-unloading.md new file mode 100644 index 00000000000..a714b023ce0 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/config-entry-unloading.md @@ -0,0 +1,46 @@ +--- +title: "Support config entry unloading" +related_rules: + - entity-event-setup +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +Integrations should support config entry unloading. +This allows Home Assistant to unload the integration on runtime, allowing the user to remove the integration or to reload it without having to restart Home Assistant. + +This improves the user experience, since the user can do more actions without having to restart Home Assistant. + +## Example implementation + +In the `async_unload_entry` interface function, the integration should clean up any subscriptions and close any connections opened during the setup of the integration. + +The method that has to be added to `__init__.py` looks very similar to the `async_setup_entry` method. +In this example we have a listener, stored in the `runtime_data` of the config entry, which we want to clean up to avoid memory leaks. + +`__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 +You can also use `entry.async_on_unload` to register a callback that will be called when the config entry is unloaded. +This can be useful to clean up resources without having to keep track of the removal methods yourself. +::: + +## Additional resources + +More information about config entries and their lifecycle can be found in the [config entry documentation](../../../config_entries_index). + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/config-flow-test-coverage.md b/docs/core/integration-quality-scale/rules/config-flow-test-coverage.md new file mode 100644 index 00000000000..36559a30248 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/config-flow-test-coverage.md @@ -0,0 +1,72 @@ +--- +title: "Full test coverage for the config flow" +related_rules: + - config-flow + - test-before-configure + - unique-config-entry + - discovery + - reauthentication-flow + - reconfiguration-flow +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +The config flow is the first interaction a user has with your integration. +It is important to ensure that the config flow is working as expected and that the user can set up the integration without any issues or (config flow related) errors. + +This means that we want to have **100%** test coverage for the config flow. +In those tests, we require verification that the flow is able to recover from an error to confirm that the user is able to finish the flow even if something goes wrong. + +Since we want the user to have a smooth experience using other integration flows, this rule also applies to the reconfigure, reauthentication, and options flows. + +The extra added benefit of having tests for an integration is that it introduces the developer to testing, making it easier to write tests for other parts of the integration. + +:::warning +Even though the code used to check the uniqueness of a config entry is most likely touched by the happy flow tests, make sure to also test that the flow doesn't allow adding more than one unique configuration entry to reach complete coverage. +::: + +## Example implementation + +We need to test the following scenarios for each way the config flow can be initiated, either by the user, by discovery, or by an import flow. + +The example below shows a basic happy flow initiated by the user. + +`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", + } +``` + +## Additional resources + +More information about config flows can be found in the [config flow documentation](../../../config_entries_config_flow_handler). +More information about testing integrations can be found in the [testing documentation](../../../development_testing). + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/config-flow.md b/docs/core/integration-quality-scale/rules/config-flow.md new file mode 100644 index 00000000000..2e6495996e2 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/config-flow.md @@ -0,0 +1,81 @@ +--- +title: "Integration needs to be able to be set up via the UI" +related_rules: + - test-before-configure + - unique-config-entry + - config-flow-test-coverage + - discovery + - reauthentication-flow + - reconfiguration-flow +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +Since its introduction in 2018, the config flow has become the standard way to set up integrations in Home Assistant. +They allow for a consistent user experience across integrations and provide a way to guide users through the setup process. + +Because of the better user experience, we want to make sure that all integrations are able to set up via the config flow. + +Since this is the entrypoint for users to start using an integration, we should make sure that the config flow is very user-friendly and understandable. +This means we should use the right selectors at the right place, validate the input where needed, and use `data_description` in the `strings.json` to give context about the input field. + +The integration should store all configuration in the `ConfigEntry.data` field, while all settings that are not needed for the connection to be made should be stored in the `ConfigEntry.options` field. + +## Example implementation + +To use a config flow in your integration, you need to create a `config_flow.py` file in your integration folder and set `config_flow` in your `manifest.json` to `true`. +The text that is shown in the config flow is defined in the `strings.json` file. + +`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, + ) +``` + +`string.json`: +```json +{ + "config": { + "step": { + "user": { + "data": { + "host": "Host" + }, + "data_description": { + "host": "The hostname or IP address of the MyIntegration device." + } + } + } + } +} +``` + +## Additional resources + +More information about config flows can be found in the [config flow documentation](../../../config_entries_config_flow_handler). +More information about the architecture decision around config flows can be found in [ADR-0010](https://github.com/home-assistant/architecture/blob/master/adr/0010-integration-configuration.md) + +## Exceptions + +The integrations that are exempt in [ADR-0010](https://github.com/home-assistant/architecture/blob/master/adr/0010-integration-configuration.md) are exempt from this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/dependency-transparency.md b/docs/core/integration-quality-scale/rules/dependency-transparency.md new file mode 100644 index 00000000000..7ed726eae92 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/dependency-transparency.md @@ -0,0 +1,25 @@ +--- +title: "Dependency transparency" +related_rules: + - async-dependency +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +Home Assistant uses a lot of dependencies to work. +These dependencies will be shipped with new versions of Home Assistant. +In order for the project to trust the dependencies, we have a set of requirements we want the dependencies to meet. + +- The source code of the dependency must be available under an OSI-approved license. +- The dependency must be available on PyPI. +- The package published to PyPi should be built and published inside a CI pipeline. +- The version of the dependency published on PyPI should correspond to a tagged release in an open online repository. + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/devices.md b/docs/core/integration-quality-scale/rules/devices.md new file mode 100644 index 00000000000..21566ed1e70 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/devices.md @@ -0,0 +1,56 @@ +--- +title: "The integration creates devices" +related_rules: + - has-entity-name +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +Devices, in Home Assistant, are used to group entities to represent either a single physical device or a service. +This is useful, since users usually think they add a device or a service to their system, not a single entity. +Home Assistant stores the device information in the device registry. +In order for the user to have the best experience, the information about the device should be as complete as possible. + +## Example implementation + +In this example, there is a sensor entity that defines which device it should be added to in the device registry, together with some metadata about the device. +This will provide a rich device info page, where the user can recognize the device by its name, serial number, and other fields. + +`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 +If the device represents a service, be sure to add `entry_type=DeviceEntryType.SERVICE` to the `DeviceInfo` object to mark the device as such. +::: + +## Additional resources + +More information about devices can be found in the [device](../../../device_registry_index) documentation. + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/diagnostics.md b/docs/core/integration-quality-scale/rules/diagnostics.md new file mode 100644 index 00000000000..9a16ecec4b5 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/diagnostics.md @@ -0,0 +1,38 @@ +--- +title: "Implements diagnostics" +--- + +## Reasoning + +Diagnostics are an easy way for the user to gather data about the integration and can prove useful when debugging an integration. + +We consider it a good practice to have the diagnostics implemented. +Something to keep in mind is that the diagnostics should not expose any sensitive information, such as passwords, tokens, or coordinates. + +## Example implementation + +In the following example we provide diagnostics which includes data from various sources, such as the configuration and the current state of the integration. +Since the configuration may contain sensitive information, we redact the sensitive information before returning the 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, + } +``` + +## Exceptions + +There are no exceptions to this rule. \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/discovery-update-info.md b/docs/core/integration-quality-scale/rules/discovery-update-info.md new file mode 100644 index 00000000000..333b69e02ac --- /dev/null +++ b/docs/core/integration-quality-scale/rules/discovery-update-info.md @@ -0,0 +1,68 @@ +--- +title: "Integration uses discovery info to update network information" +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +Most networks of end users are using dynamic IP addresses. +This means that devices and services can get a different IP address than the one they had when they were first set up. +To avoid the need for users to set devices to static IP addresses (which is not always possible), integrations should use the discovery information to update the network information of the device or service. + +We should only update the IP address of a device or service if the integration is sure that the device or service is the same one as set up previously. + +## Example implementation + +In the following example we have an integration that uses mDNS to discover devices. +Every time a zeroconf discovery flow is started, the integration will set the unique ID of the flow to the serial number of the device. +If the unique ID is already set, the device IP address will be updated if it has changed, and the flow will be aborted. + +`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 +If you are using DHCP discovery, and you want to receive discovery flows for updated IP addresses, be sure to register the MAC address in the device info and set `registered_devices` to `true` in the manifest. +This will create discovery flows for those devices. +::: + +## Additional resources + +To learn more information about config flows, checkout the [config flow documentation](../../../config_entries_config_flow_handler). +To learn more about network protocols and discovery, checkout the [Networking and discovery documentation](../../../network_discovery). + +## Exceptions + +The exception to this rule is that not every device can be discovered. +Integrations where the devices can't be discovered are exempt from this rule. + diff --git a/docs/core/integration-quality-scale/rules/discovery.md b/docs/core/integration-quality-scale/rules/discovery.md new file mode 100644 index 00000000000..3c0ae570160 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/discovery.md @@ -0,0 +1,126 @@ +--- +title: "Can be discovered" +related_rules: + - config-flow + - test-before-configure + - unique-config-entry + - config-flow-test-coverage +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +A lot of devices have the ability to be discovered. +This can happen using one of the following methods: +- Add-on +- Bluetooth +- DHCP +- HomeKit +- mDNS +- MQTT +- SSDP +- USB + +This is a great way to make it easier for users to find and set up devices, since they don't have to manually look up which integration to use and then enter the host. +This greatly reduces the effort required to set up a device and thus improves the user experience. + +Using a network-based setup, also allows the configuration of the integration to be updated once the device receives a new IP address. + +## Example implementation + +In the following example, the integration is discoverable using mDNS. +The device would make itself discoverable by providing a `_mydevice._tcp.local.` service. +Home Assistant will pick this up and start a discovery flow for the user. +The user will then be able to confirm the discovery and set up the integration. + +`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, + ) +``` + +## Additional resources + +To learn more information about config flows, checkout the [config flow documentation](../../../config_entries_config_flow_handler). +To learn more about discovery on network protocols, checkout the [Networking and discovery documentation](../../../network_discovery). +To learn more about discovery for bluetooth devices, checkout the [Bluetooth documentation](../../../bluetooth). + +## Exceptions + +The exception to this rule is that not every device can be discovered. +Integrations where the devices can't be discovered are exempt from this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/docs-actions.md b/docs/core/integration-quality-scale/rules/docs-actions.md new file mode 100644 index 00000000000..a26193cd0ba --- /dev/null +++ b/docs/core/integration-quality-scale/rules/docs-actions.md @@ -0,0 +1,27 @@ +--- +title: "The documentation describes the provided service actions that can be used" +--- + +## Reasoning + +Integrations can register service actions to provide functionality that is not possible with standard entities. +These service actions can be harder to use than the standard service actions, so we want to make sure that the documentation describes both what they do, and what the parameters are. + +## Example implementation + +```markdown showLineNumbers +## Actions + +The integration provides the following actions. + +### Action: Get schedule + +The `my_integration.get_schedule` service is used to fetch a schedule from the integration. + +| Data attribute | Optional | Description | +|------------------------|----------|------------------------------------------------------| +| `config_entry_id` | No | The ID of the config entry to get the schedule from. | +``` + +## Exceptions + diff --git a/docs/core/integration-quality-scale/rules/docs-configuration-parameters.md b/docs/core/integration-quality-scale/rules/docs-configuration-parameters.md new file mode 100644 index 00000000000..715b19393af --- /dev/null +++ b/docs/core/integration-quality-scale/rules/docs-configuration-parameters.md @@ -0,0 +1,30 @@ +--- +title: "The documentation describes all integration configuration options" +--- + +## Reasoning + +Integrations can provide an options flow to allow users to change integration configuration. +This rule ensures that all configuration options are documented so that users can understand what each option does and how to use it. + +## Example implementation + +The following example is for an integration with multiple configuration options, using the `configuration_basic` tag. + +```markdown showLineNumbers +## Configuration options + +The integration provides the following configuration options: + +{% configuration_basic %} +Country code: + description: You can specify the country code (NL or BE) of the country to display on the camera. +Timeframe: + description: Minutes to look ahead for precipitation forecast sensors (minimum 5, maximum 120). +{% end configuration_basic %} + +``` + +## Exceptions + +There are no exceptions to this rule. diff --git a/docs/core/integration-quality-scale/rules/docs-data-update.md b/docs/core/integration-quality-scale/rules/docs-data-update.md new file mode 100644 index 00000000000..320387a9e84 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/docs-data-update.md @@ -0,0 +1,24 @@ +--- +title: "The documentation describes how data is updated" +--- + +## Reasoning + +For the user to know how the integration works, we should describe how the data of the integration is updated. +Because this will help users create an expectation on how well the integration works for their use case. +A motion sensor that only polls every 5 minutes is less usable than a motion sensor that actively pushes updates. + +Since users can define their own polling interval for polling integrations, we should add at what rate we poll now and describe any limitations. +For example if the device we connect to has known problems handling too many requests, we should describe that in the documentation. + +## Example implementation + +```markdown showLineNumbers +## Data updates + +My integration fetches data from the device every 5 minutes by default. +Newer devices (the ones running MyOS) have the possibility to {% term push %} data. +At the start of the integration we try to enable that, and if it fails we fall back to {% term polling %}. +``` + +## Exceptions \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/docs-examples.md b/docs/core/integration-quality-scale/rules/docs-examples.md new file mode 100644 index 00000000000..9756fbf1c78 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/docs-examples.md @@ -0,0 +1,26 @@ +--- +title: "The documentation provides automation examples the user can use." +--- + +## Reasoning + +To show how the integration can be used, we should provide a limited set of blueprints, containing common or useful ones. +This will help users to get started with the integration faster and easier. + +The documentation pages should not be used as a collection or as a replacement of the blueprint exchange on the forums. + +## Example implementation + +```markdown showLineNumbers +## Examples + +### Turning off the LEDs during the night +The status LEDs on the device can be quite bright. +To tackle this, you can use this blueprint to easily automate the LEDs turning off when the sun goes down. + +link to blueprint +``` + +## Exceptions + +There are no exceptions to this rule. \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/docs-high-level-description.md b/docs/core/integration-quality-scale/rules/docs-high-level-description.md new file mode 100644 index 00000000000..e4083000075 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/docs-high-level-description.md @@ -0,0 +1,20 @@ +--- +title: "The documentation includes a high-level description of the integration brand, product, or service" +--- + +## Reasoning + +User documentation for an integration should provide a high-level description of the integration brand, product, or service. +This information might help users decide whether an integration suits them and their use case. +The documentation should also contain a link to the brand, product, or service website for further information, if possible. + +## Example implementation + +```markdown showLineNumbers +The **my integration** {% term integration %} is used to integrate with the devices of [MyCompany](https://www.mycompany.com). +They create various smart home appliances and devices and are known for their MyProduct. +``` + +## Exceptions + +Integrations that do not integrate with a device or service, such as internal integrations, can't include a description of the device or service, and are exempt. \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/docs-installation-instructions.md b/docs/core/integration-quality-scale/rules/docs-installation-instructions.md new file mode 100644 index 00000000000..8e1dc5c12b7 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/docs-installation-instructions.md @@ -0,0 +1,27 @@ +--- +title: "The documentation provides step-by-step installation instructions for the integration, including, if needed, prerequisites" +--- + +## Reasoning + +We want users to have a smooth experience when setting up an integration. +This means that the documentation should provide clear and concise instructions on how to install the integration. +This includes any prerequisites that are needed to install the integration. + +## Example implementation + +```markdown showLineNumbers +## Prerequisites + +1. Open the app store and install the **MyProduct** app. +2. Create an account. +3. Add a device to the app. +4. Open the app and go to the **Settings** page. +5. Select **Expose API**. + +{% include integrations/config_flow.md %} +``` + +## Exceptions + +There are no exceptions to this rule. \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/docs-installation-parameters.md b/docs/core/integration-quality-scale/rules/docs-installation-parameters.md new file mode 100644 index 00000000000..c5511dbaede --- /dev/null +++ b/docs/core/integration-quality-scale/rules/docs-installation-parameters.md @@ -0,0 +1,28 @@ +--- +title: "The documentation describes all integration installation parameters" +--- + +## Reasoning + +When setting up an integration, there's nothing more frustrating than not knowing what information is asked for. +To improve the user experience, the documentation should describe all the parameters that are required during the installation process. +This should help the user to gather all the necessary information before starting the installation process. + +## Example implementation + +```markdown showLineNumbers +{% configuration_basic %} +Host: + description: "The IP address of your bridge. You can find it in your router or in the Integration app under **Bridge Settings** -> **Local API**." + required: false + type: string +Local access token: + description: "The local access token for your bridge. You can find it in the Integration app under **Bridge Settings** -> **Local API**." + required: false + type: string +{% endconfiguration_basic %} +``` + +## Exceptions + +There are no exceptions to this rule. \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/docs-known-limitations.md b/docs/core/integration-quality-scale/rules/docs-known-limitations.md new file mode 100644 index 00000000000..8c8992ff66e --- /dev/null +++ b/docs/core/integration-quality-scale/rules/docs-known-limitations.md @@ -0,0 +1,21 @@ +--- +title: "The documentation describes known limitations of the integration (not to be confused with bugs)" +--- + +## Reasoning + +Describing the known limitations of the integration in the documentation will allow users to know what to expect from the integration. + +We should refrain from noting down bugs, since we use the issue tracker at GitHub for that and we don't want to duplicate information. + +## Example implementation + +```markdown showLineNumbers +## Known limitations + +The integration does not provide the ability to reboot, which can instead be done via the manufacturer's app. +``` + +## Exceptions + +There are no exceptions to this rule. \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/docs-removal-instructions.md b/docs/core/integration-quality-scale/rules/docs-removal-instructions.md new file mode 100644 index 00000000000..b3c1c0a7692 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/docs-removal-instructions.md @@ -0,0 +1,19 @@ +--- +title: "The documentation provides removal instructions" +--- + +## Reasoning + +Removing a device or service from Home Assistant isn't always straightforward. +The documentation should provide clear instructions on how to remove the device or service. + +## Example implementation + +```markdown showLineNumbers +To remove the integration, go to {% my integrations title="**Settings** > **Devices & services**" %} and select the integration card. Then, select the three dots {% icon "mdi:dots-vertical" %} menu and select **Delete**. +After deleting the integration, go to the app of the manufacturer and remove the Home Assistant integration from there as well. +``` + +## Exceptions + +There are no exceptions to this rule. \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/docs-supported-devices.md b/docs/core/integration-quality-scale/rules/docs-supported-devices.md new file mode 100644 index 00000000000..27b8c793d98 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/docs-supported-devices.md @@ -0,0 +1,31 @@ +--- +title: "The integration documents known supported / unsupported devices" +--- + +## Reasoning + +A lot of Home Assistant users buy devices based on if Home Assistant supports them. +To make it easier for users to find out if a device is supported, the documentation should document the known supported or unsupported devices. +This will decrease the amount of bad experiences where the user finds out their device is not supported when they try to set it up. + +## Example implementation + +```markdown showLineNumbers +## Supported devices + +The following devices are known to be supported by the integration: +- Device 1 +- Device 2 +- Every appliance that runs MyOS + +## Unsupported devices + +The following devices are not supported by the integration: +- Device 3 +- Appliances built before 2010 +``` + +## Exceptions + +This rule does not apply to integrations that do not connect to a device or service. +This rule also does not apply to integrations that don't integrate physical devices. diff --git a/docs/core/integration-quality-scale/rules/docs-supported-functions.md b/docs/core/integration-quality-scale/rules/docs-supported-functions.md new file mode 100644 index 00000000000..50a8e993297 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/docs-supported-functions.md @@ -0,0 +1,88 @@ +--- +title: "The documentation describes the supported functionality, including entities, and platforms" +--- + +## Reasoning + +Users should be able to understand what value the integration will add for their (to be bought) device. +This will help set users' expectations. + +For example, if a user is looking for a new fridge, we should try to be clear about what they can expect from the integration. +If the integration only supports checking if the door is open or closed, they would be disappointed if they expected to be able to view the temperature of the fridge. + +## Example implementation + +Example, sorted by entity types: + +```markdown showLineNumbers +## Supported functionality + +### Entities + +The XY integration provides the following entities. + +#### Buttons + +- **Start backflush** + - **Description**: Starts the backflush process on your machine. You got 15 seconds to turn the paddle after activation. + - **Available for machines**: all + +#### Numbers + +- **Dose** + - **Description**: Dosage (in ticks) for each key + - **Available for machines**: GS3 AV, Linea Mini. + - **Remarks**: GS3 has this multiple times, one for each physical key (1-4), and the entities are disabled by default. + +#### Sensors + +- **Current coffee temperature** + - **Description**: Current temperature of the coffee boiler. + - **Available for machines**: all + - **Remarks**: When the machine reaches temperature, this will be approximately 3 degrees higher than the `Coffee target temperature`, due to different measurement points. + +- **Current steam temperature** + - **Description**: Current temperature of the steam boiler. + - **Available for machines**: Linea Micra, GS3 AV, GS3 MP. + - **Remarks**: - + +#### Updates + +- **Gateway firmware** + - **Description**: Firmware status of the gateway. + - **Available for machines**: all + +#### Selects + +- **Prebrew/-infusion mode** + - **Description**: Whether to use prebrew, preinfusion, or neither. + - **Options**: Disabled, Prebrew, Preinfusion + - **Available for machines**: Linea Micra, Linea Mini, GS3 AV + +- **Steam level** + - **Description**: The level your steam boiler should run at. + - **Options**: 1, 2, 3 + - **Available for machines**: Linea Micra +``` + +Example, sorted by device: + +```markdown +## Supported functionality + +### XYZ productname Air Purifier, Air Humidifier and Standing Fan + +#### Sensors + +- **Filter lifetime remaining**: The remaining life of the filter in number of years. Enabled by default. +- **Purify volume**: The volume of purified air in cubic meters. Disabled by default. + +#### Numbers + +- **Favorite level**: Set the favorite level. Possible values are 0 to 10. `0` means it is turned off.) +- **Volume**: Set the volume. In percent. `0%` means it is off. +``` + +## Exceptions + +There are no exceptions to this rule. \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/docs-troubleshooting.md b/docs/core/integration-quality-scale/rules/docs-troubleshooting.md new file mode 100644 index 00000000000..72522e541b1 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/docs-troubleshooting.md @@ -0,0 +1,48 @@ +--- +title: "The documentation provides troubleshooting information" +--- + +## Reasoning + +We should provide instructions on how to fix a common issue. +If possible, a troubleshooting topic should include a description of the symptom and the steps needed to fix the situation. +This decreases the amount of support requests and improves the user experience. + +## Example implementation + +```markdown showLineNumbers +## Troubleshooting + +### Can’t setup the device + +#### Symptom: “This device can’t be reached” + +When trying to setup the integration, the form shows the message “This device can’t be reached”. + +##### Description + +This means the settings on the device are incorrect, since the device needs to be enabled for local communication. + +##### Resolution + +To resolve this issue, try the following steps: + +1. Make sure your device is powered up (LEDs are on). +2. Make sure your device is connected to the internet: + - Make sure the app of the manufacturer can see the device. +3. Make sure the device has the local communication enabled: + - Check the device’s settings. + - Check the device’s manual. +... + +### I can't see my devices +Make sure the devices are visible and controllable via the manufacturer's app. +If they are not, check the device's power and network connection. + +### The device goes unavailable after a day +Make sure you turned off the device's power-saving mode. +``` + +## Exceptions + +There are no exceptions to this rule. \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/docs-use-cases.md b/docs/core/integration-quality-scale/rules/docs-use-cases.md new file mode 100644 index 00000000000..c86668941db --- /dev/null +++ b/docs/core/integration-quality-scale/rules/docs-use-cases.md @@ -0,0 +1,21 @@ +--- +title: "The documentation describes use cases to illustrate how this integration can be used" +--- + +## Reasoning + +Sometimes, seeing a device or service integrated into Home Assistant can make you wonder, "Why would I integrate this?" +For some integrations, the intended use and benefit are more obvious than for others. + +Use case examples in the documentation can showcase the value of an integration. + +## Example implementation + +```markdown showLineNumbers +The motion detection devices of MyCompany are cheap and usable. +When you combine it with their other device you can do x. +``` + +## Exceptions + +There are no exceptions to this rule. \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/dynamic-devices.md b/docs/core/integration-quality-scale/rules/dynamic-devices.md new file mode 100644 index 00000000000..b6baa1132d5 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/dynamic-devices.md @@ -0,0 +1,73 @@ +--- +title: "Devices added after integration setup" +related_rules: + - stale-devices +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +Like explained in IQS021, devices should be removed automatically when we can be sure that the device is not connected anymore. +This rule explains the other side, once a new device is connected, we should automatically create the relevant entities for the device. + +This makes the user experience better, since the user only adds the device to the integration, and it will automatically show up in Home Assistant. + +## Example implementation + +In the example below we use a coordinator to fetch all the data from the service. +Every update `_check_device` will check if there are new devices to create entities for and add them to 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: AddEntitiesCallback, +) -> 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) + ) +``` + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/entity-category.md b/docs/core/integration-quality-scale/rules/entity-category.md new file mode 100644 index 00000000000..aa192adce7c --- /dev/null +++ b/docs/core/integration-quality-scale/rules/entity-category.md @@ -0,0 +1,30 @@ +--- +title: "Entities are assigned an appropriate EntityCategory" +--- + +## Reasoning + +Entities should be assigned an appropriate EntityCategory to ensure that they are correctly classified and can be easily identified, when the default category is inappropriate. +The entity category is used in, for example, auto-generated dashboards. + +## Example implementation + +In this example, we have a sensor that returns a diagnostic value. + +`sensor.py` +```python {4} showLineNumbers +class MySensor(SensorEntity): + """Representation of a sensor.""" + + _attr_entity_category = EntityCategory.DIAGNOSTIC + + def __init__(self, ...) -> None: +``` + +## Additional resources + +To learn more about the registry properties, checkout the [documentation](../../entity#registry-properties) about it. + +## Exceptions + +There are no exceptions to this rule. diff --git a/docs/core/integration-quality-scale/rules/entity-device-class.md b/docs/core/integration-quality-scale/rules/entity-device-class.md new file mode 100644 index 00000000000..a3ab8e01f69 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/entity-device-class.md @@ -0,0 +1,54 @@ +--- +title: "Entities use device classes where possible" +related_rules: + - has-entity-name + - entity-translations + - icon-translations +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +Device classes are a way to give context to an entity. +These are used by Home Assistant for various purposes like: +- Allowing the user to switch to another unit of measurement than what the device provides. +- They are used for voice control to ask questions like "What is the temperature in the living room?". +- They are used for exposing entities to cloud based ecosystems like Google Assistant and Amazon Alexa. +- They are used to adjust the representation in the Home Assistant UI. +- They can be used to set a default name of the entity to decrease the burden on our translators. + +Because of these reasons, it is important to use device classes where possible. + +## Example implementation + +In the example below we have a temperature sensor that uses the device class `temperature`. +The name of this entity will be `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", + ) +``` + +## Additional resources + +A list of available device classes can be found in the entity pages under the [entity](../../entity) page. +More information about entity naming can be found in the [entity](../../entity#has_entity_name-true-mandatory-for-new-integrations) documentation. + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/entity-disabled-by-default.md b/docs/core/integration-quality-scale/rules/entity-disabled-by-default.md new file mode 100644 index 00000000000..4a7b364bacf --- /dev/null +++ b/docs/core/integration-quality-scale/rules/entity-disabled-by-default.md @@ -0,0 +1,54 @@ +--- +title: "Integration disables less popular (or noisy) entities" +related_rules: + - appropriate-polling +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +Home Assistant keeps track of how the states of entities changes. +This is done to be able to show the history of the entity in the UI. +Every state that is tracked takes up a bit of resources. +Entities that change state a lot (noisy entities), do this more often than entities that change state less often. + +We consider it a good practice to disable less popular or noisy entities by default. +If users have a use case for such an entity, they can enable it. +This way users that don't have a use case for the entity, don't have to pay the cost of tracking the state of the entity. + +There is no hard rule on what is considered a popular entity, since that depends on the integration and the device. +So for example, a bluetooth temperature sensor can have an entity that represents the signal strength of the device. +This entity is not very useful for most users, so it should be disabled by default. +While if there was an integration providing a device to measure signal strength, that entity would be useful for most users and should be enabled by default. + +## Example implementation + +In the example below, the entity is disabled by default. + +`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.""" + ... +``` + +## Additional resources + +To learn more about the entity registry properties, checkout the [documentation](../../entity#registry-properties) about it. + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/entity-event-setup.md b/docs/core/integration-quality-scale/rules/entity-event-setup.md new file mode 100644 index 00000000000..b03e00104c6 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/entity-event-setup.md @@ -0,0 +1,61 @@ +--- +title: "Entities event setup" +--- + +## Reasoning + +Entities may need to subscribe to events, eg. from the integration library, and update state when a new event comes in. +In order to do this correctly, the entities should subscribe and register the update callback in the entity method `async_added_to_hass`. +This entity method is called after the entity has been registered by the entity platform helper and the entity will now have all its interfaces available to call, such as `self.hass` and `self.async_write_ha_state`. +Registering an update callback before this stage will cause errors if the callback eg. tries to access `self.hass` or write a state update. +To avoid memory leaks, the entities should unsubscribe from the events, ie. unregister the update callback, in the entity method `async_will_remove_from_hass`. + +## Example implementation + +In the example below, the `self.client.events.subscribe` returns a function that when called, unsubscribes the entity from the event. +So we subscribe to the event in `async_added_to_hass` and unsubscribe in `async_will_remove_from_hass`. + +`sensor.py` +```python {10-13,15-19} showLineNumbers +class MySensor(SensorEntity): + """Representation of a sensor.""" + + unsubscribe: Callable[[], 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 +The above example can be simplified using lifecycle functions. +This saves the need to store the callback function in the entity. +```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) + ) +``` +::: + +## Exceptions + +There are no exceptions to this rule. diff --git a/docs/core/integration-quality-scale/rules/entity-translations.md b/docs/core/integration-quality-scale/rules/entity-translations.md new file mode 100644 index 00000000000..2109c6b4534 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/entity-translations.md @@ -0,0 +1,66 @@ +--- +title: "Entities have translated names" +related_rules: + - has-entity-name + - entity-device-class + - icon-translations + - exception-translations +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +Home Assistant is used by people all over the world. +To also make it easier for non-English speakers to use Home Assistant, it is important that entities have translated names. +This makes it easier for people to understand what the entity is. + +## Example implementation + +In this example, the sensor has the name "Phase voltage" in English. +Combined with the device name, this entity will name itself "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 +If the entity's platform is either `binary_sensor`, `number`, `sensor`, or `update` and it has a device class set, and you want the entity to have the same name as the device class, you can omit the translation key because the entity will then automatically use the device class name. +::: + +## Additional resources + +More information about the translation process can be found in the [translation](../../../internationalization/core) documentation, it also contains information about the [entity translations](../../../internationalization/core#name-of-entities). +More information about entity naming can be found in the [entity](../../entity#has_entity_name-true-mandatory-for-new-integrations) documentation. + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/entity-unavailable.md b/docs/core/integration-quality-scale/rules/entity-unavailable.md new file mode 100644 index 00000000000..bc49ed7dc7b --- /dev/null +++ b/docs/core/integration-quality-scale/rules/entity-unavailable.md @@ -0,0 +1,88 @@ +--- +title: "Mark entity unavailable if appropriate" +related_rules: + - log-when-unavailable +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +If we can't fetch data from a device or service, we should mark it as unavailable. +We do this to reflect a better state, than just showing the last known state. + +If we can successfully fetch data but are temporarily missing a few pieces of data, we should mark the entity state as unknown instead. + +## Example implementation + +Since there are many different ways this can be implemented, we will only provide the example for integrations using the coordinator and for entities updating via `async_update`. + +### Example for integrations using the coordinator + +In this example, we have an integration that uses a coordinator to fetch data. +The coordinator, when combined with a `CoordinatorEntity` has the logic for availability built-in. +If there is any extra availability logic needed, be sure to incorporate the `super().available` value. +In the sensor in the example, we mark the entity unavailable when the update fails, or when the data for that device is missing. + +`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 +``` + +### Example for entities updating via `async_update` + +In this example, we have a sensor that updates its value via `async_update`. +If we can't fetch the data, we set the entity as unavailable using shorthand notation. +If we can fetch the data, we set the entity as available and update the value. + +`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 +``` + +## Additional resources + +For more information about managing integration state, see the [documentation](../../../integration_fetching_data). + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/entity-unique-id.md b/docs/core/integration-quality-scale/rules/entity-unique-id.md new file mode 100644 index 00000000000..ee3b054163d --- /dev/null +++ b/docs/core/integration-quality-scale/rules/entity-unique-id.md @@ -0,0 +1,45 @@ +--- +title: "Entities have a unique ID" +related_rules: + - unique-config-entry +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +In the past, entities weren't persisted. +Home Assistant didn't track which entities it knew from the past and which it did not. +To allow customizations to entities, like renaming the entity or changing the unit of measurement, Home Assistant needed a way to keep track of each individual entity across restarts. + +To solve this, Home Assistant introduced the entity registry. +The entity registry is a central place where Home Assistant keeps track of all entities it knows about. +Each entity in the entity registry has a unique ID, which is unique per integration domain and per platform domain. + +If an entity doesn't have a unique ID, the user has less control over the entity. +Thus, making sure that entities have a unique ID improves the user experience. + +## Example implementation + +In this example there is a temperature sensor that sets its unique ID using the shorthand notation. + +`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" +``` + +## Additional resources + +More information about the requirements for a unique identifier can be found in the [documentation](../../../entity_registry_index#unique-id-requirements). + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/exception-translations.md b/docs/core/integration-quality-scale/rules/exception-translations.md new file mode 100644 index 00000000000..464a2b18fd0 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/exception-translations.md @@ -0,0 +1,63 @@ +--- +title: "Exception messages are translatable" +related_rules: + - entity-translations + - action-exceptions +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +Sometimes something goes wrong and we want to show an error message to the user. +Since Home Assistant is used by people all over the world, it is important that these error messages are translatable. +This increases the usability of Home Assistant for people who do not use the application in English. + +Home Assistant has builtin support for translating messages coming from the `HomeAssistantError` exception. + +## Example implementation + +In this example, we show a function registered as a Home Assistant service action. +The integration domain and the key to the translation are passed along when raising the exception. +The exception should inherit `HomeAssistantError` to support translations. +The error message is then defined in the integration `strings.json` file. + +```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": "The end date cannot be before the start date.", + "cannot_connect_to_schedule": "Cannot connect to the schedule." + } +} +``` + +## Additional resources + +For more info on raising exceptions, check the [documentation](../../platform/raising_exceptions). + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + diff --git a/docs/core/integration-quality-scale/rules/has-entity-name.md b/docs/core/integration-quality-scale/rules/has-entity-name.md new file mode 100644 index 00000000000..c8032b3a4dd --- /dev/null +++ b/docs/core/integration-quality-scale/rules/has-entity-name.md @@ -0,0 +1,68 @@ +--- +title: "Entities use has_entity_name = True" +related_rules: + - entity-translations + - entity-device-class + - devices +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +`has_entity_name` is an entity attribute that is used to improve the naming of entities in Home Assistant. +It is introduced to show a better name of the entity to the user depending on the context where the name is shown. + +We consider this a good practice because it allows for consistency in naming between integrations. + +## Example implementation + +In the example below, if the name of the device is "My device" and the field is "temperature", the name of the entity will be shown as "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 +``` + +However, when the name of the entity is set to `None`, the name of the device will be used as the name of the entity. +In this case, the lock entity will just be called "My device". +This should be done for the main feature of the 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, + ) +``` + +## Additional resources + +More information about entity naming can be found in the [entity](../../entity#has_entity_name-true-mandatory-for-new-integrations) documentation. +More information about devices can be found in the [device](../../../device_registry_index) documentation. + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/icon-translations.md b/docs/core/integration-quality-scale/rules/icon-translations.md new file mode 100644 index 00000000000..5b88f53d29c --- /dev/null +++ b/docs/core/integration-quality-scale/rules/icon-translations.md @@ -0,0 +1,64 @@ +--- +title: "Icon translations" +related_rules: + - entity-translations + - entity-device-class +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +In the past, icons were part of the state of the integration. +This was not really necessary, as they were usually either static or had a fixed set of states. + +To relieve the state machine, icon translations were introduced. +The name of this feature sounds weird since it is not about translating the icon itself, but rather referencing an icon by a translation key. +The idea behind icon translations is that the integration defines icons in a file, which is then used by the frontend to display the icon. +This also adds support for different icons for state attribute values, for example the possible preset modes of a climate entity. + +:::info +Be aware that entities can also get icons from the device class. +If the context of the entity is exactly the same as the device class, we should not overwrite this icon to maintain consistency between integrations. +For example, a PM2.5 sensor entity would not get a custom icon, as the device class already provides it in the same context. +::: + +## Example implementation + +In this example, we define a sensor entity with a translation key. +In the `icons.json` file, we define the icon for the sensor entity and a state icon for the state `high`. +So when the state of the entity is `high`, we will show the icon `mdi:tree-outline`, otherwise we will show `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": "mdi:tree", + "state": { + "high": "mdi:tree-outline" + } + } + } +} +``` + +## Additional resources + +For more information about icon translations, check the [entity](../../entity#icon-translations) documentation. + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/inject-websession.md b/docs/core/integration-quality-scale/rules/inject-websession.md new file mode 100644 index 00000000000..503b167142d --- /dev/null +++ b/docs/core/integration-quality-scale/rules/inject-websession.md @@ -0,0 +1,39 @@ +--- +title: "The integration dependency supports passing in a websession" +related_rules: + - async-dependency +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +Since many devices and services are connected via HTTP, the number of active web sessions can be high. +To improve the efficiency of those web sessions, it is recommended to support passing in a web session to the dependency client that is used by the integration. + +Home Assistants supports this for [`aiohttp`](https://docs.aiohttp.org/en/stable/) and [`httpx`](https://www.python-httpx.org/). +This means that the integration dependency should use either of those two libraries. + +## Example implementation + +In the example below, an `aiohttp` session is passed in to the client. +The equivalent for `httpx` would be `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 +There are cases where you might not want a shared session, for example when cookies are used. +In that case, you can create a new session using `async_create_clientsession` for `aiohttp` and `create_async_httpx_client` for `httpx`. +::: + +## Exceptions + +If the integration is not making any HTTP requests, this rule does not apply. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/integration-owner.md b/docs/core/integration-quality-scale/rules/integration-owner.md new file mode 100644 index 00000000000..b2cc5d8c4a0 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/integration-owner.md @@ -0,0 +1,39 @@ +--- +title: "Has an integration owner" +--- + +## Reasoning + +Home Assistant integrates with thousands of different devices and services, and most integrations are contributed by people other than the core maintainers of the project. +The contributors that add and maintain an integration are encouraged to become the "integration owner". +This is a role that grants the contributor more power when handling issues and pull requests for the integration in GitHub and it means that the contributor has taken on the responsibility for the stewardship of the integration. +Integration owners will automatically get notified whenever there is a new issue or pull request for their integration. +On GitHub the integration owner is referred to as the "codeowner". + +Integration owners are tracked in the `manifest.json` file of each integration. +To become an integration owner, submit a pull request adding your GitHub username to the `"codeowners"` field in the manifest. +An integration can have more than one owner. + +We love integration owners! +We believe that integrations that have an owner are better maintained. +During reviews, we see the integration owner as the expert on the integration, and weigh their opinion higher than others. + +## Example implementation + +Integration owners are set in the `manifest.json`. + +```json {3} showLineNumbers +{ + "domain": "my_integration", + "name": "My Integration", + "codeowners": ["@me"] +} +``` + +## Additional resources + +More information about integration owners can be found in [ADR-0008](https://github.com/home-assistant/architecture/blob/master/adr/0008-code-owners.md). + +## Exceptions + +There are no exceptions to this rule. \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/log-when-unavailable.md b/docs/core/integration-quality-scale/rules/log-when-unavailable.md new file mode 100644 index 00000000000..556fe837bc2 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/log-when-unavailable.md @@ -0,0 +1,91 @@ +--- +title: "If internet/device/service is unavailable, log once when unavailable and once when back connected" +related_rules: + - entity-unavailable +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +When a device or service is not reachable, the entities will usually go to unavailable. +To allow the user to find out why this is happening, the integration should log when this happens. +Be sure to log only once in total to avoid spamming the logs. + +When the device or service is reachable again, the integration should log that as well. +This can prove useful for using the logs to find out when the device or service was unavailable and when it was back online. + +:::info +Logging should happen at `info` level. +::: + +## Example implementation + +Since there are many different ways this can be implemented, we will only provide the example for integrations using the coordinator and for entities updating via `async_update`. + +### Example for integrations using the coordinator + +In this example, we have an integration that uses a coordinator to fetch data. +The coordinator has the logic for logging once built in. +The only thing that you need to do in the coordinator is to raise `UpdateFailed` when the device or service is unavailable. + +`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}") +``` + +### Example for entities updating via `async_update` + +In this example, we have a sensor that updates its value via `async_update`. +The example will log when the sensor is unavailable and log when the sensor is back online. +Note that an instance attribute is used to track if the message has been logged to avoid spamming the logs. + +`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 +``` + +## Additional resources + +For more information about managing integration state, see the [documentation](../../../integration_fetching_data) + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + diff --git a/docs/core/integration-quality-scale/rules/parallel-updates.md b/docs/core/integration-quality-scale/rules/parallel-updates.md new file mode 100644 index 00000000000..0e02cd824c3 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/parallel-updates.md @@ -0,0 +1,43 @@ +--- +title: "Set Parallel updates" +--- + +## Reasoning + +Some devices or services don't like receiving a lot of requests at the same time. +To avoid that, Home Assistant has a built-in feature to limit the number of requests that are sent to a device or service at the same time. + +This will be applied to both entity updates and actions calls. + +We consider it a good practice to explicitly set the number of parallel updates. + +## Example implementation + +In the example below, we set the number of parallel updates to 1. +Which means if there are more entities on the sensor platform, they will be updated one by one. +If there is no need to limit the number of parallel updates, you can set it to 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.""" + ... +``` + +:::info +When using a coordinator, you are already centralizing the data updates. +This means that usually only the action calls will be relevant to consider for setting the number of parallel updates. +::: + +## Additional resources + +For more information about request parallelism, check the [documentation](../../../integration_fetching_data#request-parallelism) for it. + +## Exceptions + +There are no exceptions to this rule. \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/reauthentication-flow.md b/docs/core/integration-quality-scale/rules/reauthentication-flow.md new file mode 100644 index 00000000000..15fbc337cb5 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/reauthentication-flow.md @@ -0,0 +1,104 @@ +--- +title: "Reauthentication flow" +related_rules: + - config-flow + - test-before-configure + - config-flow-test-coverage + - test-before-setup + - reconfiguration-flow +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +It can happen that users change their password of a device or service and forget that their device or account is still linked to Home Assistant. +To avoid that the user has to remove the configuration entry and re-add it, we start a reauthentication flow. +During this flow, the user can provide the new credentials to use from now on. + +This is a very user-friendly way to let the user know that they need to take action and update their credentials. + +## Example implementation + +In the example below, we show an authentication flow that allows the user to reauthenticate with a new API token. +When we receive the new token, we check if we can connect to the service to avoid the user from entering an invalid token. +If the connection is successful, we update the configuration entry with the new 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, + ) +``` + +## Additional resources + +For more info about handling expired credentials, check the [documentation](../../../integration_setup_failures#handling-expired-credentials). + +## Exceptions + +If the integration doesn't require any form of authentication, this rule doesn't apply. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/reconfiguration-flow.md b/docs/core/integration-quality-scale/rules/reconfiguration-flow.md new file mode 100644 index 00000000000..d3183e9a9b5 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/reconfiguration-flow.md @@ -0,0 +1,100 @@ +--- +title: "Integrations should have a reconfigure flow" +related_rules: + - config-flow + - test-before-configure + - unique-config-entry + - config-flow-test-coverage + - reauthentication-flow +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +It can happen that a user changes something to a device or service, like changing passwords or changing the IP address. +Ideally, Home Assistant catches those events and lets the user know that it requires a reauthentication or attention. +A reconfigure flow gives users the power to trigger a reconfiguration and allows them to update the configuration of the device or service, without the need to remove and re-add the device or service. + +This gives users more ways to try and fix their issues, without the need for the software to be restarted or reauthentication to be triggered. + +## Example implementation + +In the `config_flow.py` file, add a new step called `reconfigure` that allows users to reconfigure the integration. +In the following example, we check if the new api token is valid. +We also double-check if the user is not trying to reconfigure the integration with a different account, since the account used for the integration should not change. + +`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="user", + 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, + ) +``` + +## Additional resources + +For more information on the reconfiguration flow, see the [reconfigure flow documentation](../../../config_entries_config_flow_handler#reconfigure). + +## Exceptions + +Integrations that don't have settings in their configuration flow are exempt from this rule. + +## Related rules + + diff --git a/docs/core/integration-quality-scale/rules/repair-issues.md b/docs/core/integration-quality-scale/rules/repair-issues.md new file mode 100644 index 00000000000..bb281b2d672 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/repair-issues.md @@ -0,0 +1,48 @@ +--- +title: "Repair issues and repair flows are used when user intervention is needed" +--- + +## Reasoning + +Repair issues and repair flows are a very user-friendly manner to let the user know something is wrong and that they can do something about it. +Repair issues are just a way to let the user know that they can fix it themselves, while repair flows can fix it for them. + +Repair issues and repair flows should be actionable and informative about the problem. +Thus, we should not raise repair issues for just letting users know that something is wrong, which they can't fix themselves. + +## Example implementation + +In the example below we have an integration for a locally hosted service. +On boot, we check if we support the version of the service that is running. +If we do not, we raise a repair issue where we let the user know that they should update their service before they can use the integration again. + +`__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, + ) +``` + +## Additional resources + +For more information about repair issues and repair flows, see the [repairs](../../platform/repairs) documentation. + +## Exceptions + +There are no exceptions to this rule. \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/runtime-data.md b/docs/core/integration-quality-scale/rules/runtime-data.md new file mode 100644 index 00000000000..413f62e0ae6 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/runtime-data.md @@ -0,0 +1,48 @@ +--- +title: "Use ConfigEntry.runtime_data to store runtime data" +related_rules: + - test-before-setup +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +The `ConfigEntry` object has a `runtime_data` attribute that can be used to store runtime data. +This is useful for storing data that is not persisted to the configuration file storage, but is needed during the lifetime of the configuration entry. + +By using `runtime_data`, we maintain consistency for developers to store runtime data in a consistent and typed way. +Because of the added typing, we can use tooling to avoid typing mistakes. + +## Example implementation + +The type of a `ConfigEntry` can be extended with the type of the data put in `runtime_data`. +In the following example, we extend the `ConfigEntry` type with `MyClient`, which means that the `runtime_data` attribute will be of type `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 +``` + +## Additional resources + +More information about configuration entries and their lifecycle can be found in the [config entry documentation](../../../config_entries_index). + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/stale-devices.md b/docs/core/integration-quality-scale/rules/stale-devices.md new file mode 100644 index 00000000000..1e1d4cc978d --- /dev/null +++ b/docs/core/integration-quality-scale/rules/stale-devices.md @@ -0,0 +1,90 @@ +--- +title: "Clean up stale devices" +related_rules: + - dynamic-devices +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +When a device is removed from a hub or account, it should also be removed from Home Assistant. +This way, the user interface will not show devices that are no longer available. + +We should only remove devices that we are sure are no longer available. +If you can't be sure if a device is still available, be sure to implement `async_remove_config_entry_device`. +This allows the user to delete the device from the device registry manually. + +## Example implementation + +In this example, we have a coordinator that fetches data from a service. +When the data is updated, we check if any devices have been removed. +If so, we remove them from the device registry. +This also causes all entities associated with the device to be removed. + +`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(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 +``` + +To show a second example where someone can delete the device from the device registry manually, we implement `async_remove_config_entry_device` in `__init__.py`. +Having this function defined will enable the delete button on the device page in the UI. +In this example, the integration is only able to get updates for a device and not get a full list of connected devices, hence it can't automatically delete devices. +In `async_remove_config_entry_device`, we should implement a function that checks if the device is still available. +If it is not, we return `True` to allow the user to delete the device manually. +Here, we assume that the device is not working if we haven't got any updates for it in a while. + +`__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 + ) +``` + +## Additional resources + +For more info on devices, checkout the [device registry documentation](../../../device_registry_index). + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/strict-typing.md b/docs/core/integration-quality-scale/rules/strict-typing.md new file mode 100644 index 00000000000..b23f8d73adb --- /dev/null +++ b/docs/core/integration-quality-scale/rules/strict-typing.md @@ -0,0 +1,24 @@ +--- +title: "Strict typing" +--- + +## Reasoning + +Python is a dynamically typed language, which can be the source of many bugs. +By using type hints, you can catch bugs early and avoid introducing them. + +Type hints are checked by mypy, a static type checker for Python. +Because of the way typing in Python works, and type hints being optional in Python, mypy will only check the code that it knows to be type annotated. +To improve on this, we recommend fully typing your library and making your library PEP-561 compliant. +This means that you need to add a `py.typed` file to your library. +This file tells mypy that your library is fully typed, after which it can read the type hints from your library. + +In the Home Assistant codebase, you can add your integration to the [`.strict-typing`](https://github.com/home-assistant/core/blob/dev/.strict-typing) file, which will enable strict type checks for your integration. + +## Additional resources + +To read more about the `py.typed` file, see [PEP-561](https://peps.python.org/pep-0561/). + +## Exceptions + +There are no exceptions to this rule. \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/test-before-configure.md b/docs/core/integration-quality-scale/rules/test-before-configure.md new file mode 100644 index 00000000000..530aa378618 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/test-before-configure.md @@ -0,0 +1,76 @@ +--- +title: "Test a connection in the config flow" +related_rules: + - config-flow + - unique-config-entry + - config-flow-test-coverage + - discovery + - reauthentication-flow + - reconfiguration-flow +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +Apart from being very easy to use, config flows are also a great way to let the user know that something is not going to work when the configuration has been completed. +This can catch issues like: +- DNS issues +- Firewall issues +- Wrong credentials +- Wrong IP address or port +- Unsupported devices + +Issues like this are often hard to debug once the integration is set up, so it's better to catch them early so users are not stuck with an integration that doesn't work. + +Since this improves the user experience, it's required to test the connection in the config flow. + +## Example implementation + +To validate the user input, you can call your library with the data as you normally would and do a test call. +If the call fails, you can return an error message to the user. + +In the following example, if the `client.get_data()` call raises a `MyException`, the user will see an error message that the integration is unable to connect. + +`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, + ) +``` + +## Additional resources + +More information about config flows can be found in the [config flow documentation](../../../config_entries_config_flow_handler). + +## Exceptions + +Integrations that don't have a connection to a device or service (for example helpers) don't need to test a connection in the config flow and are exempt from this rule. +Integrations that rely on auto-discovery on runtime (like Google Cast) are also exempt from this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/test-before-setup.md b/docs/core/integration-quality-scale/rules/test-before-setup.md new file mode 100644 index 00000000000..77da0ace7e4 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/test-before-setup.md @@ -0,0 +1,55 @@ +--- +title: "Check during integration initialization if we are able to set it up correctly" +related_rules: + - runtime-data +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +When we initialize an integration, we should check if we are able to set it up correctly. +This way we can immediately let the user know that it doesn't work. + +Implementing these checks increases the confidence that the integration will work correctly and provides a user-friendly way to show errors. +This will improve the user experience. + +## Example implementation + +When the reason for the failure is temporary (like a temporary offline device), we should raise `ConfigEntryNotReady` and Home Assistant will retry the setup later. +If the reason for the failure is that the password is incorrect or the api key is invalid, we should raise `ConfigEntryAuthFailed` and Home Assistant will ask the user to reauthenticate (if the reauthentication flow is implemented). +If we don't expect the integration to work in the foreseeable future, we should raise `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: + raise ConfigEntryNotReady("Device is offline") + except InvalidAuthException: + raise ConfigEntryAuthFailed("Invalid authentication") + except AccountClosedException: + raise ConfigEntryError("Account closed") + + entry.runtime_data = client + + await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS) + + return True +``` + +## Additional resources + +More information about config entries and their lifecycle can be found in the [config entry documentation](../../../config_entries_index). + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + diff --git a/docs/core/integration-quality-scale/rules/test-coverage.md b/docs/core/integration-quality-scale/rules/test-coverage.md new file mode 100644 index 00000000000..4baf3c5ea84 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/test-coverage.md @@ -0,0 +1,26 @@ +--- +title: "Above 95% test coverage for all integration modules" +related_rules: + - config-flow-test-coverage +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +Since we support a lot of different integrations, we don't have every device or service available for hands-on testing. +To make sure that we don't break anything, when accepting a code change, we need to have a good test coverage for all integration modules. +This prevents the introduction of bugs and regressions. + +It also allows new developers to understand the codebase and make changes without breaking any existing use case. + +## Additional resources + +For more information about testing and how to calculate test coverage, see the [Testing your code](../../../development_testing) page. + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/core/integration-quality-scale/rules/unique-config-entry.md b/docs/core/integration-quality-scale/rules/unique-config-entry.md new file mode 100644 index 00000000000..76e30e838d4 --- /dev/null +++ b/docs/core/integration-quality-scale/rules/unique-config-entry.md @@ -0,0 +1,118 @@ +--- +title: "Don't allow the same device or service to be able to be set up twice" +related_rules: + - config-flow + - test-before-configure + - config-flow-test-coverage + - discovery + - reauthentication-flow + - reconfiguration-flow +--- +import RelatedRules from './_includes/related_rules.jsx' + +## Reasoning + +Since integrations are easy to set up with the UI, the user could accidentally set up the same device or service twice. +This can lead to duplicated devices and entities with unique identifiers colliding, which has negative side effects. +Any discovery flow must also ensure that a config entry is uniquely identifiable, as otherwise, it would discover devices already set up. + +To prevent this, we need to ensure that the user can only set up a device or service once. + +## Example implementation + +There are 2 common ways an integration checks if it has already been set up. +The first way is by assigning a `unique_id` to the configuration entry. +The second way is by checking if pieces of the data in the configuration entry are unique. + +The following examples show how to implement these checks in a config flow. + +### Unique identifier + +The first way is by assigning a `unique_id` to the configuration entry. +This unique ID is unique per integration domain, so another integration can use the same unique ID without problems. +Below is an example of a config flow that fetches the `unique_id` for the entered configuration with the client and checks if the `unique_id` already exists. +If it does, the flow will abort and show an error message to the user. + +`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, + ) +``` + +### Unique data + +The second way is by checking if pieces of the data in the configuration entry are unique. +In the following example, the user fills in a host and a password. +If a configuration entry already exists for the same host, the flow will abort and show an error message to the user. + +`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, + ) +``` + + +## Additional resources + +More information about config flows can be found in the [config flow documentation](../../../config_entries_config_flow_handler). +More information about the requirements for a unique identifier can be found in the [documentation](../../../entity_registry_index#unique-id-requirements). + +## Exceptions + +There are no exceptions to this rule. + +## Related rules + + \ No newline at end of file diff --git a/docs/integration_quality_scale_index.md b/docs/integration_quality_scale_index.md deleted file mode 100644 index 3830a572af6..00000000000 --- a/docs/integration_quality_scale_index.md +++ /dev/null @@ -1,68 +0,0 @@ ---- -title: "Integration quality scale" ---- - -The Integration Quality Scale scores each integration based on the code quality and user experience. Each level of the quality scale consists of a list of requirements. If an integration matches all requirements, it's considered to have reached that level. - -:::info -Suggestions for changes can be done by creating an issue in the [architecture repo](https://github.com/home-assistant/architecture/discussions). -::: - -## No score - -This integration passes the bare minimum requirements to become part of the index. - -- Satisfy all requirements for [creating components](creating_component_code_review.md) and [creating platforms](creating_platform_code_review.md). -- Configurable via `configuration.yaml` - -## Silver 🥈 - -This integration is able to cope when things go wrong. It will not print any exceptions nor will it fill the log with retry attempts. - -- Satisfying all No score level requirements. -- Connection/configuration is handled via a component. -- Set an appropriate `SCAN_INTERVAL` (if a polling integration) -- Raise [`PlatformNotReady`](integration_setup_failures.md#integrations-using-async_setup_platform) if unable to connect during platform setup (if appropriate) -- Handles expiration of auth credentials. Refresh if possible or print correct error and fail setup. If based on a config entry, should trigger a new config entry flow to re-authorize. ([docs](config_entries_config_flow_handler.md#reauthentication)) -- Handles internet unavailable. Log a warning once when unavailable, log once when reconnected. -- Handles device/service unavailable. Log a warning once when unavailable, log once when reconnected. -- Operations like service action calls and entity methods (e.g. *Set HVAC Mode*) have proper exception handling. Raise `ServiceValidationError` on invalid user input and raise `HomeAssistantError` for other failures such as a problem communicating with a device. [Read more](/docs/core/platform/raising_exceptions) about raising exceptions. -- Set `available` property to `False` if appropriate ([docs](core/entity.md#generic-properties)) -- Entities have unique ID (if available) ([docs](entity_registry_index.md#unique-id-requirements)) - -## Gold 🥇 - -This is a solid integration that is able to survive poor conditions and can be configured via the user interface. - -- Satisfying all Silver level requirements. -- Configurable via config entries. - - Don't allow configuring already configured device/service (example: no 2 entries for same hub) - - Discoverable (if available) - - Set unique ID in config flow (if available) - - Raise [`ConfigEntryNotReady`](integration_setup_failures.md#integrations-using-async_setup_entry) if unable to connect during entry setup (if appropriate) -- Entities have device info (if available) ([docs](device_registry_index.md#defining-devices)) -- Tests - - Full test coverage for the config flow - - Above average test coverage for all integration modules - - Tests for fetching data from the integration and controlling it ([docs](development_testing.md)) -- Has a code owner ([docs](creating_integration_manifest.md#code-owners)) -- Entities only subscribe to updates inside `async_added_to_hass` and unsubscribe inside `async_will_remove_from_hass` ([docs](core/entity.md#lifecycle-hooks)) -- Entities have correct device classes where appropriate ([docs](core/entity.md#generic-properties)) -- Supports entities being disabled and leverages `Entity.entity_registry_enabled_default` to disable less popular entities ([docs](core/entity.md#advanced-properties)) -- If the device/service API can remove entities, the integration should make sure to clean up the entity and device registry. -- When communicating with a device or service, the integration implements the diagnostics platform which redacts sensitive information. - -## Platinum 🏆 - -Best of the best. The integration is completely async, meaning it's super fast. Integrations that reach platinum level will require approval by the code owner for each PR. - -- Satisfying all Gold level requirements. -- Set appropriate `PARALLEL_UPDATES` constant ([docs](integration_fetching_data.md#request-parallelism)) -- Support config entry unloading (called when config entry is removed) -- Integration + dependency are async ([docs](asyncio_working_with_async.md)) -- Uses aiohttp or httpx and allows passing in websession (if making HTTP requests) -- [Handles expired credentials](integration_setup_failures.md#handling-expired-credentials) (if appropriate) - -## Internal 🏠 - -Integrations which are part of Home Assistant are not rated but marked as **internal**. diff --git a/sidebars.js b/sidebars.js index 5ced7b39a35..28803c6e9fb 100644 --- a/sidebars.js +++ b/sidebars.js @@ -5,6 +5,16 @@ * LICENSE file in the root directory of this source tree. */ +const iqs_rules_by_tier = require('./docs/core/integration-quality-scale/_includes/tiers.json'); +const iqs_rules = Object.values(iqs_rules_by_tier).flat().map((rule) => { + if (typeof rule === "string") { + return rule; + } + return rule.id; +}); + + + module.exports = { Addons: [ "add-ons", @@ -134,9 +144,21 @@ module.exports = { "development_checklist", "creating_component_code_review", "creating_platform_code_review", - "integration_quality_scale_index", ], }, + { + type: "category", + label: "Integration Quality Scale", + link: {type: 'doc', id: 'core/integration-quality-scale/index'}, + items: [ + {type: 'doc', id: 'core/integration-quality-scale/checklist'}, + {type: 'category', label: 'Rules', items: iqs_rules.map(rule => ({ + type: 'doc', + id: `core/integration-quality-scale/rules/${rule.toLowerCase()}` + }) + )} + ] + }, { type: "category", label: "The `hass` object", diff --git a/static/_redirects b/static/_redirects index fc90e37b3a3..084be59832b 100644 --- a/static/_redirects +++ b/static/_redirects @@ -55,6 +55,7 @@ /docs/hassio_addon_tutorial /docs/add-ons/tutorial /docs/hassio_debugging /docs/supervisor/debugging /docs/hassio_hass /docs/supervisor/developing +/docs/integration_quality_scale_index /docs/core/integration-quality-scale /docs/internationalization_backend_localization /docs/internationalization/core /docs/internationalization_custom_component_localization /docs/internationalization/custom_integration /docs/internationalization_index /docs/internationalization From 4f545c0a817e446b04d449ffb799445762d222ab Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Wed, 20 Nov 2024 17:43:49 +0000 Subject: [PATCH 06/55] Add section for unit of measurement translations (#2451) Co-authored-by: epenet <6771947+epenet@users.noreply.github.com> --- docs/internationalization/core.md | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/docs/internationalization/core.md b/docs/internationalization/core.md index 4294e063ddb..a5d07517808 100644 --- a/docs/internationalization/core.md +++ b/docs/internationalization/core.md @@ -467,6 +467,24 @@ If your integration provides entities under its domain, you will want to transla } ``` +#### Unit of measurement of entities + +Integrations can provide translations for units of measurement of its entities. To do this, provide an `entity` object, that contains translations for the units and set the entity's `translation_key` property to a key under a domain in the `entity` object. +If the entity's `translation_key` property is not `None` and the `entity` object provides a translated unit, `SensorEntityDescription.native_unit_of_measurement` and `NumberEntityDescription.native_unit_of_measurement` will be ignored. + +The following example `strings.json` is for a `sensor` entity with its `translation_key` property set to `goal`: +```json +{ + "entity": { + "sensor": { + "goal": { + "unit_of_measurement": "steps" + } + } + } +} +``` + ## Test translations In order to test changes to translation files, the translation strings must be compiled into Home Assistant’s translation directories by running the following script: From 6e5f5613dc9347f61ca0df7751eb41090841310a Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 20 Nov 2024 19:00:12 +0100 Subject: [PATCH 07/55] Add blog post about the Integration Quality Scale (#2460) Co-authored-by: Franck Nijhof --- blog/2024-11-20-integration-quality-scale.md | 29 ++++++++++++++++++++ 1 file changed, 29 insertions(+) create mode 100644 blog/2024-11-20-integration-quality-scale.md diff --git a/blog/2024-11-20-integration-quality-scale.md b/blog/2024-11-20-integration-quality-scale.md new file mode 100644 index 00000000000..15ef7eee06f --- /dev/null +++ b/blog/2024-11-20-integration-quality-scale.md @@ -0,0 +1,29 @@ +--- +author: Joost Lekkerkerker +authorURL: https://github.com/joostlek +authorImageURL: https://avatars.githubusercontent.com/u/7083755?v=4 +title: "Integration quality scale" +--- + +Since the original quality scale was introduced in 2020, Home Assistant changed quite a lot. +Both in how integrations are developed and what features they can provide. +The quality scale, however, has not been updated to reflect these changes. + +For the last couple of months, we have been working with the community on a new integration quality scale that fits the current state of Home Assistant. +To give an overview of the changes: + +- The quality scale now has four scaled tiers: Bronze, Silver, Gold, and Platinum. The newly added Bronze tier is the new minimum requirement for new core integrations going forward. This does lower the bar for new integrations for a bit, but it does make it clear upfront what is expected from a core integration in this day and age. +- A new non-scaled tier, legacy, has been added for integrations that are not configurable via the UI yet. This tier would indicate to the user that the integration might not meet their expectation compared to integrations configurable via the UI. +- To allow the scale to grow with Home Assistant and to avoid another major rework in the future, we have designed a rulebook for the scale so it's known what needs to happen when a rule is added. +- To show that the project has a commitment on keeping the scale up to date, we have created an architecture decision record (ADR) that describes the goal and the process of keeping the scale up to date. +- The developer documentation now has [a brand new section](/docs/core/integration-quality-scale/) dedicated to the Integration Quality Scale. It includes the rules and the reasoning for every rule, and it provides examples that can be used to give an idea of how to implement it. There is also [a checklist](/docs/core/integration-quality-scale/checklist) that can be used for changing the scale of an integration. +- Documentation now also plays a role in the quality of the integrations, since the user experience of an integration goes beyond code. +- To track the progress of integration, we have added [a `quality_scale.yaml` file](https://github.com/home-assistant/core/blob/dev/homeassistant/components/airgradient/quality_scale.yaml) to the integration folder. This will contain a list of all the rules it is adhering to and notes down possible exemptions and the reason behind the exemption. + +With this in place, you might wonder what the next steps are. +Because this is a major rework of the scale, we have decided to drop the scale of the current integrations. +This means that those integrations have to create a PR to get their scale back to the level they think it should be. + +I would also like to motivate the community to help out with getting as many integrations to Bronze (or above) as possible. +I also want to thank the community for their input in the quality scale and their effort in maintaining the integrations. +Home Assistant would not be where it is today without the community ❤️. \ No newline at end of file From bb2b8ab46010d6416be4f4223963feaac2632a1e Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Wed, 20 Nov 2024 20:24:38 +0100 Subject: [PATCH 08/55] Fix comment on blogpost (#2462) --- blog/2024-11-20-integration-quality-scale.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/blog/2024-11-20-integration-quality-scale.md b/blog/2024-11-20-integration-quality-scale.md index 15ef7eee06f..d5fc9583f83 100644 --- a/blog/2024-11-20-integration-quality-scale.md +++ b/blog/2024-11-20-integration-quality-scale.md @@ -18,7 +18,7 @@ To give an overview of the changes: - To show that the project has a commitment on keeping the scale up to date, we have created an architecture decision record (ADR) that describes the goal and the process of keeping the scale up to date. - The developer documentation now has [a brand new section](/docs/core/integration-quality-scale/) dedicated to the Integration Quality Scale. It includes the rules and the reasoning for every rule, and it provides examples that can be used to give an idea of how to implement it. There is also [a checklist](/docs/core/integration-quality-scale/checklist) that can be used for changing the scale of an integration. - Documentation now also plays a role in the quality of the integrations, since the user experience of an integration goes beyond code. -- To track the progress of integration, we have added [a `quality_scale.yaml` file](https://github.com/home-assistant/core/blob/dev/homeassistant/components/airgradient/quality_scale.yaml) to the integration folder. This will contain a list of all the rules it is adhering to and notes down possible exemptions and the reason behind the exemption. +- To track the progress of integration, we have added [a `quality_scale.yaml` file](https://github.com/home-assistant/core/blob/dev/homeassistant/components/airgradient/quality_scale.yaml) to the integration folder. This will contain a list of all the rules the integration is adhering to and notes of possible exemptions and the reasons behind them. With this in place, you might wonder what the next steps are. Because this is a major rework of the scale, we have decided to drop the scale of the current integrations. From 7d40b0877c879a97dc058f20ab3c80b326ecdb99 Mon Sep 17 00:00:00 2001 From: dougiteixeira <31328123+dougiteixeira@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:47:03 -0300 Subject: [PATCH 09/55] Remove duplicate text in integration quality scale (#2463) --- docs/core/integration-quality-scale/index.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core/integration-quality-scale/index.md b/docs/core/integration-quality-scale/index.md index 850e5881e79..5187700524f 100644 --- a/docs/core/integration-quality-scale/index.md +++ b/docs/core/integration-quality-scale/index.md @@ -107,7 +107,7 @@ The world of IoT and all technologies used by Home Assistant are changing at a f This also means that new insights and newly developed and adopted best practices will occur over time, resulting in new additions and improvements to the individual integration quality scale rules. -If a tier is adjusted, all integrations in that tier need to be re-evaluated and adjusted accordingly. One exception to this is integrations that have devices that are part of the Works with Home Assistant program. Those integrations will be flagged as grandfathered into their existing tier. +If a tier is adjusted, all integrations in that tier need to be re-evaluated and adjusted accordingly. :::info One exception to this is integrations that have devices that are part of the Works with Home Assistant program. Those integrations will be flagged as grandfathered into their existing tier. @@ -183,4 +183,4 @@ Characteristics: - Maintained by individual developers or community members. - User experience may vary widely. - Functionality, security, and stability can vary widely. -- Documentation may be limited. \ No newline at end of file +- Documentation may be limited. From 16f69ed65ea5ae97bb542383b1516bb731f9df40 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Thu, 21 Nov 2024 19:00:44 +0000 Subject: [PATCH 10/55] Add blog post on unit of measurement translations (#2461) --- ...-11-21-unit-of-measurement-translations.md | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) create mode 100644 blog/2024-11-21-unit-of-measurement-translations.md diff --git a/blog/2024-11-21-unit-of-measurement-translations.md b/blog/2024-11-21-unit-of-measurement-translations.md new file mode 100644 index 00000000000..cd85514bc39 --- /dev/null +++ b/blog/2024-11-21-unit-of-measurement-translations.md @@ -0,0 +1,23 @@ +--- +author: Abílio Costa +authorURL: https://github.com/abmantis +title: "Translating units of measurement" +--- + +Home Assistant 2024.12 will support the translation of custom units of measurement. Previously, those would have been hardcoded in their integrations. + +Just like for entity names, integrations just need to set the `translation_key` on the entity definition and provide the unit designation in the `strings.json` file (with the new `unit_of_measurement` key): + +```json +{ + "entity": { + "sensor": { + "subscribers_count": { + "unit_of_measurement": "subscribers" + }, + } + } +} +``` + +Check our [backend localization documentation](/docs/internationalization/core#unit-of-measurement-of-entities) for details. From 3eccdecae16d31572c7544446643ed6f699ab9b5 Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Thu, 21 Nov 2024 20:33:59 +0100 Subject: [PATCH 11/55] IQS - Update removal instructions for integrations (#2465) --- .../rules/docs-removal-instructions.md | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/docs/core/integration-quality-scale/rules/docs-removal-instructions.md b/docs/core/integration-quality-scale/rules/docs-removal-instructions.md index b3c1c0a7692..a1371336e87 100644 --- a/docs/core/integration-quality-scale/rules/docs-removal-instructions.md +++ b/docs/core/integration-quality-scale/rules/docs-removal-instructions.md @@ -10,7 +10,12 @@ The documentation should provide clear instructions on how to remove the device ## Example implementation ```markdown showLineNumbers -To remove the integration, go to {% my integrations title="**Settings** > **Devices & services**" %} and select the integration card. Then, select the three dots {% icon "mdi:dots-vertical" %} menu and select **Delete**. +## Remove integration + +This integration can be removed by following these steps: + +{% include integrations/remove_device_service.md %} + After deleting the integration, go to the app of the manufacturer and remove the Home Assistant integration from there as well. ``` From b385a5fec9f8a591af4a5b318e9dbc1c6785423b Mon Sep 17 00:00:00 2001 From: Josef Zweck <24647999+zweckj@users.noreply.github.com> Date: Thu, 21 Nov 2024 20:48:17 +0100 Subject: [PATCH 12/55] Add mL/s for volume flow rate (#2453) --- docs/core/entity/number.md | 2 +- docs/core/entity/sensor.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core/entity/number.md b/docs/core/entity/number.md index 6f09384bca9..529c0824412 100644 --- a/docs/core/entity/number.md +++ b/docs/core/entity/number.md @@ -74,7 +74,7 @@ If specifying a device class, your number entity will need to also return the co | `NumberDeviceClass.VOLATILE_ORGANIC_COMPOUNDS` | µg/m³ | Concentration of volatile organic compounds | `NumberDeviceClass.VOLTAGE` | V, mV, µV | Voltage | `NumberDeviceClass.VOLUME` | L, mL, gal, fl. oz., m³, ft³, CCF | Generic volume, this device class should be used to represent a consumption, for example the amount of fuel consumed by a vehicle. -| `NumberDeviceClass.VOLUME_FLOW_RATE` | m³/h, ft³/min, L/min, gal/min | Volume flow rate, this device class should be used for sensors representing a flow of some volume, for example the amount of water consumed momentarily. +| `NumberDeviceClass.VOLUME_FLOW_RATE` | m³/h, ft³/min, L/min, gal/min, mL/s | Volume flow rate, this device class should be used for sensors representing a flow of some volume, for example the amount of water consumed momentarily. | `NumberDeviceClass.VOLUME_STORAGE` | L, mL, gal, fl. oz., m³, ft³, CCF | Generic stored volume, this device class should be used to represent a stored volume, for example the amount of fuel in a fuel tank. | `NumberDeviceClass.WATER` | L, gal, m³, ft³, CCF | Water consumption | `NumberDeviceClass.WEIGHT` | kg, g, mg, µg, oz, lb, st | Generic mass; `weight` is used instead of `mass` to fit with every day language. diff --git a/docs/core/entity/sensor.md b/docs/core/entity/sensor.md index 0d5300d128c..9d502e114c8 100644 --- a/docs/core/entity/sensor.md +++ b/docs/core/entity/sensor.md @@ -80,7 +80,7 @@ If specifying a device class, your sensor entity will need to also return the co | `SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS` | ppm, ppb | Ratio of volatile organic compounds | `SensorDeviceClass.VOLTAGE` | V, mV, µV | Voltage | `SensorDeviceClass.VOLUME` | L, mL, gal, fl. oz., m³, ft³, CCF | Generic volume, this device class should be used for sensors representing a consumption, for example the amount of fuel consumed by a vehicle. -| `SensorDeviceClass.VOLUME_FLOW_RATE` | m³/h, ft³/min, L/min, gal/min | Volume flow rate, this device class should be used for sensors representing a flow of some volume, for example the amount of water consumed momentarily. +| `SensorDeviceClass.VOLUME_FLOW_RATE` | m³/h, ft³/min, L/min, gal/min, mL/s | Volume flow rate, this device class should be used for sensors representing a flow of some volume, for example the amount of water consumed momentarily. | `SensorDeviceClass.VOLUME_STORAGE` | L, mL, gal, fl. oz., m³, ft³, CCF | Generic stored volume, this device class should be used for sensors representing a stored volume, for example the amount of fuel in a fuel tank. | `SensorDeviceClass.WATER` | L, gal, m³, ft³, CCF | Water consumption | `SensorDeviceClass.WEIGHT` | kg, g, mg, µg, oz, lb, st | Generic mass; `weight` is used instead of `mass` to fit with every day language. From 23d946d7fd579b43f1988ad2c949d3db0bad1fb1 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:18:50 +0100 Subject: [PATCH 13/55] Add path to exceptions in IQS action-exceptions.md (#2468) Co-authored-by: Joost Lekkerkerker --- .../integration-quality-scale/rules/action-exceptions.md | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/docs/core/integration-quality-scale/rules/action-exceptions.md b/docs/core/integration-quality-scale/rules/action-exceptions.md index 9f5a7768e5e..c9082ca635c 100644 --- a/docs/core/integration-quality-scale/rules/action-exceptions.md +++ b/docs/core/integration-quality-scale/rules/action-exceptions.md @@ -21,7 +21,9 @@ When the problem is caused by an error in the service action itself (for example In this example, we show a function that is registered as a service action in Home Assistant. If the input is incorrect (when the end date is before the start date), a `ServiceValidationError` is raised, and if we can't reach the service, we raise a `HomeAssistantError`. -```python {6,10} showLineNumbers +```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] @@ -44,4 +46,4 @@ There are no exceptions to this rule. ## Related rules - \ No newline at end of file + From 5a5a911881553abb488cc829167c3f36e5c11f4a Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Fri, 22 Nov 2024 13:43:00 +0100 Subject: [PATCH 14/55] Deprecate camera frontend_stream_type (#2459) --- docs/core/entity/camera.md | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/docs/core/entity/camera.md b/docs/core/entity/camera.md index 7e35102831b..eee5e2f0b5f 100644 --- a/docs/core/entity/camera.md +++ b/docs/core/entity/camera.md @@ -15,7 +15,6 @@ Properties should always only return information from memory and not do I/O (lik | ------------------------ | ------------------------------------| ------- | --------------------------------------------------------------------------------------------------- | | brand | str | None | `None` | The brand (manufacturer) of the camera. | | frame_interval | `float` | 0.5 | The interval between frames of the stream. | -| frontend_stream_type | StreamType | None | `None` | Used with `CameraEntityFeature.STREAM` to tell the frontend which type of stream to use (`StreamType.HLS` or `StreamType.WEB_RTC`) | | is_on | `bool` | `True` | Indication of whether the camera is on. | | is_recording | `bool` | `False` | Indication of whether the camera is recording. Used to determine `state`. | | is_streaming | `bool` | `False` | Indication of whether the camera is streaming. Used to determine `state`. | @@ -93,15 +92,14 @@ A common way for a camera entity to render a camera still image is to pass the s ### WebRTC streams -WebRTC enabled cameras can be used by facilitating a direct connection with the home assistant frontend. This usage requires `CameraEntityFeature.STREAM` with `frontend_stream_type` set to `StreamType.WEB_RTC`. - -The integration must implement the two following methods to support native WebRTC: +WebRTC enabled cameras can be used by facilitating a direct connection with the home assistant frontend. This usage requires `CameraEntityFeature.STREAM` and the integration must implement the two following methods to support native WebRTC: - `async_handle_async_webrtc_offer`: To initialize a WebRTC stream. Any messages/errors coming in async should be forwared to the frontend with the `send_message` callback. - `async_on_webrtc_candidate`: The frontend will call it with any candidate coming in after the offer is sent. The following method can optionally be implemented: - `close_webrtc_session` (Optional): The frontend will call it when the stream is closed. Can be used to clean up things. WebRTC streams do not use the `stream` component and do not support recording. +By implementing the WebRTC methods, the frontend assumes that the camera supports only WebRTC and therefore will not fallbac to HLS. ```python class MyCamera(Camera): @@ -113,7 +111,7 @@ class MyCamera(Camera): Async means that it could take some time to process the offer and responses/message will be sent with the send_message callback. - This method is used by cameras with CameraEntityFeature.STREAM and StreamType.WEB_RTC. + This method is used by cameras with CameraEntityFeature.STREAM An integration overriding this method must also implement async_on_webrtc_candidate. Integrations can override with a native WebRTC implementation. From b2914fe212b9d8c386706e97b399406f46194ab0 Mon Sep 17 00:00:00 2001 From: martetassyns <78635303+martetassyns@users.noreply.github.com> Date: Fri, 22 Nov 2024 20:38:24 +0100 Subject: [PATCH 15/55] Document required step before full test suite can be executed (#2466) Co-authored-by: Franck Nijhof --- docs/development_testing.md | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/docs/development_testing.md b/docs/development_testing.md index 3e17ec6b15a..2c39f377f65 100644 --- a/docs/development_testing.md +++ b/docs/development_testing.md @@ -17,6 +17,12 @@ To run our linters, on the full code base, run the following command: pre-commit run --all-files ``` +To run the full test suite, more dependencies are required than what is set up in the devcontainer by default. To install all dependencies, activate the virtual environment and run the command: + +```shell +uv pip install -r requirements_test_all.txt +``` + To start the tests, and run the full test suite, activate the virtual environment and run the command: ```shell From a2230fe3252f4e5830de4595cd57f64efd58fc3d Mon Sep 17 00:00:00 2001 From: Robert Resch Date: Tue, 26 Nov 2024 19:16:14 +0100 Subject: [PATCH 16/55] Add blog about camera deprecations (#2473) * Add blog about camera deprecations * Tweaks * Apply suggestions from code review Co-authored-by: Martin Hjelmare * Change title * Add link * Update blog/2024-11-26-camera-deprecations.md Co-authored-by: Martin Hjelmare * fix link * Update blog/2024-11-26-camera-deprecations.md Co-authored-by: Martin Hjelmare --------- Co-authored-by: Martin Hjelmare --- blog/2024-11-26-camera-deprecations.md | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 blog/2024-11-26-camera-deprecations.md diff --git a/blog/2024-11-26-camera-deprecations.md b/blog/2024-11-26-camera-deprecations.md new file mode 100644 index 00000000000..76f4f91f3f7 --- /dev/null +++ b/blog/2024-11-26-camera-deprecations.md @@ -0,0 +1,17 @@ +--- +author: Robert Resch +authorURL: https://github.com/edenhaus +authorImageURL: https://avatars.githubusercontent.com/u/26537646?s=96&v=4 +title: "Camera API changes" +--- + +With Home Assistant `2024.11` we added WebRTC for most cameras. To add support for it we needed to refactor and improve the camera entity. +Today we would like to announce that with the upcoming Home Assistant release `2024.12` the following methods are deprecated and will be removed with Home Assistant version `2025.6`: + +- The property `frontend_stream_type` will be removed. As of `2024.11` Home assistant will identify the frontend stream type by checking if the camera entity implements the native WebRTC methods ([#130932](https://github.com/home-assistant/core/pull/130932)). Card developers can use the new websocket command `camera/capabilities` to get the frontend stream types. + +- The method `async_handle_web_rtc_offer` will be removed. Please use `async_handle_async_webrtc_offer` and the async WebRTC signaling approach ([#131285](https://github.com/home-assistant/core/pull/131285)). + +- The method `async_register_rtsp_to_web_rtc_provider` has been deprecated. Please use `async_register_webrtc_provider`, which offers more flexibility and supports the async WebRTC signaling approach ([#131462](https://github.com/home-assistant/core/pull/131462)). + +More information about the replacements can be found in the [camera entity documentation](/docs/core/entity/camera). From e34755845c0cb0fd50142163e609f199644cef0b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Wed, 27 Nov 2024 13:21:46 +0000 Subject: [PATCH 17/55] Add page for config entry diagnostics (#2469) * Add page for config entry diagnostics * Address review suggestions * Revert sidebar * Move page out of config entries and address suggestions * Update docs/core/integration_diagnostics.md Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> * Reword warning * Remove device ids from sensitive list; link cleanup * Clarify diag download methods --------- Co-authored-by: coderabbitai[bot] <136622811+coderabbitai[bot]@users.noreply.github.com> --- .../rules/diagnostics.md | 6 ++- docs/core/integration_diagnostics.md | 49 +++++++++++++++++++ sidebars.js | 1 + 3 files changed, 55 insertions(+), 1 deletion(-) create mode 100644 docs/core/integration_diagnostics.md diff --git a/docs/core/integration-quality-scale/rules/diagnostics.md b/docs/core/integration-quality-scale/rules/diagnostics.md index 9a16ecec4b5..31fa156a856 100644 --- a/docs/core/integration-quality-scale/rules/diagnostics.md +++ b/docs/core/integration-quality-scale/rules/diagnostics.md @@ -33,6 +33,10 @@ async def async_get_config_entry_diagnostics( } ``` +## Additional resources + +To learn more information about diagnostics, check out the [diagnostics documentation](/docs/core/integration_diagnostics). + ## Exceptions -There are no exceptions to this rule. \ No newline at end of file +There are no exceptions to this rule. diff --git a/docs/core/integration_diagnostics.md b/docs/core/integration_diagnostics.md new file mode 100644 index 00000000000..b32480bc9fe --- /dev/null +++ b/docs/core/integration_diagnostics.md @@ -0,0 +1,49 @@ +--- +title: Integration diagnostics +sidebar_label: "Diagnostics" +--- + +Integrations can provide diagnostics to help the user gather data to aid in troubleshooting. Diagnostics can be provided for config entries but also individually for each device entry. + +Users can download config entry diagnostics from the config entry options menu, on the integration page. For device diagnostics, users can download them from the device info section (or from its menu, depending on the integration). Note that if an integration does not implement device diagnostics, the device page will provide config entry diagnostics. + +:::warning +It is critical to ensure that no sensitive data is exposed. This includes but is not limited to: +- Passwords and API keys +- Authentication tokens +- Location data +- Personal information + +Home Assistant provides the `async_redact_data` utility function which you can use to safely remove sensitive data from the diagnostics output. +::: + +The following is an example on how to implement both config entry and 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, + } +``` + +An integration can provide both types of diagnostics or just one of them. diff --git a/sidebars.js b/sidebars.js index 28803c6e9fb..a711dd5e3aa 100644 --- a/sidebars.js +++ b/sidebars.js @@ -117,6 +117,7 @@ module.exports = { "creating_integration_manifest", "config_entries_config_flow_handler", "config_entries_options_flow_handler", + "core/integration_diagnostics", "configuration_yaml_index", "dev_101_services", "creating_platform_index", From a7abce4bce9bf94c3b80c2b77fe8bd6f6eabffa2 Mon Sep 17 00:00:00 2001 From: c0ffeeca7 <38767475+c0ffeeca7@users.noreply.github.com> Date: Thu, 28 Nov 2024 09:17:49 +0100 Subject: [PATCH 18/55] IQS: docs for supported devices: streamline title (#2475) --- .../rules/docs-supported-devices.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/docs/core/integration-quality-scale/rules/docs-supported-devices.md b/docs/core/integration-quality-scale/rules/docs-supported-devices.md index 27b8c793d98..f01d5cc987c 100644 --- a/docs/core/integration-quality-scale/rules/docs-supported-devices.md +++ b/docs/core/integration-quality-scale/rules/docs-supported-devices.md @@ -1,16 +1,17 @@ --- -title: "The integration documents known supported / unsupported devices" +title: "The documentation lists known supported / unsupported devices" --- ## Reasoning A lot of Home Assistant users buy devices based on if Home Assistant supports them. -To make it easier for users to find out if a device is supported, the documentation should document the known supported or unsupported devices. +To make it easier for users to find out if a device is supported, the documentation should list the known supported or unsupported devices. This will decrease the amount of bad experiences where the user finds out their device is not supported when they try to set it up. ## Example implementation ```markdown showLineNumbers + ## Supported devices The following devices are known to be supported by the integration: From 37bd537580743cf511f3d66fc75272cccd913b1f Mon Sep 17 00:00:00 2001 From: c0ffeeca7 <38767475+c0ffeeca7@users.noreply.github.com> Date: Thu, 28 Nov 2024 11:54:06 +0100 Subject: [PATCH 19/55] IQS supported devices: remove term 'list' (#2478) - as the point is to have it documented somehow. - It can be another format. the format does not have to be a list- --- .../integration-quality-scale/rules/docs-supported-devices.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core/integration-quality-scale/rules/docs-supported-devices.md b/docs/core/integration-quality-scale/rules/docs-supported-devices.md index f01d5cc987c..893826fc24a 100644 --- a/docs/core/integration-quality-scale/rules/docs-supported-devices.md +++ b/docs/core/integration-quality-scale/rules/docs-supported-devices.md @@ -1,11 +1,11 @@ --- -title: "The documentation lists known supported / unsupported devices" +title: "The documentation describes known supported / unsupported devices" --- ## Reasoning A lot of Home Assistant users buy devices based on if Home Assistant supports them. -To make it easier for users to find out if a device is supported, the documentation should list the known supported or unsupported devices. +To make it easier for users to find out if a device is supported, the documentation should describe the known supported or unsupported devices. This will decrease the amount of bad experiences where the user finds out their device is not supported when they try to set it up. ## Example implementation From 501e18287934c61c2aeaa4910120d7dc29a480eb Mon Sep 17 00:00:00 2001 From: c0ffeeca7 <38767475+c0ffeeca7@users.noreply.github.com> Date: Thu, 28 Nov 2024 12:34:39 +0100 Subject: [PATCH 20/55] IQS docs: integration removal: rephrase example (#2477) --- .../rules/docs-removal-instructions.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/core/integration-quality-scale/rules/docs-removal-instructions.md b/docs/core/integration-quality-scale/rules/docs-removal-instructions.md index a1371336e87..67fe2d1e9c4 100644 --- a/docs/core/integration-quality-scale/rules/docs-removal-instructions.md +++ b/docs/core/integration-quality-scale/rules/docs-removal-instructions.md @@ -10,9 +10,9 @@ The documentation should provide clear instructions on how to remove the device ## Example implementation ```markdown showLineNumbers -## Remove integration +## Removing the integration -This integration can be removed by following these steps: +This integration follows standard integration removal. No extra steps are required. {% include integrations/remove_device_service.md %} @@ -21,4 +21,4 @@ After deleting the integration, go to the app of the manufacturer and remove the ## Exceptions -There are no exceptions to this rule. \ No newline at end of file +There are no exceptions to this rule. From b78f6127cced926b1c6e8b58ae44eb75eedc0a78 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Thu, 28 Nov 2024 17:00:58 +0100 Subject: [PATCH 21/55] Add blog about homeassistant.util.dt.utc_to_timestamp deprecation (#2476) --- ...2024-11-28-dt-util-utc-to-timestamp-deprecation.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 blog/2024-11-28-dt-util-utc-to-timestamp-deprecation.md diff --git a/blog/2024-11-28-dt-util-utc-to-timestamp-deprecation.md b/blog/2024-11-28-dt-util-utc-to-timestamp-deprecation.md new file mode 100644 index 00000000000..3a8d602857c --- /dev/null +++ b/blog/2024-11-28-dt-util-utc-to-timestamp-deprecation.md @@ -0,0 +1,11 @@ +--- +author: Erik Montnemery +authorURL: https://github.com/emontnemery +title: "Utility function homeassistant.util.dt.utc_to_timestamp is deprecated" +--- + +The utility function `homeassistant.util.dt.utc_to_timestamp` is deprecated and will be removed in Home Assistant Core 2026.1, custom integrations which call it should instead call the stdlib method `datetime.datetime.timestamp()` + +The reason for deprecation is that the stdlib method is just as fast as the utility function. + +More details can be found in the [core PR](https://github.com/home-assistant/core/pull/131787). \ No newline at end of file From 9dcf4c72b777726d883f1fcad06b55f927b5550f Mon Sep 17 00:00:00 2001 From: Klaas Schoute Date: Mon, 2 Dec 2024 00:11:29 +0100 Subject: [PATCH 22/55] IQC - Improve installation parameter examples (#2474) * IQC - Improve installation parameter examples * tweak breadcrumb style --------- Co-authored-by: c0ffeeca7 <38767475+c0ffeeca7@users.noreply.github.com> --- .../rules/docs-installation-parameters.md | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/docs/core/integration-quality-scale/rules/docs-installation-parameters.md b/docs/core/integration-quality-scale/rules/docs-installation-parameters.md index c5511dbaede..2b2436296a7 100644 --- a/docs/core/integration-quality-scale/rules/docs-installation-parameters.md +++ b/docs/core/integration-quality-scale/rules/docs-installation-parameters.md @@ -10,8 +10,21 @@ This should help the user to gather all the necessary information before startin ## Example implementation +In case an integration is used via a config flow: + ```markdown showLineNumbers {% configuration_basic %} +Host: + description: "The IP address of your bridge. You can find it in your router or in the Integration app under **Bridge Settings** > **Local API**." +Local access token: + description: "The local access token for your bridge. You can find it in the Integration app under **Bridge Settings** > **Local API**." +{% endconfiguration_basic %} +``` + +In case an integration is set up via YAML in the `configuration.yaml`: + +```markdown showLineNumbers +{% configuration %} Host: description: "The IP address of your bridge. You can find it in your router or in the Integration app under **Bridge Settings** -> **Local API**." required: false @@ -20,9 +33,9 @@ Local access token: description: "The local access token for your bridge. You can find it in the Integration app under **Bridge Settings** -> **Local API**." required: false type: string -{% endconfiguration_basic %} +{% endconfiguration %} ``` ## Exceptions -There are no exceptions to this rule. \ No newline at end of file +There are no exceptions to this rule. From 766c2705ba1b8b7ab3f59f85b3dd00c370cf7f22 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Tue, 3 Dec 2024 18:05:18 +0000 Subject: [PATCH 23/55] Update remark on translated units and native unit definition (#2485) --- docs/core/entity/number.md | 2 +- docs/core/entity/sensor.md | 2 +- docs/internationalization/core.md | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/core/entity/number.md b/docs/core/entity/number.md index 529c0824412..a713a19819f 100644 --- a/docs/core/entity/number.md +++ b/docs/core/entity/number.md @@ -19,7 +19,7 @@ Properties should always only return information from memory and not do I/O (lik | native_min_value | float | 0 | The minimum accepted value in the number's `native_unit_of_measurement` (inclusive) | native_step | float | **See below** | Defines the resolution of the values, i.e. the smallest increment or decrement in the number's | native_unit_of_measurement | string | `None` | The unit of measurement that the number's value is expressed in. If the `native_unit_of_measurement` is °C or °F, and its `device_class` is temperature, the number's `unit_of_measurement` will be the preferred temperature unit configured by the user and the number's `state` will be the `native_value` after an optional unit conversion. | native_value | float | **Required** | The value of the number in the number's `native_unit_of_measurement`. -| native_unit_of_measurement | string | None | The unit of measurement that the sensor's value is expressed in. If the `native_unit_of_measurement` is °C or °F, and its `device_class` is temperature, the sensor's `unit_of_measurement` will be the preferred temperature unit configured by the user and the sensor's `state` will be the `native_value` after an optional unit conversion. +| native_unit_of_measurement | string | None | The unit of measurement that the sensor's value is expressed in. If the `native_unit_of_measurement` is °C or °F, and its `device_class` is temperature, the sensor's `unit_of_measurement` will be the preferred temperature unit configured by the user and the sensor's `state` will be the `native_value` after an optional unit conversion. If a [unit translation is provided](/docs/internationalization/core#unit-of-measurement-of-entities), `native_unit_of_measurement` should not be defined. Other properties that are common to all entities such as `icon`, `name` etc are also applicable. diff --git a/docs/core/entity/sensor.md b/docs/core/entity/sensor.md index 9d502e114c8..6e5d9a0d980 100644 --- a/docs/core/entity/sensor.md +++ b/docs/core/entity/sensor.md @@ -15,7 +15,7 @@ Properties should always only return information from memory and not do I/O (lik | ---- | ---- | ------- | ----------- | device_class | SensorDeviceClass | None | `None` | Type of sensor. | last_reset | datetime.datetime | None | `None` | The time when an accumulating sensor such as an electricity usage meter, gas meter, water meter etc. was initialized. If the time of initialization is unknown, set it to `None`. Note that the `datetime.datetime` returned by the `last_reset` property will be converted to an ISO 8601-formatted string when the entity's state attributes are updated. When changing `last_reset`, the `state` must be a valid number. -| native_unit_of_measurement | str | None | `None` | The unit of measurement that the sensor's value is expressed in. If the `native_unit_of_measurement` is °C or °F, and its `device_class` is temperature, the sensor's `unit_of_measurement` will be the preferred temperature unit configured by the user and the sensor's `state` will be the `native_value` after an optional unit conversion. +| native_unit_of_measurement | str | None | `None` | The unit of measurement that the sensor's value is expressed in. If the `native_unit_of_measurement` is °C or °F, and its `device_class` is temperature, the sensor's `unit_of_measurement` will be the preferred temperature unit configured by the user and the sensor's `state` will be the `native_value` after an optional unit conversion. If a [unit translation is provided](/docs/internationalization/core#unit-of-measurement-of-entities), `native_unit_of_measurement` should not be defined. | native_value | str | int | float | date | datetime | Decimal | None | **Required** | The value of the sensor in the sensor's `native_unit_of_measurement`. Using a `device_class` may restrict the types that can be returned by this property. | options | list[str] | None | `None` | In case this sensor provides a textual state, this property can be used to provide a list of possible states. Requires the `enum` device class to be set. Cannot be combined with `state_class` or `native_unit_of_measurement`. | state_class | SensorStateClass | str | None | `None` | Type of state. If not `None`, the sensor is assumed to be numerical and will be displayed as a line-chart in the frontend instead of as discrete values. diff --git a/docs/internationalization/core.md b/docs/internationalization/core.md index a5d07517808..7b2f82af495 100644 --- a/docs/internationalization/core.md +++ b/docs/internationalization/core.md @@ -470,7 +470,7 @@ If your integration provides entities under its domain, you will want to transla #### Unit of measurement of entities Integrations can provide translations for units of measurement of its entities. To do this, provide an `entity` object, that contains translations for the units and set the entity's `translation_key` property to a key under a domain in the `entity` object. -If the entity's `translation_key` property is not `None` and the `entity` object provides a translated unit, `SensorEntityDescription.native_unit_of_measurement` and `NumberEntityDescription.native_unit_of_measurement` will be ignored. +If the entity's `translation_key` property is not `None` and the `entity` object provides a translated unit of measurement, `SensorEntityDescription.native_unit_of_measurement` or `NumberEntityDescription.native_unit_of_measurement` should not be defined. The following example `strings.json` is for a `sensor` entity with its `translation_key` property set to `goal`: ```json From 5c04d2085a17674b0ada9b552f45e7fa18b46327 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Wed, 4 Dec 2024 11:45:05 +0100 Subject: [PATCH 24/55] Update ClimateEntity for horizontal swing support (#2309) --- blog/2024-12-03-climate-horizontal-swing.md | 73 +++++++++++++++++++++ docs/core/entity/climate.md | 39 ++++++++++- 2 files changed, 111 insertions(+), 1 deletion(-) create mode 100644 blog/2024-12-03-climate-horizontal-swing.md diff --git a/blog/2024-12-03-climate-horizontal-swing.md b/blog/2024-12-03-climate-horizontal-swing.md new file mode 100644 index 00000000000..27a72cf56ff --- /dev/null +++ b/blog/2024-12-03-climate-horizontal-swing.md @@ -0,0 +1,73 @@ +--- +author: G Johansson +authorURL: https://github.com/gjohansson-ST +authorImageURL: https://avatars.githubusercontent.com/u/62932417?v=4 +authorTwitter: GJohansson +title: "Climate entity now supports independent horizontal swing" +--- + +As of Home Assistant Core 2024.12, we have implemented an independent property and method for controlling horizontal swing in `ClimateEntity`. + +Integrations that support completely independent control and state for vertical and horizontal swing can now use the previous `swing_mode` for vertical swing only and use the new `swing_horizontal_mode` for providing the horizontal swing state and control. + +Integrations that don't have independent control should still keep using the current `swing_mode` for both vertical and horizontal support. + + +### Example + +Example requirements to implement `swing` and `swing_horizontal` in your climate entity. + +```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) + +``` + +More details can be found in the [climate documentation](/docs/core/entity/climate#swing-modes). + diff --git a/docs/core/entity/climate.md b/docs/core/entity/climate.md index f6172b23b63..597e19f2004 100644 --- a/docs/core/entity/climate.md +++ b/docs/core/entity/climate.md @@ -28,7 +28,9 @@ Properties should always only return information from memory and not do I/O (lik | preset_mode | str | None | **Required by SUPPORT_PRESET_MODE** | The current active preset. | | preset_modes | list[str] | None | **Required by SUPPORT_PRESET_MODE** | The available presets. | | swing_mode | str | None | **Required by SUPPORT_SWING_MODE** | The swing setting. | -| swing_modes | list[str] | None | **Required by SUPPORT_SWING_MODE** | Returns the list of available swing modes. | +| swing_modes | list[str] | None | **Required by SUPPORT_SWING_MODE** | Returns the list of available swing modes, only vertical modes in the case horizontal swing is implemented. | +| swing_horizontal_mode | str | None | **Required by SUPPORT_SWING_HORIZONTAL_MODE** | The horizontal swing setting. | +| swing_horizontal_modes | list[str] | None | **Required by SUPPORT_SWING_HORIZONTAL_MODE** | Returns the list of available horizontal swing modes. | | target_humidity | float | None | `None` | The target humidity the device is trying to reach. | | target_temperature | float | None | `None` | The temperature currently set to be reached. | | target_temperature_high | float | None | **Required by TARGET_TEMPERATURE_RANGE** | The upper bound target temperature | @@ -102,6 +104,12 @@ A device's fan can have different states. There are a couple of built-in fan mod The device fan can have different swing modes that it wants the user to know about/control. +:::note + +For integrations that don't have independent control of vertical and horizontal swing, all possible options should be listed in `swing_modes`, otherwise `swing_modes` provides vertical support and `swing_horizontal_modes` should provide horizontal support. + +::: + | Name | Description | | ------------------ | ------------------------------------------------- | | `SWING_OFF` | The fan is not swinging. | @@ -110,6 +118,21 @@ The device fan can have different swing modes that it wants the user to know abo | `SWING_HORIZONTAL` | The fan is swinging horizontal. | | `SWING_BOTH` | The fan is swinging both horizontal and vertical. | +### Swing horizontal modes + +The device fan can have different horizontal swing modes that it wants the user to know about/control. + +:::note + +This should only be implemented if the integration has independent control of vertical and horizontal swing. In such case the `swing_modes` property will provide vertical support and `swing_horizontal_modes` will provide horizontal support. + +::: + +| Name | Description | +| ------------------ | ------------------------------------------------- | +| `SWING_OFF` | The fan is not swinging. | +| `SWING_ON` | The fan is swinging. | + ## Supported features Supported features are defined by using values in the `ClimateEntityFeature` enum @@ -123,6 +146,7 @@ and are combined using the bitwise or (`|`) operator. | `FAN_MODE` | The device supports fan modes. | | `PRESET_MODE` | The device supports presets. | | `SWING_MODE` | The device supports swing modes. | +| `SWING_HORIZONTAL_MODE` | The device supports horizontal swing modes. | | `TURN_ON` | The device supports turn on. | | `TURN_OFF` | The device supports turn off. | @@ -243,6 +267,19 @@ class MyClimateEntity(ClimateEntity): """Set new target swing operation.""" ``` +### Set horizontal swing mode + +```python +class MyClimateEntity(ClimateEntity): + # Implement one of these methods. + + def set_swing_horizontal_mode(self, swing_mode): + """Set new target horizontal swing operation.""" + + async def async_set_swing_horizontal_mode(self, swing_mode): + """Set new target horizontal swing operation.""" +``` + ### Set temperature :::note From 529d1bc456f9eb06203b15b5490427fad7740e84 Mon Sep 17 00:00:00 2001 From: Erik Montnemery Date: Wed, 4 Dec 2024 14:23:21 +0100 Subject: [PATCH 25/55] Document update entity's display_precision property (#2414) * Document display_precision property * Update update.md --- docs/core/entity/update.md | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/docs/core/entity/update.md b/docs/core/entity/update.md index ecb7103a187..aeec724eaa5 100644 --- a/docs/core/entity/update.md +++ b/docs/core/entity/update.md @@ -24,13 +24,14 @@ Properties should always only return information from memory and not do I/O (lik | Name | Type | Default | Description | ---- | ---- | ------- | ----------- | auto_update | bool | `False` | The device or service that the entity represents has auto update logic. When this is set to `True` you can not skip updates. +| display_precision | int | `0` | Number of decimal digits for display of update progress. | in_progress | bool | `None` | Update installation progress. Should return a boolean (True if in progress, False if not). | installed_version | str | `None` | The currently installed and used version of the software. | latest_version | str | `None` | The latest version of the software available. | release_summary | str | `None` | Summary of the release notes or changelog. This is not suitable for long changelogs but merely suitable for a short excerpt update description of max 255 characters. | release_url | str | `None` | URL to the full release notes of the latest version available. | title | str | `None` | Title of the software. This helps to differentiate between the device or entity name versus the title of the software installed. -| update_percentage | int | `None` | Update installation progress. Can either return a number to indicate the progress from 0 to 100% or None. +| update_percentage | int, float | `None` | Update installation progress. Can either return a number to indicate the progress from 0 to 100% or None. Other properties that are common to all entities such as `device_class`, `entity_category`, `icon`, `name` etc are still applicable. From 07c01b39da1b38d2bad2f2bc10d72b2fe6593853 Mon Sep 17 00:00:00 2001 From: Tom Date: Thu, 5 Dec 2024 11:31:48 +0100 Subject: [PATCH 26/55] Align exception-translations with translation requirements (#2487) --- .../rules/exception-translations.md | 8 ++++++-- 1 file changed, 6 insertions(+), 2 deletions(-) diff --git a/docs/core/integration-quality-scale/rules/exception-translations.md b/docs/core/integration-quality-scale/rules/exception-translations.md index 464a2b18fd0..33be56ec96a 100644 --- a/docs/core/integration-quality-scale/rules/exception-translations.md +++ b/docs/core/integration-quality-scale/rules/exception-translations.md @@ -44,8 +44,12 @@ async def async_set_schedule(call: ServiceCall) -> ServiceResponse: ```json { "exceptions": { - "end_date_before_start_date": "The end date cannot be before the start date.", - "cannot_connect_to_schedule": "Cannot connect to the schedule." + "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." + } } } ``` From 8219141adc20ec34839eab6db8d046ebbe60f138 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Jan=20=C4=8Cerm=C3=A1k?= Date: Fri, 6 Dec 2024 15:17:22 +0100 Subject: [PATCH 27/55] Use and explain 'systemctl reboot' for reboot after RAUC install (#2490) Plain reboot is not sufficient as it ignores the reboot-param deployed to /run/systemd/ and which is required for boot to the other slot. --- docs/operating-system/update-system.md | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/docs/operating-system/update-system.md b/docs/operating-system/update-system.md index 9ae40fa8fe4..a9089c3ee4a 100644 --- a/docs/operating-system/update-system.md +++ b/docs/operating-system/update-system.md @@ -21,9 +21,13 @@ For development or testing, RAUC update bundles can be installed with the `rauc # cd /mnt/data/ # curl -L -O https://github.com/home-assistant/operating-system/releases/download/11.5.rc3/haos_rpi5-64-11.5.rc3.raucb # rauc install haos_rpi5-64-11.5.rc3.raucb -# reboot +# systemctl reboot ``` +:::note +On Raspberry Pi 5 which uses the `tryboot` mechanism, be sure to use `systemctl reboot`, as plain `reboot` wouldn't trigger booting from the other slot. Alternatively, explicit `reboot '0 tryboot'` is required. +::: + After the reboot the system should run with the newly installed HAOS version. ## Boot slots From 6aa5ec2f8cc418a5bab2b9b03b8033a97fc4384b Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Fri, 6 Dec 2024 20:33:47 +0100 Subject: [PATCH 28/55] Add note about update coordinator in test-before-setup IQS rule (#2486) Co-authored-by: Franck Nijhof --- .../core/integration-quality-scale/rules/test-before-setup.md | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/docs/core/integration-quality-scale/rules/test-before-setup.md b/docs/core/integration-quality-scale/rules/test-before-setup.md index 77da0ace7e4..57a40cd5af0 100644 --- a/docs/core/integration-quality-scale/rules/test-before-setup.md +++ b/docs/core/integration-quality-scale/rules/test-before-setup.md @@ -42,6 +42,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: MyIntegrationConfigEntry return True ``` +:::info +Please note that this may also be implemented implicitly when using a data update coordinator via `await coordinator.async_config_entry_first_refresh()`. +::: + ## Additional resources More information about config entries and their lifecycle can be found in the [config entry documentation](../../../config_entries_index). From c02d517f6025c69a2baef23fb14c7bdece753cb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Fri, 6 Dec 2024 19:45:39 +0000 Subject: [PATCH 29/55] Use absolute paths for IQS links (#2489) --- .../integration-quality-scale/_includes/rule_overview.jsx | 3 ++- docs/core/integration-quality-scale/index.md | 2 +- .../rules/_includes/related_rules.jsx | 3 ++- docs/core/integration-quality-scale/rules/action-setup.md | 2 +- .../integration-quality-scale/rules/appropriate-polling.md | 2 +- .../integration-quality-scale/rules/async-dependency.md | 2 +- .../rules/config-entry-unloading.md | 2 +- .../rules/config-flow-test-coverage.md | 4 ++-- docs/core/integration-quality-scale/rules/config-flow.md | 2 +- docs/core/integration-quality-scale/rules/devices.md | 2 +- .../rules/discovery-update-info.md | 4 ++-- docs/core/integration-quality-scale/rules/discovery.md | 6 +++--- .../core/integration-quality-scale/rules/entity-category.md | 2 +- .../integration-quality-scale/rules/entity-device-class.md | 4 ++-- .../rules/entity-disabled-by-default.md | 2 +- .../integration-quality-scale/rules/entity-translations.md | 4 ++-- .../integration-quality-scale/rules/entity-unavailable.md | 2 +- .../integration-quality-scale/rules/entity-unique-id.md | 2 +- .../rules/exception-translations.md | 2 +- .../core/integration-quality-scale/rules/has-entity-name.md | 4 ++-- .../integration-quality-scale/rules/icon-translations.md | 2 +- .../integration-quality-scale/rules/log-when-unavailable.md | 2 +- .../integration-quality-scale/rules/parallel-updates.md | 2 +- .../rules/reauthentication-flow.md | 2 +- .../integration-quality-scale/rules/reconfiguration-flow.md | 2 +- docs/core/integration-quality-scale/rules/repair-issues.md | 2 +- docs/core/integration-quality-scale/rules/runtime-data.md | 2 +- docs/core/integration-quality-scale/rules/stale-devices.md | 2 +- .../rules/test-before-configure.md | 2 +- .../integration-quality-scale/rules/test-before-setup.md | 2 +- docs/core/integration-quality-scale/rules/test-coverage.md | 2 +- .../integration-quality-scale/rules/unique-config-entry.md | 4 ++-- 32 files changed, 42 insertions(+), 40 deletions(-) diff --git a/docs/core/integration-quality-scale/_includes/rule_overview.jsx b/docs/core/integration-quality-scale/_includes/rule_overview.jsx index a3cf1d5cacc..7eb5df6cfd5 100644 --- a/docs/core/integration-quality-scale/_includes/rule_overview.jsx +++ b/docs/core/integration-quality-scale/_includes/rule_overview.jsx @@ -13,10 +13,11 @@ export default function RuleOverview({tier}) { if (typeof rule === "object") { id = rule.id; } + const absoluteRulePath = `/docs/core/integration-quality-scale/rules/${id}`; const relatedRule = docs[`core/integration-quality-scale/rules/${id}`]; return (
  • - {id} - {relatedRule.title} + {id} - {relatedRule.title}
  • ); })} diff --git a/docs/core/integration-quality-scale/index.md b/docs/core/integration-quality-scale/index.md index 5187700524f..6d2412ba60e 100644 --- a/docs/core/integration-quality-scale/index.md +++ b/docs/core/integration-quality-scale/index.md @@ -94,7 +94,7 @@ Home Assistant encourages our contributors to get their integrations to the high When an integration reaches the minimum requirements for a certain tier, a contributor can open a pull request to adjust the scale for the integration. This request needs to be accompanied by the full checklist for each rule of scale (including all rules of lower tiers), demonstrating that it has met those requirements. -The checklist can be found [here](checklist). +The checklist can be found [here](/docs/core/integration-quality-scale/checklist). Once the Home Assistant core team reviews and approves it, the integration will display the new tier as of the next major release of Home Assistant. diff --git a/docs/core/integration-quality-scale/rules/_includes/related_rules.jsx b/docs/core/integration-quality-scale/rules/_includes/related_rules.jsx index 79b9710bc8e..90e7b235d91 100644 --- a/docs/core/integration-quality-scale/rules/_includes/related_rules.jsx +++ b/docs/core/integration-quality-scale/rules/_includes/related_rules.jsx @@ -8,10 +8,11 @@ export default function RelatedRules({relatedRules}) { return (
      {relatedRules.map((rule) => { + const absoluteRulePath = `/docs/core/integration-quality-scale/rules/${rule}`; const relatedRule = docs[`core/integration-quality-scale/rules/${rule}`].title; return (
    • - {rule}: {relatedRule} + {rule}: {relatedRule}
    • ); })} diff --git a/docs/core/integration-quality-scale/rules/action-setup.md b/docs/core/integration-quality-scale/rules/action-setup.md index 0a68fbde205..0d92d60e4ef 100644 --- a/docs/core/integration-quality-scale/rules/action-setup.md +++ b/docs/core/integration-quality-scale/rules/action-setup.md @@ -48,7 +48,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: ## Additional resources -For more information on how to set up service actions, see the [service documentation](../../../dev_101_services). +For more information on how to set up service actions, see the [service documentation](/docs/dev_101_services). ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/appropriate-polling.md b/docs/core/integration-quality-scale/rules/appropriate-polling.md index c1687588917..14483b31c81 100644 --- a/docs/core/integration-quality-scale/rules/appropriate-polling.md +++ b/docs/core/integration-quality-scale/rules/appropriate-polling.md @@ -56,7 +56,7 @@ class MySensor(SensorEntity): ## Additional resources -More information about polling can be found in the [documentation](../../../integration_fetching_data). +More information about polling can be found in the [documentation](/docs/integration_fetching_data). ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/async-dependency.md b/docs/core/integration-quality-scale/rules/async-dependency.md index d9dadf268f7..59d561534be 100644 --- a/docs/core/integration-quality-scale/rules/async-dependency.md +++ b/docs/core/integration-quality-scale/rules/async-dependency.md @@ -14,7 +14,7 @@ This results not only in a more efficient system but the code is also more neat. ## Additional resources -More information on how to create a library can be found in the [documentation](../../../api_lib_index). +More information on how to create a library can be found in the [documentation](/docs/api_lib_index). ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/config-entry-unloading.md b/docs/core/integration-quality-scale/rules/config-entry-unloading.md index a714b023ce0..6ddb4c8e355 100644 --- a/docs/core/integration-quality-scale/rules/config-entry-unloading.md +++ b/docs/core/integration-quality-scale/rules/config-entry-unloading.md @@ -35,7 +35,7 @@ This can be useful to clean up resources without having to keep track of the rem ## Additional resources -More information about config entries and their lifecycle can be found in the [config entry documentation](../../../config_entries_index). +More information about config entries and their lifecycle can be found in the [config entry documentation](/docs/config_entries_index). ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/config-flow-test-coverage.md b/docs/core/integration-quality-scale/rules/config-flow-test-coverage.md index 36559a30248..94ae25a7c67 100644 --- a/docs/core/integration-quality-scale/rules/config-flow-test-coverage.md +++ b/docs/core/integration-quality-scale/rules/config-flow-test-coverage.md @@ -60,8 +60,8 @@ async def test_full_flow( ## Additional resources -More information about config flows can be found in the [config flow documentation](../../../config_entries_config_flow_handler). -More information about testing integrations can be found in the [testing documentation](../../../development_testing). +More information about config flows can be found in the [config flow documentation](/docs/config_entries_config_flow_handler). +More information about testing integrations can be found in the [testing documentation](/docs/development_testing). ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/config-flow.md b/docs/core/integration-quality-scale/rules/config-flow.md index 2e6495996e2..9a2917520be 100644 --- a/docs/core/integration-quality-scale/rules/config-flow.md +++ b/docs/core/integration-quality-scale/rules/config-flow.md @@ -69,7 +69,7 @@ class MyConfigFlow(ConfigFlow, domain=DOMAIN): ## Additional resources -More information about config flows can be found in the [config flow documentation](../../../config_entries_config_flow_handler). +More information about config flows can be found in the [config flow documentation](/docs/config_entries_config_flow_handler). More information about the architecture decision around config flows can be found in [ADR-0010](https://github.com/home-assistant/architecture/blob/master/adr/0010-integration-configuration.md) ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/devices.md b/docs/core/integration-quality-scale/rules/devices.md index 21566ed1e70..0fdba1fb08b 100644 --- a/docs/core/integration-quality-scale/rules/devices.md +++ b/docs/core/integration-quality-scale/rules/devices.md @@ -45,7 +45,7 @@ If the device represents a service, be sure to add `entry_type=DeviceEntryType.S ## Additional resources -More information about devices can be found in the [device](../../../device_registry_index) documentation. +More information about devices can be found in the [device](/docs/device_registry_index) documentation. ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/discovery-update-info.md b/docs/core/integration-quality-scale/rules/discovery-update-info.md index 333b69e02ac..ae00c888dba 100644 --- a/docs/core/integration-quality-scale/rules/discovery-update-info.md +++ b/docs/core/integration-quality-scale/rules/discovery-update-info.md @@ -58,8 +58,8 @@ This will create discovery flows for those devices. ## Additional resources -To learn more information about config flows, checkout the [config flow documentation](../../../config_entries_config_flow_handler). -To learn more about network protocols and discovery, checkout the [Networking and discovery documentation](../../../network_discovery). +To learn more information about config flows, checkout the [config flow documentation](/docs/config_entries_config_flow_handler). +To learn more about network protocols and discovery, checkout the [Networking and discovery documentation](/docs/network_discovery). ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/discovery.md b/docs/core/integration-quality-scale/rules/discovery.md index 3c0ae570160..ad54bbb895f 100644 --- a/docs/core/integration-quality-scale/rules/discovery.md +++ b/docs/core/integration-quality-scale/rules/discovery.md @@ -112,9 +112,9 @@ class MyConfigFlow(ConfigFlow, domain=DOMAIN): ## Additional resources -To learn more information about config flows, checkout the [config flow documentation](../../../config_entries_config_flow_handler). -To learn more about discovery on network protocols, checkout the [Networking and discovery documentation](../../../network_discovery). -To learn more about discovery for bluetooth devices, checkout the [Bluetooth documentation](../../../bluetooth). +To learn more information about config flows, checkout the [config flow documentation](/docs/config_entries_config_flow_handler). +To learn more about discovery on network protocols, checkout the [Networking and discovery documentation](/docs/network_discovery). +To learn more about discovery for bluetooth devices, checkout the [Bluetooth documentation](/docs/bluetooth). ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/entity-category.md b/docs/core/integration-quality-scale/rules/entity-category.md index aa192adce7c..987ec80f93e 100644 --- a/docs/core/integration-quality-scale/rules/entity-category.md +++ b/docs/core/integration-quality-scale/rules/entity-category.md @@ -23,7 +23,7 @@ class MySensor(SensorEntity): ## Additional resources -To learn more about the registry properties, checkout the [documentation](../../entity#registry-properties) about it. +To learn more about the registry properties, checkout the [documentation](/docs/core/entity#registry-properties) about it. ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/entity-device-class.md b/docs/core/integration-quality-scale/rules/entity-device-class.md index a3ab8e01f69..a1b65559f45 100644 --- a/docs/core/integration-quality-scale/rules/entity-device-class.md +++ b/docs/core/integration-quality-scale/rules/entity-device-class.md @@ -42,8 +42,8 @@ class MyTemperatureSensor(SensorEntity): ## Additional resources -A list of available device classes can be found in the entity pages under the [entity](../../entity) page. -More information about entity naming can be found in the [entity](../../entity#has_entity_name-true-mandatory-for-new-integrations) documentation. +A list of available device classes can be found in the entity pages under the [entity](/docs/core/entity) page. +More information about entity naming can be found in the [entity](/docs/core/entity#has_entity_name-true-mandatory-for-new-integrations) documentation. ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/entity-disabled-by-default.md b/docs/core/integration-quality-scale/rules/entity-disabled-by-default.md index 4a7b364bacf..7a741c5c2cb 100644 --- a/docs/core/integration-quality-scale/rules/entity-disabled-by-default.md +++ b/docs/core/integration-quality-scale/rules/entity-disabled-by-default.md @@ -43,7 +43,7 @@ class MySignalStrengthSensor(SensorEntity): ## Additional resources -To learn more about the entity registry properties, checkout the [documentation](../../entity#registry-properties) about it. +To learn more about the entity registry properties, checkout the [documentation](/docs/core/entity#registry-properties) about it. ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/entity-translations.md b/docs/core/integration-quality-scale/rules/entity-translations.md index 2109c6b4534..5f36915f7ef 100644 --- a/docs/core/integration-quality-scale/rules/entity-translations.md +++ b/docs/core/integration-quality-scale/rules/entity-translations.md @@ -54,8 +54,8 @@ If the entity's platform is either `binary_sensor`, `number`, `sensor`, or `upda ## Additional resources -More information about the translation process can be found in the [translation](../../../internationalization/core) documentation, it also contains information about the [entity translations](../../../internationalization/core#name-of-entities). -More information about entity naming can be found in the [entity](../../entity#has_entity_name-true-mandatory-for-new-integrations) documentation. +More information about the translation process can be found in the [translation](/docs/internationalization/core) documentation, it also contains information about the [entity translations](/docs/internationalization/core#name-of-entities). +More information about entity naming can be found in the [entity](/docs/core/entity#has_entity_name-true-mandatory-for-new-integrations) documentation. ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/entity-unavailable.md b/docs/core/integration-quality-scale/rules/entity-unavailable.md index bc49ed7dc7b..64abb167fc8 100644 --- a/docs/core/integration-quality-scale/rules/entity-unavailable.md +++ b/docs/core/integration-quality-scale/rules/entity-unavailable.md @@ -77,7 +77,7 @@ class MySensor(SensorEntity): ## Additional resources -For more information about managing integration state, see the [documentation](../../../integration_fetching_data). +For more information about managing integration state, see the [documentation](/docs/integration_fetching_data). ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/entity-unique-id.md b/docs/core/integration-quality-scale/rules/entity-unique-id.md index ee3b054163d..01f9e7a083c 100644 --- a/docs/core/integration-quality-scale/rules/entity-unique-id.md +++ b/docs/core/integration-quality-scale/rules/entity-unique-id.md @@ -34,7 +34,7 @@ class MySensor(SensorEntity): ## Additional resources -More information about the requirements for a unique identifier can be found in the [documentation](../../../entity_registry_index#unique-id-requirements). +More information about the requirements for a unique identifier can be found in the [documentation](/docs/entity_registry_index#unique-id-requirements). ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/exception-translations.md b/docs/core/integration-quality-scale/rules/exception-translations.md index 33be56ec96a..46358e3826d 100644 --- a/docs/core/integration-quality-scale/rules/exception-translations.md +++ b/docs/core/integration-quality-scale/rules/exception-translations.md @@ -56,7 +56,7 @@ async def async_set_schedule(call: ServiceCall) -> ServiceResponse: ## Additional resources -For more info on raising exceptions, check the [documentation](../../platform/raising_exceptions). +For more info on raising exceptions, check the [documentation](/docs/core/platform/raising_exceptions). ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/has-entity-name.md b/docs/core/integration-quality-scale/rules/has-entity-name.md index c8032b3a4dd..9f62f8d8e9c 100644 --- a/docs/core/integration-quality-scale/rules/has-entity-name.md +++ b/docs/core/integration-quality-scale/rules/has-entity-name.md @@ -56,8 +56,8 @@ class MyLock(LockEntity): ## Additional resources -More information about entity naming can be found in the [entity](../../entity#has_entity_name-true-mandatory-for-new-integrations) documentation. -More information about devices can be found in the [device](../../../device_registry_index) documentation. +More information about entity naming can be found in the [entity](/docs/core/entity#has_entity_name-true-mandatory-for-new-integrations) documentation. +More information about devices can be found in the [device](/docs/device_registry_index) documentation. ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/icon-translations.md b/docs/core/integration-quality-scale/rules/icon-translations.md index 5b88f53d29c..fed16b171de 100644 --- a/docs/core/integration-quality-scale/rules/icon-translations.md +++ b/docs/core/integration-quality-scale/rules/icon-translations.md @@ -53,7 +53,7 @@ class MySensor(SensorEntity): ## Additional resources -For more information about icon translations, check the [entity](../../entity#icon-translations) documentation. +For more information about icon translations, check the [entity](/docs/core/entity#icon-translations) documentation. ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/log-when-unavailable.md b/docs/core/integration-quality-scale/rules/log-when-unavailable.md index 556fe837bc2..74f10ada83a 100644 --- a/docs/core/integration-quality-scale/rules/log-when-unavailable.md +++ b/docs/core/integration-quality-scale/rules/log-when-unavailable.md @@ -80,7 +80,7 @@ class MySensor(SensorEntity): ## Additional resources -For more information about managing integration state, see the [documentation](../../../integration_fetching_data) +For more information about managing integration state, see the [documentation](/docs/integration_fetching_data) ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/parallel-updates.md b/docs/core/integration-quality-scale/rules/parallel-updates.md index 0e02cd824c3..fb0c40ab2da 100644 --- a/docs/core/integration-quality-scale/rules/parallel-updates.md +++ b/docs/core/integration-quality-scale/rules/parallel-updates.md @@ -36,7 +36,7 @@ This means that usually only the action calls will be relevant to consider for s ## Additional resources -For more information about request parallelism, check the [documentation](../../../integration_fetching_data#request-parallelism) for it. +For more information about request parallelism, check the [documentation](/docs/integration_fetching_data#request-parallelism) for it. ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/reauthentication-flow.md b/docs/core/integration-quality-scale/rules/reauthentication-flow.md index 15fbc337cb5..43a260a5dc5 100644 --- a/docs/core/integration-quality-scale/rules/reauthentication-flow.md +++ b/docs/core/integration-quality-scale/rules/reauthentication-flow.md @@ -93,7 +93,7 @@ class MyConfigFlow(ConfigFlow, domain=DOMAIN): ## Additional resources -For more info about handling expired credentials, check the [documentation](../../../integration_setup_failures#handling-expired-credentials). +For more info about handling expired credentials, check the [documentation](/docs/integration_setup_failures#handling-expired-credentials). ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/reconfiguration-flow.md b/docs/core/integration-quality-scale/rules/reconfiguration-flow.md index d3183e9a9b5..d4e03c40774 100644 --- a/docs/core/integration-quality-scale/rules/reconfiguration-flow.md +++ b/docs/core/integration-quality-scale/rules/reconfiguration-flow.md @@ -89,7 +89,7 @@ class MyConfigFlow(ConfigFlow, domain=DOMAIN): ## Additional resources -For more information on the reconfiguration flow, see the [reconfigure flow documentation](../../../config_entries_config_flow_handler#reconfigure). +For more information on the reconfiguration flow, see the [reconfigure flow documentation](/docs/config_entries_config_flow_handler#reconfigure). ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/repair-issues.md b/docs/core/integration-quality-scale/rules/repair-issues.md index bb281b2d672..6db995585fb 100644 --- a/docs/core/integration-quality-scale/rules/repair-issues.md +++ b/docs/core/integration-quality-scale/rules/repair-issues.md @@ -41,7 +41,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: MyConfigEntry) -> None: ## Additional resources -For more information about repair issues and repair flows, see the [repairs](../../platform/repairs) documentation. +For more information about repair issues and repair flows, see the [repairs](/docs/core/platform/repairs) documentation. ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/runtime-data.md b/docs/core/integration-quality-scale/rules/runtime-data.md index 413f62e0ae6..a6770e9e9ce 100644 --- a/docs/core/integration-quality-scale/rules/runtime-data.md +++ b/docs/core/integration-quality-scale/rules/runtime-data.md @@ -37,7 +37,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: MyIntegrationConfigEntry ## Additional resources -More information about configuration entries and their lifecycle can be found in the [config entry documentation](../../../config_entries_index). +More information about configuration entries and their lifecycle can be found in the [config entry documentation](/docs/config_entries_index). ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/stale-devices.md b/docs/core/integration-quality-scale/rules/stale-devices.md index 1e1d4cc978d..40de1b28257 100644 --- a/docs/core/integration-quality-scale/rules/stale-devices.md +++ b/docs/core/integration-quality-scale/rules/stale-devices.md @@ -79,7 +79,7 @@ async def async_remove_config_entry_device( ## Additional resources -For more info on devices, checkout the [device registry documentation](../../../device_registry_index). +For more info on devices, checkout the [device registry documentation](/docs/device_registry_index). ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/test-before-configure.md b/docs/core/integration-quality-scale/rules/test-before-configure.md index 530aa378618..1ada56f7f0c 100644 --- a/docs/core/integration-quality-scale/rules/test-before-configure.md +++ b/docs/core/integration-quality-scale/rules/test-before-configure.md @@ -64,7 +64,7 @@ class MyConfigFlow(ConfigFlow, domain=DOMAIN): ## Additional resources -More information about config flows can be found in the [config flow documentation](../../../config_entries_config_flow_handler). +More information about config flows can be found in the [config flow documentation](/docs/config_entries_config_flow_handler). ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/test-before-setup.md b/docs/core/integration-quality-scale/rules/test-before-setup.md index 57a40cd5af0..e4d98bdf608 100644 --- a/docs/core/integration-quality-scale/rules/test-before-setup.md +++ b/docs/core/integration-quality-scale/rules/test-before-setup.md @@ -48,7 +48,7 @@ Please note that this may also be implemented implicitly when using a data updat ## Additional resources -More information about config entries and their lifecycle can be found in the [config entry documentation](../../../config_entries_index). +More information about config entries and their lifecycle can be found in the [config entry documentation](/docs/config_entries_index). ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/test-coverage.md b/docs/core/integration-quality-scale/rules/test-coverage.md index 4baf3c5ea84..ae43987abcf 100644 --- a/docs/core/integration-quality-scale/rules/test-coverage.md +++ b/docs/core/integration-quality-scale/rules/test-coverage.md @@ -15,7 +15,7 @@ It also allows new developers to understand the codebase and make changes withou ## Additional resources -For more information about testing and how to calculate test coverage, see the [Testing your code](../../../development_testing) page. +For more information about testing and how to calculate test coverage, see the [Testing your code](/docs/development_testing) page. ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/unique-config-entry.md b/docs/core/integration-quality-scale/rules/unique-config-entry.md index 76e30e838d4..25a0c624690 100644 --- a/docs/core/integration-quality-scale/rules/unique-config-entry.md +++ b/docs/core/integration-quality-scale/rules/unique-config-entry.md @@ -106,8 +106,8 @@ If a configuration entry already exists for the same host, the flow will abort a ## Additional resources -More information about config flows can be found in the [config flow documentation](../../../config_entries_config_flow_handler). -More information about the requirements for a unique identifier can be found in the [documentation](../../../entity_registry_index#unique-id-requirements). +More information about config flows can be found in the [config flow documentation](/docs/config_entries_config_flow_handler). +More information about the requirements for a unique identifier can be found in the [documentation](/docs/entity_registry_index#unique-id-requirements). ## Exceptions From 4e624caf113f700d598507314165de8a4c543a53 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Fri, 6 Dec 2024 21:17:29 +0100 Subject: [PATCH 30/55] Bump @easyops-cn/docusaurus-search-local from 0.45.0 to 0.46.1 (#2480) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- package.json | 2 +- yarn.lock | 14 ++++++++++---- 2 files changed, 11 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index ba84fa0652d..d56abe0069f 100644 --- a/package.json +++ b/package.json @@ -16,7 +16,7 @@ "@docusaurus/preset-classic": "^3.5.2", "@docusaurus/theme-mermaid": "^3.5.2", "@mdx-js/react": "^3.1.0", - "@easyops-cn/docusaurus-search-local": "^0.45.0", + "@easyops-cn/docusaurus-search-local": "^0.46.1", "by-node-env": "^2.0.1", "clsx": "^2.1.1", "react-dom": "18.3.1", diff --git a/yarn.lock b/yarn.lock index 242e670ffad..eeea3225f5e 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1812,10 +1812,10 @@ cssesc "^3.0.0" immediate "^3.2.3" -"@easyops-cn/docusaurus-search-local@^0.45.0": - version "0.45.0" - resolved "https://registry.yarnpkg.com/@easyops-cn/docusaurus-search-local/-/docusaurus-search-local-0.45.0.tgz#7101d59c9359b50b1add306d2504a09bd7176adb" - integrity sha512-ccJjeYmBHrv2v8Y9eQnH79S0PEKcogACKkEatEKPcad7usQj/14jA9POUUUYW/yougLSXghwe+uIncbuUBuBFg== +"@easyops-cn/docusaurus-search-local@^0.46.1": + version "0.46.1" + resolved "https://registry.yarnpkg.com/@easyops-cn/docusaurus-search-local/-/docusaurus-search-local-0.46.1.tgz#7fac1a14417de680b5af7088814192a153d7334d" + integrity sha512-kgenn5+pctVlJg8s1FOAm9KuZLRZvkBTMMGJvTTcvNTmnFIHVVYzYfA2Eg+yVefzsC8/cSZGKKJ0kLf8I+mQyw== dependencies: "@docusaurus/plugin-content-docs" "^2 || ^3" "@docusaurus/theme-translations" "^2 || ^3" @@ -1826,6 +1826,7 @@ "@node-rs/jieba" "^1.6.0" cheerio "^1.0.0" clsx "^1.1.1" + comlink "^4.4.2" debug "^4.2.0" fs-extra "^10.0.0" klaw-sync "^6.0.0" @@ -3496,6 +3497,11 @@ combine-promises@^1.1.0: resolved "https://registry.yarnpkg.com/combine-promises/-/combine-promises-1.1.0.tgz#72db90743c0ca7aab7d0d8d2052fd7b0f674de71" integrity sha512-ZI9jvcLDxqwaXEixOhArm3r7ReIivsXkpbyEWyeOhzz1QS0iSgBPnWvEqvIQtYyamGCYA88gFhmUrs9hrrQ0pg== +comlink@^4.4.2: + version "4.4.2" + resolved "https://registry.yarnpkg.com/comlink/-/comlink-4.4.2.tgz#cbbcd82742fbebc06489c28a183eedc5c60a2bca" + integrity sha512-OxGdvBmJuNKSCMO4NTl1L47VRp6xn2wG4F/2hYzB6tiCb709otOxtEYCSvK80PtjODfXXZu8ds+Nw5kVCjqd2g== + comma-separated-tokens@^2.0.0: version "2.0.3" resolved "https://registry.yarnpkg.com/comma-separated-tokens/-/comma-separated-tokens-2.0.3.tgz#4e89c9458acb61bc8fef19f4529973b2392839ee" From 7daebad76cf6c0a21691e8a383364e257f7cb00c Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Fri, 6 Dec 2024 22:39:31 +0100 Subject: [PATCH 31/55] Add coordinator example for PARALLEL_UPDATES in IQS (#2482) --- .../rules/parallel-updates.md | 18 ++++++++++++++++-- 1 file changed, 16 insertions(+), 2 deletions(-) diff --git a/docs/core/integration-quality-scale/rules/parallel-updates.md b/docs/core/integration-quality-scale/rules/parallel-updates.md index fb0c40ab2da..d089effd021 100644 --- a/docs/core/integration-quality-scale/rules/parallel-updates.md +++ b/docs/core/integration-quality-scale/rules/parallel-updates.md @@ -31,13 +31,27 @@ class MySensor(SensorEntity): :::info When using a coordinator, you are already centralizing the data updates. -This means that usually only the action calls will be relevant to consider for setting the number of parallel updates. +This means you can set `PARALLEL_UPDATES = 0` for read-only platforms (`binary_sensor`, `sensor`, `device_tracker`, `event`) +and only the action calls will be relevant to consider for setting an appropriate number of parallel updates. ::: +`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.""" + ... +``` + ## Additional resources For more information about request parallelism, check the [documentation](/docs/integration_fetching_data#request-parallelism) for it. ## Exceptions -There are no exceptions to this rule. \ No newline at end of file +There are no exceptions to this rule. From aff6de761d9d07408b5821e5a59ea7f34e41a3b4 Mon Sep 17 00:00:00 2001 From: G Johansson Date: Sun, 8 Dec 2024 18:08:58 +0100 Subject: [PATCH 32/55] Add new vacuum state property and enum (#2360) * Add new vacuum state property and enum * Fix review comments * New date --- blog/2024-12-08-new-vacuum-state-property.md | 33 ++++++++++++++++++++ docs/core/entity/vacuum.md | 18 ++++++----- 2 files changed, 43 insertions(+), 8 deletions(-) create mode 100644 blog/2024-12-08-new-vacuum-state-property.md diff --git a/blog/2024-12-08-new-vacuum-state-property.md b/blog/2024-12-08-new-vacuum-state-property.md new file mode 100644 index 00000000000..c74ecdfb466 --- /dev/null +++ b/blog/2024-12-08-new-vacuum-state-property.md @@ -0,0 +1,33 @@ +--- +author: G Johansson +authorURL: https://github.com/gjohansson-ST +authorImageURL: https://avatars.githubusercontent.com/u/62932417?v=4 +authorTwitter: GJohansson +title: "New vacuum state property" +--- + +As of Home Assistant Core 2025.1, the constants used to return state in `StateVacuumEntity` are deprecated and replaced by the `VacuumActivity` enum. + +Also with this change, integrations should set the `activity` property instead of directly setting the `state` property. + +There is a one-year deprecation period, and the constants will stop working from 2026.1 to ensure all custom integration authors have time to adjust. + +### Example + +```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 + +``` + +More details can be found in the [vacuum documentation](/docs/core/entity/vacuum#states). diff --git a/docs/core/entity/vacuum.md b/docs/core/entity/vacuum.md index 9129b22b925..2aac63ca476 100644 --- a/docs/core/entity/vacuum.md +++ b/docs/core/entity/vacuum.md @@ -22,18 +22,20 @@ Properties should always only return information from memory and not do I/O (lik | fan_speed | string | `none` | The current fan speed. | fan_speed_list | list | `NotImplementedError()`| List of available fan speeds. | name | string | **Required** | Name of the entity. -| state | string | **Required** | One of the states listed in the states section. +| activity | VacuumActivity | **Required** | Return one of the states listed in the states section. ## States -| State | Description +Setting the state should return an enum from VacuumActivity in the `activity` property. + +| Value | Description | ----- | ----------- -| `STATE_CLEANING` | The vacuum is currently cleaning. -| `STATE_DOCKED` | The vacuum is currently docked, it is assumed that docked can also mean charging. -| `STATE_IDLE` | The vacuum is not paused, not docked and does not have any errors. -| `STATE_PAUSED` | The vacuum was cleaning but was paused without returning to the dock. -| `STATE_RETURNING` | The vacuum is done cleaning and is currently returning to the dock, but not yet docked. -| `STATE_ERROR` | The vacuum encountered an error while cleaning. +| `CLEANING` | The vacuum is currently cleaning. +| `DOCKED` | The vacuum is currently docked, it is assumed that docked can also mean charging. +| `IDLE` | The vacuum is not paused, not docked and does not have any errors. +| `PAUSED` | The vacuum was cleaning but was paused without returning to the dock. +| `RETURNING` | The vacuum is done cleaning and is currently returning to the dock, but not yet docked. +| `ERROR` | The vacuum encountered an error while cleaning. ## Supported features From 865a38b623b7ccc7bd5844927546ae6e644308fd Mon Sep 17 00:00:00 2001 From: Jens Maus Date: Mon, 9 Dec 2024 08:37:43 +0100 Subject: [PATCH 33/55] Update configuration.md (#2491) --- docs/add-ons/configuration.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/add-ons/configuration.md b/docs/add-ons/configuration.md index 222b2b61ad5..0c20efebf96 100644 --- a/docs/add-ons/configuration.md +++ b/docs/add-ons/configuration.md @@ -157,7 +157,7 @@ Avoid using `config.yaml` as filename in your add-on for anything other than the | `docker_api` | bool | `false` | Allow read-only access to the Docker API for the add-on. Works only for not protected add-ons. | `privileged` | list | | Privilege for access to hardware/system. Available access: `BPF`, `DAC_READ_SEARCH`, `IPC_LOCK`, `NET_ADMIN`, `NET_RAW`, `PERFMON`, `SYS_ADMIN`, `SYS_MODULE`, `SYS_NICE`, `SYS_PTRACE`, `SYS_RAWIO`, `SYS_RESOURCE` or `SYS_TIME`. | `full_access` | bool | `false` | Give full access to hardware like the privileged mode in Docker. Works only for not protected add-ons. Consider using other add-on options instead of this, like `devices`. If you enable this option, don't add `devices`, `uart`, `usb` or `gpio` as this is not needed. -| `apparmor` | bool/string | `false` | Enable or disable AppArmor support. If it is enabled, you can also use custom profiles with the name of the profile. +| `apparmor` | bool/string | `true` | Enable or disable AppArmor support. If it is enabled, you can also use custom profiles with the name of the profile. | `map` | list | | List of Home Assistant directory types to bind mount into your container. Possible values: `homeassistant_config`, `addon_config`, `ssl`, `addons`, `backup`, `share`, `media`, `all_addon_configs`, and `data`. Defaults to read-only, which you can change by adding the property `read_only: false`. By default, all paths map to `/` inside the addon container, but an optional `path` property can also be supplied to configure the path (Example: `path: /custom/config/path`). If used, the path must not be empty, unique from any other path defined for the addon, and not the root path. Note that the `data` directory is always mapped and writable, but the `path` property can be set using the same conventions. | `environment` | dict | | A dictionary of environment variables to run the add-on with. | `audio` | bool | `false` | Mark this add-on to use the internal audio system. We map a working PulseAudio setup into the container. If your application does not support PulseAudio, you may need to install: Alpine Linux `alsa-plugins-pulse` or Debian/Ubuntu `libasound2-plugins`. From 295afdefbc14a722121a1428f306501c97ea2f12 Mon Sep 17 00:00:00 2001 From: dotvav Date: Mon, 9 Dec 2024 23:25:28 +0100 Subject: [PATCH 34/55] Mention vscode tasks as alternative to shell commands (#2481) --- docs/development_testing.md | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/docs/development_testing.md b/docs/development_testing.md index 2c39f377f65..55e9cdc835a 100644 --- a/docs/development_testing.md +++ b/docs/development_testing.md @@ -23,12 +23,16 @@ To run the full test suite, more dependencies are required than what is set up i uv pip install -r requirements_test_all.txt ``` +Or, in Visual Studio Code, launch the **Install all Test Requirements** task. + To start the tests, and run the full test suite, activate the virtual environment and run the command: ```shell pytest tests ``` +Or, in Visual Studio Code, launch the **Pytest** task. + It might be required that you install additional packages depending on your distribution/operating system: - Fedora: `sudo dnf -y install systemd-devel gcc-c++` @@ -53,6 +57,9 @@ Next you can update all dependencies in your development environment by running: ```shell uv pip install -r requirements_test_all.txt ``` + +Or, in Visual Studio Code, launch the **Install all Test Requirements** task. + ### Running a limited test suite You can pass arguments to `pytest` to be able to run single test suites or test files. @@ -79,6 +86,8 @@ the following command is recommended: pytest ./tests/components// --cov=homeassistant.components. --cov-report term-missing -vv ``` +Or, in Visual Studio Code, launch the **Code Coverage** task. + ### Preventing linter errors Several linters are setup to run automatically when you try to commit as part of running `script/setup` in the [virtual environment](development_environment.mdx). @@ -89,6 +98,8 @@ You can also run these linters manually : pre-commit run --show-diff-on-failure ``` +Or, in Visual Studio Code, launch the **Pre-commit** task. + The linters are also available directly, you can run tests on individual files: ```shell @@ -162,6 +173,8 @@ the `--snapshot-update` flag: pytest tests/components/example/test_sensor.py --snapshot-update ``` +Or, in Visual Studio Code, launch the **Update syrupy snapshots** task. + This will create a snapshot file in the `tests/components/example/snapshots`. The snapshot file is named after the test file, in this case `test_sensor.ambr`, and is human-readable. The snapshot files must be committed to the repository. From fc93ad49781a2dd85528cdfacc5544a1bbc50ac1 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Tue, 10 Dec 2024 13:06:12 +0100 Subject: [PATCH 35/55] Add details about typed ConfigEntry in runtime-data and strict-typing IQS rule (#2479) --- .../integration-quality-scale/rules/runtime-data.md | 7 ++++++- .../rules/strict-typing.md | 13 ++++++++++++- 2 files changed, 18 insertions(+), 2 deletions(-) diff --git a/docs/core/integration-quality-scale/rules/runtime-data.md b/docs/core/integration-quality-scale/rules/runtime-data.md index a6770e9e9ce..b1f48d0c2da 100644 --- a/docs/core/integration-quality-scale/rules/runtime-data.md +++ b/docs/core/integration-quality-scale/rules/runtime-data.md @@ -1,6 +1,7 @@ --- title: "Use ConfigEntry.runtime_data to store runtime data" related_rules: + - strict-typing - test-before-setup --- import RelatedRules from './_includes/related_rules.jsx' @@ -35,6 +36,10 @@ async def async_setup_entry(hass: HomeAssistant, entry: MyIntegrationConfigEntry return True ``` +:::info +If the integration implements `strict-typing`, the use of a custom typed `MyIntegrationConfigEntry` is required and must be used throughout. +::: + ## Additional resources More information about configuration entries and their lifecycle can be found in the [config entry documentation](/docs/config_entries_index). @@ -45,4 +50,4 @@ There are no exceptions to this rule. ## Related rules - \ No newline at end of file + diff --git a/docs/core/integration-quality-scale/rules/strict-typing.md b/docs/core/integration-quality-scale/rules/strict-typing.md index b23f8d73adb..e48d0cf82be 100644 --- a/docs/core/integration-quality-scale/rules/strict-typing.md +++ b/docs/core/integration-quality-scale/rules/strict-typing.md @@ -1,6 +1,9 @@ --- title: "Strict typing" +related_rules: + - runtime-data --- +import RelatedRules from './_includes/related_rules.jsx' ## Reasoning @@ -15,10 +18,18 @@ This file tells mypy that your library is fully typed, after which it can read t In the Home Assistant codebase, you can add your integration to the [`.strict-typing`](https://github.com/home-assistant/core/blob/dev/.strict-typing) file, which will enable strict type checks for your integration. +:::warning +If the integration implements `runtime-data`, the use of a custom typed `MyIntegrationConfigEntry` is required and must be used throughout. +::: + ## Additional resources To read more about the `py.typed` file, see [PEP-561](https://peps.python.org/pep-0561/). ## Exceptions -There are no exceptions to this rule. \ No newline at end of file +There are no exceptions to this rule. + +## Related rules + + From 271215813e6ae4e7fef78710096665b947669d0b Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Fri, 13 Dec 2024 09:34:18 +0100 Subject: [PATCH 36/55] Add mWh as unit of measurement (#2493) --- docs/core/entity/number.md | 4 ++-- docs/core/entity/sensor.md | 4 ++-- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/docs/core/entity/number.md b/docs/core/entity/number.md index a713a19819f..f70b2342772 100644 --- a/docs/core/entity/number.md +++ b/docs/core/entity/number.md @@ -43,8 +43,8 @@ If specifying a device class, your number entity will need to also return the co | `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, yd, in | Generic distance -| `NumberDeviceClass.ENERGY` | Wh, kWh, MWh, GWh, TWh, MJ, GJ | Energy, this device class should used to represent energy consumption, for example an electricity meter. Represents _power_ over _time_. Not to be confused with `power`. -| `NumberDeviceClass.ENERGY_STORAGE` | Wh, kWh, MWh, GWh, TWh, MJ, GJ | Stored energy, this device class should be used to represent stored energy, for example the amount of electric energy currently stored in a battery or the capacity of a battery. Represents _power_ over _time_. Not to be confused with `power`. +| `NumberDeviceClass.ENERGY` | J, kJ, MJ, GJ, mWh, Wh, kWh, MWh, GWh, TWh, cal, kcal, Mcal, Gcal | Energy, this device class should used to represent energy consumption, for example an electricity meter. Represents _power_ over _time_. Not to be confused with `power`. +| `NumberDeviceClass.ENERGY_STORAGE` | J, kJ, MJ, GJ, mWh, Wh, kWh, MWh, GWh, TWh, cal, kcal, Mcal, Gcal | Stored energy, this device class should be used to represent stored energy, for example the amount of electric energy currently stored in a battery or the capacity of a battery. Represents _power_ over _time_. Not to be confused with `power`. | `NumberDeviceClass.FREQUENCY` | Hz, kHz, MHz, GHz | Frequency | `NumberDeviceClass.GAS` | m³, ft³, CCF | Volume of gas. Gas consumption measured as energy in kWh instead of a volume should be classified as energy. | `NumberDeviceClass.HUMIDITY` | % | Relative humidity diff --git a/docs/core/entity/sensor.md b/docs/core/entity/sensor.md index 6e5d9a0d980..1267454d49a 100644 --- a/docs/core/entity/sensor.md +++ b/docs/core/entity/sensor.md @@ -46,8 +46,8 @@ If specifying a device class, your sensor entity will need to also return the co | `SensorDeviceClass.DATE` | | Date. Requires `native_value` to be a Python `datetime.date` object, or `None`. | `SensorDeviceClass.DISTANCE` | km, m, cm, mm, mi, nmi, yd, in | Generic distance | `SensorDeviceClass.DURATION` | d, h, min, s, ms | Time period. Should not update only due to time passing. The device or service needs to give a new data point to update. -| `SensorDeviceClass.ENERGY` | J, kJ, MJ, GJ, Wh, kWh, MWh, GWh, TWh, cal, kcal, Mcal, Gcal | Energy, this device class should be used for sensors representing energy consumption, for example an electricity meter. Represents _power_ over _time_. Not to be confused with `power`. -| `SensorDeviceClass.ENERGY_STORAGE` | J, kJ, MJ, GJ, Wh, kWh, MWh, GWh, TWh, cal, kcal, Mcal, Gcal | Stored energy, this device class should be used for sensors representing stored energy, for example the amount of electric energy currently stored in a battery or the capacity of a battery. Represents _power_ over _time_. Not to be confused with `power`. +| `SensorDeviceClass.ENERGY` | J, kJ, MJ, GJ, mWh, Wh, kWh, MWh, GWh, TWh, cal, kcal, Mcal, Gcal | Energy, this device class should be used for sensors representing energy consumption, for example an electricity meter. Represents _power_ over _time_. Not to be confused with `power`. +| `SensorDeviceClass.ENERGY_STORAGE` | J, kJ, MJ, GJ, mWh, Wh, kWh, MWh, GWh, TWh, cal, kcal, Mcal, Gcal | Stored energy, this device class should be used for sensors representing stored energy, for example the amount of electric energy currently stored in a battery or the capacity of a battery. Represents _power_ over _time_. Not to be confused with `power`. | `SensorDeviceClass.ENUM` | | The sensor has a limited set of (non-numeric) states. The `options` property must be set to a list of possible states when using this device class. | `SensorDeviceClass.FREQUENCY` | Hz, kHz, MHz, GHz | Frequency | `SensorDeviceClass.GAS` | m³, ft³, CCF | Volume of gas. Gas consumption measured as energy in kWh instead of a volume should be classified as energy. From 021592b24bd61b7a081f42cba04da8cd6a2af911 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Fri, 13 Dec 2024 17:46:48 +0100 Subject: [PATCH 37/55] Blog about changed name of `WaterHeaterEntityDescription` (#2494) * Create 2024-12-13-waterheaterentitydescription.md * Update and rename 2024-12-13-waterheaterentitydescription.md to 2024-12-13-water-heater-entity-description.md * Refers to core PR * Update blog/2024-12-13-water-heater-entity-description.md Co-authored-by: Martin Hjelmare * Update blog/2024-12-13-water-heater-entity-description.md Co-authored-by: Martin Hjelmare * Update blog/2024-12-13-water-heater-entity-description.md Co-authored-by: Martin Hjelmare * Adjust language --------- Co-authored-by: Martin Hjelmare --- blog/2024-12-13-water-heater-entity-description.md | 11 +++++++++++ 1 file changed, 11 insertions(+) create mode 100644 blog/2024-12-13-water-heater-entity-description.md diff --git a/blog/2024-12-13-water-heater-entity-description.md b/blog/2024-12-13-water-heater-entity-description.md new file mode 100644 index 00000000000..fb054294bd6 --- /dev/null +++ b/blog/2024-12-13-water-heater-entity-description.md @@ -0,0 +1,11 @@ +--- +author: L Boué +authorURL: https://github.com/lboue +authorTwitter: lboue +title: "Changed name of WaterHeaterEntityDescription" +--- + +A naming error of the entity description was found in the Water Heater integration, and we have now renamed `WaterHeaterEntityEntityDescription` to `WaterHeaterEntityDescription`. +As there were no Core integrations and no custom integrations published on HACS using the entity description, we did not add a deprecation period. The changed entity description will be released in the 2025.1 release. + +See details in the core PR: [#132888](https://github.com/home-assistant/core/pull/132888). From 0f657359fe4bf4e9044ad32a717cb92441df6e24 Mon Sep 17 00:00:00 2001 From: William Poetra Yoga Date: Sun, 15 Dec 2024 18:04:25 +0700 Subject: [PATCH 38/55] Fix wrong command for local repository (#2496) Co-authored-by: Franck Nijhof --- docs/development_environment.mdx | 44 +++++++++++++++++--------------- 1 file changed, 23 insertions(+), 21 deletions(-) diff --git a/docs/development_environment.mdx b/docs/development_environment.mdx index 8c38e3cc763..2105173cbb1 100644 --- a/docs/development_environment.mdx +++ b/docs/development_environment.mdx @@ -6,11 +6,11 @@ title: "Set up development environment" import {useState} from 'react'; export const RepositoryOpener = () => { - const [value, setValue] = useState(0); + const [value, setValue] = useState(""); const repoUrl = `vscode://ms-vscode-remote.remote-containers/cloneInVolume?url=${encodeURIComponent(value)}`; return
      setValue(ev.target.value)} style={{width: "80%", display: "inline-block", marginRight: 16}} /> - +
      } ``` @@ -33,29 +33,31 @@ As this approach uses containers, you may face challenges exposing hardware like **Getting started:** -1. Go to [Home Assistant core repository](https://github.com/home-assistant/core) and click "fork". -2. Once your fork is created, copy the URL of your fork and paste it below, then click "Open": +1. Go to [Home Assistant core repository](https://github.com/home-assistant/core) and click **Fork**. +2. Copy your fork's URL and paste it below, then click **Open**: -3. Your browser will prompt you if you want to use Visual Studio Code to open the link, click "Open Link". -4. When Visual Studio Code asks if you want to install the Remote extension, click "Install". -5. The Dev Container image will then be built (this may take a few minutes), after this your development environment will be ready. +3. Your browser will prompt you if you want to use Visual Studio Code to open the link, click **Open Link**. +4. When Visual Studio Code asks if you want to install the Remote - SSH extension, click **Install**. +5. The dev container image will then be built (this may take a few minutes), after this your development environment will be ready. 6. You can verify that your dev container is set up properly by the following: - * Open the command palette in Visual Studio Code - `Shift`+`Command`+`P`(Mac) / `Ctrl`+`Shift`+`P` (Windows/Linux). - * Select `Tasks: Run Task` -> `Run Home Assistant Core` - * A terminal should open and begin outputting activity. Check for errors and wait for the output to stop/slow down. - * Navigate a web browser to `http://localhost:8123`, and you should see the Home Assistant setup screen. + 1. Open the Command Palette in Visual Studio Code: + * Mac: `Shift`+`Command`+`P` + * Windows/Linux: `Ctrl`+`Shift`+`P` + 2. Select **Tasks: Run Task** -> **Run Home Assistant Core** + 3. A terminal should open and begin outputting activity. Check for errors and wait for the output to stop/slow down. + 4. Navigate a web browser to `http://localhost:8123`, and you should see the Home Assistant setup screen. -In the future, if you want to get back to your development environment: open Visual Studio Code, click on the "Remote Explorer" button in the sidebar, select "Containers" at the top of the sidebar. +In the future, if you want to get back to your development environment: open Visual Studio Code, click on the **Remote Explorer** button in the sidebar, select **Containers** at the top of the sidebar. ### Tasks -The devcontainer comes with some useful tasks to help you with development, you can start these tasks by opening the command palette with `Shift`+`Command`+`P`(Mac) / `Ctrl`+`Shift`+`P` (Windows/Linux) and select `Tasks: Run Task` then select the task you want to run. +The dev container comes with some useful tasks to help you with development. You can run these tasks by opening the Command Palette with `Shift`+`Command`+`P`(Mac) / `Ctrl`+`Shift`+`P` (Windows/Linux) and selecting **Tasks: Run Task**, then finally selecting the task you want to run. -When a task is currently running (like `Preview` for the docs), it can be restarted by opening the command palette and selecting `Tasks: Restart Running Task`, then select the task you want to restart. +When a task is currently running (like `Preview` for the docs), it can be restarted by opening the Command Palette and selecting **Tasks: Restart Running Task**, then select the task you want to restart. ### Debugging with Visual Studio Code -If the Dev Container was set up correctly - it supports debugging by default, out-of-the-box. It provides the necessary debug configurations, so hitting F5 should launch Home Assistant. Any breakpoints put in the code should be triggered, and the debugger should stop. +If the dev container was set up correctly - it supports debugging by default, out-of-the-box. It provides the necessary debug configurations, so hitting F5 should launch Home Assistant. Any breakpoints put in the code should be triggered, and the debugger should stop. It is also possible to debug a remote Home Assistance instance (e.g., production instance) by following the procedure described [here](https://www.home-assistant.io/integrations/debugpy/). @@ -63,7 +65,7 @@ It is also possible to debug a remote Home Assistance instance (e.g., production _You only need these instructions if you do not want to use devcontainers._ -It is also possible to set up a more traditional development environment. See the section for your operating system. Make sure your Python version is 3.12. +It is also possible to set up a more traditional development environment. See the section below for your operating system. Make sure your Python version is 3.12. ### Developing on Ubuntu / Debian @@ -117,8 +119,8 @@ Visit the [Home Assistant Core repository](https://github.com/home-assistant/cor Once forked, setup your local copy of the source using the commands: ```shell -git clone https://github.com/YOUR_GIT_USERNAME/short_name_of_your_fork -cd core +git clone https://github.com/YOUR_GIT_USERNAME/name_of_your_fork +cd name_of_your_fork git remote add upstream https://github.com/home-assistant/core.git ``` @@ -128,7 +130,7 @@ Install the requirements with a provided script named `setup`. script/setup ``` -This will create a virtual environment and install all necessary requirements. You're now set! +This will create a virtual environment and install all the necessary requirements. You're now set! Each time you start a new terminal session, you will need to activate your virtual environment: @@ -142,6 +144,6 @@ After that you can run Home Assistant like this: hass -c config ``` -If you encounter a crash (`SIGKILL`) while running this command on *macOS*, it's probably about lack of Bluetooth permission. You can fix it by adding this permission for your Terminal app (System Preferences -> Security & Privacy -> Bluetooth). +If you encounter a crash (`SIGKILL`) while running this command on *macOS*, it's probably caused by the lack of Bluetooth permissions. You can fix it by adding this permission for your Terminal app (**System Preferences** -> **Security & Privacy** -> **Bluetooth**). -The Home Assistant configuration is stored in the `config` directory in your repository. +The Home Assistant configuration is stored in the `config` directory at the root of your repository. From 2d96523d784acf073e7180ad08cbee1522d12353 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Sun, 15 Dec 2024 13:32:17 +0100 Subject: [PATCH 39/55] Bump nanoid from 3.3.7 to 3.3.8 (#2499) Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com> --- yarn.lock | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/yarn.lock b/yarn.lock index eeea3225f5e..a01ca6a56d0 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7247,9 +7247,9 @@ multicast-dns@^7.2.5: thunky "^1.0.2" nanoid@^3.3.7: - version "3.3.7" - resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.7.tgz#d0c301a691bc8d54efa0a2226ccf3fe2fd656bd8" - integrity sha512-eSRppjcPIatRIMC1U6UngP8XFcz8MQWGQdt1MTBQ7NaAmvXDfvNxbvWV3x2y6CdEUciCSsDHDQZbhYaB8QEo2g== + version "3.3.8" + resolved "https://registry.yarnpkg.com/nanoid/-/nanoid-3.3.8.tgz#b1be3030bee36aaff18bacb375e5cce521684baf" + integrity sha512-WNLf5Sd8oZxOm+TzppcYk8gVOgP+l58xNy58D0nbUnOxOWRWvlcCV4kUF7ltmI6PsrLl/BgKEyS4mqsGChFN0w== negotiator@0.6.3: version "0.6.3" From af4abd1094ef80e504c5b98f64decae050b3b050 Mon Sep 17 00:00:00 2001 From: William Poetra Yoga Date: Mon, 16 Dec 2024 03:45:19 +0700 Subject: [PATCH 40/55] Clarify frontend development documentation (#2497) Co-authored-by: Franck Nijhof --- docs/frontend/development.md | 67 +++++++++++++++++++++++++++--------- 1 file changed, 50 insertions(+), 17 deletions(-) diff --git a/docs/frontend/development.md b/docs/frontend/development.md index 2f0dd764d05..f4d44ab5960 100644 --- a/docs/frontend/development.md +++ b/docs/frontend/development.md @@ -25,15 +25,9 @@ git remote add upstream https://github.com/home-assistant/frontend.git You will need to have an instance of Home Assistant set up. See our guide on [setting up a development environment](/development_environment.mdx). -Next step is to configure Home Assistant to use the development mode for the frontend. Do this by updating the frontend config in your `configuration.yaml` and set the path to the frontend repository that you cloned in the last step: +#### Developing with Visual Studio Code + dev container -```yaml -frontend: - # Example absolute path: /home/paulus/dev/hass/frontend - development_repo: /path/to/hass/frontend/ -``` - -If you are using Visual Studio Code with devcontainers for Home Assistant, you need to mount the `frontend` directory into the container. Add the following section to `.devcontainer/devcontainer.json`: +If you are using Visual Studio Code with dev containers for Home Assistant Core, you need to mount the frontend repository into the dev container. Add the following section to `.devcontainer/devcontainer.json` in the Home Assistant Core repository: ```json "mounts": [ @@ -41,18 +35,45 @@ If you are using Visual Studio Code with devcontainers for Home Assistant, you n ] ``` -The Home Assistant's devcontainer needs to get rebuilt via the `Dev Containers: Rebuild Container` with: Shift+Command+P(Mac) / Ctrl+Shift+P (Windows/Linux). The `configuration.yaml` should point to the path inside the container: +Rebuild the dev container by pressing Shift+Command+P (Mac) / Ctrl+Shift+P (Windows/Linux) to open the Command Palette, then selecting the **Dev Containers: Rebuild Container** command. + +Edit `config/configuration.yaml` at the root of the Home Assistant Core repository to add this entry: ```yaml frontend: - development_repo: /workspaces/frontend/ + development_repo: /workspaces/frontend ``` +:::note +This is the mounted path inside the dev container, see the `target` parameter above. If the `source` path is incorrect, the web frontend won't work. +::: + +Run Home Assistant Core from VS Code: +1. Open the Command Palette: + - Mac: `Shift+Command+P` + - Windows/Linux: `Ctrl+Shift+P` +2. Select **Tasks: Run Task** +3. Select **Run Home Assistant Core** + :::caution -The change to `.devcontainer/devcontainer.json` should be excluded from any PR as it contains your local path to the `frontend` repository. Since the the settings in `.devcontainer/devcontainer.json` are only processed during the container rebuild, you can safely roll back the change after the rebuild has completed. +The change to `.devcontainer/devcontainer.json` should be excluded from any PR as it contains your local path to the `frontend` repository. Since the settings in `.devcontainer/devcontainer.json` are only processed during the container rebuild, you can safely roll back the change after the rebuild has completed. +::: + +#### Developing with a manual environment + +If you set up the development environment for Home Assistant Core manually, fill in the frontend repository path in `configuration.yaml`: + +```yaml +frontend: + # Example path: /home/paulus/dev/hass/frontend + development_repo: /path/to/hass/frontend +``` + +:::tip +The `configuration.yaml` file can be found in the `config` directory at the root of the Home Assistant Core repository. If the path is incorrect or otherwise inaccessible, the web frontend won't work. ::: -### Installing Node.js +### Installing Node.js (manual environment only) Node.js is required to build the frontend. The preferred method of installing node.js is with [nvm](https://github.com/nvm-sh/nvm). Install nvm using the instructions in the [README](https://github.com/nvm-sh/nvm#install--update-script), and install the correct node.js by running the following command: @@ -62,7 +83,9 @@ nvm install [Yarn](https://yarnpkg.com/en/) is used as the package manager for node modules. [Install yarn using the instructions here.](https://yarnpkg.com/getting-started/install) -Next, development dependencies need to be installed to bootstrap the frontend development environment. First activate the right Node version and then download all the dependencies: +### Install development dependencies and fetch latest translations + +Bootstrap the frontend development environment by installing development dependencies and downloading the latest translations. ```shell nvm use @@ -70,28 +93,38 @@ script/bootstrap script/setup_translations ``` +:::note +This needs to be done manually, even if you are using dev containers. Also, you will be asked to enter a code and authorize the script to fetch the latest translations. +::: + ## Development -During development, you will need to run the development script to maintain a development build of the frontend that auto updates when you change any of the source files. To run this server, run: +### Run development server + +Run this script to build the frontend and run a development server: ```shell nvm use script/develop ``` -Make sure you have cache disabled and correct settings to avoid stale content: +When the script has completed building the frontend, and Home Assistant Core has been set up correctly, the frontend will be accessible at `http://localhost:8123`. The server will automatically rebuild the frontend when you make changes to the source files. + +### Browser settings + +Open Google Chrome's Developer tools, and make sure you have cache disabled and correct settings to avoid stale content: :::info Instructions are for Google Chrome ::: -1. Disable cache by ticking the box in `Network` > `Disable cache` +1. Disable cache by ticking the box in **Network** > **Disable cache**

      -2. Enable Bypass for network in `Application` > `Service Workers` > `Bypass for network` +2. Enable Bypass for network in **Application** > **Service Workers** > **Bypass for network**

      From b6b197370a6e79b185f94497ed0daa3f3cbc43ee Mon Sep 17 00:00:00 2001 From: Thomas J Leach Date: Mon, 16 Dec 2024 11:11:50 -0500 Subject: [PATCH 41/55] Fix typo in integration_setup_failures.md (#2504) --- docs/integration_setup_failures.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/integration_setup_failures.md b/docs/integration_setup_failures.md index 3a274f6e232..7e5213cb0ed 100644 --- a/docs/integration_setup_failures.md +++ b/docs/integration_setup_failures.md @@ -22,7 +22,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: raise ConfigEntryNotReady(f"Timeout while connecting to {device.ipaddr}") from ex ``` -If you are using a [DataUpdateCoordinator](integration_fetching_data#coordinated-single-api-poll-for-data-for-all-entities), calling `await coordinator.async_config_entry_first_refresh()` will also trigger this exception automaticlly if the first refresh failed. +If you are using a [DataUpdateCoordinator](integration_fetching_data#coordinated-single-api-poll-for-data-for-all-entities), calling `await coordinator.async_config_entry_first_refresh()` will also trigger this exception automatically if the first refresh failed. If your integration supports discovery, Home Assistant will automatically retry as soon as your device or service gets discovered. From 3b2df3729295448eb58595134bdf2938acbfc74d Mon Sep 17 00:00:00 2001 From: Matthias Alphart Date: Mon, 16 Dec 2024 17:23:27 +0100 Subject: [PATCH 42/55] Fix authentication example (#2502) --- docs/api_lib_auth.md | 20 +++----------------- 1 file changed, 3 insertions(+), 17 deletions(-) diff --git a/docs/api_lib_auth.md b/docs/api_lib_auth.md index 708fff4b0b3..22d865983af 100644 --- a/docs/api_lib_auth.md +++ b/docs/api_lib_auth.md @@ -34,13 +34,8 @@ class Auth: async def request(self, method: str, path: str, **kwargs) -> ClientResponse: """Make a request.""" - headers = kwargs.get("headers") - - if headers is None: - headers = {} - else: + if headers := kwargs.pop("headers", {}): headers = dict(headers) - headers["authorization"] = self.access_token return await self.websession.request( @@ -86,13 +81,8 @@ class Auth: def request(self, method: str, path: str, **kwargs) -> requests.Response: """Make a request.""" - headers = kwargs.get("headers") - - if headers is None: - headers = {} - else: + if headers := kwargs.pop("headers", {}): headers = dict(headers) - headers["authorization"] = self.access_token return requests.request( @@ -142,11 +132,7 @@ class AbstractAuth(ABC): async def request(self, method, url, **kwargs) -> ClientResponse: """Make a request.""" - headers = kwargs.get("headers") - - if headers is None: - headers = {} - else: + if headers := kwargs.pop("headers", {}): headers = dict(headers) access_token = await self.async_get_access_token() From d60130cc38ad5a3f1f85e4e25737131e0fa965c3 Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 16 Dec 2024 19:06:08 +0100 Subject: [PATCH 43/55] Blog post about Kelvin as the preferred color temperature unit (#2495) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Abílio Costa --- ...kelvin-preferred-color-temperature-unit.md | 105 ++++++++++++++++++ 1 file changed, 105 insertions(+) create mode 100644 blog/2024-12-14-kelvin-preferred-color-temperature-unit.md diff --git a/blog/2024-12-14-kelvin-preferred-color-temperature-unit.md b/blog/2024-12-14-kelvin-preferred-color-temperature-unit.md new file mode 100644 index 00000000000..9891136048f --- /dev/null +++ b/blog/2024-12-14-kelvin-preferred-color-temperature-unit.md @@ -0,0 +1,105 @@ +--- +author: epenet +authorURL: https://github.com/epenet +title: "Use Kelvin as the preferred color temperature unit" +--- + +### Summary of changes + +In October 2022, Home Assistant migrated the preferred color temperature unit from mired to kelvin. + +It is now time to add deprecation warnings for the corresponding attributes, constants and properties: +* Deprecate state and capability attributes `ATTR_COLOR_TEMP`, `ATTR_MIN_MIREDS` and `ATTR_MAX_MIREDS` +* Deprecate constants `ATTR_KELVIN` and `ATTR_COLOR_TEMP` from the `light.turn_on` service call +* Deprecate properties `LightEntity.color_temp`, `LightEntity.min_mireds` and `LightEntity.max_mireds` +* Deprecate corresponding attributes `LightEntity._attr_color_temp`, `LightEntity._attr_min_mired` and `LightEntity._attr_max_mired` + +### Examples + +#### Custom minimum/maximum color temperature + +```python +class MyLight(LightEntity): + """Representation of a light.""" + + # Old + # _attr_min_mireds = 200 # 5000K + # _attr_max_mireds = 400 # 2500K + + # New + _attr_min_color_temp_kelvin = 2500 # 400 mireds + _attr_max_color_temp_kelvin = 5000 # 200 mireds +``` + +#### Default minimum/maximum color temperature + +```python +from homeassistant.components.light import DEFAULT_MAX_KELVIN, DEFAULT_MIN_KELVIN + +class MyLight(LightEntity): + """Representation of a light.""" + + # Old did not need to have _attr_min_mireds / _attr_max_mireds set + # New needs to set the default explicitly + _attr_min_color_temp_kelvin = DEFAULT_MIN_KELVIN + _attr_max_color_temp_kelvin = DEFAULT_MAX_KELVIN +``` + +#### Dynamic minimum/maximum color temperature + +```python +from homeassistant.util import color as color_util + +class MyLight(LightEntity): + """Representation of a light.""" + + # Old + # 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 + + # New + 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 call + +```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.""" + # Old + # 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) + + # Old + # if ATTR_KELVIN in kwargs: + # color_temp_kelvin = kwargs[ATTR_KELVIN] + # color_temp_mired = color_util.color_temperature_kelvin_to_mired(color_temp_kelvin) + + # New + 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) +``` + +### Background information + +* [Community discussion about Kelvin temperature](https://community.home-assistant.io/t/wth-is-light-temperature-not-in-kelvin/467449/6) +* [Core PR #79591: Migration to Kelvin](https://github.com/home-assistant/core/pull/79591) +* [Architecture discussion #564](https://github.com/home-assistant/architecture/discussions/564) From ccde9b2e03e4c017cc7d538a627580ecf5c61894 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Mon, 16 Dec 2024 18:10:59 +0000 Subject: [PATCH 44/55] Replace IQS rule number with link (#2505) --- docs/core/integration-quality-scale/rules/dynamic-devices.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/core/integration-quality-scale/rules/dynamic-devices.md b/docs/core/integration-quality-scale/rules/dynamic-devices.md index b6baa1132d5..fc239cce055 100644 --- a/docs/core/integration-quality-scale/rules/dynamic-devices.md +++ b/docs/core/integration-quality-scale/rules/dynamic-devices.md @@ -7,7 +7,7 @@ import RelatedRules from './_includes/related_rules.jsx' ## Reasoning -Like explained in IQS021, devices should be removed automatically when we can be sure that the device is not connected anymore. +Like explained in the rule [stale-devices](/docs/core/integration-quality-scale/rules/stale-devices), devices should be removed automatically when we can be sure that the device is not connected anymore. This rule explains the other side, once a new device is connected, we should automatically create the relevant entities for the device. This makes the user experience better, since the user only adds the device to the integration, and it will automatically show up in Home Assistant. From 0b07f023725064cd2ed5f76e7d908d4f338c4b3d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ludovic=20BOU=C3=89?= Date: Mon, 16 Dec 2024 19:24:53 +0100 Subject: [PATCH 45/55] Update development_environment.mdx (#2503) --- docs/development_environment.mdx | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/development_environment.mdx b/docs/development_environment.mdx index 2105173cbb1..e2cd91a0415 100644 --- a/docs/development_environment.mdx +++ b/docs/development_environment.mdx @@ -113,7 +113,7 @@ brew install python3 autoconf ffmpeg cmake make If you encounter build issues with `cryptography` when running the `script/setup` script below, check the cryptography documentation for [installation instructions](https://cryptography.io/en/latest/installation/#building-cryptography-on-macos). -## Setup Local Repository +### Setup Local Repository Visit the [Home Assistant Core repository](https://github.com/home-assistant/core) and click **Fork**. Once forked, setup your local copy of the source using the commands: From 4cafa2053ddd7c0b93f8c19bc95692612dbf477c Mon Sep 17 00:00:00 2001 From: William Poetra Yoga Date: Tue, 17 Dec 2024 03:48:37 +0700 Subject: [PATCH 46/55] Use italics for emphasis (#2498) --- docs/documenting/standards.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/documenting/standards.md b/docs/documenting/standards.md index 13e39065f06..19396d4a4ec 100644 --- a/docs/documenting/standards.md +++ b/docs/documenting/standards.md @@ -15,13 +15,13 @@ A few of the most common cases picked up in reviews are listed below: - Use a serial comma (also known as the Oxford comma) before the conjunction in a list of three or more items. For example, "Through the use of additional adapters, Home Assistant allows the use of Zigbee, Z-Wave, and other protocols". - There is no limit for the line length. You are allowed to write in a flowing text style. This will make it easier to use the GitHub online editor in the future. - Be objective and not gender favoring, polarizing, race related or religion inconsiderate. Contributions which do not follow this may be in breach of our [Code of Conduct](https://github.com/home-assistant/core/blob/master/CODE_OF_CONDUCT.md). -- The case of brand names, services, protocols, integrations and platforms must match its respective counterpart. For example, "Z-Wave" **not** "Zwave", "Z-wave", "Z Wave" or "ZWave". Also, "Input Select" **not** "input select" or "Input select". +- The case of brand names, services, protocols, integrations and platforms must match its respective counterpart. For example, "Z-Wave" _not_ "Zwave", "Z-wave", "Z Wave" or "ZWave". Also, "Input Select" _not_ "input select" or "Input select". - Do not use ALL CAPITALS for emphasis - use _italics_ instead. - Use [sentence-style capitalization](https://learn.microsoft.com/en-us/style-guide/capitalization), also in headings. - Use **bold** to markup UI strings, for example: - Under **Settings**, select the three dots menu. Then, select **Restart Home Assistant** > **Quick reload**. - Don't use "e.g.". Instead, use _for example_, _such as_, or _like_. -- All examples containing Jinja2 templates should be wrapped **outside** of the code markdown with the `{% raw %}` tag. +- All examples containing Jinja2 templates should be wrapped _outside_ of the code markdown with the `{% raw %}` tag. ## Integration and platform pages From bb937d9e15000461f8bbe59856c2a4d469ea13ae Mon Sep 17 00:00:00 2001 From: Christopher Fenner <9592452+CFenner@users.noreply.github.com> Date: Wed, 18 Dec 2024 22:31:49 +0100 Subject: [PATCH 47/55] Update tiers.json (#2509) --- docs/core/integration-quality-scale/_includes/tiers.json | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core/integration-quality-scale/_includes/tiers.json b/docs/core/integration-quality-scale/_includes/tiers.json index 42f6fdd234b..ab09232c38b 100644 --- a/docs/core/integration-quality-scale/_includes/tiers.json +++ b/docs/core/integration-quality-scale/_includes/tiers.json @@ -3,7 +3,7 @@ { "id": "config-flow", "subchecks": [ - "Uses `data-description` to give context to fields", + "Uses `data_description` to give context to fields", "Uses `ConfigEntry.data` and `ConfigEntry.options` correctly" ] }, @@ -65,4 +65,4 @@ "inject-websession", "strict-typing" ] -} \ No newline at end of file +} From 4520e5747263ff187977ff0cfc1ce71344a81368 Mon Sep 17 00:00:00 2001 From: Stefan Agner Date: Thu, 19 Dec 2024 12:45:28 +0100 Subject: [PATCH 48/55] Add mW as unit of measurement of power (#2508) --- docs/core/entity/number.md | 2 +- docs/core/entity/sensor.md | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/docs/core/entity/number.md b/docs/core/entity/number.md index f70b2342772..5fb90ff5db6 100644 --- a/docs/core/entity/number.md +++ b/docs/core/entity/number.md @@ -60,7 +60,7 @@ If specifying a device class, your number entity will need to also return the co | `NumberDeviceClass.PM1` | µg/m³ | Concentration of particulate matter less than 1 micrometer | | `NumberDeviceClass.PM25` | µg/m³ | Concentration of particulate matter less than 2.5 micrometers | | `NumberDeviceClass.PM10` | µg/m³ | Concentration of particulate matter less than 10 micrometers | -| `NumberDeviceClass.POWER` | W, kW, MW, GW, TW | Power. +| `NumberDeviceClass.POWER` | mW, W, kW, MW, GW, TW | Power. | `NumberDeviceClass.POWER_FACTOR` | %, None | Power Factor | `NumberDeviceClass.PRECIPITATION` | cm, in, mm | Precipitation | `NumberDeviceClass.PRECIPITATION_INTENSITY` | in/d, in/h, mm/d, mm/h | Precipitation intensity diff --git a/docs/core/entity/sensor.md b/docs/core/entity/sensor.md index 1267454d49a..ed593d20f45 100644 --- a/docs/core/entity/sensor.md +++ b/docs/core/entity/sensor.md @@ -64,7 +64,7 @@ If specifying a device class, your sensor entity will need to also return the co | `SensorDeviceClass.PM1` | µg/m³ | Concentration of particulate matter less than 1 micrometer | `SensorDeviceClass.PM25` | µg/m³ | Concentration of particulate matter less than 2.5 micrometers | `SensorDeviceClass.PM10` | µg/m³ | Concentration of particulate matter less than 10 micrometers -| `SensorDeviceClass.POWER` | W, kW, MW, GW, TW | Power. +| `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 From a15a4e955a9ca3d8f2f7c7ee5c953574aebcc4b7 Mon Sep 17 00:00:00 2001 From: Tudor Sandu Date: Fri, 20 Dec 2024 10:40:21 +0200 Subject: [PATCH 49/55] Fix broken intents sentence contribution link (#2513) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * Fix broken contribution link https://discord.com/channels/330944238910963714/1319070552761307177/1319070552761307177 The link to the contribution page does not work * Update docs/voice/intent-recognition/supported-languages.md Co-authored-by: Joakim Sørensen * Fix link --------- Co-authored-by: Joakim Sørensen --- docs/voice/intent-recognition/supported-languages.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/voice/intent-recognition/supported-languages.md b/docs/voice/intent-recognition/supported-languages.md index c4db099d495..860575238b2 100644 --- a/docs/voice/intent-recognition/supported-languages.md +++ b/docs/voice/intent-recognition/supported-languages.md @@ -5,7 +5,7 @@ title: "Supported languages" import languages from '!!yaml-loader!../../../intents/languages.yaml'; import intents from '!!yaml-loader!../../../intents/intents.yaml'; -If you don't see your language below, [help us translate!](contributing) +If you don't see your language below, [help us translate!](/docs/voice/intent-recognition/contributing) For a full progress report per language, [click here.](https://home-assistant.github.io/intents/) From 27576501ab105c1e8a3a1d4fbeb3bd9f9cae6582 Mon Sep 17 00:00:00 2001 From: Joost Lekkerkerker Date: Sat, 21 Dec 2024 13:15:37 +0100 Subject: [PATCH 50/55] Add blogpost for pydantic v2 (#2492) Co-authored-by: J. Nick Koston Co-authored-by: Franck Nijhof --- blog/2024-12-21-moving-to-pydantic-v2.md | 14 ++++++++++++++ 1 file changed, 14 insertions(+) create mode 100644 blog/2024-12-21-moving-to-pydantic-v2.md diff --git a/blog/2024-12-21-moving-to-pydantic-v2.md b/blog/2024-12-21-moving-to-pydantic-v2.md new file mode 100644 index 00000000000..a30cf781746 --- /dev/null +++ b/blog/2024-12-21-moving-to-pydantic-v2.md @@ -0,0 +1,14 @@ +--- +author: Joost Lekkerkerker +authorURL: https://github.com/joostlek +authorImageURL: https://avatars.githubusercontent.com/u/7083755?v=4 +title: "Moving to Pydantic v2" +--- + +Pydantic is a widely used library in Python for data validation. On June 30, 2023, Pydantic v2 was released, introducing significant changes that are not backward compatible with Pydantic v1. + +Starting with Home Assistant Core 2025.1, Pydantic v2 will replace v1. If your custom integration uses Pydantic, it must be updated to support Pydantic v2 to keep working in the upcoming release. + +Over the past year, our community has worked hard to ensure that the libraries used by Home Assistant Core are compatible with both Pydantic v1 and v2. This dual compatibility has helped make our transition to Pydantic v2 as smooth as possible. + +For a quick migration, you can use the Pydantic v1 shims included in Pydantic v2. Detailed information about using these shims in a v1/v2 environment can be found in the [Pydantic migration guide](https://docs.pydantic.dev/latest/migration/#using-pydantic-v1-features-in-a-v1v2-environment). \ No newline at end of file From 318fbd73305fed924eee377a6d7204cc4bb95c8a Mon Sep 17 00:00:00 2001 From: epenet <6771947+epenet@users.noreply.github.com> Date: Mon, 23 Dec 2024 11:29:04 +0100 Subject: [PATCH 51/55] Update 2024-12-13-water-heater-entity-description.md (#2500) --- blog/2024-12-13-water-heater-entity-description.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/blog/2024-12-13-water-heater-entity-description.md b/blog/2024-12-13-water-heater-entity-description.md index fb054294bd6..aa26c9f0d0e 100644 --- a/blog/2024-12-13-water-heater-entity-description.md +++ b/blog/2024-12-13-water-heater-entity-description.md @@ -5,7 +5,7 @@ authorTwitter: lboue title: "Changed name of WaterHeaterEntityDescription" --- -A naming error of the entity description was found in the Water Heater integration, and we have now renamed `WaterHeaterEntityEntityDescription` to `WaterHeaterEntityDescription`. -As there were no Core integrations and no custom integrations published on HACS using the entity description, we did not add a deprecation period. The changed entity description will be released in the 2025.1 release. +A naming error of the entity description was found in the Water Heater integration, and we have now renamed `WaterHeaterEntityEntityDescription` to `WaterHeaterEntityDescription` in the 2025.1 release. +The old `WaterHeaterEntityEntityDescription` is deprecated, due for removal in 2026.1, and developpers are advised to use the new `WaterHeaterEntityDescription` instead. See details in the core PR: [#132888](https://github.com/home-assistant/core/pull/132888). From f869ec049553bf3eefafb50cc2b3f77915979565 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ab=C3=ADlio=20Costa?= Date: Mon, 23 Dec 2024 20:45:21 +0000 Subject: [PATCH 52/55] Update manifest docs with new the IQS requirement (#2511) --- docs/creating_integration_manifest.md | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/docs/creating_integration_manifest.md b/docs/creating_integration_manifest.md index fad834983f8..915c757797a 100644 --- a/docs/creating_integration_manifest.md +++ b/docs/creating_integration_manifest.md @@ -412,11 +412,9 @@ For example: ## Integration quality scale -The [Integration Quality Scale](https://www.home-assistant.io/docs/quality_scale/) scores an integration on the code quality and user experience. Each level of the quality scale consists of a list of requirements. If an integration matches all requirements, it's considered to have reached that level. +The [Integration Quality Scale](/docs/core/integration-quality-scale) scores an integration on the code quality and user experience. Each level of the quality scale consists of a list of requirements. If an integration matches all requirements, it's considered to have reached that level. -When your integration has no score, then don't add it to the manifest of your integration. However, be sure to look at the [Integration Quality Scale](https://www.home-assistant.io/docs/quality_scale/) list of requirements. It helps to improve the code and user experience tremendously. - -We highly recommend getting your integration scored. +New integrations are required to fulfill at least the bronze tier so be sure to look at the [Integration Quality Scale](/docs/core/integration-quality-scale) list of requirements. It helps to improve the code and user experience tremendously. ```json { From c5c3d94f29d77db4448a10c673238285f5666503 Mon Sep 17 00:00:00 2001 From: "Teemu R." Date: Mon, 23 Dec 2024 21:55:27 +0100 Subject: [PATCH 53/55] Clarify DHCP entry matching (#2470) --- docs/creating_integration_manifest.md | 19 ++++++++----------- 1 file changed, 8 insertions(+), 11 deletions(-) diff --git a/docs/creating_integration_manifest.md b/docs/creating_integration_manifest.md index 915c757797a..0a6737a7b5a 100644 --- a/docs/creating_integration_manifest.md +++ b/docs/creating_integration_manifest.md @@ -319,7 +319,7 @@ Integrations depending on MQTT should wait using `await mqtt.async_wait_for_mqtt ## DHCP -If your integration supports discovery via dhcp, you can add the type to your manifest. If the user has the `dhcp` integration loaded, it will load the `dhcp` step of your integration's config flow when it is discovered. We support passively listening for DHCP discovery by the `hostname` and [OUI](https://en.wikipedia.org/wiki/Organizationally_unique_identifier), or matching device registry mac address when `registered_devices` is set to `true`. The manifest value is a list of matcher dictionaries, your integration is discovered if all items of any of the specified matchers are found in the DHCP data. It's up to your config flow to filter out duplicates. +If your integration supports discovery via DHCP, you can add the type to your manifest. If the user has the `dhcp` integration loaded, it will load the `dhcp` step of your integration's config flow when it is discovered. We support passively listening for DHCP discovery by the `hostname` and [OUI](https://en.wikipedia.org/wiki/Organizationally_unique_identifier), or matching device registry mac address when `registered_devices` is set to `true`. The manifest value is a list of matcher dictionaries, your integration is discovered if all items of any of the specified matchers are found in the DHCP data. [Unix filename pattern matching](https://docs.python.org/3/library/fnmatch.html) is used for matching. It's up to your config flow to filter out duplicates. If an integration wants to receive discovery flows to update the IP Address of a device when it comes online, but a `hostname` or `oui` match would be too broad, and it has registered in the device registry with mac address using the `CONNECTION_NETWORK_MAC`, @@ -328,13 +328,14 @@ it should add a DHCP entry with `registered_devices` set to `true`. If the integration supports `zeroconf` or `ssdp`, these should be preferred over `dhcp` as it generally offers a better user experience. -The following example has three matchers consisting of two items. All of the items in any of the three matchers must match for discovery to happen by this config. +The following example has two matchers consisting of two items. All of the items in any of the matchers must match for discovery to happen by this config. For example: -- If the `hostname` was `Rachio-XYZ` and the `macaddress` was `00:9D:6B:55:12:AA`, the discovery would happen. -- If the `hostname` was `Rachio-XYZ` and the `macaddress` was `00:00:00:55:12:AA`, the discovery would not happen. -- If the `hostname` was `NotRachio-XYZ` and the `macaddress` was `00:9D:6B:55:12:AA`, the discovery would not happen. +- If the `hostname` was `Rachio-XYZ` and the `macaddress` was `00:9D:6B:55:12:AA`, the discovery would happen (1st matcher). +- If the `hostname` was `Dachio-XYZ` or `Pachio-XYZ`, and the `macaddress` was `00:9D:6B:55:12:AA`, the discovery would happen (3rd matcher). +- If the `hostname` was `Rachio-XYZ` and the `macaddress` was `00:00:00:55:12:AA`, the discovery would not happen (no matching MAC). +- If the `hostname` was `NotRachio-XYZ` and the `macaddress` was `00:9D:6B:55:12:AA`, the discovery would not happen (no matching hostname). ```json @@ -345,12 +346,8 @@ For example: "macaddress": "009D6B*" }, { - "hostname": "rachio-*", - "macaddress": "F0038C*" - }, - { - "hostname": "rachio-*", - "macaddress": "74C63B*" + "hostname": "[dp]achio-*", + "macaddress": "009D6B*" } ] } From 4d2ede8eed677102eb0a39ef8f654b71fda5a407 Mon Sep 17 00:00:00 2001 From: Martin Hjelmare Date: Tue, 24 Dec 2024 12:22:48 +0100 Subject: [PATCH 54/55] Add new backup platform interface (#2514) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Co-authored-by: Abílio Costa --- docs/core/platform/backup.md | 126 ++++++++++++++++++++++++++++++++++- 1 file changed, 124 insertions(+), 2 deletions(-) diff --git a/docs/core/platform/backup.md b/docs/core/platform/backup.md index 25852f890a0..559c5959817 100644 --- a/docs/core/platform/backup.md +++ b/docs/core/platform/backup.md @@ -2,11 +2,133 @@ title: "Backup" --- +There are two main purposes for an integration to implement a backup platform: + +1. Add a backup agent that can upload backups to some local or remote location. +2. Pause or prepare integration operations before creating a backup and/or run some operation after a backup. + +## Backup Agents + +To add one or more backup agents, implement the two methods, `async_get_backup_agents` and `async_register_backup_agents_listener` in `backup.py`. Example: + +```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 +``` + +The listener stored in `async_register_backup_agents_listener` should be called every time there is the need to reload backup agents, to remove stale agents and add new ones, such as when the integration is reloaded. For example: + +```python +async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool: + """Unload config entry.""" + # Notify backup listeners + hass.async_create_task(_notify_backup_listeners(hass), eager_start=False) + + return await hass.config_entries.async_unload_platforms( + entry, PLATFORMS + ) + + +async def _notify_backup_listeners(hass: HomeAssistant) -> None: + for listener in hass.data.get(DATA_BACKUP_AGENT_LISTENERS, []): + listener() +``` + +A backup agent should implement the abstract interface of the `BackupAgent` base class as shown in this example: + +```python +from homeassistant.components.backup import BackupAgent, BackupAgentError + +from .const import DOMAIN + + +class ExampleBackupAgent(BackupAgent): + """Backup agent interface.""" + + domain = DOMAIN + name = "example" + + async def async_download_backup( + self, + backup_id: str, + **kwargs: Any, + ) -> AsyncIterator[bytes]: + """Download a backup file. + + :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, + **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. + """ + + async def async_delete_backup( + self, + backup_id: str, + **kwargs: Any, + ) -> None: + """Delete a backup file. + + :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 | None: + """Return a backup.""" +``` + +Backup agents should raise a `BackupAgentError` exception on error. Other exceptions are not expected to leave the backup agent. + +## Pre- and post-operations + When Home Assistant is creating a backup, there might be a need to pause certain operations in the integration, or dumping data so it can properly be restored. -This is done by adding 2 functions (`async_pre_backup` and `async_post_backup`) to `backup.py` +This is done by adding two functions (`async_pre_backup` and `async_post_backup`) to `backup.py` -## Adding support +### Adding support The quickest way to add backup support to a new integration is by using our built-in scaffold template. From a Home Assistant dev environment, run `python3 -m script.scaffold backup` and follow the instructions. From 979cd620f5ad01c80e3e7a52f4f7a51ecd00b8c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Krzysztof=20D=C4=85browski?= Date: Thu, 26 Dec 2024 00:32:10 +0100 Subject: [PATCH 55/55] Fix typos in integration quality scale rules (#2515) --- docs/core/integration-quality-scale/rules/common-modules.md | 2 +- .../core/integration-quality-scale/rules/integration-owner.md | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/docs/core/integration-quality-scale/rules/common-modules.md b/docs/core/integration-quality-scale/rules/common-modules.md index 71f422f07c3..e03a90d0042 100644 --- a/docs/core/integration-quality-scale/rules/common-modules.md +++ b/docs/core/integration-quality-scale/rules/common-modules.md @@ -45,7 +45,7 @@ class MyEntity(CoordinatorEntity[MyCoordinator]): def __init__(self, coordinator: MyCoordinator) -> None: """Initialize the entity.""" super().__init__(coordinator) - self._attr_device_infp = ... + self._attr_device_info = ... ``` ## Exceptions diff --git a/docs/core/integration-quality-scale/rules/integration-owner.md b/docs/core/integration-quality-scale/rules/integration-owner.md index b2cc5d8c4a0..1185cdea650 100644 --- a/docs/core/integration-quality-scale/rules/integration-owner.md +++ b/docs/core/integration-quality-scale/rules/integration-owner.md @@ -22,7 +22,7 @@ During reviews, we see the integration owner as the expert on the integration, a Integration owners are set in the `manifest.json`. -```json {3} showLineNumbers +```json {4} showLineNumbers { "domain": "my_integration", "name": "My Integration", @@ -36,4 +36,4 @@ More information about integration owners can be found in [ADR-0008](https://git ## Exceptions -There are no exceptions to this rule. \ No newline at end of file +There are no exceptions to this rule.