diff --git a/.github/ISSUE_TEMPLATE/feedback.yml b/.github/ISSUE_TEMPLATE/feedback.yml index e645919c62af..a0b4c907f2f3 100644 --- a/.github/ISSUE_TEMPLATE/feedback.yml +++ b/.github/ISSUE_TEMPLATE/feedback.yml @@ -38,7 +38,7 @@ body: label: Version placeholder: "e.g., 2022.7.0" description: > - Latest stable version version of Home Assistant available + Latest stable version of Home Assistant available (which does not have to match the version you are using). - type: textarea attributes: diff --git a/.github/workflows/restrict-task-creation.yml b/.github/workflows/restrict-task-creation.yml index 83653b159e96..e6c6e1a14300 100644 --- a/.github/workflows/restrict-task-creation.yml +++ b/.github/workflows/restrict-task-creation.yml @@ -12,7 +12,7 @@ jobs: if: github.event.issue.type.name == 'Task' steps: - name: Check if user is authorized - uses: actions/github-script@ed597411d8f924073f98dfc5c65a23a2325f34cd # v8.0.0 + uses: actions/github-script@3a2844b7e9c422d3c10d287c895573f7108da1b3 # v9.0.0 with: script: | const issueAuthor = context.payload.issue.user.login; diff --git a/.textlintrc.json b/.textlintrc.json index 057c3edc2bbf..5d6f77b94878 100644 --- a/.textlintrc.json +++ b/.textlintrc.json @@ -8,6 +8,7 @@ "homeassistant:", "homekit:", "led:", + "mqtt:", "sms:", "sql:", "ssl:", diff --git a/Gemfile b/Gemfile index 2eb59a95b7ad..3322fde55e12 100644 --- a/Gemfile +++ b/Gemfile @@ -6,8 +6,8 @@ group :development do gem 'rake', '13.3.1' gem 'jekyll', '4.4.1' gem 'stringex', '2.8.6' - gem 'sass-embedded', '1.98.0' - gem 'rubocop', '1.86.0' + gem 'sass-embedded', '1.99.0' + gem 'rubocop', '1.86.1' gem 'ruby-lsp', '0.26.9' gem 'rackup', '2.3.1' end diff --git a/Gemfile.lock b/Gemfile.lock index 5446e791fdc2..126882517b1d 100644 --- a/Gemfile.lock +++ b/Gemfile.lock @@ -1,11 +1,11 @@ GEM remote: https://rubygems.org/ specs: - addressable (2.8.9) + addressable (2.9.0) public_suffix (>= 2.0.2, < 8.0) ast (2.4.3) base64 (0.3.0) - bigdecimal (4.1.0) + bigdecimal (4.1.1) colorator (1.1.0) commonmarker (0.23.12) concurrent-ruby (1.3.6) @@ -84,7 +84,7 @@ GEM nokogiri (1.19.2-x86_64-linux-gnu) racc (~> 1.4) ostruct (0.6.3) - parallel (1.27.0) + parallel (2.0.1) parser (3.3.11.1) ast (~> 2.4.1) racc @@ -93,12 +93,12 @@ GEM prism (1.9.0) public_suffix (7.0.5) racc (1.8.1) - rack (3.2.5) + rack (3.2.6) rack-protection (4.2.1) base64 (>= 0.1.0) logger (>= 1.6.0) rack (>= 3.0.0, < 4) - rack-session (2.1.1) + rack-session (2.1.2) base64 (>= 0.1.0) rack (>= 3.0.0) rackup (2.3.1) @@ -112,14 +112,14 @@ GEM logger prism (>= 1.6.0) tsort - regexp_parser (2.11.3) + regexp_parser (2.12.0) rexml (3.4.4) rouge (4.7.0) - rubocop (1.86.0) + rubocop (1.86.1) json (~> 2.3) language_server-protocol (~> 3.17.0.2) lint_roller (~> 1.1.0) - parallel (~> 1.10) + parallel (>= 1.10) parser (>= 3.3.0.2) rainbow (>= 2.2.2, < 4.0) regexp_parser (>= 2.9.3, < 3.0) @@ -136,11 +136,11 @@ GEM ruby-progressbar (1.13.0) ruby2_keywords (0.0.5) safe_yaml (1.0.5) - sass-embedded (1.98.0-aarch64-linux-gnu) + sass-embedded (1.99.0-aarch64-linux-gnu) google-protobuf (~> 4.31) - sass-embedded (1.98.0-arm64-darwin) + sass-embedded (1.99.0-arm64-darwin) google-protobuf (~> 4.31) - sass-embedded (1.98.0-x86_64-linux-gnu) + sass-embedded (1.99.0-x86_64-linux-gnu) google-protobuf (~> 4.31) sinatra (4.2.1) logger (>= 1.6.0) @@ -177,9 +177,9 @@ DEPENDENCIES ostruct (= 0.6.3) rackup (= 2.3.1) rake (= 13.3.1) - rubocop (= 1.86.0) + rubocop (= 1.86.1) ruby-lsp (= 0.26.9) - sass-embedded (= 1.98.0) + sass-embedded (= 1.99.0) sinatra (= 4.2.1) stringex (= 2.8.6) tzinfo (~> 2.0) diff --git a/_config.yml b/_config.yml index 601fad289b90..19b73cc1e9c0 100644 --- a/_config.yml +++ b/_config.yml @@ -67,6 +67,9 @@ titlecase: true # Converts page and post titles to titlecase collections: integrations: output: true + template_functions: + output: true + permalink: /template-functions/:path/ docs: output: true apps: @@ -108,13 +111,13 @@ social: # Home Assistant release details current_major_version: 2026 current_minor_version: 4 -current_patch_version: 0 -date_released: 2026-04-01 +current_patch_version: 1 +date_released: 2026-04-03 # Either # or the anchor link to latest release notes in the blog post. # Must be prefixed with a # and have double quotes around it. # Major release: -patch_version_notes: "" +patch_version_notes: "#202641---april-3" # Minor release (Example #release-0431---april-25): # Set Front matter defaults @@ -156,6 +159,11 @@ defaults: type: integrations values: toc: true + - scope: + path: "" + type: template_functions + values: + toc: true - scope: path: "" type: docs diff --git a/package-lock.json b/package-lock.json index 8edc53640661..9bbb72e061a3 100644 --- a/package-lock.json +++ b/package-lock.json @@ -20,7 +20,7 @@ "remark-lint-prohibited-strings": "^4.0.0", "remark-lint-unordered-list-marker-style": "^4.0.1", "remark-stringify": "^11.0.0", - "textlint": "^15.5.2", + "textlint": "^15.5.4", "textlint-filter-rule-allowlist": "^4.0.0", "textlint-filter-rule-comments": "^1.3.0", "textlint-rule-common-misspellings": "^1.0.1", @@ -70,9 +70,9 @@ } }, "node_modules/@hono/node-server": { - "version": "1.19.10", - "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.10.tgz", - "integrity": "sha512-hZ7nOssGqRgyV3FVVQdfi+U4q02uB23bpnYpdvNXkYTRRyWx84b7yf1ans+dnJ/7h41sGL3CeQTfO+ZGxuO+Iw==", + "version": "1.19.13", + "resolved": "https://registry.npmjs.org/@hono/node-server/-/node-server-1.19.13.tgz", + "integrity": "sha512-TsQLe4i2gvoTtrHje625ngThGBySOgSK3Xo2XRYOdqGN1teR8+I7vchQC46uLJi8OF62YTYA3AhSpumtkhsaKQ==", "dev": true, "license": "MIT", "engines": { @@ -162,9 +162,9 @@ "license": "MIT" }, "node_modules/@modelcontextprotocol/sdk": { - "version": "1.26.0", - "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.26.0.tgz", - "integrity": "sha512-Y5RmPncpiDtTXDbLKswIJzTqu2hyBKxTNsgKqKclDbhIgg1wgtf1fRuvxgTnRfcnxtvvgbIEcqUOzZrJ6iSReg==", + "version": "1.29.0", + "resolved": "https://registry.npmjs.org/@modelcontextprotocol/sdk/-/sdk-1.29.0.tgz", + "integrity": "sha512-zo37mZA9hJWpULgkRpowewez1y6ML5GsXJPY8FI0tBBCd77HEvza4jDqRKOXgHNn867PVGCyTdzqpz0izu5ZjQ==", "dev": true, "license": "MIT", "dependencies": { @@ -257,69 +257,69 @@ } }, "node_modules/@textlint/ast-node-types": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.5.2.tgz", - "integrity": "sha512-fCaOxoup5LIyBEo7R1oYWE7V4bSX0KQeHh66twon9e9usaLE3ijgF8QjYsR6joCssdeCHVd0wHm7ppsEyTr6vg==", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/ast-node-types/-/ast-node-types-15.5.4.tgz", + "integrity": "sha512-bVtB6VEy9U9DpW8cTt25k5T+lz86zV5w6ImePZqY1AXzSuPhqQNT77lkMPxonXzUducEIlSvUu3o7sKw3y9+Sw==", "dev": true, "license": "MIT" }, "node_modules/@textlint/ast-tester": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/ast-tester/-/ast-tester-15.5.2.tgz", - "integrity": "sha512-xlGt3fEUC9NkGNmd7WIzIoQvxs/fOISvrco6fedSCCji0iqVwc6fw4atb2iTM/eGg4IbPlBVpjnLBduB5FpekA==", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/ast-tester/-/ast-tester-15.5.4.tgz", + "integrity": "sha512-5XOZP84GhqBQgs9JKxYOw2nCX/b+ql9kqZRrxadCKdB/XwUHXk9jKv+aQCDh0GB3FRlP77Ob1dGlsnLkyxrbkg==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/ast-node-types": "15.5.2", + "@textlint/ast-node-types": "15.5.4", "debug": "^4.4.3" } }, "node_modules/@textlint/ast-traverse": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/ast-traverse/-/ast-traverse-15.5.2.tgz", - "integrity": "sha512-8lpRDjFAN+I/4hGo+0YRIpsk1YfiI223oulFkb8gL0/PCGrdw798mfV+czKIS0/fr1Eq/fYbOzbHpoATDA4GBA==", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/ast-traverse/-/ast-traverse-15.5.4.tgz", + "integrity": "sha512-j6ToGmPUyNkp2FCJ0vlRnx1EPlPM19/XFyY9l95ToJ9XJZSGf3tcEa2CP8laZy9yuTVcGYDvQp2RdV3NxjbZpg==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/ast-node-types": "15.5.2" + "@textlint/ast-node-types": "15.5.4" } }, "node_modules/@textlint/config-loader": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/config-loader/-/config-loader-15.5.2.tgz", - "integrity": "sha512-9DiYEyX4BUx8cNqnFyYv0yF/Ckt+A23pCOaAE13iWCmKXOMTyGLxr0CnqB+pB4cDmRUykYJgI9Rvp5jQyYCu/w==", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/config-loader/-/config-loader-15.5.4.tgz", + "integrity": "sha512-jlCfkYp3ICwPTSeBbnfYn04Q4vMzCWi3AsmPC3JSTmyNuHXnij/6Gb9AfCB3kl9K23iNmtSHNBnbv5j9i8QqvQ==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/kernel": "15.5.2", - "@textlint/module-interop": "15.5.2", - "@textlint/resolver": "15.5.2", - "@textlint/types": "15.5.2", - "@textlint/utils": "15.5.2", + "@textlint/kernel": "15.5.4", + "@textlint/module-interop": "15.5.4", + "@textlint/resolver": "15.5.4", + "@textlint/types": "15.5.4", + "@textlint/utils": "15.5.4", "debug": "^4.4.3", - "rc-config-loader": "^4.1.3" + "rc-config-loader": "^4.1.4" } }, "node_modules/@textlint/feature-flag": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/feature-flag/-/feature-flag-15.5.2.tgz", - "integrity": "sha512-1KYloMwk20rkrqES15O+wSVUIPk/Vg1ol3zdtp6vT3E43Yj7mlQPwHkTr0J/XSmoJdm/s8cn86ds/KgWrm+i+g==", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/feature-flag/-/feature-flag-15.5.4.tgz", + "integrity": "sha512-Jw0uTRwxq9CZzkiP8PzNiertZ+j48Zo2Kb5uxDpGizNEjExgpemOCu3dZkc4lKy8e2W9M8lSrP4kO8rK2Dp9Pg==", "dev": true, "license": "MIT" }, "node_modules/@textlint/fixer-formatter": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/fixer-formatter/-/fixer-formatter-15.5.2.tgz", - "integrity": "sha512-zVJTrrsSRVnZULQAI7qbE8RIVPlpGKKkvCFVfm8jSOjgXdAz3gOy4VmYxLWPLlvihB4KT9y9PmvFv47q23iQnw==", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/fixer-formatter/-/fixer-formatter-15.5.4.tgz", + "integrity": "sha512-wG/FLzcoI9pGsTL11lraS/QdagyVpIxollAADsXCVbna5eAaqqtOeX2dCYZ6l4sNY/PteD3h02FjUfUjVNwNrw==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/module-interop": "15.5.2", - "@textlint/resolver": "15.5.2", - "@textlint/types": "15.5.2", + "@textlint/module-interop": "15.5.4", + "@textlint/resolver": "15.5.4", + "@textlint/types": "15.5.4", "chalk": "^4.1.2", "debug": "^4.4.3", - "diff": "^8.0.3", + "diff": "^8.0.4", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", "text-table": "^0.2.0" @@ -332,40 +332,40 @@ "dev": true }, "node_modules/@textlint/kernel": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/kernel/-/kernel-15.5.2.tgz", - "integrity": "sha512-UyDgebb5TQnZiFGGlF5yRIDXzvPa7TF+0ProCvrOp7/w88beZOkCx4YY53h5q69DdlKMOyUI9AdMjqLzpfzvnA==", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/kernel/-/kernel-15.5.4.tgz", + "integrity": "sha512-IZSChEFO1Yei1rr0WOTO4JnVjDuz8tlay9gTvqJmvNCe8IcFAbUtlb5gkFP1t57Pix5xXhkUY8HuNICedg67DQ==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/ast-node-types": "15.5.2", - "@textlint/ast-tester": "15.5.2", - "@textlint/ast-traverse": "15.5.2", - "@textlint/feature-flag": "15.5.2", - "@textlint/source-code-fixer": "15.5.2", - "@textlint/types": "15.5.2", - "@textlint/utils": "15.5.2", + "@textlint/ast-node-types": "15.5.4", + "@textlint/ast-tester": "15.5.4", + "@textlint/ast-traverse": "15.5.4", + "@textlint/feature-flag": "15.5.4", + "@textlint/source-code-fixer": "15.5.4", + "@textlint/types": "15.5.4", + "@textlint/utils": "15.5.4", "debug": "^4.4.3", "fast-equals": "^4.0.3", "structured-source": "^4.0.0" } }, "node_modules/@textlint/linter-formatter": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.5.2.tgz", - "integrity": "sha512-jAw7jWM8+wU9cG6Uu31jGyD1B+PAVePCvnPKC/oov+2iBPKk3ao30zc/Itmi7FvXo4oPaL9PmzPPQhyniPVgVg==", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/linter-formatter/-/linter-formatter-15.5.4.tgz", + "integrity": "sha512-D9qJedKBLmAo+kiudop4UKgSxXMi4O8U86KrCidVXZ9RsK0NSVIw6+r2rlMUOExq79iEY81FRENyzmNVRxDBsg==", "dev": true, "license": "MIT", "dependencies": { "@azu/format-text": "^1.0.2", "@azu/style-format": "^1.0.1", - "@textlint/module-interop": "15.5.2", - "@textlint/resolver": "15.5.2", - "@textlint/types": "15.5.2", + "@textlint/module-interop": "15.5.4", + "@textlint/resolver": "15.5.4", + "@textlint/types": "15.5.4", "chalk": "^4.1.2", "debug": "^4.4.3", "js-yaml": "^4.1.1", - "lodash": "^4.17.23", + "lodash": "^4.18.1", "pluralize": "^2.0.0", "string-width": "^4.2.3", "strip-ansi": "^6.0.1", @@ -374,13 +374,13 @@ } }, "node_modules/@textlint/markdown-to-ast": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/markdown-to-ast/-/markdown-to-ast-15.5.2.tgz", - "integrity": "sha512-eLEeIb7jcyWiG1jahr3pl3z8dEYEgLPdz7lBG3AP8aB4m8Sv0SdL0mCD0tfR5hNp8FrOEXldqh1g2PMuiXlN3w==", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/markdown-to-ast/-/markdown-to-ast-15.5.4.tgz", + "integrity": "sha512-BBAmPYAQ2Y3sqJoivT7S+tTqksoKKi2qwLOPRYVPcAM55B3x/8eLxQDd0qARo7XvJKscDawwpfAGcLOR9n6SBQ==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/ast-node-types": "15.5.2", + "@textlint/ast-node-types": "15.5.4", "debug": "^4.4.3", "mdast-util-gfm-autolink-literal": "^0.1.3", "neotraverse": "^0.6.18", @@ -635,9 +635,9 @@ } }, "node_modules/@textlint/module-interop": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.5.2.tgz", - "integrity": "sha512-mg6rMQ3+YjwiXCYoQXbyVfDucpTa1q5mhspd/9qHBxUq4uY6W8GU42rmT3GW0V1yOfQ9z/iRrgPtkp71s8JzXg==", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/module-interop/-/module-interop-15.5.4.tgz", + "integrity": "sha512-JyAUd26ll3IFF87LP0uGoa8Tzw5ZKiYvGs6v8jLlzyND1lUYCI4+2oIAslrODLkf0qwoCaJrBQWM3wsw+asVGQ==", "dev": true, "license": "MIT" }, @@ -665,69 +665,69 @@ } }, "node_modules/@textlint/resolver": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.5.2.tgz", - "integrity": "sha512-YEITdjRiJaQrGLUWxWXl4TEg+d2C7+TNNjbGPHPH7V7CCnXm+S9GTjGAL7Q2WSGJyFEKt88Jvx6XdJffRv4HEA==", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/resolver/-/resolver-15.5.4.tgz", + "integrity": "sha512-5GUagtpQuYcmhlOzBGdmVBvDu5lKgVTjwbxtdfoidN4OIqblIxThJHHjazU+ic+/bCIIzI2JcOjHGSaRmE8Gcg==", "dev": true, "license": "MIT" }, "node_modules/@textlint/source-code-fixer": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/source-code-fixer/-/source-code-fixer-15.5.2.tgz", - "integrity": "sha512-9MMhrvX4uQOEYM+C3kbVsq1O2U7tOTc0l3rMiJzniwqq57AgP/AxDRz20ujh2OGE7Qw4E2y8KyM/3XMi0HvUHQ==", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/source-code-fixer/-/source-code-fixer-15.5.4.tgz", + "integrity": "sha512-IVBpT4chGOtTGCs29NRpeaDVC+bxFRnnyPWsFEkCX5a/uDFwJuVsBcm4KxdgTLgOac5tOCjKa+/6e+HohnnkLA==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/types": "15.5.2", + "@textlint/types": "15.5.4", "debug": "^4.4.3" } }, "node_modules/@textlint/text-to-ast": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/text-to-ast/-/text-to-ast-15.5.2.tgz", - "integrity": "sha512-7cFC1Em9W3g8ilrelDftGmRzlwG+5+jx2HLJ4h4uAl+Ib42bfCtDdBnvh3dRomgu4Z36Tj0F3qMBFgxNoQhZeA==", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/text-to-ast/-/text-to-ast-15.5.4.tgz", + "integrity": "sha512-9Vhjd0Ri/J85mwr1sX7MMfG52NS3ytOF+nTyXBYTtOLZ63G46ySWWr34y+/v+IBYjUelfY+sxC0bs9OZwVOn2g==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/ast-node-types": "15.5.2" + "@textlint/ast-node-types": "15.5.4" } }, "node_modules/@textlint/textlint-plugin-markdown": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/textlint-plugin-markdown/-/textlint-plugin-markdown-15.5.2.tgz", - "integrity": "sha512-aaXWlFmhX+8uUibMX/+nUvqH2/C/SptW24YFUVMhCLPiEtfiYsZbT8qVtuYMH6L/MXAzjOgVSRuDcceoI7csJA==", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/textlint-plugin-markdown/-/textlint-plugin-markdown-15.5.4.tgz", + "integrity": "sha512-j9csK+Tsl6wc4PfTwqOo4Ol2m/W/iSEvrojzuwqMHGQEIRpIKEF0pRsSS2U9b9y6OLLMBRFwEFJ+TOM7R7uvjQ==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/markdown-to-ast": "15.5.2", - "@textlint/types": "15.5.2" + "@textlint/markdown-to-ast": "15.5.4", + "@textlint/types": "15.5.4" } }, "node_modules/@textlint/textlint-plugin-text": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/textlint-plugin-text/-/textlint-plugin-text-15.5.2.tgz", - "integrity": "sha512-KlXPdhz5AFoBAu0bYqBuwz0ws0P5ACmXs7BFoc8719o+nK+ONSgQ+UEyl89NzGepweIqQHnlxTGcibvxu0z8Ew==", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/textlint-plugin-text/-/textlint-plugin-text-15.5.4.tgz", + "integrity": "sha512-5Rghi/o9IWY/IO4kuqeNfwl4fCD5LxGCQOv09k8lsWrXNUZRUuJEyqrCW4cSKwpX1RgnHNK49Tt75BNjAq44pQ==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/text-to-ast": "15.5.2", - "@textlint/types": "15.5.2" + "@textlint/text-to-ast": "15.5.4", + "@textlint/types": "15.5.4" } }, "node_modules/@textlint/types": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.5.2.tgz", - "integrity": "sha512-sJOrlVLLXp4/EZtiWKWq9y2fWyZlI8GP+24rnU5avtPWBIMm/1w97yzKrAqYF8czx2MqR391z5akhnfhj2f/AQ==", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/types/-/types-15.5.4.tgz", + "integrity": "sha512-mY28j2U7nrWmZbxyKnRvB8vJxJab4AxqOobLfb6iozrLelJbqxcOTvBQednadWPfAk9XWaZVMqUr9Nird3mutg==", "dev": true, "license": "MIT", "dependencies": { - "@textlint/ast-node-types": "15.5.2" + "@textlint/ast-node-types": "15.5.4" } }, "node_modules/@textlint/utils": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/@textlint/utils/-/utils-15.5.2.tgz", - "integrity": "sha512-g7Zs8QDZJspno9C7i5iGdwuJ07SxCHWIgxpAebwX503sup4w5IOo3q0X/LxRdl1F4sSAFw/m2glYZomOieNPCw==", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/@textlint/utils/-/utils-15.5.4.tgz", + "integrity": "sha512-lz/EClunydTwr1pbOpw8OZ28n4JDW/WwvWjyUa1WxwQI6KWMvTAASjVZ+9CS6IceIlIA4xqLmk9Oi3gms9/x7g==", "dev": true, "license": "MIT" }, @@ -856,9 +856,9 @@ } }, "node_modules/ajv": { - "version": "8.17.1", - "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.17.1.tgz", - "integrity": "sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==", + "version": "8.18.0", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.18.0.tgz", + "integrity": "sha512-PlXPeEWMXMZ7sPYOHqmDyCJzcfNrUr3fGNKtezX14ykXOEIvyK81d+qydx89KY5O71FKMPaQ2vBfBFI5NHR63A==", "dev": true, "license": "MIT", "dependencies": { @@ -1280,9 +1280,9 @@ } }, "node_modules/content-disposition": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.0.1.tgz", - "integrity": "sha512-oIXISMynqSqm241k6kcQ5UwttDILMK4BiurCfGEREw6+X9jkkpEe5T9FZaApyLGGOnFuyMWZpdolTXMtvEJ08Q==", + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-1.1.0.tgz", + "integrity": "sha512-5jRCH9Z/+DRP7rkvY83B+yGIGX96OYdJmzngqnw2SBSxqCFPd0w2km3s5iawpGX8krnwSGmF0FW5Nhr0Hfai3g==", "dev": true, "license": "MIT", "engines": { @@ -1324,9 +1324,9 @@ } }, "node_modules/cors": { - "version": "2.8.5", - "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.5.tgz", - "integrity": "sha512-KIHbLJqu73RGr/hnbrO9uBeixNGuvSQjul/jdFvS/KFSIH1hWVd1ng7zOHx+YrEfInLG7q4n6GHQ9cDtxv/P6g==", + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", "dev": true, "license": "MIT", "dependencies": { @@ -1335,6 +1335,10 @@ }, "engines": { "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" } }, "node_modules/cross-spawn": { @@ -1445,9 +1449,9 @@ } }, "node_modules/diff": { - "version": "8.0.3", - "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.3.tgz", - "integrity": "sha512-qejHi7bcSD4hQAZE0tNAawRK1ZtafHDmMTMkrrIGgSLl7hTnQHmKCeB45xAcbfTqK2zowkM3j3bHt/4b/ARbYQ==", + "version": "8.0.4", + "resolved": "https://registry.npmjs.org/diff/-/diff-8.0.4.tgz", + "integrity": "sha512-DPi0FmjiSU5EvQV0++GFDOJ9ASQUVFh5kD+OzOnYdi7n3Wpm9hWWGfB/O2blfHcMVTL5WkQXSnRiK9makhrcnw==", "dev": true, "license": "BSD-3-Clause", "engines": { @@ -1650,13 +1654,13 @@ } }, "node_modules/express-rate-limit": { - "version": "8.2.1", - "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.2.1.tgz", - "integrity": "sha512-PCZEIEIxqwhzw4KF0n7QF4QqruVTcF73O5kFKUnGOyjbCCgizBBiFaYpd/fnBLUMPw/BWw9OsiN7GgrNYr7j6g==", + "version": "8.3.2", + "resolved": "https://registry.npmjs.org/express-rate-limit/-/express-rate-limit-8.3.2.tgz", + "integrity": "sha512-77VmFeJkO0/rvimEDuUC5H30oqUC4EyOhyGccfqoLebB0oiEYfM7nwPrsDsBL1gsTpwfzX8SFy2MT3TDyRq+bg==", "dev": true, "license": "MIT", "dependencies": { - "ip-address": "10.0.1" + "ip-address": "10.1.0" }, "engines": { "node": ">= 16" @@ -2006,9 +2010,9 @@ } }, "node_modules/hono": { - "version": "4.12.7", - "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.7.tgz", - "integrity": "sha512-jq9l1DM0zVIvsm3lv9Nw9nlJnMNPOcAtsbsgiUhWcFzPE99Gvo6yRTlszSLLYacMeQ6quHD6hMfId8crVHvexw==", + "version": "4.12.12", + "resolved": "https://registry.npmjs.org/hono/-/hono-4.12.12.tgz", + "integrity": "sha512-p1JfQMKaceuCbpJKAPKVqyqviZdS0eUxH9v82oWo1kb9xjQ5wA6iP3FNVAPDFlz5/p7d45lO+BpSk1tuSZMF4Q==", "dev": true, "license": "MIT", "engines": { @@ -2121,9 +2125,9 @@ } }, "node_modules/ip-address": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.0.1.tgz", - "integrity": "sha512-NWv9YLW4PoW2B7xtzaS3NCot75m6nK7Icdv0o3lfMceJVRfSoQwqD4wEH5rLwoKJwUiZ/rfpiVBhnaF0FK4HoA==", + "version": "10.1.0", + "resolved": "https://registry.npmjs.org/ip-address/-/ip-address-10.1.0.tgz", + "integrity": "sha512-XXADHxXmvT9+CRxhXg56LJovE+bmWnEWB78LB83VZTprKTmaC5QfruXocxzTZ2Kl0DNwKuBdlIhjL8LeY8Sf8Q==", "dev": true, "license": "MIT", "engines": { @@ -2378,9 +2382,9 @@ } }, "node_modules/jose": { - "version": "6.1.3", - "resolved": "https://registry.npmjs.org/jose/-/jose-6.1.3.tgz", - "integrity": "sha512-0TpaTfihd4QMNwrz/ob2Bp7X04yuxJkjRGi4aKmOqwhov54i6u79oCv7T+C7lo70MKH6BesI3vscD1yb/yzKXQ==", + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/jose/-/jose-6.2.2.tgz", + "integrity": "sha512-d7kPDd34KO/YnzaDOlikGpOurfF0ByC2sEV4cANCtdqLlTfBlw2p14O/5d/zv40gJPbIQxfES3nSx1/oYNyuZQ==", "dev": true, "license": "MIT", "funding": { @@ -2500,9 +2504,9 @@ } }, "node_modules/lodash": { - "version": "4.17.23", - "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.23.tgz", - "integrity": "sha512-LgVTMpQtIopCi79SJeDiP0TfWi5CNEc/L/aRdTh3yIvmZXTnheWpKjSZhnvMl8iXbC1tFg9gdHHDMLoV7CnG+w==", + "version": "4.18.1", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.18.1.tgz", + "integrity": "sha512-dMInicTPVE8d1e5otfwmmjlxkZoUpiVLwyeTdUsi/Caj/gfzzblBcCE5sRHV/AsjuCmxWrte2TNGSYuCeCq+0Q==", "dev": true, "license": "MIT" }, @@ -4533,9 +4537,9 @@ "dev": true }, "node_modules/path-to-regexp": { - "version": "8.3.0", - "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.3.0.tgz", - "integrity": "sha512-7jdwVIRtsP8MYpdXSwOS0YdD0Du+qOoF/AEPIt88PcCFrZCzx41oxku1jD88hZBwbNUIEfpqvuhjFaMAqMTWnA==", + "version": "8.4.2", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-8.4.2.tgz", + "integrity": "sha512-qRcuIdP69NPm4qbACK+aDogI5CBDMi1jKe0ry5rSQJz8JVLsC7jV8XpiJjGRLLol3N+R5ihGYcrPLTno6pAdBA==", "dev": true, "license": "MIT", "funding": { @@ -4614,9 +4618,9 @@ } }, "node_modules/qs": { - "version": "6.14.2", - "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.2.tgz", - "integrity": "sha512-V/yCWTTF7VJ9hIh18Ugr2zhJMP01MY7c5kh4J870L7imm6/DIzBsNLTXzMwUA3yZ5b/KBqLx8Kp3uRvd7xSe3Q==", + "version": "6.15.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.15.1.tgz", + "integrity": "sha512-6YHEFRL9mfgcAvql/XhwTvf5jKcOiiupt2FiJxHkiX1z4j7WL8J/jRHYLluORvc1XxB5rV20KoeK00gVJamspg==", "dev": true, "license": "BSD-3-Clause", "dependencies": { @@ -4666,15 +4670,15 @@ } }, "node_modules/rc-config-loader": { - "version": "4.1.3", - "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.3.tgz", - "integrity": "sha512-kD7FqML7l800i6pS6pvLyIE2ncbk9Du8Q0gp/4hMPhJU6ZxApkoLcGD8ZeqgiAlfwZ6BlETq6qqe+12DUL207w==", + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/rc-config-loader/-/rc-config-loader-4.1.4.tgz", + "integrity": "sha512-3GiwEzklkbXTDp52UR5nT8iXgYAx1V9ZG/kDZT7p60u2GCv2XTwQq4NzinMoMpNtXhmt3WkhYXcj6HH8HdwCEQ==", "dev": true, "license": "MIT", "dependencies": { - "debug": "^4.3.4", - "js-yaml": "^4.1.0", - "json5": "^2.2.2", + "debug": "^4.4.3", + "js-yaml": "^4.1.1", + "json5": "^2.2.3", "require-from-string": "^2.0.2" } }, @@ -6504,14 +6508,14 @@ } }, "node_modules/side-channel-list": { - "version": "1.0.0", - "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", - "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.1.tgz", + "integrity": "sha512-mjn/0bi/oUURjc5Xl7IaWi/OJJJumuoJFQJfDDyO46+hBWsfaVM65TBHq2eoZBhzl9EchxOijpkbRC8SVBQU0w==", "dev": true, "license": "MIT", "dependencies": { "es-errors": "^1.3.0", - "object-inspect": "^1.13.3" + "object-inspect": "^1.13.4" }, "engines": { "node": ">= 0.4" @@ -6793,33 +6797,33 @@ "dev": true }, "node_modules/textlint": { - "version": "15.5.2", - "resolved": "https://registry.npmjs.org/textlint/-/textlint-15.5.2.tgz", - "integrity": "sha512-L0Z00xDx4mI3L44nBO5v/Z00ZIhX932dKNwRpSpwGGCWTHFJ7tx1imfTQH441xXb/EhuqnrHhchtSs/WBuJEnQ==", - "dev": true, - "license": "MIT", - "dependencies": { - "@modelcontextprotocol/sdk": "^1.26.0", - "@textlint/ast-node-types": "15.5.2", - "@textlint/ast-traverse": "15.5.2", - "@textlint/config-loader": "15.5.2", - "@textlint/feature-flag": "15.5.2", - "@textlint/fixer-formatter": "15.5.2", - "@textlint/kernel": "15.5.2", - "@textlint/linter-formatter": "15.5.2", - "@textlint/module-interop": "15.5.2", - "@textlint/resolver": "15.5.2", - "@textlint/textlint-plugin-markdown": "15.5.2", - "@textlint/textlint-plugin-text": "15.5.2", - "@textlint/types": "15.5.2", - "@textlint/utils": "15.5.2", + "version": "15.5.4", + "resolved": "https://registry.npmjs.org/textlint/-/textlint-15.5.4.tgz", + "integrity": "sha512-W7pJKjeNyRfyzE9gLVfFE3stscas1dcNqDvge2WTkDxroa5FklKtrHYBa8sG8z2BWAJvbqOH/d29dRPzEww0FA==", + "dev": true, + "license": "MIT", + "dependencies": { + "@modelcontextprotocol/sdk": "^1.29.0", + "@textlint/ast-node-types": "15.5.4", + "@textlint/ast-traverse": "15.5.4", + "@textlint/config-loader": "15.5.4", + "@textlint/feature-flag": "15.5.4", + "@textlint/fixer-formatter": "15.5.4", + "@textlint/kernel": "15.5.4", + "@textlint/linter-formatter": "15.5.4", + "@textlint/module-interop": "15.5.4", + "@textlint/resolver": "15.5.4", + "@textlint/textlint-plugin-markdown": "15.5.4", + "@textlint/textlint-plugin-text": "15.5.4", + "@textlint/types": "15.5.4", + "@textlint/utils": "15.5.4", "debug": "^4.4.3", "file-entry-cache": "^10.1.4", "glob": "^11.1.0", "md5": "^2.3.0", "optionator": "^0.9.4", "path-to-glob-pattern": "^2.0.1", - "rc-config-loader": "^4.1.3", + "rc-config-loader": "^4.1.4", "read-package-up": "^11.0.0", "structured-source": "^4.0.0", "zod": "^3.25.76" @@ -8204,13 +8208,13 @@ } }, "node_modules/zod-to-json-schema": { - "version": "3.25.1", - "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.1.tgz", - "integrity": "sha512-pM/SU9d3YAggzi6MtR4h7ruuQlqKtad8e9S0fmxcMi+ueAK5Korys/aWcV9LIIHTVbj01NdzxcnXSN+O74ZIVA==", + "version": "3.25.2", + "resolved": "https://registry.npmjs.org/zod-to-json-schema/-/zod-to-json-schema-3.25.2.tgz", + "integrity": "sha512-O/PgfnpT1xKSDeQYSCfRI5Gy3hPf91mKVDuYLUHZJMiDFptvP41MSnWofm8dnCm0256ZNfZIM7DSzuSMAFnjHA==", "dev": true, "license": "ISC", "peerDependencies": { - "zod": "^3.25 || ^4" + "zod": "^3.25.28 || ^4" } }, "node_modules/zwitch": { diff --git a/package.json b/package.json index 45bf31a38c3e..15059c062455 100644 --- a/package.json +++ b/package.json @@ -15,7 +15,7 @@ "remark-lint-prohibited-strings": "^4.0.0", "remark-lint-unordered-list-marker-style": "^4.0.1", "remark-stringify": "^11.0.0", - "textlint": "^15.5.2", + "textlint": "^15.5.4", "textlint-filter-rule-allowlist": "^4.0.0", "textlint-filter-rule-comments": "^1.3.0", "textlint-rule-common-misspellings": "^1.0.1", diff --git a/plugins/example.rb b/plugins/example.rb new file mode 100644 index 000000000000..9cf79daeb779 --- /dev/null +++ b/plugins/example.rb @@ -0,0 +1,71 @@ +require 'cgi' +require 'safe_yaml' +require_relative 'terminology_helpers' + +module Jekyll + class ExampleBlock < Liquid::Raw + INPUT_TYPES = { + 'action' => { label: 'Action', language: 'yaml', icon: 'mdi:play-circle-outline' }, + 'automation' => { label: 'Automation', language: 'yaml', icon: 'mdi:robot-happy' }, + 'condition' => { label: 'Condition', language: 'yaml', icon: 'mdi:help-circle-outline' }, + 'script' => { label: 'Script', language: 'yaml', icon: 'mdi:script-text-outline' }, + 'template' => { label: 'Template', language: 'template', icon: 'mdi:code-braces' }, + 'trigger' => { label: 'Trigger', language: 'yaml', icon: 'mdi:flash-outline' } + } + + ARROW_SVG = '
' + + include Jekyll::TerminologyHelpers + + def render(context) + vars = SafeYAML.load(@body) + return '' unless vars.is_a?(Hash) + + input_type = INPUT_TYPES.keys.find { |t| vars.key?(t) } + input_content = input_type ? vars[input_type].to_s.strip : nil + output_content = vars['output']&.to_s&.strip + result_type = vars['type']&.to_s&.strip + title = vars['title']&.to_s&.strip + + has_input = input_type && input_content && !input_content.empty? + has_output = output_content && !output_content.empty? + return '' unless has_input || has_output + + html = ['
'] + html.concat(render_input(input_type, input_content, title, context)) if has_input + html << " #{ARROW_SVG}" if has_input && has_output + html.concat(render_output(output_content, result_type, context)) if has_output + html << '
' + html.join("\n") + end + + private + + def render_input(input_type, input_content, title, context) + type_info = INPUT_TYPES[input_type] + icon = " " + label = "#{icon}#{render_term(type_info[:label], context)}" + label = "#{label}: #{title}" if title + language = type_info[:language] + [ + '
', + " #{label}", + "
#{CGI.escapeHTML(input_content)}
", + '
' + ] + end + + def render_output(output_content, result_type, context) + label_icon = "" + label = result_type ? "#{label_icon} Result (#{render_term(result_type, context)})" : "#{label_icon} Result" + [ + '
', + " #{label}", + "
#{CGI.escapeHTML(output_content)}
", + '
' + ] + end + end +end + +Liquid::Template.register_tag('example', Jekyll::ExampleBlock) diff --git a/plugins/function_parameters.rb b/plugins/function_parameters.rb new file mode 100644 index 000000000000..5c3c6f4ea03c --- /dev/null +++ b/plugins/function_parameters.rb @@ -0,0 +1,28 @@ +require 'safe_yaml' + +module Jekyll + class FunctionParametersBlock < ConfigurationBlock + + def render(context) + contents = Liquid::Block.instance_method(:render).bind_call(self, context) + + site = context.registers[:site] + converter = site.find_converter_instance(::Jekyll::Converters::Markdown) + + vars = SafeYAML.load(contents) + + <<~MARKUP +
+ #{render_config_vars( + vars: vars, + component: '', + platform: '', + converter: converter + )} +
+ MARKUP + end + end +end + +Liquid::Template.register_tag('function_parameters', Jekyll::FunctionParametersBlock) diff --git a/plugins/jinja.rb b/plugins/jinja.rb new file mode 100644 index 000000000000..9423bb54b2fb --- /dev/null +++ b/plugins/jinja.rb @@ -0,0 +1,16 @@ +require 'cgi' + +module Jekyll + class JinjaInlineTag < Liquid::Raw + # Renders inline Jinja2 code with syntax highlighting. + # Usage: {% jinja %}{{ now() }}{% endjinja %} + # Content is treated as raw (no Liquid processing). + def render(_context) + content = @body.strip + escaped = CGI.escapeHTML(content) + %(#{escaped}) + end + end +end + +Liquid::Template.register_tag('jinja', Jekyll::JinjaInlineTag) diff --git a/plugins/output_modder.rb b/plugins/output_modder.rb index a2ca8164ce18..c71556dfebca 100644 --- a/plugins/output_modder.rb +++ b/plugins/output_modder.rb @@ -10,57 +10,60 @@ require 'nokogiri' module Jekyll - module OutputModder - def output_modder(content) - dom = Nokogiri::HTML.fragment(content) + module OutputModder + def output_modder(content) + dom = Nokogiri::HTML.fragment(content) - # Find all links, make all external links rel='external nofollow' - dom.css('a').each do |link| - rel = ['external', 'nofollow'] + # Find all links, make all external links rel='external nofollow' + dom.css('a').each do |link| + rel = %w[external nofollow] - # All external links start with 'http', skip when this one does not - next unless link.get_attribute('href') =~ /\Ahttp/i + # All external links start with 'http', skip when this one does not + next unless link.get_attribute('href') =~ /\Ahttp/i - # Skip our own links - next if link.get_attribute('href') =~ /\Ahttps?:\/\/\w*.?home-assistant.io/i + # Skip our own links + next if link.get_attribute('href') =~ %r{\Ahttps?://\w*.?home-assistant.io}i - # Play nice with our own links - next if link.get_attribute('href') =~ /\Ahttps?:\/\/(?:\w+\.)?(?:home-assistant\.io|esphome\.io|nabucasa\.com|openhomefoundation\.org)/i + # Play nice with our own links + if link.get_attribute('href') =~ %r{\Ahttps?://(?:\w+\.)?(?:home-assistant\.io|esphome\.io|nabucasa\.com|openhomefoundation\.org)}i + next + end + + # Play nice with links that already have a rel attribute set + rel.unshift(link.get_attribute('rel')) + + # Add rel attribute to link + link.set_attribute('rel', rel.join(' ').strip) + end - # Play nice with links that already have a rel attribute set - rel.unshift(link.get_attribute('rel')) + # Find all headers, make them linkable with unique slug names + used_slugs = {} - # Add rel attribute to link - link.set_attribute('rel', rel.join(' ').strip) - end + dom.css('h2,h3,h4,h5,h6,h7,h8').each do |header| + # Skip linked headers + next if header.at_css('a') - # Find all headers, make them linkable with unique slug names - used_slugs = {} - - dom.css('h2,h3,h4,h5,h6,h7,h8').each do |header| - # Skip linked headers - next if header.at_css('a') + title = header.inner_html + title_text = header.content - title = header.content - - # Clean the title to create a slug - base_slug = title.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '') - - # Make slug unique by adding counter if needed - if used_slugs[base_slug] - used_slugs[base_slug] += 1 - slug = "#{base_slug}-#{used_slugs[base_slug] - 1}" - else - used_slugs[base_slug] = 1 - slug = base_slug - end - - header.children = "#{title} " - end + # Clean the title to create a slug + base_slug = title_text.downcase.strip.gsub(' ', '-').gsub(/[^\w-]/, '') - dom.to_s + # Make slug unique by adding counter if needed + if used_slugs[base_slug] + used_slugs[base_slug] += 1 + slug = "#{base_slug}-#{used_slugs[base_slug] - 1}" + else + used_slugs[base_slug] = 1 + slug = base_slug end + + header.children = "#{title} " + end + + dom.to_s end + end end Liquid::Template.register_filter(Jekyll::OutputModder) diff --git a/plugins/raw_code_fences.rb b/plugins/raw_code_fences.rb new file mode 100644 index 000000000000..9c81fc50fe9a --- /dev/null +++ b/plugins/raw_code_fences.rb @@ -0,0 +1,78 @@ +# Automatically protect code fence contents from Liquid processing +# by replacing {{ }} and {% %} with placeholders before Liquid runs, +# then restoring them after rendering. + +module Jekyll + module RawCodeFences + OPEN_VAR = "___CODEVAR_OPEN___" + CLOSE_VAR = "___CODEVAR_CLOSE___" + OPEN_TAG = "___CODETAG_OPEN___" + CLOSE_TAG = "___CODETAG_CLOSE___" + OPEN_COMMENT = "___CODECOMMENT_OPEN___" + CLOSE_COMMENT = "___CODECOMMENT_CLOSE___" + end +end + +# Before Liquid: replace Liquid-like syntax inside code fences and inline +# backtick code with placeholders +Jekyll::Hooks.register [:documents, :pages], :pre_render do |doc| + # First: protect code fences (``` blocks) + doc.content = doc.content.gsub(/(^[ \t]*)(`{3,})([^\n]*)\n(.*?)\n\1\2[ \t]*$/m) do + indent = $1 + fence = $2 + info = $3 + inner = $4 + fence_open = "#{indent}#{fence}#{info}\n" + fence_close = "#{indent}#{fence}" + + # Skip fences that don't contain any Liquid-like syntax + next "#{fence_open}#{inner}\n#{fence_close}" unless inner.match?(/\{\{|\{%|\{#/) + + # Replace Liquid syntax with placeholders, but preserve {% raw %} and {% endraw %} tags + inner = inner.gsub(/\{%-?\s*raw\s*-?%\}/) { |m| "___PRESERVE_RAW_#{m.hash.abs}___" } + inner = inner.gsub(/\{%-?\s*endraw\s*-?%\}/) { |m| "___PRESERVE_ENDRAW_#{m.hash.abs}___" } + + inner = inner + .gsub("{{", Jekyll::RawCodeFences::OPEN_VAR) + .gsub("}}", Jekyll::RawCodeFences::CLOSE_VAR) + .gsub("{%", Jekyll::RawCodeFences::OPEN_TAG) + .gsub("%}", Jekyll::RawCodeFences::CLOSE_TAG) + .gsub("{#", Jekyll::RawCodeFences::OPEN_COMMENT) + .gsub("#}", Jekyll::RawCodeFences::CLOSE_COMMENT) + + # Restore preserved raw/endraw tags so Liquid can process them + inner = inner.gsub(/___PRESERVE_RAW_\d+___/) { "{% raw %}" } + inner = inner.gsub(/___PRESERVE_ENDRAW_\d+___/) { "{% endraw %}" } + + "#{fence_open}#{inner}\n#{fence_close}" + end + + # Second: protect inline backtick code (`code` and ``code``) + doc.content = doc.content.gsub(/(`{1,2})([^`\n]+?)\1/) do + ticks = $1 + inner = $2 + + next "#{ticks}#{inner}#{ticks}" unless inner.match?(/\{\{|\{%|\{#/) + + inner = inner + .gsub("{{", Jekyll::RawCodeFences::OPEN_VAR) + .gsub("}}", Jekyll::RawCodeFences::CLOSE_VAR) + .gsub("{%", Jekyll::RawCodeFences::OPEN_TAG) + .gsub("%}", Jekyll::RawCodeFences::CLOSE_TAG) + .gsub("{#", Jekyll::RawCodeFences::OPEN_COMMENT) + .gsub("#}", Jekyll::RawCodeFences::CLOSE_COMMENT) + + "#{ticks}#{inner}#{ticks}" + end +end + +# After rendering: restore the placeholders in the final HTML +Jekyll::Hooks.register [:documents, :pages], :post_render do |doc| + doc.output = doc.output + .gsub(Jekyll::RawCodeFences::OPEN_VAR, "{{") + .gsub(Jekyll::RawCodeFences::CLOSE_VAR, "}}") + .gsub(Jekyll::RawCodeFences::OPEN_TAG, "{%") + .gsub(Jekyll::RawCodeFences::CLOSE_TAG, "%}") + .gsub(Jekyll::RawCodeFences::OPEN_COMMENT, "{#") + .gsub(Jekyll::RawCodeFences::CLOSE_COMMENT, "#}") +end diff --git a/plugins/template_function_usage.rb b/plugins/template_function_usage.rb new file mode 100644 index 000000000000..5ac5fb8b3aa2 --- /dev/null +++ b/plugins/template_function_usage.rb @@ -0,0 +1,145 @@ +require 'securerandom' +require_relative 'terminology_helpers' + +module Jekyll + class TemplateFunctionUsageBlock < Liquid::Raw + USAGE_TYPES = { + 'function' => { label: 'As a function', icon: 'mdi:function' }, + 'filter' => { label: 'As a filter', icon: 'mdi:filter-outline' }, + 'test' => { label: 'As a test', icon: 'mdi:test-tube' } + } + + include Jekyll::TerminologyHelpers + + def render(context) + # Split on YAML document separator for multiple variants + documents = @body.split(/^---\s*$/) + parsed = documents.map { |doc| parse_document(doc.strip) }.compact + + return '' if parsed.empty? + + if parsed.length == 1 + render_single(parsed[0], context) + else + render_tabbed(parsed, context) + end + end + + private + + def parse_document(text) + return nil if text.empty? + + vars = SafeYAML.load(text) + return nil unless vars.is_a?(Hash) + + input_type = USAGE_TYPES.keys.find { |t| vars.key?(t) } + return nil unless input_type + + { + input_type: input_type, + input_content: vars[input_type].to_s.strip, + output_content: vars['output']&.to_s&.strip, + result_type: vars['type']&.to_s&.strip + } + end + + def render_output(output_content, result_type, has_input, context) + escaped_output = CGI.escapeHTML(output_content) + if result_type + type_term = render_term(result_type, context) + result_label = " Result (#{type_term})" + else + result_label = " Result" + end + + html = [] + if has_input + html << '
' + end + html << '
' + html << " #{result_label}" + html << "
#{escaped_output}
" + html << '
' + html + end + + def render_single(v, context) + type_info = USAGE_TYPES[v[:input_type]] + escaped_input = CGI.escapeHTML(v[:input_content]) + + html = ['
'] + html << '
' + html << " #{render_term(type_info[:label], context)}" + html << "
#{escaped_input}
" + html << '
' + + if v[:output_content] && !v[:output_content].empty? + html.concat(render_output(v[:output_content], v[:result_type], true, context)) + end + + html << '
' + html.join("\n") + end + + def render_tabbed(variants, context) + uuid = "tfu" + SecureRandom.hex(6) + + tab_labels = variants.map.with_index do |v, i| + type_info = USAGE_TYPES[v[:input_type]] + checked = i == 0 ? " checked" : "" + "" + end + + tab_contents = variants.map.with_index do |v, i| + display = i == 0 ? "block" : "none" + escaped_input = CGI.escapeHTML(v[:input_content]) + + template_label = " #{render_term('Template', context)}" + inner = [] + inner << "
#{template_label}" + inner << "
#{escaped_input}
" + + if v[:output_content] && !v[:output_content].empty? + inner.concat(render_output(v[:output_content], v[:result_type], true, context)) + end + + "
#{inner.join("\n")}
" + end + + # Tab switching script (once per page) + script = "" + unless context['tfu_script_included'] + context['tfu_script_included'] = true + script = <<~SCRIPT + + SCRIPT + end + + html = [] + html << script unless script.empty? + html << '
' + html << '
' + html << "
#{tab_labels.join}
" + html << " #{tab_contents.join("\n ")}" + html << '
' + html << '
' + html.join("\n") + end + end +end + +Liquid::Template.register_tag('template_function_usage', Jekyll::TemplateFunctionUsageBlock) diff --git a/plugins/template_functions_data.rb b/plugins/template_functions_data.rb new file mode 100644 index 000000000000..b74d5d351e24 --- /dev/null +++ b/plugins/template_functions_data.rb @@ -0,0 +1,74 @@ +require 'json' +require 'safe_yaml' + +# Generate a JSON lookup of all template functions and their parameters +# for use by prism-template-links.js (hover tooltips and clickable links) +module Jekyll + class TemplateFunctionsDataGenerator < Generator + safe true + priority :low + + # Aliases must be word-only names (no operators like ==, >=, <) + # and lowercase to match how they appear in template code. + WORD_ALIAS_PATTERN = /\A[a-z_]\w*\z/ + FUNCTION_PARAMETERS_PATTERN = /\{%\s*function_parameters\s*%\}(.*?)\{%\s*endfunction_parameters\s*%\}/m + MAX_PARAM_DESCRIPTION_LENGTH = 120 + + def generate(site) + funcs = {} + + site.collections['template_functions']&.docs&.each do |doc| + func_name = doc.data['function_name'] + next unless func_name + + entry = { + 'd' => doc.data['description'].to_s, + 'u' => doc.url + } + + params = extract_parameters(doc, func_name) + entry['p'] = params if params && !params.empty? + + funcs[func_name] = entry + + register_aliases(funcs, doc.data['aliases'], entry) + end + + # Escape tags when embedded in HTML + site.data['template_functions_json'] = JSON.generate(funcs).gsub(' MAX_PARAM_DESCRIPTION_LENGTH + params[name] = desc + end + params + rescue => e + Jekyll.logger.warn "TemplateFunctionsData:", "Failed to parse params for #{func_name}: #{e.message}" + nil + end + + def register_aliases(funcs, aliases, entry) + (aliases || []).each do |alias_name| + name = alias_name.to_s + # Only register word-shaped aliases, and never overwrite an existing function name + next unless name.match?(WORD_ALIAS_PATTERN) + next if funcs.key?(name) + funcs[name] = entry + end + end + end +end diff --git a/plugins/terminology_helpers.rb b/plugins/terminology_helpers.rb new file mode 100644 index 000000000000..13056985222e --- /dev/null +++ b/plugins/terminology_helpers.rb @@ -0,0 +1,30 @@ +module Jekyll + # Shared helpers for wrapping glossary terms with tooltips. + # Included by template function Liquid tags that render user-facing labels. + module TerminologyHelpers + def render_term(text, context) + glossary = context.registers[:site].data['glossary'] + return text unless glossary + + entry = glossary.find do |e| + e.key?('term') && ( + text.casecmp(e['term']).zero? || + (e.key?('aliases') && e['aliases'].any? { |a| a.casecmp(text).zero? }) + ) + end + + return text unless entry && entry.key?('definition') + + definition = entry['excerpt'] || entry['definition'] + + if entry.key?('link') + rendered_link = Liquid::Template.parse(entry['link']).render(context).strip + link = " [Learn more]" + definition = "#{definition.strip}#{link}".strip + end + + tooltip = "#{definition.strip}" + "#{text}#{tooltip}" + end + end +end diff --git a/sass/homeassistant/_homeassistant.scss b/sass/homeassistant/_homeassistant.scss index d1fdb2b60906..81ee733b59ad 100644 --- a/sass/homeassistant/_homeassistant.scss +++ b/sass/homeassistant/_homeassistant.scss @@ -35,6 +35,7 @@ @use 'plugins/tabbed_block' as *; @use 'plugins/terminology_tooltip' as *; @use 'plugins/integration_alert' as *; +@use 'plugins/template_functions' as *; @use 'base/sidebar' as *; @use 'aside/buy_dialog' as *; diff --git a/sass/homeassistant/_overrides.scss b/sass/homeassistant/_overrides.scss index 34e9fcb0c1f8..5f2f82823c24 100644 --- a/sass/homeassistant/_overrides.scss +++ b/sass/homeassistant/_overrides.scss @@ -427,7 +427,8 @@ ul#toc { display: inline-block; } - #integration-sidebar #toc-module { + #integration-sidebar #toc-module, + #integration-sidebar #learn-more-module { display: none; } @@ -437,6 +438,12 @@ ul#toc { } } +@media only screen and (max-width: $desk-wide-start) { + #toc-bar-learn-more { + display: none; + } +} + // Rounded background for numbered lists @@ -1101,6 +1108,18 @@ a.material-card:hover { display: flex; align-items: center; gap: 12px; + transform: translateY(0); + + @media (prefers-reduced-motion: no-preference) { + transition: transform 0.4s ease-out; + } + + @media (prefers-reduced-motion: reduce) { + transition: none; + } + &--visible { + transform: translateY(0); + } div { flex-grow: 1; @@ -1138,7 +1157,18 @@ a.material-card:hover { display: block; width: 44px; height: 32px; + background-size: contain; + background-repeat: no-repeat; align-self: flex-end; + + @media only screen and (max-width: $palm-end) { + width: 37px; + height: 27px; + } + } + + @media only screen and (max-width: $palm-end) { + padding: 10px 12px; } } @@ -1992,4 +2022,4 @@ h6 { position: fixed; width: 100%; z-index: 999; -} \ No newline at end of file +} diff --git a/sass/homeassistant/base/_syntax.scss b/sass/homeassistant/base/_syntax.scss index 5e5418893e15..b9de6ffa45bb 100644 --- a/sass/homeassistant/base/_syntax.scss +++ b/sass/homeassistant/base/_syntax.scss @@ -78,3 +78,64 @@ pre.highlight { .highlight .vg { color: #008080 } /* Name.Variable.Global */ .highlight .vi { color: #008080 } /* Name.Variable.Instance */ .highlight .il { color: #009999 } /* Literal.Number.Integer.Long */ + +/* Prism: Signature language tokens */ +.language-signature .token.parameter { + color: black; +} +.language-signature .token.builtin { + color: #c92c2c; +} +.language-signature .token.boolean { + color: #007fa8; +} +.language-signature .token.string { + color: #2f9c0a; +} +.language-signature .token.operator { + color: #a67f59; +} + +/* Prism: Template/Jinja2 function highlighting */ +.language-jinja2 .token.function, +.language-django .token.function, +.language-template .token.function, +.language-signature .token.function { + color: #6f42c1; + font-weight: 600; +} + +/* Linked template functions in code blocks */ +.tf-linked { + cursor: pointer; + border-bottom: 1px dotted #6f42c1; +} + +.tf-linked:hover { + background: rgba(111, 66, 193, 0.1); + border-radius: 2px; +} + +/* Linked parameters: subtler style */ +.tf-param { + border-bottom-style: dashed; + border-bottom-color: #999; +} + +.tf-param:hover { + background: rgba(0, 0, 0, 0.06); +} + +.tf-code-tooltip { + position: fixed; + z-index: 1000; + background: #333; + color: #fff; + font-size: 0.8em; + font-family: "Instrument Sans", "Helvetica Neue", Helvetica, Arial, sans-serif; + padding: 6px 10px; + border-radius: 6px; + max-width: 300px; + pointer-events: none; + line-height: 1.4; +} diff --git a/sass/homeassistant/plugins/_template_functions.scss b/sass/homeassistant/plugins/_template_functions.scss new file mode 100644 index 000000000000..e6473c4a78d1 --- /dev/null +++ b/sass/homeassistant/plugins/_template_functions.scss @@ -0,0 +1,188 @@ +@use '../variables' as *; + +// Template example block colors +$tf-input-label-bg: #358ccb; // Blue accent for template input block +$tf-output-tint: #f8fdf9; // Light green tint behind output +$tf-output-stripe: rgba(39, 174, 96, 0.04); +$tf-arrow-color: #b0b8c1; // Subtle arrow between input and output +$tf-border-light: $grayLighter; +$tf-tooltip-bg: $grayDark; +$tf-param-border: $grayLight; + +$tf-code-font: Consolas, Monaco, 'Andale Mono', 'Ubuntu Mono', monospace; + +/* Fix list indentation in function meta sidebar */ +#integration-sidebar .aside-module ul:not(.divided):not(.sidebar-menu) { + margin-left: 2em; + margin-bottom: 0; +} + +/* Preserve case for code elements inside uppercase headings */ +.title code { + text-transform: none; +} + +/* Template functions search box */ +.component-search { + margin-bottom: 24px; + min-height: 80px; + + input { + width: 100%; + padding: 10px; + background-color: #fefefe; + border-radius: 8px; + border: 1px solid; + border-color: #7c7c7c #c3c3c3 #ddd; + } +} + +/* Template functions index page */ +#tf-container h3 { + margin-top: 1.5em; + margin-bottom: 0.5em; + padding-bottom: 0.4em; + border-bottom: 2px solid $primary-color; + font-size: 1.1em; + scroll-margin-top: 100px; +} + +.tf-list { + list-style: none; + margin: 0; + padding: 0; +} + +.tf-item { + padding: 0.6em 0; + border-bottom: 1px solid $tf-border-light; + + &:last-child { + border-bottom: none; + } + + a { + text-decoration: none; + display: block; + + &:hover .tf-fn { + text-decoration: underline; + } + } +} + +.tf-title { + font-weight: 600; + color: $grayDark; +} + +.tf-fn { + font-size: 0.8em; + color: $grayLight; + margin-left: 0.3em; +} + +.tf-desc { + display: block; + color: $grayLight; + font-size: 0.85em; + margin-top: 0.15em; +} + +.tf-item.hidden { display: none; } +.tf-category.hidden { display: none; } + +/* Template example: connected input/output pair */ +.template-example { + display: flex; + flex-direction: column; + margin: 0.5em 0.5em 2em; +} + +.template-example .tabbed-content-block { + padding-bottom: 1em; + margin-bottom: -1em; +} + +.template-example-input, +.template-example-output { + position: relative; +} + +.template-example-label { + display: inline-block; + font-family: $heading-font; + font-size: 0.7em; + font-weight: 600; + text-transform: uppercase; + letter-spacing: 0.05em; + padding: 0.15em 0.6em; + border-radius: 3px; + margin-bottom: 0.2em; +} + +.template-example-input .template-example-label { + color: $white; + background: $tf-input-label-bg; +} + +.template-example-input pre, +.template-example-input pre[class*="language-"] { + margin: 0.2em 0 0 0 !important; +} + +.template-example-input pre[class*="language-"]:before, +.template-example-input pre[class*="language-"]:after { + display: none; +} + +.template-example-arrow { + text-align: center; + color: $tf-arrow-color; + line-height: 1; + margin-top: 0.5em; + padding: 0.15em 0; +} + +.template-example-arrow svg { + width: 32px; + height: 32px; +} + +// The negative top margin pulls the output block up against the arrow, +// keeping the input/arrow/output trio visually connected. +.template-example-output { + margin-top: -1.5em; +} + +.template-example-output .template-example-label { + color: $white; + background: $green; +} + +.template-example-output pre { + margin: 0 !important; + position: relative; +} + +.template-example-output pre:before, +.template-example-output pre:after { + display: none; +} + +.template-example-output pre > code { + border-left: 10px solid $green; + box-shadow: -1px 0px 0px 0px $green, 0px 0px 0px 1px $tf-border-light; + background-color: $tf-output-tint; + background-image: linear-gradient(transparent 50%, $tf-output-stripe 50%); + background-size: 3em 3em; + background-origin: content-box; + background-attachment: local; + padding: 0.5em 1em; + display: block; + font-family: $tf-code-font; + font-size: 0.85em; + white-space: pre-wrap; + word-wrap: normal; + line-height: 1.5; +} diff --git a/sass/homeassistant/plugins/_terminology_tooltip.scss b/sass/homeassistant/plugins/_terminology_tooltip.scss index df2db28d20cb..80a49635497a 100644 --- a/sass/homeassistant/plugins/_terminology_tooltip.scss +++ b/sass/homeassistant/plugins/_terminology_tooltip.scss @@ -10,8 +10,6 @@ } .terminology-tooltip { - --horizontal-move: 0px; - visibility: hidden; width: 250px; background-color: $primary-color; @@ -23,48 +21,30 @@ opacity: 0; transition: opacity 0.5s; - position: absolute; - z-index: 1; - - bottom: 100%; - left: calc(50% + var(--horizontal-move)); - margin-left: -125px; + position: fixed; + z-index: 1000; a { color: #fff; font-weight: 500; } - @mixin arrow { + &:after { content: " "; position: absolute; - left: calc(50% - var(--horizontal-move)); + left: 50%; margin-left: -5px; border-width: 5px; border-style: solid; - } - - &:after { - @include arrow; - top: 100%; border-color: $primary-color transparent transparent transparent; } &.below { - bottom: auto; - top: 1lh; - - &:before { - @include arrow; - + &:after { top: -10px; border-color: transparent transparent $primary-color transparent; } - - &:after { - display: none; - } } } code { diff --git a/source/_dashboards/entities.markdown b/source/_dashboards/entities.markdown index a7743dc36a43..7fc196efb78d 100644 --- a/source/_dashboards/entities.markdown +++ b/source/_dashboards/entities.markdown @@ -14,7 +14,7 @@ related: title: Card naming --- -The entities card is the most common type of card. It groups items together into lists. It can be used to display an entity's state or attribute, but also contain buttons, web links, etc. +The entities card is the most common type of card. It groups items together into lists. It can be used to display an entity's state or attribute, but also contain buttons, web links, and more. {% include dashboard/edit_dashboard.md %} @@ -125,7 +125,7 @@ confirmation: ## Special row elements -Rather than only displaying an entity's state as a text output, the entities card supports multiple special rows for buttons, attributes, web links, dividers and sections, etc. +Rather than only displaying an entity's state as a text output, the entities card supports multiple special rows for buttons, attributes, web links, dividers, sections, and more. ### Attribute @@ -349,7 +349,7 @@ type: type: string url: required: true - description: "Website URL (or internal URL e.g., `/hassio/dashboard` or `/panel_custom_name`)." + description: "Website URL, or internal URL such as `/hassio/dashboard` or `/panel_custom_name`." type: string name: required: false @@ -358,7 +358,7 @@ name: default: URL path icon: required: false - description: "Icon to display (e.g., `mdi:home`)." + description: "Icon to display, for example `mdi:home`." type: string default: "`mdi:link`" new_tab: diff --git a/source/_dashboards/iframe.markdown b/source/_dashboards/iframe.markdown index 1a258d3e1a25..faab29a0e10c 100644 --- a/source/_dashboards/iframe.markdown +++ b/source/_dashboards/iframe.markdown @@ -68,7 +68,7 @@ allow: default: "fullscreen" disable_sandbox: required: false - description: Disables the [sandbox](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox) attribute of the iframe, e.g. required for Chrome when viewing PDFs. This is less secure and should only be used if you trust the content of the iframe. + description: Disables the [sandbox](https://developer.mozilla.org/en-US/docs/Web/HTML/Element/iframe#attr-sandbox) attribute of the iframe, for example when viewing PDFs in Chrome. This is less secure and should only be used if you trust the content of the iframe. type: boolean default: false {% endconfiguration %} diff --git a/source/_dashboards/markdown.markdown b/source/_dashboards/markdown.markdown index aed7d5a9b488..d0091f1bb03b 100644 --- a/source/_dashboards/markdown.markdown +++ b/source/_dashboards/markdown.markdown @@ -32,7 +32,7 @@ type: type: string content: required: true - description: "Content to render as [Markdown](https://commonmark.org/help/). May contain [templates](/docs/configuration/templating/)." + description: "Content to render as [Markdown](https://commonmark.org/help/). May contain [templates](/docs/templating/)." type: string title: required: false @@ -43,7 +43,7 @@ card_size: required: false type: integer default: none - description: The algorithm for placing cards aesthetically may have problems with the Markdown card if it contains templates. You can use this value to help it estimate the height of the card in units of 50 pixels (approximately 3 lines of text in default size). (e.g., `4`) + description: The algorithm for placing cards aesthetically may have problems with the Markdown card if it contains templates. You can use this value to help it estimate the height of the card in units of 50 pixels (approximately 3 lines of text in default size), for example `4`. entity_id: required: false type: [string, list] @@ -93,7 +93,6 @@ A special template variable - `config` is set up for the `content` of the card. For example: -{% raw %} ```yaml type: entity-filter @@ -114,13 +113,11 @@ card: And the door is {% if is_state('binary_sensor.door', 'on') %} open {% else %} closed {% endif %}. ``` -{% endraw %} A special template variable - `user` is set up for the `content` of the card. It contains the currently logged in user. For example: -{% raw %} ```yaml type: markdown @@ -128,7 +125,6 @@ content: | Hello, {{user}} ``` -{% endraw %} ### Icons @@ -136,7 +132,6 @@ You can use [Material Design Icons](https://pictogrammers.com/library/mdi/) icon For example: -{% raw %} ```yaml type: markdown @@ -144,7 +139,6 @@ content: | ``` -{% endraw %} ## ha-alert diff --git a/source/_dashboards/picture-elements.markdown b/source/_dashboards/picture-elements.markdown index 1cd81d630f52..a2bcff436477 100644 --- a/source/_dashboards/picture-elements.markdown +++ b/source/_dashboards/picture-elements.markdown @@ -77,7 +77,7 @@ dark_mode_filter: ## Elements -Elements are the active components (icons, badges, buttons, text, etc.) that overlay the image. +Elements are the active components (icons, badges, buttons, text, and more) that overlay the image. There are several different element types that can be added to a Picture Elements card: @@ -266,7 +266,7 @@ type: type: string icon: required: true - description: "Icon to display (e.g., `mdi:home`)." + description: "Icon to display, for example `mdi:home`." type: string title: required: false @@ -402,7 +402,7 @@ for more information. {% configuration %} type: required: true - description: 'Card name with `custom:` prefix (e.g., `custom:my-custom-card`).' + description: 'Card name with `custom:` prefix, for example `custom:my-custom-card`.' type: string style: required: true diff --git a/source/_dashboards/picture-entity.markdown b/source/_dashboards/picture-entity.markdown index a5fa3a872a70..b0047cb89734 100644 --- a/source/_dashboards/picture-entity.markdown +++ b/source/_dashboards/picture-entity.markdown @@ -131,7 +131,6 @@ state_image: Displaying a live feed from an FFmpeg camera: -{% raw %} ```yaml type: picture-entity @@ -145,7 +144,6 @@ tap_action: filename: '/shared/backdoor-{{ now().strftime("%Y-%m-%d-%H%M%S") }}.jpg' ``` -{% endraw %} The filename needs to be a path that is writable by Home Assistant in your system. You may need to configure `allowlist_external_dirs` ([documentation](/integrations/homeassistant/#allowlist_external_dirs)). diff --git a/source/_dashboards/sections.markdown b/source/_dashboards/sections.markdown index 1d41c55755f3..514d81a1e72d 100644 --- a/source/_dashboards/sections.markdown +++ b/source/_dashboards/sections.markdown @@ -51,7 +51,7 @@ You can group cards without using horizontal or vertical stack cards. Editing the header

-1. To add a title, select the **Add title** button. The title supports [Markdown](https://commonmark.org/help/) and [templating](/docs/configuration/templating/). +1. To add a title, select the **Add title** button. The title supports [Markdown](https://commonmark.org/help/) and [templating](/docs/templating/). 2. To add badges, select the **Add badge** button. Follow [steps on adding badges](/dashboards/badges) to see the different possible options. 3. To change the title and badges disposition, select the edit {% icon "mdi:edit" %} button to access header settings. diff --git a/source/_data/glossary.yml b/source/_data/glossary.yml index 0c3bf6e903f0..362167f729cb 100644 --- a/source/_data/glossary.yml +++ b/source/_data/glossary.yml @@ -667,3 +667,49 @@ definition: >- Home Assistant saves long-term statistics for a sensor if the entity has a state_class of measurement, total, or total_increasing. For short-term statistics, a snapshot is taken every 5 minutes. For long-term statistics, an hourly aggregate is stored of the short-term statistics. Short-term statistics are automatically purged after a predefined period (default is 10 days). Long-term statistics are never purged. link: /blog/2021/08/04/release-20218/#long-term-statistics +- term: boolean + definition: >- + A value that is either true or false. Used for on/off states, yes/no conditions, and similar binary choices. + aliases: + - bool +- term: float + definition: >- + A number that can have decimal places, like 21.5 or 3.14. Used for temperatures, percentages, and other measurements that need precision. +- term: integer + definition: >- + A whole number without decimal places, like 1, 42, or -5. Used for counts, indices, and whole values. + aliases: + - int +- term: string + definition: >- + A piece of text, like a name, message, or entity ID. In templates, wrap strings in quotes, like "living_room" or "lights are on". +- term: list + definition: >- + An ordered collection of values, like a list of entity IDs or a list of numbers. Written with square brackets in templates, for example [1, 2, 3]. +- term: iterable + definition: >- + A sequence of values produced by filters like map, select, or selectattr. You can loop through it once. To reuse it or work with it as a list, apply `| list` at the end of the chain. +- term: datetime + definition: >- + A value representing a specific moment in time, including the date, time, and time zone. For example, 2026-04-05 14:30:00+00:00. Used for timestamps, scheduling, and time-based calculations. +- term: any + definition: >- + A flexible type that can be a string, number, list, or any other type. Used when a function or filter works with multiple types, and the actual result depends on what you pass in. +- term: "template function" + definition: >- + A template function is called by name with parentheses, like states("sensor.temperature") or now(). It takes input values as arguments and returns a result. + link: /template-functions/ + aliases: + - function +- term: "template filter" + definition: >- + A template filter transforms a value using the pipe (|) operator. It takes the value on the left and returns a modified result. For example, states("sensor.temperature") | float converts the state to a number. + link: /template-functions/ + aliases: + - filter +- term: "template test" + definition: >- + A template test checks a condition using the "is" keyword and returns true or false. For example, value is number checks if a value is a number. + link: /template-functions/ + aliases: + - test diff --git a/source/_data/template_function_categories.yml b/source/_data/template_function_categories.yml new file mode 100644 index 000000000000..c865b1ecefff --- /dev/null +++ b/source/_data/template_function_categories.yml @@ -0,0 +1,119 @@ +- key: area + label: Areas + icon: "mdi:map-marker-radius" + description: "These template functions let you work with the areas you've set up in Home Assistant. Look up which entities or devices belong to an area, get area names and IDs, and build automations that operate on entire rooms at once." + learn_more: + - title: Working with states + url: /docs/templating/states/ +- key: collection + label: Collections + icon: "mdi:format-list-group" + description: "These template functions help you work with collections of data: lists, sets, dictionaries, and JSON. Use them to combine, filter, sort, and reshape data from your entities, API responses, and incoming messages." + learn_more: + - title: Common template patterns + url: /docs/templating/patterns/ + - title: Types and conversion + url: /docs/templating/types/ + - title: Loops and conditions + url: /docs/templating/loops-and-conditions/ +- key: comparison + label: Comparison + icon: "mdi:compare-horizontal" + description: "These template tests let you compare values in your templates. Use them inside if conditions and filters to check equality, order, or membership." + learn_more: + - title: Loops and conditions + url: /docs/templating/loops-and-conditions/ + - title: Types and conversion + url: /docs/templating/types/ +- key: datetime + label: Date & Time + icon: "mdi:clock-outline" + description: "These template functions help you work with dates, times, and durations. Use them to get the current time, format timestamps for display, calculate how long ago something happened, or convert between different time representations." + learn_more: + - title: Working with dates and times + url: /docs/templating/dates-and-times/ +- key: device + label: Devices + icon: "mdi:devices" + description: "These template functions let you work with devices in Home Assistant. Look up device names, IDs, attributes, and find which entities belong to a device." + learn_more: + - title: Working with states + url: /docs/templating/states/ +- key: encoding + label: Encoding + icon: "mdi:lock-outline" + description: "These template functions encode, decode, and hash data. Use them to turn bytes into readable text, generate checksums, or prepare data for APIs that expect a specific encoding." + learn_more: + - title: Common template patterns + url: /docs/templating/patterns/ +- key: entity + label: Entities + icon: "mdi:shape-outline" + description: "These template functions let you work with entities in Home Assistant. Get entity names, check visibility, and look up which integration or config entry an entity belongs to." + learn_more: + - title: Working with states + url: /docs/templating/states/ +- key: floor + label: Floors + icon: "mdi:layers-outline" + description: "These template functions let you work with floors in Home Assistant. Look up floor names and IDs, find which areas belong to a floor, and get all entities across an entire floor." + learn_more: + - title: Working with states + url: /docs/templating/states/ +- key: functional + label: Functional + icon: "mdi:cog-outline" + description: "These template functions help with control flow, conditional logic, and general utility operations. They let you make decisions, transform values, and work with data in flexible ways." + learn_more: + - title: Loops and conditions + url: /docs/templating/loops-and-conditions/ +- key: label + label: Labels + icon: "mdi:tag-outline" + description: "These template functions let you work with labels in Home Assistant. Labels are tags you can assign to entities, devices, and areas to organize them. Use these functions to look up labels, find items with specific labels, and build automations that act on labeled groups of items." + learn_more: + - title: Working with states + url: /docs/templating/states/ +- key: math + label: Math + icon: "mdi:calculator-variant-outline" + description: "These template functions provide mathematical operations for your templates. From basic arithmetic helpers to trigonometry, statistics, and value constraining, they let you perform calculations on sensor data, averages across rooms, and more." + learn_more: + - title: Common template patterns + url: /docs/templating/patterns/ +- key: regex + label: Regex + icon: "mdi:regex" + description: "These template functions let you search, match, and transform text with regular expressions. Use them when you need to extract specific parts of a string, validate formats, or replace patterns in sensor values." + learn_more: + - title: Python methods in templates + url: /docs/templating/python-methods/ + - title: Common template patterns + url: /docs/templating/patterns/ +- key: repairs + label: Repairs + icon: "mdi:wrench-outline" + description: "These template functions let you read the repair issues Home Assistant has flagged. Use them to build dashboards or notifications that surface problems needing your attention." +- key: state + label: States + icon: "mdi:toggle-switch-outline" + description: "These template functions let you access and check the states of your entities. They are the foundation of most template logic in Home Assistant, letting you read sensor values, check if devices are on or off, and react to changes in your smart home." + learn_more: + - title: Working with states + url: /docs/templating/states/ +- key: strings + label: Strings + icon: "mdi:format-text" + description: "These template functions help you format, transform, and shape text. Use them to build friendly messages, clean up sensor values, or prepare text for dashboards and notifications." + learn_more: + - title: Python methods in templates + url: /docs/templating/python-methods/ +- key: type + label: Type Conversion + icon: "mdi:swap-horizontal" + description: "These template functions convert values between text, numbers, and other types, and let you check what type a value currently is. Use them whenever you need to turn sensor states into numbers for math, or verify the shape of data before acting on it." + learn_more: + - title: Types and conversion + url: /docs/templating/types/ + - title: Debugging templates + url: /docs/templating/debugging/ diff --git a/source/_docs/automation/action.markdown b/source/_docs/automation/action.markdown index 2bed597f082c..4b586385a5bf 100644 --- a/source/_docs/automation/action.markdown +++ b/source/_docs/automation/action.markdown @@ -10,7 +10,6 @@ For actions, you can specify the `entity_id` that it should apply to and optiona You can also perform the action to activate [a scene](/integrations/scene/) which will allow you to define how you want your devices to be and have Home Assistant perform the right action. -{% raw %} ```yaml automation: @@ -47,11 +46,9 @@ automation 2: message: "Oh wow you really missed something great." ``` -{% endraw %} Conditions can also be part of an action. You can combine multiple actions and conditions in a single action, and they will be processed in the order you put them in. If the result of a condition is false, the action will stop there so any action after that condition will not be executed. -{% raw %} ```yaml automation: @@ -83,4 +80,3 @@ automation: label_id: "{{ ['office_evening', 'office_after_15'] }}" ``` -{% endraw %} diff --git a/source/_docs/automation/condition.markdown b/source/_docs/automation/condition.markdown index e9d9d21823e3..eecc6f6829b1 100644 --- a/source/_docs/automation/condition.markdown +++ b/source/_docs/automation/condition.markdown @@ -12,7 +12,6 @@ The available conditions for an automation are the same as for the script syntax Example of using condition: -{% raw %} ```yaml automation: @@ -36,11 +35,9 @@ automation: entity_id: scene.office_lights ``` -{% endraw %} The `condition` option of an automation, also accepts a single condition template directly. For example: -{% raw %} ```yaml automation: @@ -56,4 +53,3 @@ automation: entity_id: scene.office_lights ``` -{% endraw %} diff --git a/source/_docs/automation/templating.markdown b/source/_docs/automation/templating.markdown index 3a6ab3d8d8a0..cf8bd3a2bdf5 100644 --- a/source/_docs/automation/templating.markdown +++ b/source/_docs/automation/templating.markdown @@ -3,14 +3,12 @@ title: "Automation Templates" description: "List all trigger variables available to templates." --- -Automations support the advanced features of [templating](/docs/configuration/templating/) in the same way as scripts do. In addition to the [Home Assistant template extensions](/docs/configuration/templating/#home-assistant-template-extensions) available to scripts, the `trigger` and `this` template variables are available for automations. +Automations support the advanced features of [templating](/docs/templating/) in the same way as scripts do. In addition to the [Home Assistant template extensions](/docs/templating/) available to scripts, the `trigger` and `this` template variables are available for automations. Example of variables used in templates: -{% raw %} - `{{ this.name }}` is the name of the automation executing from this trigger - `{{ trigger.platform }}` is the type of trigger object, like `calendar` -{% endraw %} ## Available state data @@ -240,7 +238,6 @@ These are the properties available for a [Zone trigger](/docs/automation/trigger ## Examples -{% raw %} ```yaml # Example configuration.yaml entries @@ -309,6 +306,5 @@ automation 4: - light.living_room ``` -{% endraw %} [state object]: /docs/configuration/state_object/ diff --git a/source/_docs/automation/trigger.markdown b/source/_docs/automation/trigger.markdown index 90a868747ea0..defdeec4078c 100644 --- a/source/_docs/automation/trigger.markdown +++ b/source/_docs/automation/trigger.markdown @@ -66,9 +66,8 @@ There are two different types of variables available for triggers. Both work lik The first variant allows you to define variables that will be set when the trigger fires. The variables will be able to use templates and have access to [the `trigger` variable](/docs/automation/templating#available-trigger-data). -The second variant is setting variables that are available when attaching a trigger when the trigger can contain templated values. These are defined using the `trigger_variables` key at an automation level. These variables can only contain [limited templates](/docs/configuration/templating/#limited-templates). The triggers will not re-apply if the value of the template changes. Trigger variables are a feature meant to support using blueprint inputs in triggers. +The second variant is setting variables that are available when attaching a trigger when the trigger can contain templated values. These are defined using the `trigger_variables` key at an automation level. These variables can only contain [limited templates](/docs/templating/where-to-use/#limited-templates). The triggers will not re-apply if the value of the template changes. Trigger variables are a feature meant to support using blueprint inputs in triggers. -{% raw %} ```yaml automation: @@ -83,7 +82,6 @@ automation: name: "{{ trigger.event.data.name }}" ``` -{% endraw %} ## Event trigger @@ -118,13 +116,12 @@ automation: - scene_reloaded ``` -It's also possible to use [limited templates](/docs/configuration/templating/#limited-templates) in the `event_type`, `event_data` and `context` options. +It's also possible to use [limited templates](/docs/templating/where-to-use/#limited-templates) in the `event_type`, `event_data` and `context` options. {% important %} The `event_type`, `event_data` and `context` templates are only evaluated when setting up the trigger, they will not be reevaluated for every event. {% endimportant %} -{% raw %} ```yaml automation: @@ -137,7 +134,6 @@ automation: event_type: "{{ 'MY_CUSTOM_EVENT_' ~ sub_event }}" ``` -{% endraw %} ## Home Assistant trigger @@ -172,7 +168,6 @@ automation: The `payload` option can be combined with a `value_template` to process the message received on the given MQTT topic before matching it with the payload. The trigger in the example below will trigger only when the message received on `living_room/switch/ac` is valid JSON, with a key `state` which has the value `"on"`. -{% raw %} ```yaml automation: @@ -183,15 +178,13 @@ automation: value_template: "{{ value_json.state }}" ``` -{% endraw %} -It's also possible to use [limited templates](/docs/configuration/templating/#limited-templates) in the `topic` and `payload` options. +It's also possible to use [limited templates](/docs/templating/where-to-use/#limited-templates) in the `topic` and `payload` options. {% note %} The `topic` and `payload` templates are only evaluated when setting up the trigger, they will not be re-evaluated for every incoming MQTT message. {% endnote %} -{% raw %} ```yaml automation: @@ -207,7 +200,6 @@ automation: encoding: "utf-8" ``` -{% endraw %} ## Numeric state trigger @@ -218,7 +210,6 @@ Crossing the threshold means that the trigger only fires if the state wasn't pre If the current state of your entity is `50` and you set the threshold to `below: 75`, the trigger would not fire if the state changed to `49` or `72` because the threshold was never crossed. The state would first have to change to `76` and then to `74` for the trigger to fire. {% endnote %} -{% raw %} ```yaml automation: @@ -239,7 +230,6 @@ automation: seconds: 5 ``` -{% endraw %} {% note %} Listing above and below together means the numeric_state has to be between the two values. @@ -248,7 +238,6 @@ In the example above, the trigger would fire a single time if a numeric_state go When the `attribute` option is specified the trigger is compared to the given `attribute` instead of the state of the entity. -{% raw %} ```yaml automation: @@ -259,13 +248,11 @@ automation: above: 23 ``` -{% endraw %} More dynamic and complex calculations can be done with `value_template`. The variable 'state' is the [state object](/docs/configuration/state_object) of the entity specified by `entity_id`. The state of the entity can be referenced like this: -{% raw %} ```yaml automation: @@ -276,11 +263,9 @@ automation: above: 70 ``` -{% endraw %} Attributes of the entity can be referenced like this: -{% raw %} ```yaml automation: @@ -291,7 +276,6 @@ automation: above: 3 ``` -{% endraw %} Number helpers (`input_number` entities), `number`, `sensor`, and `zone` entities that contain a numeric value, can be used in the `above` and `below` thresholds. @@ -308,7 +292,6 @@ automation: The `for:` can also be specified as `HH:MM:SS` like this: -{% raw %} ```yaml automation: @@ -323,11 +306,9 @@ automation: for: "01:10:05" ``` -{% endraw %} You can also use templates in the `for` option. -{% raw %} ```yaml automation: @@ -347,7 +328,6 @@ automation: {{ trigger.to_state.name }} too high for {{ trigger.for }}! ``` -{% endraw %} The `for` template(s) will be evaluated when an entity changes as specified. @@ -513,7 +493,6 @@ automation: You can also use templates in the `for` option. -{% raw %} ```yaml automation: @@ -532,7 +511,6 @@ automation: entity_id: lock.my_place ``` -{% endraw %} The `for` template(s) will be evaluated when an entity changes as specified. @@ -568,7 +546,6 @@ automation: Sometimes you may want more granular control over an automation than simply sunset or sunrise and specify an exact elevation of the sun. This can be used to layer automations to occur as the sun lowers on the horizon or even after it is below the horizon. This is also useful when the "sunset" event is not dark enough outside and you would like the automation to run later at a precise solar angle instead of the time offset such as turning on exterior lighting. For most automations intended to run during dusk or dawn, a number between 0° and -6° is suitable; -4° is used in this example: -{% raw %} ```yaml automation: @@ -585,7 +562,6 @@ automation: entity_id: switch.exterior_lighting ``` -{% endraw %} If you want to get more precise, you can use this [solar calculator](https://gml.noaa.gov/grad/solcalc/), which will help you estimate what the solar elevation will be at any specific time. Then from this, you can select from the defined twilight numbers. @@ -639,13 +615,12 @@ automation: ## Template trigger -Template triggers work by evaluating a [template](/docs/configuration/templating/) when any of the recognized entities change state. The trigger will fire if the state change caused the template to render 'true' (a non-zero number or any of the strings `true`, `yes`, `on`, `enable`) when it was previously 'false' (anything else). +Template triggers work by evaluating a [template](/docs/templating/) when any of the recognized entities change state. The trigger will fire if the state change caused the template to render 'true' (a non-zero number or any of the strings `true`, `yes`, `on`, `enable`) when it was previously 'false' (anything else). -This is achieved by having the template result in a true boolean expression (for example `{% raw %}{{ is_state('device_tracker.paulus', 'home') }}{% endraw %}`) or by having the template render `true` (example below). +This is achieved by having the template result in a true boolean expression (for example `{{ is_state('device_tracker.paulus', 'home') }}`) or by having the template render `true` (example below). -With template triggers you can also evaluate attribute changes by using is_state_attr (like `{% raw %}{{ is_state_attr('climate.living_room', 'away_mode', 'off') }}{% endraw %}`) +With template triggers you can also evaluate attribute changes by using is_state_attr (like `{{ is_state_attr('climate.living_room', 'away_mode', 'off') }}`) -{% raw %} ```yaml automation: @@ -657,11 +632,9 @@ automation: for: "00:01:00" ``` -{% endraw %} You can also use templates in the `for` option. -{% raw %} ```yaml automation: @@ -672,7 +645,6 @@ automation: minutes: "{{ states('input_number.minutes')|int(0) }}" ``` -{% endraw %} The `for` template(s) will be evaluated when the `value_template` becomes 'true'. @@ -710,7 +682,6 @@ The entity ID of an [input datetime](/integrations/input_datetime/). | `true` | `false` | Will fire at midnight on specified date. | | `false` | `true` | Will fire once a day at specified time. | -{% raw %} ```yaml automation: @@ -738,7 +709,6 @@ automation: entity_id: climate.office ``` -{% endraw %} ### Sensors of datetime device class @@ -795,9 +765,8 @@ automation: ### Limited templates -It's also possible to use [limited templates](/docs/configuration/templating/#limited-templates) for times. +It's also possible to use [limited templates](/docs/templating/where-to-use/#limited-templates) for times. -{% raw %} ```yaml blueprint: @@ -823,7 +792,6 @@ blueprint: - "{{ my_hour }}:30:00" ``` -{% endraw %} ### Weekday filtering @@ -967,7 +935,7 @@ See the [Persistent Notification](/integrations/persistent_notification/) integr ## Webhook trigger -Webhook trigger fires when a web request is made to the webhook endpoint: `/api/webhook/`. The webhook endpoint is created automatically when you set it as the `webhook_id` in an automation trigger. The `webhook_id` can either be a static value or computed using [limited templates](/docs/configuration/templating/#limited-templates). +Webhook trigger fires when a web request is made to the webhook endpoint: `/api/webhook/`. The webhook endpoint is created automatically when you set it as the `webhook_id` in an automation trigger. The `webhook_id` can either be a static value or computed using [limited templates](/docs/templating/where-to-use/#limited-templates). {% note %} The `webhook_id` template is only evaluated when setting up the trigger, they will not be re-evaluated for incoming webhook triggers. @@ -1123,12 +1091,10 @@ This allows you to match sentences with variable parts, such as album/artist nam For example, the sentence `play {album} by {artist}` will match "play the white album by the beatles" and have the following variables available in the action templates: -{% raw %} - `{{ trigger.slots.album }}` - "the white album" - `{{ trigger.slots.artist }}` - "the beatles" -{% endraw %} Wildcards will match as much text as possible, which may lead to surprises: "play day by day by taken by trees" will match `album` as "day" and `artist` as "day by taken by trees". Including extra words in your template can help: `play {album} by artist {artist}` can now correctly match "play day by day by artist taken by trees". @@ -1184,7 +1150,6 @@ automation: Triggers can also be disabled based on limited templates or blueprint inputs. These are only evaluated once when the automation is loaded. -{% raw %} ```yaml blueprint: @@ -1212,7 +1177,6 @@ blueprint: enabled: "{{ _enable_number < 50 }}" ``` -{% endraw %} ## Merging lists of triggers diff --git a/source/_docs/automation/troubleshooting.markdown b/source/_docs/automation/troubleshooting.markdown index a0bfc7bcd633..22c530936486 100644 --- a/source/_docs/automation/troubleshooting.markdown +++ b/source/_docs/automation/troubleshooting.markdown @@ -55,20 +55,18 @@ Automations created in YAML must have an [`id`](/docs/automation/yaml/#migrating The last 5 traces are recorded for all automations. It is possible to change this by adding the following code to your automation. -{% raw %} ```yaml trace: stored_traces: 20 ``` -{% endraw %} ## Testing templates -If your automation uses [templates](/docs/configuration/templating/) in any part, you can do the following to make sure it works as expected: +If your automation uses [templates](/docs/templating/) in any part, you can do the following to make sure it works as expected: 1. Go to {% my developer_template title="**Settings** > **Developer tools** > **Template**" %} tab. -2. Create all variables (sources) required for your template as described at the end of [this](https://www.home-assistant.io/docs/configuration/templating/#processing-incoming-data) paragraph. +2. Create all variables (sources) required for your template as described at the end of [this](https://www.home-assistant.io/docs/templating/where-to-use/#processing-incoming-data) paragraph. 3. Copy your template code and paste it in Template editor straight after your variables. 4. If necessary, change your sources' value and check if the template works as you want and does not generate any errors. diff --git a/source/_docs/automation/yaml.markdown b/source/_docs/automation/yaml.markdown index e45eb29bf0db..c6e9cbb7bd29 100644 --- a/source/_docs/automation/yaml.markdown +++ b/source/_docs/automation/yaml.markdown @@ -67,7 +67,7 @@ trigger_variables: type: map keys: PARAMETER_NAME: - description: "The value of the variable. Any YAML is valid. Only [limited templates](/docs/configuration/templating/#limited-templates) can be used." + description: "The value of the variable. Any YAML is valid. Only [limited templates](/docs/templating/where-to-use/#limited-templates) can be used." type: any mode: description: "Controls what happens when the automation is invoked while it is still running from one or more previous invocations. See [Automation modes](#automation-modes)." @@ -129,7 +129,6 @@ actions: Example of a {% term YAML %} based automation that you can add to {% term "`configuration.yaml`" %}. -{% raw %} ```yaml # Example of entry in configuration.yaml @@ -202,7 +201,6 @@ automation my_lights: message: "Cube has triggered this event: {{ trigger.event }}" ``` -{% endraw %} ## Extra options @@ -237,7 +235,6 @@ automation: If you want to migrate your manual automations to use the editor, you'll have to copy them to `automations.yaml`. Make sure that `automations.yaml` remains a list! For each automation that you copy over, you'll have to add an `id`. This can be any string as long as it's unique. -{% raw %} ```yaml # Example automations.yaml entry. Note, automations.yaml is always a list! @@ -258,7 +255,6 @@ If you want to migrate your manual automations to use the editor, you'll have to - action: light.turn_on ``` -{% endraw %} ### Deleting automations diff --git a/source/_docs/blueprint/schema.markdown b/source/_docs/blueprint/schema.markdown index c52b5d5a91c4..6388b7ba9e1e 100644 --- a/source/_docs/blueprint/schema.markdown +++ b/source/_docs/blueprint/schema.markdown @@ -193,7 +193,6 @@ input: {% endconfiguration %} - The following example shows a *blueprint schema* with some inputs in a section: ```yaml diff --git a/source/_docs/blueprint/selectors.markdown b/source/_docs/blueprint/selectors.markdown index 752e3a9d61cb..f41c4c19c0c7 100644 --- a/source/_docs/blueprint/selectors.markdown +++ b/source/_docs/blueprint/selectors.markdown @@ -360,13 +360,11 @@ choose: Following this example, if the user entered a value in both selectors, but submitted with 'Icon' option selected, the output might be: -{% raw %} ```yaml active_choice: Icon Icon: mdi:light Template: "{{ something else }}" ``` -{% endraw %} ## Color temperature selector diff --git a/source/_docs/blueprint/tutorial.markdown b/source/_docs/blueprint/tutorial.markdown index 8437eaaea5d8..84406e1db2e9 100644 --- a/source/_docs/blueprint/tutorial.markdown +++ b/source/_docs/blueprint/tutorial.markdown @@ -41,7 +41,6 @@ For this tutorial, we use a simple automation. The process for converting a comp The automation we're going to use in this tutorial controls a light based on a motion sensor: -{% raw %} ```yaml triggers: @@ -59,7 +58,6 @@ actions: entity_id: light.kitchen ``` -{% endraw %} The options that can be used with the `trigger` object are listed under [automation trigger variables](/docs/automation/templating/#available-trigger-data). In this example, a [state trigger](/docs/automation/templating/#state) is used. @@ -100,7 +98,6 @@ For the light, we can offer some more flexibility. We want to allow the user to Inputs are not limited to strings. They can contain complex objects too. So in this case, we're going to mark the whole `target` as input: -{% raw %} ```yaml actions: @@ -113,7 +110,6 @@ actions: target: !input target_light ``` -{% endraw %} #### Add the inputs to the metadata @@ -209,7 +205,6 @@ By limiting our blueprint to working with lights and motion sensors, we unlock a After we have added all the steps, our blueprint will look like this: -{% raw %} ```yaml blueprint: @@ -247,7 +242,6 @@ actions: target: !input target_light ``` -{% endraw %} ## Using the blueprint via the UI diff --git a/source/_docs/configuration/splitting_configuration.markdown b/source/_docs/configuration/splitting_configuration.markdown index f25d78e0933a..35b98da35c64 100644 --- a/source/_docs/configuration/splitting_configuration.markdown +++ b/source/_docs/configuration/splitting_configuration.markdown @@ -185,7 +185,6 @@ This small example illustrates how the "split" files work. In this case, we star This (large) sensor configuration gives us another example: -{% raw %} ```yaml ### sensor.yaml @@ -225,7 +224,6 @@ This (large) sensor configuration gives us another example: name: "Ann Arbor" ``` -{% endraw %} You'll notice that this example includes a secondary parameter section (under the steam section) as well as a better example of the way comments can be used to break down files into sections. @@ -340,7 +338,6 @@ It is important to note that each file must contain only **one** entry when usin `configuration.yaml` ```yaml -{% raw %} alexa: intents: LocateIntent: @@ -366,7 +363,7 @@ alexa: iPhone is home. {%- else -%} iPhone is not home. - {% endif %}{% endraw %} + {% endif %} ``` can be turned into: @@ -381,7 +378,6 @@ alexa: `alexa/LocateIntent.yaml` ```yaml -{% raw %} actions: action: notify.pushover data: @@ -395,13 +391,12 @@ speech: {%- endif -%} {%- else -%} I am sorry. Pootie! I do not know where {{User}} is. - {%- endfor -%}{% endraw %} + {%- endfor -%} ``` `alexa/WhereAreWeIntent.yaml` ```yaml -{% raw %} speech: type: plaintext text: > @@ -409,7 +404,7 @@ speech: iPhone is home. {%- else -%} iPhone is not home. - {% endif %}{% endraw %} + {% endif %} ``` ### Example: `!include_dir_merge_list` diff --git a/source/_docs/configuration/state_object.markdown b/source/_docs/configuration/state_object.markdown index 31e79d333110..44a54329459d 100644 --- a/source/_docs/configuration/state_object.markdown +++ b/source/_docs/configuration/state_object.markdown @@ -73,7 +73,7 @@ The table lists common state attributes that may be present, depending on the en | `device_class` | The type of device that an entity represents. Used to display device specific information in the UI. | | `supported_features` | The features an entity supports. For covers, for example, it might list `opening`, `closing`, `stopping`, `setting position`. For media players, it might list `play`, `pause`, `stop`, and `volume control` | -When an attribute contains spaces, you can retrieve it like this: `state_attr('sensor.livingroom', 'Battery numeric')`. +When an attribute contains spaces, you can retrieve it with the [`state_attr`](/template-functions/state_attr/) function: `state_attr('sensor.livingroom', 'Battery numeric')`. ## Context @@ -88,39 +88,33 @@ Context is a property used in state objects and events. It ties {% term events % ## Examples - Evaluate the `state.last_changed` of a switch entity: - {% raw %} ```jinja {{ states.switch.my_switch.last_changed }} ``` - {% endraw %} result type: `string` representing a datetime object, for example `2025-11-11 12:56:10.244125+00:00` *** - Evaluate the `state.context.id` of this switch: - {% raw %} ```jinja {{ states.switch.my_switch.context.id }} - ``` + ``` - {% endraw %} result type: `string` representing an id code, for example `01K9SF2R36KRV5N4PTC38S6KJ2F` *** - Evaluate the `state.context.user_id` of this switch: - {% raw %} ```jinja {{ states.switch.my_switch.context.user_id }} ``` - {% endraw %} result type: `string` representing a user id code, for example `01K9SF2R36KRV5N4PTC38SKS4LW6` diff --git a/source/_docs/configuration/templating.markdown b/source/_docs/configuration/templating.markdown deleted file mode 100644 index 9c8772a1dc45..000000000000 --- a/source/_docs/configuration/templating.markdown +++ /dev/null @@ -1,1855 +0,0 @@ ---- -title: "Templating" -description: "Instructions on how to use the templating feature of Home Assistant." ---- - -This is an advanced feature of Home Assistant. You'll need a basic understanding of: - -- [Home Assistant architecture](/developers/architecture/), especially states. -- The [State object](/topics/state_object/). - -Templating is a powerful feature that allows you to control information going into and out of the system. It is used for: - -- Formatting outgoing messages in, for example, the [notify](/integrations/notify/) platforms and [Alexa](/integrations/alexa/) integration. -- Process incoming data from sources that provide raw data, like [MQTT](/docs/configuration/templating/#using-templates-with-the-mqtt-integration), [`rest` sensor](/integrations/rest/) or the [`command_line` sensor](/integrations/sensor.command_line/). -- [Automation Templating](/docs/automation/templating/). - -## Building templates - -Templating in Home Assistant is powered by the [Jinja2](https://palletsprojects.com/p/jinja) templating engine. This means that we are using their syntax and make some custom Home Assistant variables available to templates during rendering. Jinja2 supports a wide variety of operations: - -- [Mathematical operation](https://jinja.palletsprojects.com/en/latest/templates/#math) -- [Comparisons](https://jinja.palletsprojects.com/en/latest/templates/#comparisons) -- [Logic](https://jinja.palletsprojects.com/en/latest/templates/#logic) - -We will not go over the basics of the syntax, as Jinja2 does a great job of this in their [templates documentation](https://jinja.palletsprojects.com/en/latest/templates/). - -The frontend has a template editor tool to help develop and debug templates. Go to {% my developer_template title="**Settings** > **Developer tools** > **Template**" %}, create your template in the **Template editor** and check the results on the right. - -Templates can get big pretty fast. To keep a clear overview, consider using YAML multiline strings to define your templates: - -{% raw %} - -```yaml -script: - msg_who_is_home: - sequence: - - action: notify.notify - data: - message: > - {% if is_state('device_tracker.paulus', 'home') %} - Ha, Paulus is home! - {% else %} - Paulus is at {{ states('device_tracker.paulus') }}. - {% endif %} -``` - -{% endraw %} - -### Important template rules - -There are a few very important rules to remember when adding templates to YAML: - -1. You **must** surround single-line templates with double quotes (`"`) or single quotes (`'`). -2. It is advised that you prepare for undefined variables by using `if ... is not none` or the [`default` filter](https://jinja.palletsprojects.com/en/latest/templates/#jinja-filters.default), or both. -3. It is advised that when comparing numbers, you convert the number(s) to a [`float`](https://jinja.palletsprojects.com/en/latest/templates/#jinja-filters.float) or an [`int`](https://jinja.palletsprojects.com/en/latest/templates/#jinja-filters.int) by using the respective [filter](https://jinja.palletsprojects.com/en/latest/templates/#list-of-builtin-filters). -4. While the [`float`](https://jinja.palletsprojects.com/en/latest/templates/#jinja-filters.float) and [`int`](https://jinja.palletsprojects.com/en/latest/templates/#jinja-filters.int) filters do allow a default fallback value if the conversion is unsuccessful, they do not provide the ability to catch undefined variables. - -Remembering these simple rules will help save you from many headaches and endless hours of frustration when using automation templates. - -### Enabled Jinja extensions - -Jinja supports a set of language extensions that add new functionality to the language. -To improve the experience of writing Jinja templates, we have enabled the following -extensions: - -- [Loop Controls](https://jinja.palletsprojects.com/en/stable/extensions/#loop-controls) (`break` and `continue`) -- [Expression Statement](https://jinja.palletsprojects.com/en/stable/extensions/#expression-statement) (`do`) - -### Reusing templates - -You can write reusable Jinja templates by adding them to a `custom_templates` folder under your -configuration directory. All template files must have the `.jinja` extension and be less than 5MiB. -Templates in this folder will be loaded at startup. To reload the templates without -restarting Home Assistant, invoke the {% my developer_call_service service="homeassistant.reload_custom_templates" %} action. - -Once the templates are loaded, Jinja [includes](https://jinja.palletsprojects.com/en/3.0.x/templates/#include) and [imports](https://jinja.palletsprojects.com/en/3.0.x/templates/#import) will work -using `config/custom_templates` as the base directory. - -For example, you might define a macro in a template in `config/custom_templates/formatter.jinja`: - -{% raw %} - -```jinja -{% macro format_entity(entity_id) %} -{{ state_attr(entity_id, 'friendly_name') }} - {{ states(entity_id) }} -{% endmacro %} -``` - -{% endraw %} - -In your automations, you could then reuse this macro by importing it: - -{% raw %} - -```jinja -{% from 'formatter.jinja' import format_entity %} -{{ format_entity('sensor.temperature') }} -``` - -{% endraw %} - -Home Assistant also allows you to write macros with non-string return values by -taking a named argument called `returns` and calling it with a return value. Once created, -pass the macro into the `as_function` filter to use the returned value: - -{% raw %} - -```jinja -{%- macro macro_is_switch(entity_name, returns) -%} - {%- do returns(entity_name.startswith('switch.')) -%} -{%- endmacro -%} -{%- set is_switch = macro_is_switch | as_function -%} -{{ "It's a switch!" if is_switch("switch.my_switch") else "Not a switch!" }} -``` - -{% endraw %} - -In this way, you can export utility functions that return scalar or complex values rather than -just macros that render to strings. - -## Home Assistant template extensions - -Extensions allow templates to access all the Home Assistant specific states and adds other convenience functions and filters. - -### Limited templates - -Templates for some [triggers](/docs/automation/trigger/) as well as `trigger_variables` only support a subset of the Home Assistant template extensions. This subset is referred to as "Limited Templates". - -### This - -State-based and trigger-based template entities have the special template variable `this` available in their templates and actions. See more details and examples in the [Template integration documentation](/integrations/template). - -### States - -Not supported in [limited templates](#limited-templates). - -- Iterating `states` will yield each state object. -- Iterating `states.domain` will yield each state object of that domain. -- `states.sensor.temperature` returns the state object for `sensor.temperature` (avoid when possible, see note below). -- `states` can also be used as a function, `states(entity_id, rounded=False, with_unit=False)`, which returns the state string (not the state object) of the given entity, `unknown` if it doesn't exist, and `unavailable` if the object exists but is not available. - - The optional arguments `rounded` and `with_unit` control the formatting of sensor state strings, please see the [examples](#formatting-sensor-states) below. -- `states.sensor.temperature.state_with_unit` formats the state string in the same way as if calling `states('sensor.temperature', rounded=True, with_unit=True)`. -- `is_state` compares an entity's state with a specified state or list of states and returns `True` or `False`. `is_state('device_tracker.paulus', 'home')` will test if the given entity is the specified state. `is_state('device_tracker.paulus', ['home', 'work'])` will test if the given entity is any of the states in the list. -- `state_attr('device_tracker.paulus', 'battery')` will return the value of the attribute or None if it doesn't exist. -- `is_state_attr('device_tracker.paulus', 'battery', 40)` will test if the given entity attribute is the specified state (in this case, a numeric value). Note that the attribute can be `None` and you want to check if it is `None`, you need to use `state_attr('sensor.my_sensor', 'attr') is none` or `state_attr('sensor.my_sensor', 'attr') == None` (note the difference in the capitalization of none in both versions). -- `has_value('sensor.my_sensor')` will test if the given entity is not unknown or unavailable. Can be used as a filter or a test. - -{% warning %} -Avoid using `states.sensor.temperature.state`, instead use `states('sensor.temperature')`. It is strongly advised to use the `states()`, `is_state()`, `state_attr()` and `is_state_attr()` as much as possible, to avoid errors and error message when the entity isn't ready yet (such as during Home Assistant startup). -{% endwarning %} - -#### States examples - -The next two statements result in the same value if the state exists. The second one will result in an error if the state does not exist. - -{% raw %} - -```text -{{ states('device_tracker.paulus') }} -{{ states.device_tracker.paulus.state }} -``` - -{% endraw %} - -Print out a list of all the sensor states: - -{% raw %} - -```text -{% for state in states.sensor %} - {{ state.entity_id }}={{ state.state }}, -{% endfor %} -``` - -{% endraw %} - -Print out a list of all the sensor states sorted by `entity_id`: - -{% raw %} - -```text -{% for state in states.sensor | sort(attribute='entity_id') %} - {{ state.entity_id }}={{ state.state }}, -{% endfor %} -``` - -{% endraw %} - -Entities that are on: - -{% raw %} - -```text -{{ ['light.kitchen', 'light.dining_room'] | select('is_state', 'on') | list }} -``` - -{% endraw %} - -Other state examples: -{% raw %} - -```text -{% if is_state('device_tracker.paulus', 'home') %} - Ha, Paulus is home! -{% else %} - Paulus is at {{ states('device_tracker.paulus') }}. -{% endif %} - -#check sensor.train_departure_time state -{% if states('sensor.train_departure_time') in ("unavailable", "unknown") %} - {{ ... }} - -{% if has_value('sensor.train_departure_time') %} - {{ ... }} - - -{% set state = states('sensor.temperature') %}{{ state | float + 1 if is_number(state) else "invalid temperature" }} - -{% set state = states('sensor.temperature') %}{{ (state | float * 10) | round(2) if is_number(state)}} - -{% set state = states('sensor.temperature') %} -{% if is_number(state) and state | float > 20 %} - It is warm! -{% endif %} - -{{ as_timestamp(states.binary_sensor.garage_door.last_changed) }} - -{{ as_local(states.binary_sensor.garage_door.last_changed) }} - -{{ as_timestamp(now()) - as_timestamp(states.binary_sensor.garage_door.last_changed) }} - -{{ as_local(states.sensor.time.last_changed) }} - -{{ states('sensor.expires') | as_datetime }} - -# Make a list of states -{{ ['light.kitchen', 'light.dining_room'] | map('states') | list }} -``` - -{% endraw %} - -#### Formatting sensor states - -The examples below show the output of a temperature sensor with state `20.001`, unit `°C` and user configured presentation rounding set to 1 decimal. - -The following example results in the number `20.001`: - -{% raw %} -```text -{{ states('sensor.temperature') }} -``` -{% endraw %} - -The following example results in the string `"20.0 °C"`: - -{% raw %} -```text -{{ states('sensor.temperature', with_unit=True) }} -``` -{% endraw %} - -The following example result in the string `"20.001 °C"`: - -{% raw %} -```text -{{ states('sensor.temperature', with_unit=True, rounded=False) }} -``` -{% endraw %} - -The following example results in the number `20.0`: - -{% raw %} -```text -{{ states('sensor.temperature', rounded=True) }} -``` -{% endraw %} - -The following example results in the number `20.001`: - -{% raw %} -```text -{{ states.sensor.temperature.state }} -``` -{% endraw %} - -The following example results in the string `"20.0 °C"`: - -{% raw %} -```text -{{ states.sensor.temperature.state_with_unit }} -``` -{% endraw %} - -### Attributes - -Not supported in [limited templates](#limited-templates). - -You can print an attribute with `state_attr` if state is defined. - -#### Attributes examples - -{% raw %} - -```text -{% if states.device_tracker.paulus %} - {{ state_attr('device_tracker.paulus', 'battery') }} -{% else %} - ?? -{% endif %} -``` - -{% endraw %} - -With strings: - -{% raw %} - -```text -{% set tracker_name = "paulus"%} - -{% if states("device_tracker." + tracker_name) != "unknown" %} - {{ state_attr("device_tracker." + tracker_name, "battery")}} -{% else %} - ?? -{% endif %} -``` - -{% endraw %} - -List of friendly names: - -{% raw %} - -```text -{{ ['binary_sensor.garage_door', 'binary_sensor.front_door'] | map('state_attr', 'friendly_name') | list }} -``` - -{% endraw %} - -List of lights that are on with a brightness of 255: - -{% raw %} - -```text -{{ ['light.kitchen', 'light.dining_room'] | select('is_state', 'on') | select('is_state_attr', 'brightness', 255) | list }} -``` - -{% endraw %} - - -### State translated - -Not supported in [limited templates](#limited-templates). - -The `state_translated` function returns a translated state of an entity using a language that is currently configured in the [general settings](https://my.home-assistant.io/redirect/general/). - -#### State translated examples - -{% raw %} - -```text -{{ states("sun.sun") }} # below_horizon -{{ state_translated("sun.sun") }} # Below horizon -{{ "sun.sun" | state_translated }} # Below horizon -``` - -```text -{{ states("binary_sensor.movement_backyard") }} # on -{{ state_translated("binary_sensor.movement_backyard") }} # Detected -{{ "binary_sensor.movement_backyard" | state_translated }} # Detected -``` - -{% endraw %} - - -### State attribute translated - -Not supported in [limited templates](#limited-templates). - -The `state_attr_translated` function returns a translated attribute value of an entity using the language that is currently configured in {% my general title="**Settings** > **System** > **General**" %}. This is useful for attributes like `fan_mode`, `hvac_action`, `preset_mode`, or `color_mode`, which have translations defined but are stored as untranslated values in the state. - -#### State attribute translated examples - -{% raw %} - -```text -{{ state_attr("climate.living_room", "fan_mode") }} # low -{{ state_attr_translated("climate.living_room", "fan_mode") }} # Low -{{ "climate.living_room" | state_attr_translated("fan_mode") }} # Low -``` - -```text -{{ state_attr("climate.living_room", "hvac_action") }} # heating -{{ state_attr_translated("climate.living_room", "hvac_action") }} # Heating -{{ "climate.living_room" | state_attr_translated("hvac_action") }} # Heating -``` - -{% endraw %} - - -### Working with groups - -Not supported in [limited templates](#limited-templates). - -The `expand` function and filter can be used to sort entities and expand groups. It outputs a sorted array of entities with no duplicates. - -#### Expand examples - -{% raw %} - -```text -{% for tracker in expand('device_tracker.paulus', 'group.child_trackers') %} - {{ state_attr(tracker.entity_id, 'battery') }} - {%- if not loop.last %}, {% endif -%} -{% endfor %} -``` - -{% endraw %} - -The same thing can also be expressed as a filter: - -{% raw %} - -```text -{{ expand(['device_tracker.paulus', 'group.child_trackers']) - | selectattr("attributes.battery", 'defined') - | join(', ', attribute="attributes.battery") }} -``` - -{% endraw %} - -{% raw %} - -```text -{% for energy in expand('group.energy_sensors') if is_number(energy.state) %} - {{ energy.state }} - {%- if not loop.last %}, {% endif -%} -{% endfor %} -``` - -{% endraw %} - -The same thing can also be expressed as a test: - -{% raw %} - -```text -{{ expand('group.energy_sensors') - | selectattr("state", 'is_number') | join(', ') }} -``` - -{% endraw %} - - -### Entities - -- `entity_name(entity_id)` returns the name of an entity for a given entity ID. Can also be used as a filter. -- `is_hidden_entity(entity_id)` returns whether an entity has been hidden. Can also be used as a test. - -#### Entities examples - -{% raw %} - -```text -{{ entity_name('light.main_light') }} # Main light -``` - -```text -{{ area_entities('kitchen') | reject('is_hidden_entity') }} # Gets a list of visible entities in the kitchen area -``` - -{% endraw %} - -### Devices - -- `device_entities(device_id)` returns a list of entities that are associated with a given device ID. Can also be used as a filter. -- `device_attr(device_or_entity_id, attr_name)` returns the value of `attr_name` for the given device or entity ID. Can also be used as a filter. Not supported in [limited templates](#limited-templates). -- `is_device_attr(device_or_entity_id, attr_name, attr_value)` returns whether the value of `attr_name` for the given device or entity ID matches `attr_value`. Can also be used as a test. Not supported in [limited templates](#limited-templates). -- `device_id(entity_id)` returns the device ID for a given entity ID or device name. Can also be used as a filter. -- `device_name(lookup_value)` returns the device name for a given device ID or entity ID. Can also be used as a filter. - -#### Devices examples - -{% raw %} - -```text -{{ device_attr('deadbeefdeadbeefdeadbeefdeadbeef', 'manufacturer') }} # Sony -``` - -```text -{{ is_device_attr('deadbeefdeadbeefdeadbeefdeadbeef', 'manufacturer', 'Sony') }} # true -``` - -```text -{{ device_id('sensor.sony') }} # deadbeefdeadbeefdeadbeefdeadbeef -``` - -```text -{{ device_name('deadbeefdeadbeefdeadbeefdeadbeef') }} # Sony speaker -{{ device_name('sensor.sony') }} # Sony speaker -``` - -{% endraw %} - -### Config entries - -- `config_entry_id(entity_id)` returns the config entry ID for a given entity ID. Can also be used as a filter. -- `config_entry_attr(config_entry_id, attr)` returns the value of `attr` for the config entry of the given entity ID. Can also be used as a filter. The following attributes are allowed: `domain`, `title`, `state`, `source`, `disabled_by`, `pref_disable_polling`. Not supported in [limited templates](#limited-templates). - -#### Config entries examples - -{% raw %} - -```text -{{ config_entry_id('sensor.sony') }} # deadbeefdeadbeefdeadbeefdeadbeef -``` - -```text -{{ config_entry_attr(config_entry_id('sensor.sony'), 'title') }} # Sony Bravia TV -``` - - - -{% endraw %} - -### Floors - -- `floors()` returns the full list of floor IDs. -- `floor_id(lookup_value)` returns the floor ID for a given floor name or alias, area name or alias, entity ID or device ID. Can also be used as a filter. -- `floor_name(lookup_value)` returns the floor name for a given device ID, entity ID, area ID, or floor ID. Can also be used as a filter. -- `floor_areas(floor_name_or_id)` returns the list of area IDs tied to a given floor ID or name. Can also be used as a filter. -- `floor_entities(floor_name_or_id)` returns the list of entity IDs tied to a given floor ID or name. Can also be used as a filter. - -#### Floors examples - -{% raw %} - -```text -{{ floors() }} # ['floor_id'] -``` - -```text -{{ floor_id('First floor') }} # 'first_floor' -``` - -```text -{{ floor_id('First floor alias') }} # 'first_floor' -``` - -```text -{{ floor_id('my_device_id') }} # 'second_floor' -``` - -```text -{{ floor_id('sensor.sony') }} # 'first_floor' -``` - -```text -{{ floor_name('first_floor') }} # 'First floor' -``` - -```text -{{ floor_name('my_device_id') }} # 'Second floor' -``` - -```text -{{ floor_name('sensor.sony') }} # 'First floor' -``` - -```text -{{ floor_areas('first_floor') }} # ['living_room', 'kitchen'] -``` - -{% endraw %} - -### Areas - -- `areas()` returns the full list of area IDs -- `area_id(lookup_value)` returns the area ID for a given area name or alias, entity ID or device ID. Can also be used as a filter. -- `area_name(lookup_value)` returns the area name for a given device ID, entity ID, or area ID. Can also be used as a filter. -- `area_entities(area_name_or_id)` returns the list of entity IDs tied to a given area ID or name. Can also be used as a filter. -- `area_devices(area_name_or_id)` returns the list of device IDs tied to a given area ID or name. Can also be used as a filter. - -#### Areas examples - -{% raw %} - -```text -{{ areas() }} # ['area_id'] -``` - -```text -{{ area_id('Living Room') }} # 'deadbeefdeadbeefdeadbeefdeadbeef' -``` - -```text -{{ area_id('Living Room Alias') }} # 'deadbeefdeadbeefdeadbeefdeadbeef' -``` - -```text -{{ area_id('my_device_id') }} # 'deadbeefdeadbeefdeadbeefdeadbeef' -``` - -```text -{{ area_id('sensor.sony') }} # 'deadbeefdeadbeefdeadbeefdeadbeef' -``` - -```text -{{ area_name('deadbeefdeadbeefdeadbeefdeadbeef') }} # 'Living Room' -``` - -```text -{{ area_name('my_device_id') }} # 'Living Room' -``` - -```text -{{ area_name('sensor.sony') }} # 'Living Room' -``` - -```text -{{ area_entities('deadbeefdeadbeefdeadbeefdeadbeef') }} # ['sensor.sony'] -``` - -```text -{{ area_devices('Living Room') }} # ['my_device_id'] -``` - -{% endraw %} - -### Entities for an integration - -- `integration_entities(integration)` returns a list of entities that are associated with a given integration, such as `hue` or `zwave_js`. -- `integration_entities(config_entry_title)` if you have multiple entries set-up for an integration, you can also use the title you've set for the integration in case you only want to target a specific entry. - -If there is more than one entry with the same title, the entities for all the matching entries will be returned, even if the entries are for different integrations. It's not possible to search for entities of an untitled integration. - -#### Integrations examples - -{% raw %} - -```text -{{ integration_entities('hue') }} # ['light.hue_light_upstairs', 'light.hue_light_downstairs'] -``` - -```text -{{ integration_entities('Hue bridge downstairs') }} # ['light.hue_light_downstairs'] -``` - -{% endraw %} - -### Labels - -- `labels()` returns the full list of label IDs, or those for a given area ID, device ID, or entity ID. -- `label_id(lookup_value)` returns the label ID for a given label name. -- `label_name(lookup_value)` returns the label name for a given label ID. -- `label_description(lookup_value)` returns the label description for a given label ID. -- `label_areas(label_name_or_id)` returns the list of area IDs tied to a given label ID or name. -- `label_devices(label_name_or_id)` returns the list of device IDs tied to a given label ID or name. -- `label_entities(label_name_or_id)` returns the list of entity IDs tied to a given label ID or name. - -Each of the label template functions can also be used as a filter. - -#### Labels examples - -{% raw %} - -```text -{{ labels() }} # ['christmas_decorations', 'energy_saver', 'security'] -``` - -```text -{{ labels("living_room") }} # ['christmas_decorations', 'energy_saver'] -``` - -```text -{{ labels("my_device_id") }} # ['security'] -``` - -```text -{{ labels("light.christmas_tree") }} # ['christmas_decorations'] -``` - -```text -{{ label_id('Energy saver') }} # 'energy_saver' -``` - -```text -{{ label_name('energy_saver') }} # 'Energy saver' -``` - -```text -{{ label_areas('security') }} # ['driveway', 'garden', 'porch'] -``` - -```text -{{ label_devices('energy_saver') }} # ['deadbeefdeadbeefdeadbeefdeadbeef'] -``` - -```text -{{ label_entities('security') }} # ['camera.driveway', 'binary_sensor.motion_garden', 'camera.porch'] -``` - -{% endraw %} - -### Issues - -- `issues()` returns all open issues as a mapping of (domain, issue_id) tuples to the issue object. -- `issue(domain, issue_id)` returns a specific issue for the provided domain and issue_id. - -#### Issues examples - -{% raw %} - -```text -{{ issues() }} # { ("homeassistant", "deprecated_yaml_ping"): {...}, ("cloud", "legacy_subscription"): {...} } -``` - -```text -{{ issue('homeassistant', 'python_version') }} # {"breaks_in_ha_version": "2024.4", "domain": "homeassistant", "issue_id": "python_version", "is_persistent": False, ...} -``` - -{% endraw %} - -### Immediate if (iif) - -A common case is to conditionally return a value based on another value. -For example, return a "Yes" or "No" when the light is on or off. - -This can be written as: - -{% raw %} - -```text -{% if is_state('light.kitchen', 'on') %} - Yes -{% else %} - No -{% endif %} -``` - -{% endraw %} - -Or using a shorter syntax: - -{% raw %} - -```text -{{ 'Yes' if is_state('light.kitchen', 'on') else 'No' }} -``` - -{% endraw %} - -Additionally, to the above, you can use the `iif` function/filter, which is -an immediate if. - -Syntax: `iif(condition, if_true, if_false, if_none)` - -`iif` returns the value of `if_true` if the condition is truthy, the value of `if_false` if it's `falsy` and the value of `if_none` if it's `None`. -An empty string, an empty mapping or an an empty list, are all falsy, refer to [the Python documentation](https://docs.python.org/3/library/stdtypes.html#truth-value-testing) for an in depth explanation. - -`if_true` is optional, if it's omitted `True` is returned if the condition is truthy. -`if_false` is optional, if it's omitted `False` is returned if the condition is falsy. -`if_none` is optional, if it's omitted the value of `if_false` is returned if the condition is `None`. - -Examples using `iif`: - -{% raw %} - -```text -{{ iif(is_state('light.kitchen', 'on'), 'Yes', 'No') }} - -{{ is_state('light.kitchen', 'on') | iif('Yes', 'No') }} - -{{ (states('light.kitchen') == 'on') | iif('Yes', 'No') }} -``` - -{% endraw %} - -{% warning %} -The immediate if filter does not short-circuit like you might expect with a typical conditional statement. The `if_true`, `if_false` and `if_none` expressions will all be evaluated and the filter will simply return one of the resulting values. This means you cannot use this filter to prevent executing an expression which would result in an error. - -For example, if you wanted to select a field from `trigger` in an automation based on the platform you might go to make this template: `trigger.platform == 'event' | iif(trigger.event.data.message, trigger.to_state.state)`. This won't work because both expressions will be evaluated and one will fail since the field doesn't exist. Instead, you have to do this `trigger.event.data.message if trigger.platform == 'event' else trigger.to_state.state`. This form of the expression short-circuits so if the platform is `event` the expression `trigger.to_state.state` will never be evaluated and won't cause an error. -{% endwarning%} - -### Time - -`now()`, `time_since()`, `time_until()`, `today_at()`, and `utcnow()` are not supported in [limited templates](#limited-templates). - -- `now()` returns a datetime object that represents the current time in your time zone. - - You can also use: `now().second`, `now().minute`, `now().hour`, `now().day`, `now().month`, `now().year`, `now().weekday()` and `now().isoweekday()` and other [`datetime`](https://docs.python.org/3/library/datetime.html#datetime.datetime) attributes and functions. - - Using `now()` will cause templates to be refreshed at the start of every new minute. -- `utcnow()` returns a datetime object of the current time in the UTC timezone. - - For specific values: `utcnow().second`, `utcnow().minute`, `utcnow().hour`, `utcnow().day`, `utcnow().month`, `utcnow().year`, `utcnow().weekday()` and `utcnow().isoweekday()`. - - Using `utcnow()` will cause templates to be refreshed at the start of every new minute. -- `today_at(value)` converts a string containing a 24-hour time format to a datetime object with today's date in your time zone. Defaults to midnight (`00:00`). - - - Using `today_at()` will cause templates to be refreshed at the start of every new minute. - - {% raw %} - - ```text - # Is the current time past 10:15? - {{ now() > today_at("10:15") }} - ``` - - {% endraw %} - -- `as_datetime(value, default)` converts a string containing a timestamp or a valid UNIX timestamp to a datetime object. If conversion fails, the function returns the `default` value. If no `default` is provided and the input is a string that cannot be converted to a datetime, it returns `None`. For other invalid inputs (such as a list, dictionary, or a numeric value too large to convert), it raises an error when no `default` is supplied. In case the input is already a datetime object, it is returned unchanged. If the input is a `datetime.date` object, midnight is added as the time. This function can also be used as a filter. -- `as_timestamp(value, default)` converts a datetime object or string to UNIX timestamp. If that fails, returns the `default` value, or if omitted raises an error. This function can also be used as a filter. -- `as_local()` converts a datetime object to local time. This function can also be used as a filter. -- `strptime(string, format, default)` parses a string based on a [format](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-behavior) and returns a datetime object. If that fails, it returns the `default` value or, if omitted, raises an error. -- `relative_time` converts a datetime object to its human-friendly "age" string. The age can be in seconds, minutes, hours, days, months, or years (but only the biggest unit is considered. For example, if it's 2 days and 3 hours, "2 days" will be returned). Note that it only works for dates _in the past_. - - Using `relative_time()` will cause templates to be refreshed at the start of every new minute. -- `time_since(datetime, precision)` converts a datetime object into its human-readable time string. The time string can be in seconds, minutes, hours, days, months, and years. `precision` takes an integer (full number) and indicates the number of units returned. The last unit is rounded. For example: `precision = 1` could return "2 years" while `precision = 2` could return "1 year 11 months". This function can also be used as a filter. -If the datetime is in the future, returns 0 seconds. -A precision of 0 returns all available units, default is 1. -- `time_until(datetime, precision)` converts a datetime object into a human-readable time string. The time string can be in seconds, minutes, hours, days, months, and years. `precision` takes an integer (full number) and indicates the number of units returned. The last unit is rounded. For example: `precision = 1` could return "2 years" while `precision = 2` could return "1 year 11 months". This function can also be used as a filter. -If the datetime is in the past, returns 0 seconds. -A precision of 0 returns all available units, default is 1. -- `timedelta` returns a timedelta object, which represents a duration (an amount of time between two datetimes). It accepts the same arguments as the Python `datetime.timedelta` function -- days, seconds, microseconds, milliseconds, minutes, hours, weeks. - - {% raw %} - - ```text - # 77 minutes before current time. - {{ now() - timedelta( hours = 1, minutes = 17 ) }} - ``` - - {% endraw %} - -- `as_timedelta(string)` converts a string to a timedelta object, which represents a duration (an amount of time between two datetimes). Expects data in the format `DD HH:MM:SS.uuuuuu`, `DD HH:MM:SS,uuuuuu`, or as specified by ISO 8601 (for example, `P4DT1H15M20S` which is equivalent to `4 1:15:20`) or PostgreSQL’s day-time interval format (such as `3 days 04:05:06`). This function can also be used as a filter. - - {% raw %} - - ```text - # Renders to "00:10:00" - {{ as_timedelta("PT10M") }} - ``` - - {% endraw %} - -- Filter `timestamp_local(default)` converts a UNIX timestamp to the ISO format string representation as date/time in your local timezone. If that fails, returns the `default` value, or if omitted raises an error. If a custom string format is needed in the string, use `timestamp_custom` instead. -- Filter `timestamp_utc(default)` converts a UNIX timestamp to the ISO format string representation representation as date/time in UTC timezone. If that fails, returns the `default` value, or if omitted raises an error. If a custom string format is needed in the string, use `timestamp_custom` instead. -- Filter `timestamp_custom(format_string, local=True, default)` converts a UNIX timestamp to its string representation based on a custom format, the use of a local timezone is the default. If that fails, returns the `default` value, or if omitted raises an error. Supports the standard [Python time formatting options](https://docs.python.org/3/library/time.html#time.strftime). - -{% tip %} -[UNIX timestamp](https://en.wikipedia.org/wiki/Unix_time) is the number of seconds that have elapsed since 00:00:00 UTC on 1 January 1970. Therefore, if used as a function's argument, it can be substituted with a numeric value (`int` or `float`). -{% endtip %} - -{% important %} -If your template is returning a timestamp that should be displayed in the frontend (such as a sensor entity with `device_class: timestamp`), you have to ensure that it is the ISO 8601 format (meaning it has the "T" separator between the date and time portion). Otherwise, frontend rendering on macOS and iOS devices will show an error. The following value template would result in such an error: - -{% raw %} - -`{{ states.sun.sun.last_changed }}` => `2023-07-30 20:03:49.253717+00:00` (missing "T" separator) - -{% endraw %} - -To fix it, enforce the ISO conversion via `isoformat()`: - -{% raw %} - -`{{ states.sun.sun.last_changed.isoformat() }}` => `2023-07-30T20:03:49.253717+00:00` (contains "T" separator) - -{% endraw %} - -{% endimportant %} - -{% raw %} - -```text -{{ 120 | timestamp_local }} -``` - -{% endraw %} - -### To/From JSON - -The `to_json` filter serializes an object to a JSON string. In some cases, it may be necessary to format a JSON string for use with a webhook, as a parameter for command-line utilities or any number of other applications. This can be complicated in a template, especially when dealing with escaping special characters. Using the `to_json` filter, this is handled automatically. - -`to_json` also accepts boolean arguments for `pretty_print`, which will pretty print the JSON with a 2-space indent to make it more human-readable, and `sort_keys`, which will sort the keys of the JSON object, ensuring that the resulting string is consistent for the same input. - -If you need to generate JSON that will be used by a parser that lacks support for Unicode characters, you can add `ensure_ascii=True` to have `to_json` generate Unicode escape sequences in strings. - -The `from_json` filter operates similarly, but in the other direction, de-serializing a JSON string back into an object. - - -### To/From JSON examples - -#### Template - -{% raw %} - -```text -{% set temp = {'temperature': 25, 'unit': '°C'} %} -stringified object: {{ temp }} -object|to_json: {{ temp|to_json(sort_keys=True) }} -``` - -{% endraw %} - -#### Output - -{% raw %} - -```text -stringified object: {'temperature': 25, 'unit': '°C'} -object|to_json: {"temperature": 25, "unit": "°C"} -``` - -{% endraw %} - -Conversely, `from_json` can be used to de-serialize a JSON string back into an object to make it possible to easily extract usable data. - -#### Template - -{% raw %} - -```text -{% set temp = '{"temperature": 25, "unit": "°C"}'|from_json %} -The temperature is {{ temp.temperature }}{{ temp.unit }} -``` - -{% endraw %} - -#### Output - -{% raw %} - -```text -The temperature is 25°C -``` - -{% endraw %} - -`from_json(default)` function will attempt to convert the input to `json`. If that fails, returns the `default` value, or if omitted raises an error. - -#### Template - -{% raw %} - -```text -{% set result = 'not json'|from_json('not json') %} -The value is {{ result }} -``` - -{% endraw %} - -#### Output - -{% raw %} - -```text -The value is not json -``` - -{% endraw %} - -### Is defined - -Sometimes a template should only return if a value or object is defined, if not, the supplied default value should be returned. This can be useful to validate a JSON payload. -The `is_defined` filter allows to throw an error if a value or object is not defined. - -Example using `is_defined` to parse a JSON payload: - -{% raw %} - -```text -{{ value_json.val | is_defined }} -``` - -{% endraw %} - -This will throw an error `UndefinedError: 'value_json' is undefined` if the JSON payload has no `val` attribute. - -### Version - -- `version()` Returns an [AwesomeVersion object](https://github.com/ludeeus/awesomeversion) for the value given inside the brackets. - - This is also available as a filter (`| version`). - -Examples: - -{% raw %} - -- `{{ version("2099.9.9") > "2000.0.0" }}` Will return `True` -- `{{ version("2099.9.9") < "2099.10" }}` Will return `True` -- `{{ "2099.9.9" | version < "2099.10" }}` Will return `True` -- `{{ (version("2099.9.9") - "2100.9.10").major }}` Will return `True` -- `{{ (version("2099.9.9") - "2099.10.9").minor }}` Will return `True` -- `{{ (version("2099.9.9") - "2099.9.10").patch }}` Will return `True` - -{% endraw %} - -### Distance - -Not supported in [limited templates](#limited-templates). - -- `distance()` measures the distance between home, an entity, or coordinates. The unit of measurement (kilometers or miles) depends on the system's configuration settings. -- `closest()` will find the closest entity. - -#### Distance examples - -If only one location is passed in, Home Assistant will measure the distance from home. - -{% raw %} - -```text - -Using Lat Lng coordinates: {{ distance(123.45, 123.45) }} - -Using State: {{ distance(states.device_tracker.paulus) }} - -These can also be combined in any combination: -{{ distance(123.45, 123.45, 'device_tracker.paulus') }} -{{ distance('device_tracker.anne_therese', 'device_tracker.paulus') }} -``` - -{% endraw %} - -#### Closest examples - -The closest function and filter will find the closest entity to the Home Assistant location: - -{% raw %} - -```text -Query all entities: {{ closest(states) }} -Query all entities of a specific domain: {{ closest(states.device_tracker) }} -Query all entities in group.children: {{ closest('group.children') }} -Query all entities in group.children: {{ closest(states.group.children) }} -``` - -{% endraw %} - -Find entities closest to a coordinate or another entity. All previous arguments still apply for second argument. - -{% raw %} - -```text -Closest to a coordinate: {{ closest(23.456, 23.456, 'group.children') }} -Closest to an entity: {{ closest('zone.school', 'group.children') }} -Closest to an entity: {{ closest(states.zone.school, 'group.children') }} -``` - -{% endraw %} - -Since closest returns a state, we can combine it with distance too. - -{% raw %} - -```text -{{ closest(states).name }} is {{ distance(closest(states)) }} kilometers away. -``` - -{% endraw %} - -The last argument of the closest function has an implicit `expand`, and can take any iterable sequence of states or entity IDs, and will expand groups: - -{% raw %} - -```text -Closest out of given entities: - {{ closest(['group.children', states.device_tracker]) }} -Closest to a coordinate: - {{ closest(23.456, 23.456, ['group.children', states.device_tracker]) }} -Closest to some entity: - {{ closest(states.zone.school, ['group.children', states.device_tracker]) }} -``` - -{% endraw %} - -It will also work as a filter over an iterable group of entities or groups: - -{% raw %} - -```text -Closest out of given entities: - {{ ['group.children', states.device_tracker] | closest }} -Closest to a coordinate: - {{ ['group.children', states.device_tracker] | closest(23.456, 23.456) }} -Closest to some entity: - {{ ['group.children', states.device_tracker] | closest(states.zone.school) }} -``` - -{% endraw %} - -### Contains - -Jinja provides by default a [`in` operator](https://jinja.palletsprojects.com/en/latest/templates/#other-operators) how return `True` when one element is `in` a provided list. -The `contains` test and filter allow you to do the exact opposite and test for a list containing an element. This is particularly useful in `select` or `selectattr` filter, as well as to check if a device has a specific attribute, a `supported_color_modes`, a specific light effect. - -Some examples: -{% raw %} - -- `{{ state_attr('light.dining_room', 'effect_list') | contains('rainbow') }}` will return `true` if the light has a `rainbow` effect. -- `{{ expand('light.office') | selectattr("attributes.supported_color_modes", 'contains', 'color_temp') | list }}` will return all light that support color_temp in the office group. -- ```text - {% set current_month = now().month %} - {% set extra_ambiance = [ - {'name':'Halloween', 'month': [10,11]}, - {'name':'Noel', 'month': [1,11,12]} - ]%} - {% set to_add = extra_ambiance | selectattr('month', 'contains', current_month ) | map(attribute='name') | list %} - {% set to_remove = extra_ambiance | map(attribute='name') | reject('in', to_add) | list %} - {{ (state_attr('input_select.light_theme', 'options') + to_add ) | unique | reject('in', to_remove) | list }} - ``` - This more complex example uses the `contains` filter to match the current month with a list. In this case, it's used to generate a list of light theme to give to the `Input select: Set options` action. - -{% endraw %} - -### Numeric functions and filters - -Some of these functions can also be used in a [filter](https://jinja.palletsprojects.com/en/latest/templates/#id11). This means they can act as a normal function like this `sqrt(2)`, or as part of a filter like this `2|sqrt`. - -{% note %} - -The numeric functions and filters raise an error if the input is not a valid number, optionally a default value can be specified which will be returned instead. The `is_number` function and filter can be used to check if a value is a valid number. Errors can be caught by the `default` filter. - -{% raw %} - -- `{{ float("not_a_number") }}` - the template will fail to render -- `{{ "not_a_number" | sin }}` - the template will fail to render -- `{{ float("not_a_number", default="Invalid number!") }}` - renders as `"Invalid number!"` -- `{{ "not_a_number" | sin(default="Invalid number!") }}` - renders as `"Invalid number!"` - -{% endraw %} - -{% endnote %} - -- `float(value, default)` function will attempt to convert the input to a `float`. If that fails, returns the `default` value, or if omitted raises an error. -- `float(default)` filter will attempt to convert the input to a `float`. If that fails, returns the `default` value, or if omitted raises an error. -- `is_number` will return `True` if the input can be parsed by Python's `float` function and the parsed input is not `inf` or `nan`, in all other cases returns `False`. Note that a Python `bool` will return `True` but the strings `"True"` and `"False"` will both return `False`. Can be used as a filter. -- `int(value, default)` function is similar to `float`, but converts to an `int` instead. Like `float`, it has a filter form, and an error is raised if the `default` value is omitted. Fractional part is discarded: `int("1.5")` is `1`. -- `bool(value, default)` function converts the value to either `true` or `false`. - The following values are considered to be `true`: boolean `true`, non-zero `int`s and `float`s, and the strings `"true"`, `"yes"`, `"on"`, `"enable"`, and `"1"` (case-insensitive). `false` is returned for the opposite values: boolean `false`, integer or floating-point `0`, and the strings `"false"`, `"no"`, `"off"`, `"disable"`, and `"0"` (also case-insensitive). - If the value is not listed here, the function returns the `default` value, or if omitted raises an error. - This function is intended to be used on states of [binary sensors](/integrations/binary_sensor/), [switches](/integrations/switch/), or similar entities, so its behavior is different from Python's built-in `bool` conversion, which would consider values like `"on"`, `"off"`, and `"unknown"` all to be `true`, but `""` to be `false`; if that is desired, use `not not value` or a similar construct instead. - Like `float` and `int`, `bool` has a filter form. Using `none` as the default value is particularly useful in combination with the [immediate if filter](#immediate-if-iif): it can handle all three possible cases in a single line. - -- `log(value, base, default)` will take the logarithm of the input. When the base is omitted, it defaults to `e` - the natural logarithm. If `value` or `base` can't be converted to a `float`, returns the `default` value, or if omitted raises an error. Can also be used as a filter. -- `sin(value, default)` will return the sine of the input. The input value is in radians. If `value` can't be converted to a `float`, returns the `default` value, or if omitted raises an error. Can be used as a filter. -- `cos(value, default)` will return the cosine of the input. The input value is in radians. If `value` can't be converted to a `float`, returns the `default` value, or if omitted raises an error. Can be used as a filter. -- `tan(value, default)` will return the tangent of the input. The input value is in radians. If `value` can't be converted to a `float`, returns the `default` value, or if omitted raises an error. Can be used as a filter. -- `asin(value, default)` will return the arcus sine of the input. The return value is in radians. If `value` can't be converted to a `float`, returns the `default` value, or if omitted raises an error. Can be used as a filter. -- `acos(value, default)` will return the arcus cosine of the input. The return value is in radians. If `value` can't be converted to a `float`, returns the `default` value, or if omitted raises an error. Can be used as a filter. -- `atan(value, default)` will return the arcus tangent of the input. The return value is in radians. If `value` can't be converted to a `float`, returns the `default` value, or if omitted raises an error. Can be used as a filter. -- `atan2(y, x, default)` will return the four quadrant arcus tangent of y / x. The return value is in radians. If `y` or `x` can't be converted to a `float`, returns the `default` value, or if omitted raises an error. Can be used as a filter. -- `sqrt(value, default)` will return the square root of the input. If `value` can't be converted to a `float`, returns the `default` value, or if omitted raises an error. Can be used as a filter. -- `max([x, y, ...])` will obtain the largest item in a sequence. Uses the same parameters as the built-in [max](https://jinja.palletsprojects.com/en/latest/templates/#jinja-filters.max) filter. -- `min([x, y, ...])` will obtain the smallest item in a sequence. Uses the same parameters as the built-in [min](https://jinja.palletsprojects.com/en/latest/templates/#jinja-filters.min) filter. -- `average([x, y, ...], default)` will return the average value of the sequence. If list is empty or contains non-numeric value, returns the `default` value, or if omitted raises an error. Can be used as a filter. -- `median([x, y, ...], default)` will return the median value of the sequence. If list is empty or contains non-numeric value, returns the `default` value, or if omitted raises an error. Can be used as a filter. -- `statistical_mode([x, y, ...], default)` will return the statistical mode value (most frequent occurrence) of the sequence. If the list is empty, it returns the `default` value, or if omitted raises an error. It can be used as a filter. -- `clamp(v, min, max)` limits the value `v` to be between `min` and `max`, [clamping at the edges](https://en.wikipedia.org/wiki/Clamp_(function)). If any of the arguments cannot be converted to a float, an error is raised. Can be used as a filter. -- `wrap(v, min, max)` limits the value to be between min and max, wrapping the value at the edges. In mathematical terms, this is [modular arithmetic](https://en.wikipedia.org/wiki/Modular_arithmetic), sometimes called "clock face math". If `v`, `min`, or `max` cannot be converted to numbers, an error is raised. Can be used as a filter. -- `remap(v, in_min, in_max, out_min, out_max, *, [steps], [edges])` remaps a value `v` from the range `in_min`..`in_max` to the range `out_min`..`out_max`. - If any of the values `v`, `in_min`, `in_max`, `out_min`, `out_max` cannot be converted to numbers, an error is raised. Can be used as a filter. - - You can optionally set the `edges` parameter to control how out-of-bounds input values are handled: - - `edges='clamp'` (the default) will clamp the output to the min/max output range. - - `edges='wrap'` will wrap the input value around the input range before remapping. - - `edges='mirror'` will bounce the input value back and forth within the input range before remapping. - - You can optionally set the `steps` parameter to a positive integer to quantize the output to a number of discrete steps. -- `e` mathematical constant, approximately 2.71828. -- `pi` mathematical constant, approximately 3.14159. -- `tau` mathematical constant, approximately 6.28318. -- Filter `round(precision, method, default)` will convert the input to a number and round it to `precision` decimals. Round has four modes and the default mode (with no mode specified) will [round-to-even](https://en.wikipedia.org/wiki/Rounding#Roundhalfto_even). If the input value can't be converted to a `float`, returns the `default` value, or if omitted raises an error. - - `round(precision, "floor", default)` will always round down to `precision` decimals - - `round(precision, "ceil", default)` will always round up to `precision` decimals - - `round(1, "half", default)` will always round to the nearest .5 value. `precision` should be 1 for this mode -- Filter `value_one|bitwise_and(value_two)` perform a bitwise and(&) operation with two values. -- Filter `value_one|bitwise_or(value_two)` perform a bitwise or(\|) operation with two values. -- Filter `value_one|bitwise_xor(value_two)` perform a bitwise xor(\^) operation with two values. -- Filter `ord` will return for a string of length one an integer representing the Unicode code point of the character when the argument is a Unicode object, or the value of the byte when the argument is an 8-bit string. -- Filter `multiply(arg)` will convert the input to a number and multiply it by `arg`. Useful in list operations in conjunction with `map`. -- Filter `add(arg)` will convert the input to a number and add it to `arg`. Useful in list operations in conjunction with `map`. - -### Complex type checking - -In addition to strings and numbers, Python (and Jinja) supports lists, sets, and dictionaries. To help you with testing these types, you can use the following tests: - -- `x is list` will return whether `x` is a `list` or not (for example, `[1, 2] is list` will return `True`). -- `x is set` will return whether `x` is a `set` or not (for example, `{1, 2} is set` will return `True`). -- `x is tuple` will return whether `x` is a `tuple` or not (for example, `(1, 2) is tuple` will return `True`). -- `x is datetime` will return whether `x` is a `datetime` or not (for example, `datetime(2020, 1, 1, 0, 0, 0) is datetime` will return `True`). -- `x is string_like` will return whether `x` is a string, bytes, or bytearray object. - -Note that, in Home Assistant, Jinja has built-in tests for `boolean` (`True`/`False`), `callable` (any function), `float` (a number with a decimal), `integer` (a number without a decimal), `iterable` (a value that can be iterated over such as a `list`, `set`, `string`, or generator), `mapping` (mainly `dict` but also supports other dictionary like types), `number` (`float` or `int`), `sequence` (a value that can be iterated over and indexed such as `list` and `string`), and `string`. - -### Type conversions - -While Jinja natively supports the conversion of an iterable to a `list`, it does not support conversion to a `tuple` or `set`. To help you with using these types, you can use the following functions: - -- `set(x)` will convert any iterable `x` to a `set` (for example, `set([1, 2]) == {1, 2}`) -- `tuple(x)` will convert any iterable `x` to a `tuple` (for example, `tuple("abc") == ("a", "b", "c")`) - -Note that, in Home Assistant, to convert a value to a `list`, a `string`, an `int`, or a `float`, Jinja has built-in functions with names that correspond to each type. - -### Iterating multiple objects - -The `zip()` function can be used to iterate over multiple collections in one operation. - -{% raw %} - -```text -{% set names = ['Living Room', 'Dining Room'] %} -{% set entities = ['sensor.living_room_temperature', 'sensor.dining_room_temperature'] %} -{% for name, entity in zip(names, entities) %} - The {{ name }} temperature is {{ states(entity) }} -{% endfor %} -``` - -{% endraw %} - -`zip()` can also unzip lists. - -{% raw %} - -```text -{% set information = [ - ('Living Room', 'sensor.living_room_temperature'), - ('Dining Room', 'sensor.dining_room_temperature') -] %} -{% set names, entities = zip(*information) %} -The names are {{ names | join(', ') }} -The entities are {{ entities | join(', ') }} -``` - -{% endraw %} - -### Functions and filters to process raw data - -These functions are used to process raw value's in a `bytes` format to values in a native Python type or vice versa. -The `pack` and `unpack` functions can also be used as a filter. They make use of the Python 3 `struct` library. -See: [Python struct library documentation](https://docs.python.org/3/library/struct.html) - -- Filter `value | pack(format_string)` will convert a native type to a `bytes` type object. This will call function `struct.pack(format_string, value)`. Returns `None` if an error occurs or when `format_string` is invalid. -- Function `pack(value, format_string)` will convert a native type to a `bytes` type object. This will call function `struct.pack(format_string, value)`. Returns `None` if an error occurs or when `format_string` is invalid. -- Filter `value | unpack(format_string, offset=0)` will try to convert a `bytes` object into a native Python object. The `offset` parameter defines the offset position in bytes from the start of the input `bytes` based buffer. This will call function `struct.unpack_from(format_string, value, offset=offset)`. Returns `None` if an error occurs or when `format_string` is invalid. Note that the filter `unpack` will only return the first `bytes` object, despite the function `struct.unpack_from` supporting to return multiple objects (for example, with `format_string` being `">hh"`. -- Function `unpack(value, format_string, offset=0)` will try to convert a `bytes` object into a native Python object. The `offset` parameter defines the offset position in bytes from the start of the input `bytes` based buffer. This will call function `struct.unpack_from(format_string, value, offset=offset)`. Returns `None` if an error occurs or when `format_string` is invalid. Note that the function `unpack` will only return the first `bytes` object, despite the function `struct.unpack_from` supporting to return multiple objects (for example, with `format_string` being `">hh"`. - -{% note %} - -Some examples: -{% raw %} - -- `{{ 0xDEADBEEF | pack(">I") }}` - renders as `b"\xde\xad\xbe\xef"` -- `{{ pack(0xDEADBEEF, ">I") }}` - renders as `b"\xde\xad\xbe\xef"` -- `{{ "0x%X" % 0xDEADBEEF | pack(">I") | unpack(">I") }}` - renders as `0xDEADBEEF` -- `{{ "0x%X" % 0xDEADBEEF | pack(">I") | unpack(">H", offset=2) }}` - renders as `0xBEEF` - -{% endraw %} - -{% endnote %} - -### String filters - -- Filter `urlencode` will convert an object to a percent-encoded ASCII text string (for example, for use in HTTP requests using `application/x-www-form-urlencoded`). -- Filter `slugify(separator="_")` will convert a given string into a "slug". -- Filter `ordinal` will convert an integer into a number defining a position in a series (such as `1st`, `2nd`, `3rd`, or `4th`). -- Filter `value | from_hex` Decodes a hex string to raw bytes. -- Filter `value | base64_encode` Encodes a string or bytes to a base 64 string. -- Filter `value | base64_decode` Decodes a base 64 string to a string, by default utf-8 encoding is used. -- Filter `value | base64_decode("ascii")` Decodes a base 64 string to a string, using ASCII encoding. -- Filter `value | base64_decode(None)` Decodes a base 64 string to raw bytes. - -
- -Some examples: -{% raw %} -- `{{ "homeassistant" | base64_encode }}` - renders as `aG9tZWFzc2lzdGFudA==` -- `{{ "aG9tZWFzc2lzdGFudA==" | base64_decode }}` - renders as `homeassistant` -- `{{ "aG9tZWFzc2lzdGFudA==" | base64_decode(None) }}` - renders as `b'homeassistant'` -- `{{ "0F010003" | from_hex }}` - renders as `b'\x0f\x01\x00\x03'` -- `{{ "0F010003" | from_hex | base64_encode }}` - renders as `DwEAAw==` - -{% endraw %} - -
- -### Hashing - -The template engine contains a few filters and functions to hash a string of -data. A few very common hashing algorithms are supported: `md5`, `sha1`, -`sha256`, and `sha512`. - -Some examples: - -{% raw %} - -- `{{ md5("Home Assistant") }}` - renders as `f3f2b8b3b40084aa87e92b7ffb02ed13885fea2d07` -- `{{ "Home Assistant" | md5 }}` - renders as `f3f2b8b3b40084aa87e92b7ffb02ed13885fea2d07` - -- `{{ sha1("Home Assistant") }}` - renders as `14bffd017c73917bfda2372aaf287570597b8e82` -- `{{ "Home Assistant" | sha1 }}` - renders as `14bffd017c73917bfda2372aaf287570597b8e82` - -- `{{ sha256("Home Assistant") }}` - renders as `a18f473c9d3ed968a598f996dcf0b9de84de4ee04c950d041b61297a25bcea49` -- `{{ "Home Assistant" | sha256 }}` - renders as `a18f473c9d3ed968a598f996dcf0b9de84de4ee04c950d041b61297a25bcea49` - -- `{{ sha512("Home Assistant") }}` - renders as `f251e06eb7d3439e1a86d6497d6a4531c3e8c809f538be62f89babf147d7d63aca4e77ae475b94c654fd38d8f543f778ce80007d6afef379d8a0e5d3ddf7349d` -- `{{ "Home Assistant" | sha512 }}` - renders as `f251e06eb7d3439e1a86d6497d6a4531c3e8c809f538be62f89babf147d7d63aca4e77ae475b94c654fd38d8f543f778ce80007d6afef379d8a0e5d3ddf7349d` - -{% endraw %} - - -### Regular expressions - -For more information on regular expressions -See: [Python regular expression operations](https://docs.python.org/3/library/re.html) - -- Test `string is match(find, ignorecase=False)` will match the find expression at the beginning of the string using regex. -- Test `string is search(find, ignorecase=False)` will match the find expression anywhere in the string using regex. -- Filter `string|regex_replace(find='', replace='', ignorecase=False)` will replace the find expression with the replace string using regex. Access to the matched groups in `replace` is possible with `'\\1'`, `'\\2'`, and so on. -- Filter `value | regex_findall(find='', ignorecase=False)` will find all regex matches of the find expression in `value` and return the array of matches. -- Filter `value | regex_findall_index(find='', index=0, ignorecase=False)` will do the same as `regex_findall` and return the match at index. - -### Shuffling - -The template engine contains a filter and function to shuffle a list. - -Shuffling can happen randomly or reproducibly using a seed. When using a seed -it will always return the same shuffled list for the same seed. - -Some examples: - -{% raw %} - -- `{{ [1, 2, 3] | shuffle }}` - renders as `[3, 1, 2]` (_random_) -- `{{ shuffle([1, 2, 3]) }}` - renders as `[3, 1, 2]` (_random_) -- `{{ shuffle(1, 2, 3) }}` - renders as `[3, 1, 2]` (_random_) - -- `{{ [1, 2, 3] | shuffle("random seed") }}` - renders as `[2, 3, 1] (_reproducible_) -- `{{ shuffle([1, 2, 3], seed="random seed") }}` - renders as `[2, 3, 1] (_reproducible_) -- `{{ shuffle([1, 2, 3], "random seed") }}`- renders as `[2, 3, 1] (_reproducible_) -- `{{ shuffle(1, 2, 3, seed="random seed") }}` - renders as `[2, 3, 1] (_reproducible_) - -{% endraw %} - -### Flatten a list of lists - -The template engine provides a filter to flatten a list of lists: `flatten`. - -It will take a list of lists and return a single list with all the elements. -The depth of the flattening can be controlled using the `levels` parameter. -The flattening process is recursive, so it will flatten all nested lists, until -the number of levels (if specified) is reached. - -Some examples: - -{% raw %} - -- `{{ flatten([1, [2, [3]], 4, [5 , 6]]) }}` - renders as `[1, 2, 3, 4, 5, 6]` -- `{{ [1, [2, [3]], 4, [5 , 6]] | flatten }}` - renders as `[1, 2, 3, 4, 5, 6]` - -- `{{ flatten([1, [2, [3]]], levels=1) }}` - renders as `[1, 2, [3]]` -- `{{ [1, [2, [3]]], flatten(levels=1) }}` - renders as `[1, 2, [3]]` - -- `{{ flatten([1, [2, [3]]], 1) }}` - renders as `[1, 2, [3]]` -- `{{ [1, [2, [3]]], flatten(1) }}` - renders as `[1, 2, [3]]` - -{% endraw %} - -### Find common elements between lists - -The template engine provides a filter to find common elements between two lists: `intersect`. - -This function returns a list containing all elements that are present in both input lists. - -Some examples: - -{% raw %} - -- `{{ intersect([1, 2, 5, 3, 4, 10], [1, 2, 3, 4, 5, 11, 99]) }}` - renders as `[1, 2, 3, 4, 5]` -- `{{ [1, 2, 5, 3, 4, 10] | intersect([1, 2, 3, 4, 5, 11, 99]) }}` - renders as `[1, 2, 3, 4, 5]` -- `{{ intersect(['a', 'b', 'c'], ['b', 'c', 'd']) }}` - renders as `['b', 'c']` -- `{{ ['a', 'b', 'c'] | intersect(['b', 'c', 'd']) }}` - renders as `['b', 'c']` - -{% endraw %} - -### Find elements in first list not in second list - -The template engine provides a filter to find elements that are in the first list but not in the second list: `difference`. -This function returns a list containing all elements that are present in the first list but absent from the second list. - -Some examples: - -{% raw %} - -- `{{ difference([1, 2, 5, 3, 4, 10], [1, 2, 3, 4, 5, 11, 99]) }}` - renders as `[10]` -- `{{ [1, 2, 5, 3, 4, 10] | difference([1, 2, 3, 4, 5, 11, 99]) }}` - renders as `[10]` -- `{{ difference(['a', 'b', 'c'], ['b', 'c', 'd']) }}` - renders as `['a']` -- `{{ ['a', 'b', 'c'] | difference(['b', 'c', 'd']) }}` - renders as `['a']` - -{% endraw %} - -### Find elements that are in either list but not in both - -The template engine provides a filter to find elements that are in either of the input lists but not in both: `symmetric_difference`. -This function returns a list containing all elements that are present in either the first list or the second list, but not in both. - -Some examples: - -{% raw %} - -- `{{ symmetric_difference([1, 2, 5, 3, 4, 10], [1, 2, 3, 4, 5, 11, 99]) }}` - renders as `[10, 11, 99]` -- `{{ [1, 2, 5, 3, 4, 10] | symmetric_difference([1, 2, 3, 4, 5, 11, 99]) }}` - renders as `[10, 11, 99]` -- `{{ symmetric_difference(['a', 'b', 'c'], ['b', 'c', 'd']) }}` - renders as `['a', 'd']` -- `{{ ['a', 'b', 'c'] | symmetric_difference(['b', 'c', 'd']) }}` - renders as `['a', 'd']` - -{% endraw %} - -### Combine all unique elements from two lists - -The template engine provides a filter to combine all unique elements from two lists: `union`. -This function returns a list containing all unique elements that are present in either the first list or the second list. - -Some examples: - -{% raw %} - -- `{{ union([1, 2, 5, 3, 4, 10], [1, 2, 3, 4, 5, 11, 99]) }}` - renders as `[1, 2, 3, 4, 5, 10, 11, 99]` -- `{{ [1, 2, 5, 3, 4, 10] | union([1, 2, 3, 4, 5, 11, 99]) }}` - renders as `[1, 2, 3, 4, 5, 10, 11, 99]` -- `{{ union(['a', 'b', 'c'], ['b', 'c', 'd']) }}` - renders as `['a', 'b', 'c', 'd']` -- `{{ ['a', 'b', 'c'] | union(['b', 'c', 'd']) }}` - renders as `['a', 'b', 'c', 'd']` - -{% endraw %} - -### Combining dictionaries - -The template engine provides a function and filter to merge multiple dictionaries: `combine`. - -It will take multiple dictionaries and merge them into a single dictionary. When used as a filter, -the filter value is used as the first dictionary. The optional `recursive` parameter determines -whether nested dictionaries should be merged (defaults to `False`). - -Some examples: - -{% raw %} - -- `{{ {'a': 1, 'b': 2} | combine({'b': 3, 'c': 4}) }}` - renders as `{'a': 1, 'b': 3, 'c': 4}` -- `{{ combine({'a': 1, 'b': 2}, {'b': 3, 'c': 4}) }}` - renders as `{'a': 1, 'b': 3, 'c': 4}` - -- `{{ combine({'a': 1, 'b': {'x': 1}}, {'b': {'y': 2}, 'c': 4}, recursive=True) }}` - renders as `{'a': 1, 'b': {'x': 1, 'y': 2}, 'c': 4}` -- `{{ combine({'a': 1, 'b': {'x': 1}}, {'b': {'y': 2}, 'c': 4}) }}` - renders as `{'a': 1, 'b': {'y': 2}, 'c': 4}` - -{% endraw %} - -### Working with macros - -Home Assistant provides two additional functions that make macros much more powerful. - -{% raw %} - -- `apply` is both a filter and a test that allows you to use any callable (macros or functions) wherever -you can use other filters and tests. `apply` also passes along any additional parameters to the function. -For example, if you had a function called `double`, you could call -`{{ [1, 2, 3, 4] | map('apply', double) | list }}`, which would render as `[2, 4, 6, 8]`. -Alternatively, if you had a function called `is_multiple_of`, you could call -`{{ [1, 2, 3, 4] | select('apply', is_multiple_of, 2) | list }}`, which would render as `[2, 4]`. -- `as_function` is a filter that takes a macro that has a named parameter called `returns`. The macro can -then call `{%- do returns(return_value) -%}`. After passing this macro into `as_function`, the resulting -function returns your return value directly, preserving the underlying data type rather than rendering -a string. You can return dictionaries, numbers, `True`/`False` (allowing you to write your own tests when -used with `apply`), or any other value your code might produce. - -{% endraw %} - -## Merge action responses - -Using action responses we can collect information from various entities at the same time. -Using the `merge_response` template we can merge several responses into one list. - -| Variable | Description | -| -------------- | ---------------------------------- | -| `value` | The incoming value (must be an action response). | - -The `entity_id` key is appended to each dictionary within the template output list as a reference of origin. If the input dictionary already contains an `entity_id` key, the template will fail. - -The `value_key` key is appended to each dictionary within the template output list as a reference of origin if the original service call was providing a list of dictionaries, for example, `calendar.get_events` or `weather.get_forecasts`. - -Examples of these two keys can be seen in [example merge calendar action response](#example-merge-calendar-action-response) template output. - - -### Example - -```yaml -{% raw %} - -{% set combined_forecast = merge_response(response) %} -{{ combined_forecast[0].precipitation | float(0) | round(1) }} - -{% endraw %} -``` - -### Example how to sort - -Sorting the dictionaries within the list based on a specific key can be done directly by using Jinja's `sort` filter. - -```yaml -{% raw %} - -{{ merge_response(calendar_response) | sort(attribute='start') | ... }} - -{% endraw %} -``` - -### Example merge calendar action response - -```json -{ - "calendar.sports": { - "events": [ - { - "start": "2024-02-27T17:00:00-06:00", - "end": "2024-02-27T18:00:00-06:00", - "summary": "Basketball vs. Rockets", - "description": "", - } - ] - }, - "calendar.local_furry_events": {"events": []}, - "calendar.yap_house_schedules": { - "events": [ - { - "start": "2024-02-26T08:00:00-06:00", - "end": "2024-02-26T09:00:00-06:00", - "summary": "Dr. Appt", - "description": "", - }, - { - "start": "2024-02-28T20:00:00-06:00", - "end": "2024-02-28T21:00:00-06:00", - "summary": "Bake a cake", - "description": "something good", - } - ] - }, -} -``` - -```yaml -{% raw %} -{{ merge_response(response_variable) }} -{% endraw %} -``` - -```json -[ - { - "description": "", - "end": "2024-02-27T18:00:00-06:00", - "entity_id": "calendar.sports", - "start": "2024-02-27T17:00:00-06:00", - "summary": "Basketball vs. Rockets", - "value_key": "events" - }, - { - "description": "", - "end": "2024-02-26T09:00:00-06:00", - "entity_id": "calendar.yap_house_schedules", - "start": "2024-02-26T08:00:00-06:00", - "summary": "Dr. Appt", - "value_key": "events" - }, - { - "description": "something good", - "end": "2024-02-28T21:00:00-06:00", - "entity_id": "calendar.yap_house_schedules", - "start": "2024-02-28T20:00:00-06:00", - "summary": "Bake a cake", - "value_key": "events" - } -] -``` - -### Example non-list action responses - -```json -{ - "vacuum.deebot_n8_plus_1": { - "header": { - "ver": "0.0.1", - }, - "payloadType": "j", - "resp": { - "body": { - "msg": "ok", - }, - }, - }, - "vacuum.deebot_n8_plus_2": { - "header": { - "ver": "0.0.1", - }, - "payloadType": "j", - "resp": { - "body": { - "msg": "ok", - }, - }, - }, -} -``` - -```yaml -{% raw %} -{{ merge_response(response_variable) }} -{% endraw %} -``` - -```json -[ - { - "entity_id": "vacuum.deebot_n8_plus_1", - "header": { - "ver": "0.0.1", - }, - "payloadType": "j", - "resp": { - "body": { - "msg": "ok", - }, - }, - }, - { - "entity_id": "vacuum.deebot_n8_plus_2", - "header": { - "ver": "0.0.1", - }, - "payloadType": "j", - "resp": { - "body": { - "msg": "ok", - }, - }, - }, -] -``` - -## Processing incoming data - -The other part of templating is processing incoming data. It allows you to modify incoming data and extract only the data you care about. This will only work for platforms and integrations that mention support for this in their documentation. - -It depends per integration or platform, but it is common to be able to define a template using the `value_template` configuration key. When a new value arrives, your template will be rendered while having access to the following values on top of the usual Home Assistant extensions: - -| Variable | Description | -| ------------ | ---------------------------------- | -| `value` | The incoming value. | -| `value_json` | The incoming value parsed as JSON. | - -This means that if the incoming values looks like the sample below: - -```json -{ - "on": "true", - "temp": 21 -} -``` - -The template for `on` would be: - -{% raw %} - -```yaml -"{{value_json.on}}" -``` - -{% endraw %} - -Nested JSON in a response is supported as well: - -```json -{ - "sensor": { - "type": "air", - "id": "12345" - }, - "values": { - "temp": 26.09, - "hum": 56.73 - } -} -``` - -Just use the "Square bracket notation" to get the value. - -{% raw %} - -```yaml -"{{ value_json['values']['temp'] }}" -``` - -{% endraw %} - -The following overview contains a couple of options to get the needed values: - -{% raw %} - -```text -# Incoming value: -{"primes": [2, 3, 5, 7, 11, 13]} - -# Extract first prime number -{{ value_json.primes[0] }} - -# Format output -{{ "%+.1f" | value_json }} - -# Math -{{ value_json | float * 1024 if is_number(value_json) }} -{{ float(value_json) * (2**10) if is_number(value_json) }} -{{ value_json | log if is_number(value_json) }} -{{ log(1000, 10) }} -{{ sin(pi / 2) }} -{{ cos(tau) }} -{{ tan(pi) }} -{{ sqrt(e) }} - -# Timestamps -{{ value_json.tst | timestamp_local }} -{{ value_json.tst | timestamp_utc }} -{{ value_json.tst | timestamp_custom('%Y', True) }} -``` - -{% endraw %} - -To evaluate a response, go to {% my developer_template title="**Settings** > **Developer tools** > **Template**" %}, create your output in **Template editor**, and check the result. - -{% raw %} - -```yaml -{% set value_json= - {"name":"Outside", - "device":"weather-ha", - "data": - {"temp":"24C", - "hum":"35%" - } }%} - -{{value_json.data.hum[:-1]}} -``` - -{% endraw %} - -### Using templates with the MQTT integration - -The [MQTT integration](/integrations/mqtt/) relies heavily on templates. Templates are used to transform incoming payloads (value templates) to state updates or incoming actions (command templates) to payloads that configure the MQTT device. - -#### Using value templates with MQTT - -Value templates translate received MQTT payload to a valid state or attribute. -The received MQTT is available in the `value` template variable, and in the `value_json` template variable if the received MQTT payload is valid JSON. - -In addition, the template variables `entity_id`, `name` and `this` are available for MQTT entity value templates. The `this` attribute refers to the [entity state](/docs/configuration/state_object) of the MQTT item. - -{% note %} - -Example value template: - -With given payload: - -```json -{ "state": "ON", "temperature": 21.902, "humidity": null } -``` - -Template {% raw %}`{{ value_json.temperature | round(1) }}`{% endraw %} renders to `21.9`. - -Template {% raw %}`{{ value_json.humidity }}`{% endraw %} renders to `None`. - -{% endnote %} - -#### Using command templates with MQTT - -For actions, command templates are defined to format the outgoing MQTT payload to a format supported by the remote device. When an action is executed, the template variable `value` has the action data in most cases unless otherwise specified in the documentation. - -In addition, the template variables `entity_id`, `name` and `this` are available for MQTT entity command templates. The `this` attribute refers to the [entity state](/docs/configuration/state_object) of the MQTT item. - -{% note %} - -**Example command template with JSON data:** - -With given value `21.9` template {% raw %}`{"temperature": {{ value }} }`{% endraw %} renders to: - -```json -{ - "temperature": 21.9 -} -``` - -{% endnote %} - -**Example command template with raw data:** - -When a command template renders to a valid `bytes` literal, then MQTT will publish this data as raw data. In other cases, a string representation will be published. So: - -- Template {% raw %}`{{ "16" }}`{% endraw %} renders to payload encoded string `"16"`. -- Template {% raw %}`{{ 16 }}`{% endraw %} renders to payload encoded string `"16"`. -- Template {% raw %}`{{ pack(0x10, ">B") }}`{% endraw %} renders to a raw 1 byte payload `0x10`. - -### Determining types - -When working with templates, it can be useful to determine the type of -the returned value from a method or the type of a variable at times. - -For this, Home Assistant provides the `typeof()` template function and filter, -which is inspired by the [JavaScript](https://en.wikipedia.org/wiki/JavaScript) -`typeof` operator. It reveals the type of the given value. - -This is mostly useful when you are debugging or playing with templates in -the developer tools of Home Assistant. However, it might be useful in some -other cases as well. - -Some examples: - -{% raw %} - -- `{{ typeof(42) }}` - renders as `int` -- `{{ typeof(42.0) }}` - renders as `float` -- `{{ typeof("42") }}` - renders as `str` -- `{{ typeof([1, 2, 3]) }}` - renders as `list` -- `{{ typeof({"key": "value"}) }}` - renders as `dict` -- `{{ typeof(True) }}` - renders as `bool` -- `{{ typeof(None) }}` - renders as `NoneType` - -- `{{ 42 | typeof }}` - renders as `int` -- `{{ 42.0 | typeof }}` - renders as `float` -- `{{ "42" | typeof }}` - renders as `str` -- `{{ [1, 2, 3] | typeof }}` - renders as `list` -- `{{ {"key": "value"} | typeof }}` - renders as `dict` -- `{{ True | typeof }}` - renders as `bool` -- `{{ None | typeof }}` - renders as `NoneType` - -- `{{ some_variable | typeof }}` - renders the type of `some_variable` -- `{{ states("sensor.living_room") | typeof }}` - renders the type of the result of `states()` function - -{% endraw %} - -## Some more things to keep in mind - -### `entity_id` that begins with a number - -If your template uses an `entity_id` that begins with a number (example: `states.device_tracker.2008_gmc`) you must use a bracket syntax to avoid errors caused by rendering the `entity_id` improperly. In the example given, the correct syntax for the device tracker would be: `states.device_tracker['2008_gmc']` - -### Priority of operators - -The default priority of operators is that the filter (`|`) has priority over everything except brackets. This means that: - -{% raw %} - -```text -{{ states('sensor.temperature') | float / 10 | round(2) }} -``` - -{% endraw %} - -Would round `10` to 2 decimal places, then divide `states('sensor.temperature')` by `10` (rounded to 2 decimal places so 10.00). This behavior is maybe not the one expected, but priority rules imply that. diff --git a/source/_docs/organizing/areas.markdown b/source/_docs/organizing/areas.markdown index f16a91faa6d5..eb49ad0607f8 100644 --- a/source/_docs/organizing/areas.markdown +++ b/source/_docs/organizing/areas.markdown @@ -9,8 +9,8 @@ related: title: Labels - docs: /docs/organizing/categories/ title: Categories - - docs: /docs/configuration/templating/#areas - title: Using areas in template + - docs: /template-functions/#area + title: Using areas in templates - docs: /dashboards/dashboards/#home-dashboard title: Home dashboard --- diff --git a/source/_docs/organizing/floors.markdown b/source/_docs/organizing/floors.markdown index 0169ee1d917d..f649d3bad9fc 100644 --- a/source/_docs/organizing/floors.markdown +++ b/source/_docs/organizing/floors.markdown @@ -7,7 +7,7 @@ related: - docs: /docs/organizing/ - docs: /docs/organizing/labels/ title: Labels - - docs: /docs/configuration/templating/#floors + - docs: /template-functions/#floor title: Using floors in templates - docs: /voice_control/aliases/ title: Using floor alias for voice assistants diff --git a/source/_docs/organizing/labels.markdown b/source/_docs/organizing/labels.markdown index 6de1f463e05e..d2ba5e5ee7a2 100644 --- a/source/_docs/organizing/labels.markdown +++ b/source/_docs/organizing/labels.markdown @@ -8,7 +8,7 @@ related: title: Floors - docs: /docs/organizing/categories/ title: Categories - - docs: /docs/configuration/templating/#labels + - docs: /template-functions/#label title: Using labels in templates --- diff --git a/source/_docs/scripts.markdown b/source/_docs/scripts.markdown index b50114c047e2..a97cfd923768 100644 --- a/source/_docs/scripts.markdown +++ b/source/_docs/scripts.markdown @@ -57,7 +57,6 @@ Scripts may also use a shortcut syntax for activating {% term scenes %} instead The variables {% term action %} allows you to set/override variables that will be accessible by templates in {% term action %} after it. See also [script variables] for how to define variables accessible in the entire script. -{% raw %} ```yaml - alias: "Set variables" @@ -74,11 +73,9 @@ The variables {% term action %} allows you to set/override variables that will b brightness: "{{ brightness }}" ``` -{% endraw %} Variables can be templated. -{% raw %} ```yaml - alias: "Set a templated variable" @@ -90,13 +87,11 @@ Variables can be templated. message: "{{ blind_state_message }}" ``` -{% endraw %} ### Scope of variables The `variables` {% term action %} assigns the values to previously defined variables with the same name. If a variable was not previously defined, it is assigned in the top-level (script run) scope. -{% raw %} ```yaml sequence: @@ -122,7 +117,6 @@ sequence: # "There are 1 people home (including Paulus)" ``` -{% endraw %} ## Test a condition @@ -157,7 +151,6 @@ The `condition` {% term action %} only stops executing the current sequence bloc Delays are useful for temporarily suspending your script and start it at a later moment. We support different syntaxes for a delay as shown below. -{% raw %} ```yaml # Seconds @@ -187,18 +180,15 @@ Delays are useful for temporarily suspending your script and start it at a later minutes: 1 ``` -{% endraw %} All forms accept templates. -{% raw %} ```yaml # Waits however many minutes input_number.minute_delay is set to - delay: "{{ states('input_number.minute_delay') | multiply(60) | int }}" ``` -{% endraw %} ## Wait @@ -208,9 +198,8 @@ These {% term actions %} allow a script to wait for entities in the system to be This {% term action %} evaluates the template, and if true, the script will continue. If not, then it will wait until it is true. -The template is re-evaluated whenever an entity ID that it references changes state. If you use non-deterministic functions like `now()` in the template it will not be continuously re-evaluated, but only when an entity ID that is referenced is changed. If you need to periodically re-evaluate the template, reference a sensor from the [Time and Date](/integrations/time_date/) integration that will update minutely or daily. +The template is re-evaluated whenever an entity ID that it references changes state. If you use non-deterministic functions like [`now()`](/template-functions/now/) in the template it will not be continuously re-evaluated, but only when an entity ID that is referenced is changed. If you need to periodically re-evaluate the template, reference a sensor from the [Time and Date](/integrations/time_date/) integration that will update minutely or daily. -{% raw %} ```yaml # Wait until media player is stopped @@ -218,12 +207,10 @@ The template is re-evaluated whenever an entity ID that it references changes st wait_template: "{{ is_state('media_player.floor', 'stop') }}" ``` -{% endraw %} ### Wait for a trigger This {% term action %} can use the same triggers that are available in an automation's `trigger` section. See [Automation Trigger](/docs/automation/trigger). The script will continue whenever any of the triggers fires. All previously defined [trigger variables](/docs/automation/trigger#trigger-variables), [variables](#variables) and [script variables] are passed to the trigger. -{% raw %} ```yaml # Wait for a custom event or light to turn on and stay on for 10 sec @@ -237,13 +224,11 @@ This {% term action %} can use the same triggers that are available in an automa for: 10 ``` -{% endraw %} ### Wait timeout With both types of waits it is possible to set a timeout after which the script will continue its execution if the condition/event is not satisfied. Timeout has the same syntax as `delay`, and like `delay`, also accepts templates. -{% raw %} ```yaml # Wait for sensor to change to 'on' up to 1 minute before continuing to execute. @@ -251,11 +236,9 @@ With both types of waits it is possible to set a timeout after which the script timeout: "00:01:00" ``` -{% endraw %} You can also get the script to abort after the timeout by using optional `continue_on_timeout: false`. -{% raw %} ```yaml # Wait for IFTTT event or abort after specified timeout. @@ -269,7 +252,6 @@ You can also get the script to abort after the timeout by using optional `contin continue_on_timeout: false ``` -{% endraw %} Without `continue_on_timeout: false` the script will always continue since the default for `continue_on_timeout` is `true`. @@ -285,7 +267,6 @@ After each time a wait completes, either because the condition was met, the even This can be used to take different actions based on whether or not the condition was met, or to use more than one wait sequentially while implementing a single timeout overall. -{% raw %} ```yaml # Take different actions depending on if condition was met. @@ -321,7 +302,6 @@ This can be used to take different actions based on whether or not the condition entity_id: switch.some_light ``` -{% endraw %} ## Fire an event @@ -342,7 +322,6 @@ an event trigger. The `event_data` accepts templates. -{% raw %} ```yaml - event: MY_EVENT @@ -351,7 +330,6 @@ The `event_data` accepts templates. customData: "{{ myCustomVariable }}" ``` -{% endraw %} ### Raise and consume custom events @@ -371,7 +349,6 @@ The following {% term automation %} example shows how to raise a custom event ca The following {% term automation %} example shows how to capture the custom event `event_light_state_changed` with an [Event Automation Trigger](/docs/automation/trigger#event-trigger), and retrieve corresponding `entity_id` that was passed as the event trigger data, see [Available-Trigger-Data](/docs/automation/templating/#available-trigger-data) for more details. -{% raw %} ```yaml - alias: "Capture Event" @@ -384,7 +361,6 @@ The following {% term automation %} example shows how to capture the custom even message: "kitchen light is turned {{ trigger.event.data.state }}" ``` -{% endraw %} ## Repeat a group of actions @@ -396,7 +372,6 @@ There are three ways to control how many times the sequence will be run. This form accepts a count value. The value may be specified by a template, in which case the template is rendered when the repeat step is reached. -{% raw %} ```yaml script: @@ -423,7 +398,6 @@ script: count: 3 ``` -{% endraw %} ### For each @@ -435,7 +409,6 @@ iteration is available as `repeat.item`. The following example will turn a list of lights: -{% raw %} ```yaml repeat: @@ -449,12 +422,10 @@ repeat: entity_id: "light.{{ repeat.item }}" ``` -{% endraw %} Other types are accepted as list items, for example, each item can be a template, or even a mapping of key/value pairs. -{% raw %} ```yaml repeat: @@ -470,14 +441,12 @@ repeat: message: "{{ repeat.item.message }}!" ``` -{% endraw %} ### While loop This form accepts a list of conditions (see [conditions page] for available options) that are evaluated _before_ each time the sequence is run. The sequence will be run _as long as_ the condition(s) evaluate to true. -{% raw %} ```yaml script: @@ -497,12 +466,10 @@ script: - action: script.something ``` -{% endraw %} The `while` also accepts a [shorthand notation of a template condition][shorthand-template]. For example: -{% raw %} ```yaml - repeat: @@ -511,7 +478,6 @@ For example: - ... ``` -{% endraw %} ### Repeat until @@ -519,7 +485,6 @@ This form accepts a list of conditions that are evaluated _after_ each time the is run. Therefore the sequence will always run at least once. The sequence will be run _until_ the condition(s) evaluate to true. -{% raw %} ```yaml automation: @@ -547,12 +512,10 @@ automation: state: "on" ``` -{% endraw %} `until` also accepts a [shorthand notation of a template condition][shorthand-template]. For example: -{% raw %} ```yaml - repeat: @@ -561,7 +524,6 @@ For example: - ... ``` -{% endraw %} ### Repeat loop variable @@ -614,7 +576,6 @@ An _optional_ `alias` can be added to each of the sequences, excluding the `defa The `choose` {% term action %} can be used like an "if/then/elseif/then.../else" statement. The first `conditions`/`sequence` pair is like the "if/then", and can be used just by itself. Or additional pairs can be added, each of which is like an "elif/then". And lastly, a `default` can be added, which would be like the "else." -{% raw %} ```yaml # Example with "if", "elif" and "else" @@ -653,12 +614,10 @@ automation: entity_id: all ``` -{% endraw %} `conditions` also accepts a [shorthand notation of a template condition][shorthand-template]. For example: -{% raw %} ```yaml automation: @@ -689,7 +648,6 @@ automation: - action: script.left_home ``` -{% endraw %} More `choose` can be used together. This is the case of an IF-IF. @@ -697,7 +655,6 @@ The following example shows how a single {% term automation %} can control entit When the sun goes below the horizon, the `porch` and `garden` lights must turn on. If someone is watching the TV in the living room, there is a high chance that someone is in that room, therefore the living room lights have to turn on too. The same concept applies to the `studio` room. -{% raw %} ```yaml # Example with "if" and "if" @@ -746,7 +703,6 @@ automation: entity_id: light.studio ``` -{% endraw %} ## Grouping actions @@ -953,7 +909,6 @@ script: Actions can also be disabled based on limited templates or blueprint inputs. -{% raw %} ```yaml blueprint: @@ -968,7 +923,6 @@ blueprint: enabled: !input input_boolean ``` -{% endraw %} ## Respond to a conversation @@ -976,7 +930,6 @@ The `set_conversation_response` script {% term action %} allows returning a cust when an {% term automation %} is triggered by a conversation engine, for example a voice assistant. The conversation response can be templated. -{% raw %} ```yaml # Example of a templated conversation response resulting in "Testing 123" @@ -985,7 +938,6 @@ assistant. The conversation response can be templated. - set_conversation_response: "{{ 'Testing ' ~ my_var }}" ``` -{% endraw %} The response is handed to the conversation engine when the {% term automation %} finishes. If the `set_conversation_response` is executed multiple times, the most recent diff --git a/source/_docs/scripts/conditions.markdown b/source/_docs/scripts/conditions.markdown index 77cfedd8150f..e41b9349941a 100644 --- a/source/_docs/scripts/conditions.markdown +++ b/source/_docs/scripts/conditions.markdown @@ -179,7 +179,6 @@ conditions: You can optionally use a `value_template` to process the value of the state before testing it. -{% raw %} ```yaml conditions: @@ -191,7 +190,6 @@ conditions: value_template: "{{ float(state.state) + 2 }}" ``` -{% endraw %} It is also possible to test the condition against multiple entities at once. The condition will pass if **all** entities match the thresholds. @@ -322,7 +320,6 @@ conditions: You can also use templates in the `for` option. -{% raw %} ```yaml conditions: @@ -334,7 +331,6 @@ conditions: seconds: "{{ states('input_number.lock_sec')|int }}" ``` -{% endraw %} The `for` template(s) will be evaluated when the condition is tested. @@ -367,7 +363,6 @@ For an in-depth explanation of sun elevation, see [sun elevation trigger][sun_el [sun_elevation_trigger]: /docs/automation/trigger/#sun-elevation-trigger -{% raw %} ```yaml conditions: @@ -379,9 +374,6 @@ conditions: value_template: "{{ state_attr('sun.sun', 'elevation') > -6 }}" ``` -{% endraw %} - -{% raw %} ```yaml conditions: @@ -389,7 +381,6 @@ conditions: value_template: "{{ state_attr('sun.sun', 'elevation') < -6 }}" ``` -{% endraw %} ### Sunset/sunrise condition @@ -437,7 +428,6 @@ A visual timeline is provided below, showing an example of when these conditions The template condition tests if the [given template][template] renders a value equal to true. This is achieved by having the template result in a true boolean expression or by having the template render `True`. -{% raw %} ```yaml conditions: @@ -446,7 +436,6 @@ conditions: value_template: "{{ (state_attr('device_tracker.iphone', 'battery_level')|int) > 50 }}" ``` -{% endraw %} Within an automation, template conditions also have access to the `trigger` variable as [described here][automation-templating]. @@ -456,18 +445,15 @@ The template condition has a shorthand notation that can be used to make your sc For example: -{% raw %} ```yaml conditions: "{{ (state_attr('device_tracker.iphone', 'battery_level')|int) > 50 }}" ``` -{% endraw %} Or in a list of conditions, allowing to use existing conditions as described in this chapter and one or more shorthand template conditions -{% raw %} ```yaml conditions: @@ -478,13 +464,11 @@ conditions: - "{{ is_state('device_tracker.iphone', 'away') }}" ``` -{% endraw %} This shorthand notation can be used everywhere in Home Assistant where conditions are accepted. For example, in [`and`](#and-condition), [`or`](#or-condition) and [`not`](#not-condition) conditions: -{% raw %} ```yaml conditions: @@ -496,11 +480,9 @@ conditions: below: 20 ``` -{% endraw %} It's also supported in the `repeat` action's `while` or `until` option, or in a `choose` action's `conditions` option: -{% raw %} ```yaml - while: "{{ is_state('sensor.mode', 'Home') and repeat.index < 10 }}" @@ -508,9 +490,6 @@ It's also supported in the `repeat` action's `while` or `until` option, or in a - ... ``` -{% endraw %} - -{% raw %} ```yaml - choose: @@ -519,19 +498,16 @@ It's also supported in the `repeat` action's `while` or `until` option, or in a - ... ``` -{% endraw %} It's also supported in script or automation `condition` actions: -{% raw %} ```yaml - condition: "{{ is_state('device_tracker.iphone', 'away') }}" ``` -{% endraw %} -[template]: /docs/configuration/templating/ +[template]: /docs/templating/ [automation-templating]: /getting-started/automation-templating/ ## Time condition @@ -671,7 +647,6 @@ conditions: ## Examples -{% raw %} ```yaml conditions: @@ -690,7 +665,6 @@ conditions: state: "off" ``` -{% endraw %} ## Disabling a condition @@ -713,7 +687,6 @@ conditions: Conditions can also be disabled based on limited templates or blueprint inputs. -{% raw %} ```yaml blueprint: @@ -743,4 +716,3 @@ blueprint: enabled: "{{ _enable_number < 50 }}" ``` -{% endraw %} diff --git a/source/_docs/scripts/perform-actions.markdown b/source/_docs/scripts/perform-actions.markdown index d8d8ed5fed31..ca9e2bc4a88d 100644 --- a/source/_docs/scripts/perform-actions.markdown +++ b/source/_docs/scripts/perform-actions.markdown @@ -65,7 +65,6 @@ A full list of the parameters for an action can be found on the documentation pa You can use [templating] support to dynamically choose which action to perform. For example, you can perform a certain action based on if a light is on. -{% raw %} ```yaml action: > @@ -77,7 +76,6 @@ action: > entity_id: switch.ac ``` -{% endraw %} ### Using the Actions developer tool @@ -94,7 +92,6 @@ To turn a group on or off, pass the following info: Templates can also be used for the data that you pass to the action. -{% raw %} ```yaml action: thermostat.set_temperature @@ -109,11 +106,9 @@ data: temperature: "{{ 22 - distance(states.device_tracker.paulus) }}" ``` -{% endraw %} You can use a template returning a native dictionary as well, which is useful if the attributes to be set depend on the situation. -{% raw %} ```yaml action: climate.set_temperature @@ -125,7 +120,6 @@ data: > {% endif %} ``` -{% endraw %} ### Use templates to handle response data @@ -137,7 +131,6 @@ Templates can also be used for handling response data. The action can specify a `response_variable`. This is the [variable](/docs/scripts/#variables) that contains the response data. You can define any name for your `response_variable`. This example performs an action and stores the response in the variable called `agenda`. -{% raw %} ```yaml action: calendar.get_events @@ -149,7 +142,6 @@ data: response_variable: agenda ``` -{% endraw %} You may then use the response data in the variable `agenda` in another action in the same script. The example below sends a notification using the response @@ -159,7 +151,6 @@ data. Which data fields can be used in an action depends on the type of notification that is used. {% endimportant %} -{% raw %} ```yaml action: notify.gmail_com @@ -175,7 +166,6 @@ data:

``` -{% endraw %} ### `homeassistant` actions @@ -188,7 +178,7 @@ There are four `homeassistant` actions that aren't tied to any single domain, th Complete action details and examples can be found on the [Home Assistant integration][homeassistant-integration-actions] page. -[templating]: /docs/configuration/templating/ +[templating]: /docs/templating/ [google travel time]: /integrations/google_travel_time/ [template sensor]: /integrations/template/ [light]: /integrations/light/ diff --git a/source/_docs/templating.markdown b/source/_docs/templating.markdown new file mode 100644 index 000000000000..984ff87b909f --- /dev/null +++ b/source/_docs/templating.markdown @@ -0,0 +1,60 @@ +--- +title: "Templating" +description: "Learn how to use templates to make Home Assistant adapt to your needs." +--- + +Templates are short snippets of code you can use wherever Home Assistant needs to figure something out for you. Instead of typing a fixed message or value, you write a small instruction that reads your data and produces the right result. + +For example, instead of a notification that always says "Someone is home", a template can say "Frenck is home" or "Nobody is home, they're at the gym" depending on what's actually happening. + +## Quick example + +{% example %} +action: | + action: notify.mobile_app + data: + message: > + {% if is_state('device_tracker.frenck', 'home') %} + Frenck is home. + {% else %} + Frenck is at {{ states('device_tracker.frenck') }}. + {% endif %} +output: "Frenck is at gym." +{% endexample %} + +The text between `{%`, `%}` and `{{`, `}}` is the template. When the action runs, Home Assistant replaces it with the right message. + +## Learning guide + +Start here if you are new to templating in Home Assistant. The pages below walk you through the concepts step by step. + + + +## Tutorials + +Two step-by-step walkthroughs that show templating in practice. + + + +## Reference + +Home Assistant provides hundreds of template functions, filters, and tests for working with your data. Each one has its own page with explanations and examples. + +- [Template functions reference](/template-functions/). Browse all functions, filters, and tests by category. diff --git a/source/_docs/templating/custom-templates.markdown b/source/_docs/templating/custom-templates.markdown new file mode 100644 index 000000000000..cb0f0925d846 --- /dev/null +++ b/source/_docs/templating/custom-templates.markdown @@ -0,0 +1,112 @@ +--- +title: "Custom templates and macros" +description: "Share templates across your configuration with a custom_templates folder and macros." +related: + - docs: /template-functions/as_function/ + title: "`as_function` filter" + - docs: /docs/templating/loops-and-conditions/ + title: Loops and conditions + - url: https://jinja.palletsprojects.com/en/3.0.x/templates/#macros + title: Jinja2 macros documentation +--- + +When you find yourself writing the same template in two or three places, it is time to save it once and reuse it. Home Assistant has a feature for that: the `custom_templates` folder. Drop a template file in there, and every automation, script, and template entity in your configuration can call it. + +This page is optional. If you are starting out with templates, you can skip it. Come back when you notice yourself copy-pasting the same logic around. + +## Setting up the folder + +Create a folder called `custom_templates` inside your Home Assistant configuration directory (the one that holds `configuration.yaml`). Inside it, create files with the `.jinja` extension. The name before the extension is what you will import by. + +```text +config/ +├── configuration.yaml +├── automations.yaml +└── custom_templates/ + └── formatter.jinja +``` + +Each file must be under 5 MiB. Home Assistant loads everything in this folder at startup. + +### Reloading without a restart + +When you edit a file in `custom_templates`, Home Assistant does not pick up the change automatically. You need to tell it to reload in one of two ways: + +- Call the {% my developer_call_service service="homeassistant.reload_custom_templates" %} action. +- Go to {% my developer_services title="**Developer tools** > **Actions**" %} and run the `homeassistant.reload_custom_templates` action. + +Home Assistant does not need a restart for custom template changes. + +## Sharing a macro + +A macro is a reusable piece of template that takes arguments and produces a result. Macros are perfect for recipes you apply over and over, like formatting an entity in a consistent way. + +Define the macro in a file under `custom_templates`. For example, `config/custom_templates/formatter.jinja`: + +```jinja +{% macro format_entity(entity_id) %} +{{ state_attr(entity_id, 'friendly_name') }}: {{ states(entity_id) }} +{% endmacro %} +``` + +Then, in any template elsewhere in your configuration, import the macro and use it: + +{% example %} +template: | + {% from 'formatter.jinja' import format_entity %} + {{ format_entity('sensor.outdoor_temperature') }} + {{ format_entity('sensor.indoor_temperature') }} +output: | + Outdoor temperature: 22.5 + Indoor temperature: 21.0 +{% endexample %} + +If you change the format (say, you want to put the state first), you only update `formatter.jinja`, and every automation that imports it picks up the new format on the next reload. + +## Importing vs including + +Home Assistant supports both of the usual ways to pull in a template file: + +- **`{% from 'file.jinja' import thing %}`** loads a specific macro from a file. Use this when you want to call a macro by name. +- **`{% include 'file.jinja' %}`** runs the entire file as if its contents were at the current spot. Use this for drop-in content. + +For most reuse cases, `import` is cleaner because it keeps the macro names explicit. + +## Macros that return values + +A regular macro produces text as its result. When you call it, you get back whatever the macro writes. That is fine for formatting strings, but sometimes you want a macro that hands back a number, a list, or a yes-or-no answer. + +The trick is to define a macro that takes a special argument called `returns`, then call it through the [`as_function`](/template-functions/as_function/) filter. That makes the macro behave like a normal function call. + +{% example %} +template: | + {% macro macro_is_switch(entity_name, returns) -%} + {% do returns(entity_name.startswith('switch.')) %} + {% endmacro -%} + + {% set is_switch = macro_is_switch | as_function -%} + {% set result = "It's a switch!" if is_switch('switch.my_switch') + else "Not a switch!" -%} + {{ result }} +output: "It's a switch!" +{% endexample %} + +Read that out loud: "define a macro that answers whether an entity ID starts with `switch.`, then turn it into a function, then use that function inside an `if`". This pattern lets you build up your own library of small utility functions that return actual values. + +## When to use custom templates + +Reach for the `custom_templates` folder when: + +- You have a formatting snippet you repeat across notifications, dashboards, or template entities. +- You have a calculation (like a complicated condition check) that shows up in many automations. +- You want your own library of helper macros that do specific things. + +For one-off templates, this is overkill. Write the template inline where you need it. + +## Next steps + +- Review the [template functions reference](/template-functions/) for existing filters and functions that might already do what you need. +- See [`as_function`](/template-functions/as_function/) for details on macros that return values. +- For the conceptual basics of templates, see [Template syntax](/docs/templating/syntax/) and [Loops and conditions](/docs/templating/loops-and-conditions/). + +{% include template_functions/stuck.md %} diff --git a/source/_docs/templating/dates-and-times.markdown b/source/_docs/templating/dates-and-times.markdown new file mode 100644 index 000000000000..50f26af8e4b4 --- /dev/null +++ b/source/_docs/templating/dates-and-times.markdown @@ -0,0 +1,237 @@ +--- +title: "Working with dates and times" +description: "Read, format, compare, and calculate with dates and times in your templates." +related: + - docs: /template-functions/now/ + title: "`now` function" + - docs: /template-functions/strptime/ + title: "`strptime` function" + - docs: /template-functions/timestamp_custom/ + title: "`timestamp_custom` filter" + - docs: /template-functions/relative_time/ + title: "`relative_time` function" + - docs: /template-functions/#datetime + title: All date and time functions + - url: https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes + title: Python strftime format codes +--- + +Dates and times come up constantly in Home Assistant templates. "How long ago did the door open?" "Is it past sunset?" "How many days until my next bin collection?" This page gathers the tools and patterns you need, in one place. + +If you only need the current moment, start with `now()`. If you're converting or formatting, jump to the [formatting section](#formatting-with-strftime). If you're working with a timestamp from a sensor, head to the [conversion section](#converting-between-types). + +## Getting the current time + +### now() and utcnow() + +[`now()`](/template-functions/now/) returns the current date and time in your configured time zone. [`utcnow()`](/template-functions/utcnow/) returns it in UTC. + +{% example %} +template: | + Local: {{ now() }} + UTC: {{ utcnow() }} +output: | + Local: 2026-04-04 14:30:00.123456+02:00 + UTC: 2026-04-04 12:30:00.123456+00:00 +{% endexample %} + +{% important %} +Templates that use `now()` or `utcnow()` re-run once per minute. This is how clock-based templates stay current. If you need something to refresh more often, base it on a state change instead. +{% endimportant %} + +### today_at + +[`today_at`](/template-functions/today_at/) gives you a specific time on the current day, handy for "is it past bedtime?" checks. + +{% example %} +template: | + {{ today_at('22:00') }} +output: "2026-04-04 22:00:00+02:00" +{% endexample %} + +## Accessing parts of a datetime + +Once you have a datetime, you can pull out individual parts with dots. + +{% example %} +template: | + Year: {{ now().year }} + Month: {{ now().month }} + Day: {{ now().day }} + Hour: {{ now().hour }} + Minute: {{ now().minute }} + Weekday: {{ now().weekday() }} +output: | + Year: 2026 + Month: 4 + Day: 4 + Hour: 14 + Minute: 30 + Weekday: 5 +{% endexample %} + +`weekday()` is `0` for Monday through `6` for Sunday. If you prefer the other convention, `isoweekday()` gives you `1` for Monday through `7` for Sunday. + +## Formatting with strftime + +`strftime` turns a datetime into text, shaped the way you want. This is the single most useful method for displaying dates and times. + +{% example %} +template: | + 24-hour: {{ now().strftime('%H:%M') }} + 12-hour: {{ now().strftime('%I:%M %p') }} + Weekday: {{ now().strftime('%A') }} + Short date: {{ now().strftime('%Y-%m-%d') }} + Long date: {{ now().strftime('%A, %B %-d, %Y') }} + Sentence: {{ now().strftime('It is %A at %H:%M') }} +output: | + 24-hour: 14:30 + 12-hour: 02:30 PM + Weekday: Saturday + Short date: 2026-04-04 + Long date: Saturday, April 4, 2026 + Sentence: It is Saturday at 14:30 +{% endexample %} + +### Common format codes + +Here are the ones you will use most often: + +- `%Y`: four-digit year (for example, `2026`) +- `%m`: zero-padded month (for example, `04`) +- `%d`: zero-padded day (for example, `04`) +- `%-d`: day without leading zero (for example, `4`) +- `%H`: hour in 24-hour format (for example, `14`) +- `%M`: minute (for example, `30`) +- `%S`: second (for example, `00`) +- `%I`: hour in 12-hour format (for example, `02`) +- `%p`: `AM` or `PM` +- `%A`: full weekday name (for example, `Saturday`) +- `%a`: short weekday name (for example, `Sat`) +- `%B`: full month name (for example, `April`) +- `%b`: short month name (for example, `Apr`) + +The Python documentation has the [full list of format codes](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes) if you need something unusual. + +## Parsing text into a datetime with strptime + +[`strptime`](/template-functions/strptime/) is the reverse of `strftime`. It takes a piece of text and a format string, and gives you back a datetime. + +{% example %} +template: | + {% set event = strptime('2026-12-25 10:30', '%Y-%m-%d %H:%M') %} + {{ event }} +output: "2026-12-25 10:30:00" +{% endexample %} + +See the [`strptime`](/template-functions/strptime/) reference for the full signature. + +## Converting between types + +Sensors, APIs, and MQTT messages deliver timestamps in many different shapes. These functions convert between them. + +### UNIX timestamp to datetime + +A **UNIX timestamp** is the number of seconds since January 1, 1970. Many APIs use them. + +{% example %} +template: | + {% set ts = 1710510600 %} + Local: {{ ts | timestamp_local }} + UTC: {{ ts | timestamp_utc }} + Custom: {{ ts | timestamp_custom('%H:%M on %B %d') }} +output: | + Local: 2024-03-15 14:30:00+01:00 + UTC: 2024-03-15 13:30:00+00:00 + Custom: 14:30 on March 15 +{% endexample %} + +See [`timestamp_local`](/template-functions/timestamp_local/), [`timestamp_utc`](/template-functions/timestamp_utc/), and [`timestamp_custom`](/template-functions/timestamp_custom/). + +### Datetime to UNIX timestamp + +Use [`as_timestamp`](/template-functions/as_timestamp/): + +{% example %} +template: "{{ as_timestamp(now()) | int }}" +output: "1743768600" +{% endexample %} + +### Text to datetime + +[`as_datetime`](/template-functions/as_datetime/) is a forgiving version of [`strptime`](/template-functions/strptime/) that tries to figure out common formats on its own. + +{% example %} +template: | + {{ as_datetime('2026-04-04 14:30:00') }} +output: "2026-04-04 14:30:00+02:00" +{% endexample %} + +## Time differences + +When you subtract two datetimes, you get a **timedelta** that represents the duration between them. Timedeltas support their own calculations and methods. + +### How long ago did something happen? + +Every state has a `last_changed` timestamp. Subtract it from `now()` to get a timedelta. + +{% example %} +template: | + {% set since = now() - states.binary_sensor.front_door.last_changed %} + Total seconds: {{ since.total_seconds() | int }} + Total minutes: {{ (since.total_seconds() / 60) | int }} +output: | + Total seconds: 900 + Total minutes: 15 +{% endexample %} + +For a human-readable version, reach for [`relative_time`](/template-functions/relative_time/) or [`time_since`](/template-functions/time_since/) instead: + +{% example %} +template: | + {{ relative_time(states.binary_sensor.front_door.last_changed) }} ago +output: "15 minutes ago" +{% endexample %} + +### Is it more than X minutes? + +A common pattern: trigger an alert when something has been in a state too long. + +{% example %} +template: | + {{ + (now() - states.binary_sensor.front_door.last_changed) + .total_seconds() > 600 + }} +output: "True (when the door has been in its state for more than 10 minutes)" +{% endexample %} + +### Counting down to a future date + +[`time_until`](/template-functions/time_until/) turns a future datetime into a human-readable duration. + +{% example %} +template: | + {% set birthday = strptime('2026-12-25', '%Y-%m-%d') %} + Christmas: {{ time_until(birthday) }} +output: "Christmas: 8 months" +{% endexample %} + +## Time zones + +Home Assistant stores state timestamps (`last_changed`, `last_updated`) in your configured time zone. `now()` also returns your local time zone. `utcnow()` returns UTC. + +If you need to compare datetimes, both sides need to be in the same time zone. [`as_datetime`](/template-functions/as_datetime/) and [`strptime`](/template-functions/strptime/) return datetimes without a time zone by default. Apply the matching conversion before comparing, or stick to [`timestamp_local`](/template-functions/timestamp_local/) and [`timestamp_utc`](/template-functions/timestamp_utc/) which handle this for you. + +## Common gotchas + +- **Text comparison vs time comparison.** Comparing date-looking text with `<` or `>` compares alphabetically, not chronologically. Convert both sides to datetimes first. +- **[`timestamp_custom`](/template-functions/timestamp_custom/) is not the only option.** For datetime values (like `now()`), use `.strftime(...)` directly. [`timestamp_custom`](/template-functions/timestamp_custom/) is specifically for UNIX timestamps. +- **Templates using `now()` re-run every minute.** Don't use it for things that should update more often. +- **Subtraction gives you a timedelta, not seconds.** Call `.total_seconds()` on the result when you need a number. + +## Next steps + +- See the [full list of date and time functions](/template-functions/#datetime) in the reference. +- For common date/time recipes, see [Common template patterns](/docs/templating/patterns/). +- The [Python methods](/docs/templating/python-methods/) page covers `.strftime()`, `.weekday()`, and similar datetime methods. diff --git a/source/_docs/templating/debugging.markdown b/source/_docs/templating/debugging.markdown new file mode 100644 index 000000000000..9e80270cbe6a --- /dev/null +++ b/source/_docs/templating/debugging.markdown @@ -0,0 +1,190 @@ +--- +title: "Debugging templates" +description: "Find and fix mistakes in your templates using the template editor and a few good habits." +related: + - docs: /docs/templating/errors/ + title: Error messages and fixes + - docs: /docs/templating/types/ + title: Types and conversion + - docs: /template-functions/typeof/ + title: "`typeof` function" + - docs: /template-functions/has_value/ + title: "`has_value` function" + - docs: /template-functions/default/ + title: "`default` filter" + - docs: /docs/templating/yaml/ + title: Templates in YAML + - url: https://community.home-assistant.io/ + title: Home Assistant Community Forum +--- + +Every template author has stared at a template that refuses to work and wondered if they're losing it. You are not. Templates are fiddly, and even experienced people make the same mistakes over and over. This page walks through the tools and habits that turn "why isn't this working?" into "ah, that's the problem". + +## The template editor + +Home Assistant has a built-in template editor that shows the result of a template while you type. Open it from {% my developer_template title="**Settings** > **Developer tools** > **Template**" %}. + +The editor is the fastest feedback loop you have. It: + +- Shows the output of your template live. +- Points at errors with a red message. +- Has access to all your entities, which means you can try the template against real data. + +When a template misbehaves in an automation or template entity, copy it into the editor, adjust until it produces what you want, then paste it back. + +## Read the error message + +When a template has a mistake, Home Assistant shows an error. The message is often more helpful than it looks. + +**`UndefinedError: 'foo' is undefined`** +Something in your template refers to a variable that does not exist. Check the spelling and make sure anything you `{% set %}` is defined before you use it. + +**`TypeError: unsupported operand type(s)`** +You are trying to do something to a value of the wrong type, like adding a number to some text. Convert with [`float`](/template-functions/float/) or [`int`](/template-functions/int/) first, and remember to add a fallback. + +**`TemplateSyntaxError`** +A typo in the template itself. Count your brackets, check that every `{% if %}` has an `{% endif %}`, and that every `{% for %}` has an `{% endfor %}`. + +**`No first item, sequence was empty`** +You used [`first`](/template-functions/first/) or [`last`](/template-functions/last/) on an empty list. Add an `if` check, or use the [`default`](/template-functions/default/) filter. + +## Narrow down the problem + +When a long template does not work, the trick is to take it apart. Start with the smallest piece you can and add one thing at a time. The moment the result goes wrong, you know exactly which piece broke. This sounds slow but it is always faster than staring at the whole thing and hoping the problem jumps out. + +{% example %} +template: | + {# Start here #} + {{ states('sensor.outdoor_temperature') }} +output: "22.5" +{% endexample %} + +{% example %} +template: | + {# Add the conversion #} + {{ states('sensor.outdoor_temperature') | float(0) }} +output: "22.5" +{% endexample %} + +{% example %} +template: | + {# Add the rounding #} + {{ states('sensor.outdoor_temperature') | float(0) | round(1) }} +output: "22.5" +{% endexample %} + +{% example %} +template: | + {# Add the full sentence #} + It is {{ states('sensor.outdoor_temperature') + | float(0) | round(1) }}°C outside. +output: "It is 22.5°C outside." +{% endexample %} + +## Check what a value actually is + +When a template gives the wrong result, show the raw value to see what you are working with. + +{% example %} +template: | + {{ states('sensor.outdoor_temperature') }} +output: "22.5" +{% endexample %} + +That result looks like a number, but it is actually text. Many sensors return text even when the content looks numeric. Use [`typeof`](/template-functions/typeof/) to be sure: + +{% example %} +template: | + {{ states('sensor.outdoor_temperature') | typeof }} +output: "str" +{% endexample %} + +`str` is short for "string", which is how templates refer to text. If you see `str` where you expected a number, that's your clue to convert with [`float`](/template-functions/float/) or [`int`](/template-functions/int/). `int` and `float` in the output mean the value is already a whole number or decimal number. + +## Inspect attributes + +When you are not sure what attributes an entity has, show them all with [`tojson`](/template-functions/tojson/): + +{% example %} +template: | + {{ state_attr('media_player.living_room', 'source_list') | tojson }} +output: '["Spotify", "Bluetooth", "TV", "Radio"]' +{% endexample %} + +Or list every attribute: + +{% example %} +template: | + {% for key, value in states.media_player.living_room.attributes.items() %} + {{ key }}: {{ value }} + {% endfor %} +output: | + friendly_name: Living room + volume_level: 0.35 + source: Spotify + source_list: ['Spotify', 'Bluetooth', 'TV', 'Radio'] +{% endexample %} + +## When does my template run? + +A common source of confusion is "why isn't my template updating?", or the opposite: "why is it running so often?". Home Assistant decides when to re-evaluate a template based on what is _inside_ it. + +**Entity states.** When your template reads a state, like `states('sensor.temperature')`, Home Assistant watches that entity. The template runs again every time the entity's state changes. + +**`now()` and `utcnow()`.** Templates that use `now()` or `utcnow()` re-run once per minute. + +**Other variables.** Templates that depend on `this`, `trigger`, or other context variables only re-run when the context changes (a new trigger fires, the entity updates). + +If you write a template that does not read any state or use `now()`, it runs _once_ at startup and never again. That is fine for constant values, but it's a common trap when you want a template to react to something. + +### Why does it work in Developer Tools but not in my automation? + +The **Template editor** runs your template once, right when you open it, and shows the result. There is no ongoing re-evaluation. That makes it great for testing, but a working template in the editor does not guarantee it works in an automation. + +Two things commonly differ: + +- **`this` and `trigger` are not available** in the editor. If your template refers to them, the editor shows an error. In a template entity or automation, they exist. +- **Automations re-evaluate on change.** A template trigger only fires when the template's result goes from false to true. If you load an automation while the condition is already true, nothing fires. + +When a template works in the editor but not in an automation, check whether you are using `this`, `trigger`, or expecting a specific "on-change" behavior. + +## Common mistakes + +- **Sensor gives text, not a number.** Use `| float(0)` or `| int(0)`. +- **Entity is `unknown` or `unavailable`.** Add a fallback with `| default(...)` or an `if has_value(...)` check. +- **Variable changes inside a loop are lost.** Use [`namespace`](/template-functions/namespace/). +- **YAML refuses to load your template.** Check quoting. See [Templates in YAML](/docs/templating/yaml/). +- **Template appears as literal text in output.** The field does not support templating, or the template is inside a `{% raw %}` / `{% endraw %}` block. +- **Template evaluates at the wrong time.** Triggers and conditions are checked on change, not continuously. A template condition that depends on `now()` only re-checks when something else triggers. +- **Comparing text to a number.** `'6' < '10'` is `False` because text is compared alphabetically, not numerically. Convert with `| float(0)` or `| int(0)` on both sides first. + +## Common Python mistakes + +Templates use [Jinja2](https://jinja.palletsprojects.com/), a templating engine built on top of Python, but the syntax is not the same as Python. A few things that work in Python don't work in templates: + +- **No `print()`.** Write `{{ expression }}` to show a value. There is no print statement. +- **No `import`.** You cannot import modules. The functions available to you are the ones in the [template functions reference](/template-functions/) plus the standard Jinja filters. +- **No `=` inside `{{ }}` for assignment.** Use `{% set name = value %}` to create a variable. Inside `{{ }}`, `=` does not exist. +- **`None` must be capitalized.** Python writes `None`, `True`, `False` with a capital first letter. Template tests and literals use lowercase `none`, `true`, `false`. Both work in most places, but be consistent. +- **No f-strings.** Python's `f"hello {name}"` is not template syntax. Use `"hello " ~ name` (the `~` joins text) or `{{ name }}` directly. +- **No `while` loops.** Templates only support `for` loops. +- **List and dict methods are limited.** Read-only methods like `.items()`, `.values()`, `.keys()`, `.get()`, `.split()`, `.lower()`, and `.upper()` work fine. Methods that mutate a value in place, like `.append()`, `.pop()`, or `.update()`, are blocked as unsafe. To build up a list across loop iterations, use a [`namespace`](/template-functions/namespace/) instead. + +## Next steps + +- Looking up a specific error message? See [Error messages and fixes](/docs/templating/errors/). +- Need a working example to compare against? Browse [Common template patterns](/docs/templating/patterns/). +- Want to refresh the basics? Review [Template syntax](/docs/templating/syntax/). + +## Still stuck? + +The Home Assistant community is quick to help: join [Discord](https://discord.gg/home-assistant) for real-time chat, post on the [community forum](https://community.home-assistant.io) with your template and expected result, or share on [our subreddit](https://reddit.com/r/homeassistant). When you ask, include these four things and the answer usually comes within minutes: + +- The template you are using (copied from the editor, where you can see what it runs against). +- What you expected the result to be. +- What the actual result or error was. +- The entity IDs involved (from {% my developer_states title="**Settings** > **Developer tools** > **States**" %}). + +{% tip %} +AI assistants like ChatGPT or Claude can also explain or fix templates when you describe what you want in plain language. +{% endtip %} diff --git a/source/_docs/templating/errors.markdown b/source/_docs/templating/errors.markdown new file mode 100644 index 000000000000..890b97a36aa4 --- /dev/null +++ b/source/_docs/templating/errors.markdown @@ -0,0 +1,164 @@ +--- +title: "Template error messages and fixes" +description: "Common template error messages with plain-language explanations and fixes." +related: + - docs: /docs/templating/debugging/ + title: Debugging templates + - docs: /docs/templating/types/ + title: Types and conversion + - docs: /template-functions/typeof/ + title: "`typeof` function" + - docs: /template-functions/has_value/ + title: "`has_value` function" +--- + +This page lists template error messages you might run into, what each one means in plain language, and how to fix it. If the message you are seeing does not match exactly, look for the closest one. The names of types and variables change, but the shape of each error stays the same. + +For a general debugging workflow, see [Debugging templates](/docs/templating/debugging/). + +## UndefinedError: 'foo' is undefined + +**What it means.** The template is trying to use a variable named `foo` that does not exist. Either the name is misspelled, or the variable was never set. + +**How to fix it.** + +- Check the spelling of every name in the template. [`states`](/template-functions/states/) and `state` are different; `trigger.to_state` and `trigger.tostate` are different. +- If you use a variable with `{% set name = value %}`, make sure the `set` runs before the variable is used. +- If you expect the variable to come from an automation trigger (`trigger.*`) or a template entity (`this.*`), remember these only exist in those contexts. They are not available in the {% my developer_template title="Template editor" %}. + +## UndefinedError: 'dict object' has no attribute 'foo' + +**What it means.** You tried to read a key named `foo` from a dictionary, but that key does not exist. + +**How to fix it.** + +- Use `.get("foo", default_value)` to return a default when the key is missing: `data.get("foo", 0)`. +- Or check for the key first with `if "foo" in data`. +- If the dictionary comes from a JSON response, print it with `{{ data | tojson }}` to see exactly which keys are there. + +## TypeError: unsupported operand type(s) for +: 'str' and 'int' + +**What it means.** You are trying to do math between a piece of text and a number. This happens most often with entity states, because every state is stored as text. `"22.5" + 5` does not work. + +**How to fix it.** Convert the text to a number first with `| float(0)` or `| int(0)`: + +{% example %} +template: | + {{ states('sensor.temperature') | float(0) + 5 }} +output: "27.5" +{% endexample %} + +The `0` is a fallback used when the conversion fails (for example, when the sensor is `unavailable`). See [Types and conversion](/docs/templating/types/). + +## TypeError: float() argument must be a string or a real number, not 'NoneType' + +**What it means.** You tried to convert `None` to a number, and `None` is not a number. This usually comes from reading an attribute that does not exist, or calling [`state_attr`](/template-functions/state_attr/) for an entity that has not been set up yet. + +**How to fix it.** Add a default value to [`float`](/template-functions/float/) or [`int`](/template-functions/int/): + +{% example %} +template: | + {{ state_attr('light.kitchen', 'brightness') | float(0) }} +output: "0" +{% endexample %} + +Or skip the calculation when the value is missing using [`has_value`](/template-functions/has_value/). + +## TypeError: object of type 'generator' has no len() + +**What it means.** You tried to count or measure an iterable directly. Filters like [`map`](/template-functions/map/), [`select`](/template-functions/select/), [`reject`](/template-functions/reject/), [`selectattr`](/template-functions/selectattr/), and [`rejectattr`](/template-functions/rejectattr/) return iterables, not lists. Iterables cannot be counted until you materialize them. + +**How to fix it.** Add `| list` to turn the iterable into a list first: + +{% example %} +template: | + {{ states.light | selectattr('state', 'eq', 'on') | list | count }} +output: "3" +{% endexample %} + +See [Types and conversion](/docs/templating/types/#iterables-look-like-lists-but-are-not) for more on iterables. + +## TemplateSyntaxError: expected token 'end of statement block', got 'X' + +**What it means.** There is a typo or an unexpected character inside a `{% ... %}` block. Something is written that the template engine doesn't recognize. + +**How to fix it.** + +- Look at the template line number in the error. +- Check for missing commas, brackets, or quotes. +- Verify that operators are spelled right (`==`, `!=`, `and`, `or`, `not`, `in`). +- Python comparisons like `is not None` work; make sure you used `is not`, not `not is`. + +## TemplateSyntaxError: Unexpected end of template + +**What it means.** A `{% if %}`, `{% for %}`, `{% set %}`, or `{% macro %}` block was opened but not closed. Every block tag needs a matching `{% endif %}`, `{% endfor %}`, `{% endset %}`, or `{% endmacro %}`. + +**How to fix it.** + +- Count the opening and closing tags. If you have two `{% if %}`, you need two `{% endif %}`. +- Indent the template in the editor so you can see the structure. + +## TemplateSyntaxError: tag name expected + +**What it means.** You have a `{% %}` block with nothing inside, or with something the engine cannot parse as a statement. + +**How to fix it.** Check the line. Remove empty `{% %}` markers. If you intended a comment, use `{# ... #}` instead. + +## TemplateAssertionError: no test named 'foo' + +**What it means.** After `is`, you used a test name the engine does not know. + +**How to fix it.** Check the test name against the [template functions reference](/template-functions/#comparison). Common tests are [`defined`](/template-functions/defined/), [`none`](/template-functions/none/), `number`, [`string`](/template-functions/string/), `boolean`, [`iterable`](/template-functions/iterable/), [`mapping`](/template-functions/mapping/), [`even`](/template-functions/even/), [`odd`](/template-functions/odd/), [`eq`](/template-functions/eq/), [`gt`](/template-functions/gt/), [`lt`](/template-functions/lt/), and `in`. Aliases like `equalto`, `greaterthan`, and `lessthan` also work. + +## UndefinedError: 'states' has no attribute 'sensor' + +**What it means.** When using dot notation like `states.sensor.temperature.state`, one of the pieces in the chain does not exist. Usually this means the entity ID is wrong, or the entity has not been set up yet. + +**How to fix it.** + +- Use `states('sensor.temperature')` instead. The function version returns the text `'unknown'` for missing entities instead of raising an error, which is safer. +- Verify the entity ID in {% my developer_states title="**Settings** > **Developer tools** > **States**" %}. + +## No first item, sequence was empty + +**What it means.** You used [`first`](/template-functions/first/) or [`last`](/template-functions/last/) on a list that turned out to be empty. There is nothing to return. + +**How to fix it.** Check the list length first, or use [`default`](/template-functions/default/): + +{% example %} +template: | + {% set items = ['a', 'b', 'c'] %} + {{ items | first | default('nothing') }} +output: "a" +{% endexample %} + +## YAML error: could not find expected ':' + +**What it means.** Your YAML file contains a template with unquoted braces (`{{` or `{%`). YAML tries to parse `{` as the start of a flow-style mapping and fails. + +**How to fix it.** Wrap the single-line template in quotes, or use a multi-line block scalar: + +```yaml +# Correct: quoted +value_template: "{{ states('sensor.temperature') }}" + +# Correct: multi-line +value_template: > + {{ states('sensor.temperature') }} +``` + +See [Templates in YAML](/docs/templating/yaml/) for the full set of quoting rules. + +## Next steps + +- For a systematic approach to narrowing down any template problem, see [Debugging templates](/docs/templating/debugging/). +- If the error came from a quoting or indentation issue, head to [Templates in YAML](/docs/templating/yaml/). +- If the error involves state values being text, see [Working with states](/docs/templating/states/). + +## Still stuck? + +The Home Assistant community is quick to help: join [Discord](https://discord.gg/home-assistant) for real-time chat, post on the [community forum](https://community.home-assistant.io) with your template and the exact error message, or share on [our subreddit](https://reddit.com/r/homeassistant). + +{% tip %} +AI assistants like ChatGPT or Claude can also explain or fix templates when you describe what you want in plain language. Paste in your template and the error message. +{% endtip %} diff --git a/source/_docs/templating/introduction.markdown b/source/_docs/templating/introduction.markdown new file mode 100644 index 000000000000..81ab38651826 --- /dev/null +++ b/source/_docs/templating/introduction.markdown @@ -0,0 +1,105 @@ +--- +title: "Introduction to templating" +description: "Understand what templates are, when you need them, and why they are useful in Home Assistant." +related: + - docs: /docs/templating/where-to-use/ + title: Where to use templates + - docs: /docs/templating/syntax/ + title: Template syntax + - docs: /template-functions/ + title: Template functions reference +--- + +When you want a notification that reads "It is 22°C in the living room" instead of a fixed message, or an automation that fires only when more than three doors are open, you write a template. + +A template is a short snippet of code that Home Assistant runs every time it needs a value. Instead of writing a fixed piece of text or a fixed number, you write instructions for how the value should be computed from your home's current data. + +Home Assistant's templating is powered by [Jinja2](https://jinja.palletsprojects.com/), a widely used template engine in the Python world. That means you can search the web for Jinja2 examples and most of what you find will work in Home Assistant too. We add many of our own functions on top for reading states, finding entities, working with areas, and similar tasks specific to smart homes. + +Because templates are code, this is one of the more technical corners of Home Assistant. It is fair to think of it as a light form of programming. You will write small calculations, learn how to handle text and numbers together, and sometimes run into puzzling error messages. Don't worry, the [Debugging templates](/docs/templating/debugging/) page has your back when that happens. + +## You probably don't need templates + +Home Assistant is designed to be used through its interface. You can set up {% term devices %}, build {% term automations %}, create dashboards, and manage your whole smart home without ever looking at a configuration file or writing a single line of code. The {% term automation %} editor is powerful enough to handle nearly every real-world scenario, and it is getting better with every release. + +So before you invest any time on this page: templates are _not_ required to use Home Assistant. If the visual editors do what you need, you are done. Skip this section with a clear conscience. + +Templates are for when you want to go further than the interface alone allows. You might reach for them when: + +- You want a notification to say something dynamic, like "The living room is 22°C and the basement is 14°C", using live values from your sensors. +- You need an automation condition that depends on a calculation across several entities, such as "only run if more than three doors are open". +- You are creating a [template entity](/integrations/template/), a {% term sensor %} whose value is computed from other entities. +- You are processing raw data from a REST API, MQTT topic, or command-line output, and need to reshape it into something Home Assistant can use. + +If any of those sound like problems you actually have, keep reading. If not, bookmark this page and come back when you need it. + +## An example + +Imagine you want your phone to tell you the temperature when you come home. A plain notification can only say one thing: + +```text +It is warm outside. +``` + +With a template, you can include the actual temperature from your outdoor sensor: + +{% example %} +template: | + It is {{ states('sensor.outdoor_temperature') }}°C outside. +{% endexample %} + +Home Assistant replaces the bit between `{{` and `}}` with the current value every time the notification is sent. You might get: + +{% example %} +output: "It is 22.5°C outside." +{% endexample %} + +Later that day, when it is cooler, the same template produces: + +{% example %} +output: "It is 14.8°C outside." +{% endexample %} + +## What you can do with templates + +Once you are comfortable with the basics, templates can help you: + +- **Read your home's data**. Get the state of any {% term entity %}, its attributes, or information about {% term devices %}, {% term areas %}, and {% term floors %}. +- **Make decisions**. Show one message when someone is home and a different one when nobody is. Trigger an automation only when several conditions line up. +- **Do calculations**. Average values from multiple sensors, convert units, count how many lights are on, or format a number the way you want to see it. +- **Shape text**. Turn raw values into friendly sentences, lists, or table-like summaries for notifications and dashboards. +- **Process incoming data**. Parse JSON from a web API, clean up messy sensor readings, or extract only the bits you need. + +## How templates are written + +Every template mixes normal text with small pieces of code inside special markers: + +- `{{ ... }}` calculates something and inserts the result in the output. +- `{% ... %}` runs logic like [`if` and `for`](/docs/templating/loops-and-conditions/) without adding to the output. +- `{# ... #}` is a note for yourself that does not appear anywhere. + +The [Template syntax](/docs/templating/syntax/) page explains these markers in detail. + +## Try it yourself + +Home Assistant has a built-in **Template editor** that shows the result of a template while you type. It is the fastest way to experiment. + +Open it from {% my developer_template title="**Settings** > **Developer tools** > **Template**" %}. Try pasting this in: + +{% example %} +template: | + It is {{ now().strftime("%A") }}, and the time is {{ + now().strftime("%H:%M") }}. +output: "It is Saturday, and the time is 14:32." +{% endexample %} + +You should see a sentence with the current day of the week and time. Change what is inside the quotes to experiment with different formats, and watch the result update as you type. + +## Next steps + +Pick up from whichever topic is most useful to you: + +- Curious where templates show up? Read [Where to use templates](/docs/templating/where-to-use/). +- Want to understand the building blocks? Start with [Template syntax](/docs/templating/syntax/). +- Looking for ready-made examples? Head to [Common template patterns](/docs/templating/patterns/). +- Stuck on something? The [Debugging templates](/docs/templating/debugging/) page can help. diff --git a/source/_docs/templating/loops-and-conditions.markdown b/source/_docs/templating/loops-and-conditions.markdown new file mode 100644 index 000000000000..5fbfb7e7a3aa --- /dev/null +++ b/source/_docs/templating/loops-and-conditions.markdown @@ -0,0 +1,224 @@ +--- +title: "Loops and conditions" +description: "Make decisions and repeat work in your templates with if, for, and set." +related: + - docs: /template-functions/namespace/ + title: "`namespace` function" + - docs: /template-functions/range/ + title: "`range` function" + - docs: /template-functions/iif/ + title: "`iif` function" + - docs: /docs/templating/patterns/ + title: Common template patterns + - docs: /docs/templating/syntax/ + title: Template syntax +--- + +So far, the templates we've looked at have been straightforward: read a value, show it, maybe do a small calculation. But the moment your template needs to decide something ("is it cold outside?") or go through a list of things ("show me every light that is on"), you need a couple more tools. + +That is what this page is about. We will cover two ideas that work together to make templates much more useful: + +- A **condition** is a question with a yes-or-no answer that decides what happens next. "Is the front door open?" "Is the temperature below zero?" +- A **loop** is a way to do the same thing once for each item in a list. "For every light in the house, show its name and state." + +Both live inside `{% ... %}` markers, because they run logic without adding anything to the output by themselves. And both will feel familiar if you've ever written a shopping list ("if I'm out of milk, add it to the list") or followed a recipe ("for each onion, chop it finely"). + +## Conditions with if + +The `if` statement runs the text inside it only when its condition is true. When the condition is false, that text is skipped. + +{% example %} +template: | + {% if is_state('sun.sun', 'above_horizon') %} + The sun is up. + {% else %} + The sun is down. + {% endif %} +output: "The sun is up." +{% endexample %} + +Read that out loud: "If the sun is above the horizon, show 'The sun is up'. Otherwise, show 'The sun is down'." That is exactly what the template does. + +Every `if` must be closed with `{% endif %}`. The `{% else %}` part is optional but often useful. + +### Multiple paths with elif + +When you have more than two outcomes, use `{% elif %}` ("else if") to chain conditions together. Home Assistant checks them in order and runs the first one that is true. + +{% example %} +template: | + {% set temp = states('sensor.outdoor_temperature') | float(0) %} + {% if temp < 0 %} + Freezing. + {% elif temp < 15 %} + Cold. + {% elif temp < 25 %} + Comfortable. + {% else %} + Warm. + {% endif %} +output: "Comfortable." +{% endexample %} + +You can add as many `elif` branches as you need. + +### Inline if for one-liners + +If you only need to pick between two values, you can write the `if` on one line. This is often handier inside `{{ ... }}` than writing a whole `if/else` block. + +{% example %} +template: | + {% set temp = 22 %} + It is {{ 'warm' if temp > 20 else 'cool' }}. +output: "It is warm." +{% endexample %} + +Read it left to right: "warm, if the temperature is over 20, otherwise cool". Natural English. + +## Loops with for + +A `for` loop repeats whatever is inside it once for each item in a list. The word right after `for` is a name you pick; each time the loop runs, it holds the current item. + +{% example %} +template: | + {% for light in states.light %} + {{ light.name }}: {{ light.state }} + {% endfor %} +output: | + Kitchen: on + Living Room: off + Bedroom: off +{% endexample %} + +In plain English: "for each light in the list of lights, show its name and state". The name `light` is only a label; you could call it `x` or `item` or `thingy` and it would still work. + +Every `for` must be closed with `{% endfor %}`. + +### Filtering with if inside a for + +You can tell a `for` loop to skip items that don't match a condition by adding `if` at the end: + +{% example %} +template: | + Lights that are on: + {% for light in states.light if light.state == 'on' %} + - {{ light.name }} + {% endfor %} +output: | + Lights that are on: + - Kitchen + - Hallway +{% endexample %} + +That reads as: "for each light in the list of lights, where its state is 'on', show a bullet with the name". Items that don't match are skipped. + +### Knowing where you are: the loop variable + +Inside a loop, templates give you a special variable called `loop` that tells you where you are. These are the fields you'll use most: + +- `loop.first`: `True` on the first time through, `False` otherwise. +- `loop.last`: `True` on the last time through. +- `loop.index`: the current position, starting at 1. +- `loop.index0`: the current position, starting at 0 (handy if you're used to programming). +- `loop.length`: the total number of items. + +That last-item check is useful when you want to format a list neatly: + +{% example %} +template: | + {% for person in states.person %} + {{ loop.index }}. {{ person.name }}{% if not loop.last %},{% endif %} + {% endfor %} +output: | + 1. Frenck, + 2. Paulus, + 3. Zack +{% endexample %} + +The comma is added for every item except the last one. + +## Variables with set + +Templates can get long, and typing the same thing twice makes them harder to read. `set` lets you give a value a name so you can reuse it. + +{% example %} +template: | + {% set temp = states('sensor.outdoor_temperature') | float(0) %} + {% set unit = state_attr('sensor.outdoor_temperature', + 'unit_of_measurement') %} + It is {{ temp | round(1) }} {{ unit }} outside. +output: "It is 22.5 °C outside." +{% endexample %} + +Now `temp` and `unit` can be used anywhere in the template. If the name of the sensor changes, you only need to update it once. + +### A common gotcha: variables and loops + +Here is something that catches everyone at least once. A variable changed inside a loop does not stick around after the loop ends. + +{% example %} +template: | + {% set count = 0 %} + {% for light in states.light if light.state == 'on' %} + {% set count = count + 1 %} + {% endfor %} + {{ count }} +output: "0" +{% endexample %} + +You'd think `count` would end up at 3 (or however many lights are on), but it doesn't. The `set count = count + 1` inside the loop creates a brand new `count` each time, one that only exists inside that loop iteration. The outer `count` never changes. + +The fix is to use a [`namespace`](/template-functions/namespace/). Think of a namespace as a small box that holds values. Changes to what's inside the box persist, because you're updating the box, not replacing it. + +{% example %} +template: | + {% set ns = namespace(count=0) %} + {% for light in states.light if light.state == 'on' %} + {% set ns.count = ns.count + 1 %} + {% endfor %} + {{ ns.count }} +output: "3" +{% endexample %} + +This looks weird the first time, but it becomes natural quickly. Whenever you need to count something or build up a result inside a loop, reach for [`namespace`](/template-functions/namespace/). + +## Breaking out of loops early + +Sometimes you want a loop to stop before reaching the end of the list. Home Assistant has two extra statements for that: + +- `{% break %}` stops the loop right away. +- `{% continue %}` skips to the next item without finishing the current one. + +{% example %} +template: | + {# Show the first three lights that are on #} + {% set ns = namespace(shown=0) %} + {% for light in states.light %} + {% if light.state != 'on' %} + {% continue %} + {% endif %} + {% if ns.shown >= 3 %} + {% break %} + {% endif %} + {{ light.name }} + {% set ns.shown = ns.shown + 1 %} + {% endfor %} +output: | + Kitchen + Hallway + Desk +{% endexample %} + +This one skips lights that are off (with `continue`), and stops entirely once three have been shown (with `break`). + +## The do statement + +You may see `{% do %}` mentioned in Jinja documentation elsewhere. It runs an expression without printing anything, which in plain Jinja is useful for things like `{% do items.append(value) %}` to mutate a list. + +Home Assistant's template environment is sandboxed. Mutation methods like `.append()`, `.pop()`, and `.update()` are blocked for safety, so `{% do %}` is rarely needed in Home Assistant templates. To build up a list or counter across loop iterations, use a [`namespace`](/template-functions/namespace/) with `{% set %}` instead. + +## Next steps + +- For the full list of filters and tests you can use inside `if` conditions, see the [template functions reference](/template-functions/). +- Common counting and aggregation patterns live on the [Common template patterns](/docs/templating/patterns/) page. +- When using these statements inside YAML, keep the [Templates in YAML](/docs/templating/yaml/) page handy for quoting rules. diff --git a/source/_docs/templating/patterns.markdown b/source/_docs/templating/patterns.markdown new file mode 100644 index 000000000000..87bff831572b --- /dev/null +++ b/source/_docs/templating/patterns.markdown @@ -0,0 +1,521 @@ +--- +title: "Common template patterns" +description: "Cookbook-style recipes for the things you'll actually want to do with templates." +related: + - docs: /docs/templating/types/ + title: Types and conversion + - docs: /template-functions/selectattr/ + title: "`selectattr` filter" + - docs: /template-functions/rejectattr/ + title: "`rejectattr` filter" + - docs: /template-functions/map/ + title: "`map` filter" + - docs: /template-functions/namespace/ + title: "`namespace` function" + - docs: /template-functions/from_json/ + title: "`from_json` filter" + - docs: /docs/templating/debugging/ + title: Debugging templates +--- + +Templates are most useful when you can reach for an example that already does what you need. This page collects recipes for the situations that come up again and again: counting things, summarizing data, building nice-sounding sentences, and dealing with values that might be missing. + +Each recipe explains what it does, shows you a working template, and links to the reference pages for the functions it uses. Copy the template you need, swap in your own entity IDs, and adjust the wording. + +## Counting entities + +Counting is one of the most common things you do with templates. "How many lights are on?" "How many windows are open?" These templates follow the same shape every time: list the entities, filter them, count the result. + +{% tip %} +If all you need is a count on a dashboard, a [Group helper](/integrations/group/) can gather the entities and its state shows how many are active. No template needed. +{% endtip %} + +**Count how many lights are on:** + +{% example %} +template: | + {{ states.light | selectattr('state', 'eq', 'on') | list | count }} +output: "3" +{% endexample %} + +Read the template left to right: take every light, keep only the ones whose state is `on`, turn that into a list, count the items in the list. + +**Count how many doors are open:** + +{% example %} +template: | + {{ + states.binary_sensor + | selectattr('attributes.device_class', 'eq', 'door') + | selectattr('state', 'eq', 'on') + | list | count + }} +output: "2" +{% endexample %} + +Here the filter is stricter. First keep only binary sensors whose device class is `door`, then only the ones that are `on` (binary sensors that represent doors report `on` when the door is open). + +See [`selectattr`](/template-functions/selectattr/), [`list`](/template-functions/list/), and [`count`](/template-functions/count/). + +## Finding the highest or lowest value + +When you have several sensors of the same kind, you often want to pick out the extreme. The warmest room. The coldest fridge. The lowest battery. The heaviest power draw. + +{% tip %} +A [Group helper](/integrations/group/) can pick the minimum, maximum, or mean from a group of sensors and expose it as a regular sensor. No template needed if you only need the value. +{% endtip %} + +**The lowest battery:** + +{% example %} +template: | + {{ + states.sensor + | selectattr('attributes.device_class', 'eq', 'battery') + | selectattr('entity_id', 'has_value') + | map(attribute='state') + | map('float') + | min + | round(0) + }}% +output: "18%" +{% endexample %} + +We gather all battery sensors, use [`has_value`](/template-functions/has_value/) to remove any that are `unknown` or `unavailable`, convert their states to numbers, then [`min`](/template-functions/min/) picks the smallest. The `| float` conversion is important because sensor states are text, and comparing text alphabetically gives wrong results (`"9"` would beat `"23"` because `"9"` comes after `"2"`). + +**The warmest room (with the room name):** + +The previous example gives you the value but not which sensor it came from. When you need both, loop through the sensors and track the winner in a [`namespace`](/template-functions/namespace/): + +{% example %} +template: | + {% set temps = states.sensor + | selectattr('attributes.device_class', 'eq', 'temperature') + | selectattr('entity_id', 'has_value') + | list %} + {% set ns = namespace(warmest=temps[0]) %} + {% for sensor in temps %} + {% if sensor.state | float > ns.warmest.state | float %} + {% set ns.warmest = sensor %} + {% endif %} + {% endfor %} + {{ ns.warmest.name }}: {{ ns.warmest.state }}°C +output: "Kitchen: 23.5°C" +{% endexample %} + +Same filtering as before, but instead of extracting the values and losing track of which entity they belong to, we keep the full entity reference. The loop compares each sensor's state as a number and remembers the winner. At the end, we can show both the name and the value. + +See also [`map`](/template-functions/map/) and [`namespace`](/template-functions/namespace/). + +## Safe numbers from a sensor + +This one deserves its own section because it comes up constantly. Sensors return their state as text, even when the content looks like a number. If you try to do math on that text directly, you get errors. The fix is always the same: convert with a fallback. + +{% example %} +template: | + {% set temp = states('sensor.outdoor_temperature') | float(0) %} + {{ temp | round(1) }}°C +output: "22.5°C" +{% endexample %} + +Use [`float(0)`](/template-functions/float/) for decimals and [`int(0)`](/template-functions/int/) for whole numbers. The `0` inside the parentheses is the fallback value: if the sensor is offline or reports something that cannot be converted to a number, the template uses `0` instead of crashing. + +Pick a fallback value that makes sense for your situation. For a temperature, `0` might be misleading. For a counter, `0` is fine. For a power draw, `0` is accurate. Think about what should happen when things go wrong. + +## Building a sentence from a list of names + +When you're sending a notification, writing "Kitchen, Hallway, Desk" looks robotic. "Kitchen, Hallway, and Desk are on." reads much better. Here is a template that does that formatting with a proper Oxford comma: + +**All lights that are on, in one sentence:** + +{% example %} +template: | + {% set lights = states.light + | selectattr('state', 'eq', 'on') + | map(attribute='name') | list %} + {% if lights | count == 0 %} + No lights are on. + {% elif lights | count == 1 %} + {{ lights[0] }} is on. + {% elif lights | count == 2 %} + {{ lights[0] }} and {{ lights[1] }} are on. + {% else %} + {{ lights[:-1] | join(', ') }}, and {{ lights[-1] }} are on. + {% endif %} +output: "Kitchen, Hallway, and Desk are on." +{% endexample %} + +The template handles three cases: nothing, exactly one thing, or more than one. The tricky part is `lights[:-1]` which means "all items except the last", and `lights[-1]` which means "the last item". That lets us put "and" before the final name. + +See [`join`](/template-functions/join/) and [`map`](/template-functions/map/). + +## Picking the right word: pluralization + +"1 door is open" and "3 doors are open" both need to sound right. Templates handle this with a small [inline `if`](/docs/templating/loops-and-conditions/#inline-if-for-one-liners). + +{% example %} +template: | + {% set count = states.binary_sensor + | selectattr('attributes.device_class', 'eq', 'door') + | selectattr('state', 'eq', 'on') | list | count %} + {{ count }} door{{ 's' if count != 1 }} + {{- ' is' if count == 1 else ' are' }} open. +output: "2 doors are open." +{% endexample %} + +Two small tricks there: `{{ 's' if count != 1 }}` adds an `s` when the count is not 1 (so "doors" instead of "door"), and `{{ 'is' if count == 1 else 'are' }}` picks the right verb. + +## Time differences + +Home Assistant records when each state last changed, and you can reach that timestamp from any state in a template. That opens up "how long ago" questions. + +**How long ago did the front door change?** + +{% example %} +template: | + The front door changed {{ + relative_time(states.binary_sensor.front_door.last_changed) + }} ago. +output: "The front door changed 15 minutes ago." +{% endexample %} + +[`relative_time`](/template-functions/relative_time/) produces nice human-readable text like "15 minutes" or "2 hours". + +**Has it been more than 10 minutes?** + +{% example %} +template: | + {{ + (now() - states.binary_sensor.front_door.last_changed) + .total_seconds() > 600 + }} +output: "True" +{% endexample %} + +`now()` gives you the current moment. Subtracting two moments gives you the duration between them. `.total_seconds()` turns that duration into a number of seconds, and `600` is ten minutes. This pattern is handy for conditions like "only notify me if the door has been open for more than 10 minutes". + +See [`now`](/template-functions/now/) and [`relative_time`](/template-functions/relative_time/). + +## Formatting timestamps + +Humans read dates and times in all sorts of ways. Templates use `strftime` (the standard date-formatting tool) to turn a timestamp into whichever shape you want. + +{% example %} +template: | + {{ now().strftime('%A %B %-d at %H:%M') }} +output: "Saturday April 4 at 14:30" +{% endexample %} + +The mysterious characters (`%A`, `%B`, `%H`, `%M`) are format codes. Each one stands for a piece of the date or time. You can mix them freely with plain text. + +Common codes: + +- `%H:%M` for 24-hour time (`14:30`). +- `%I:%M %p` for 12-hour time with AM/PM (`02:30 PM`). +- `%A` for the full weekday name (`Saturday`). +- `%B` for the full month name (`April`). +- `%Y-%m-%d` for ISO-style dates (`2026-04-04`). + +The Python documentation has the [full list of format codes](https://docs.python.org/3/library/datetime.html#strftime-and-strptime-format-codes) if you need something unusual. + +## Summing values across sensors + +When you have several sensors measuring the same thing, summing them gives you a household total. Total power draw, total water usage, total number of people home. + +{% tip %} +A [Group helper](/integrations/group/) with a `sum` type adds up its members and exposes the result as a sensor. For energy specifically, the [Energy dashboard](/docs/energy/) handles totals automatically. +{% endtip %} + +**Total power draw across all power sensors:** + +{% example %} +template: | + {{ states.sensor + | selectattr('attributes.device_class', 'eq', 'power') + | selectattr('entity_id', 'has_value') + | map(attribute='state') | map('float') | sum | round(1) }} W +output: "427.3 W" +{% endexample %} + +The steps: keep only power sensors, skip offline ones, pull out their states, convert to numbers, add them up, round the total. See [`sum`](/template-functions/sum/). + +## Parsing JSON + +REST sensors, MQTT topics, and command-line sensors often return their data as JSON. [`from_json`](/template-functions/from_json/) turns JSON text into something you can read with dots and brackets. + +{% example %} +template: | + {% set data = '{"temp": 22.5, "humidity": 60}' | from_json %} + Temperature: {{ data.temp }}°C + Humidity: {{ data.humidity }}% +output: | + Temperature: 22.5°C + Humidity: 60% +{% endexample %} + +In real use, you'd pass a variable (like `value` from an MQTT payload) instead of a hardcoded piece of text. + +## Handling missing values + +Home Assistant entities can go missing, go offline, or report `unknown`. Templates that don't handle these cases produce noisy output or outright errors. Add a safety net. + +**With [`default`](/template-functions/default/):** + +{% example %} +template: | + {{ states('sensor.might_not_exist') | default('not available') }} +output: "not available" +{% endexample %} + +The [`default`](/template-functions/default/) filter replaces a missing value with one you choose. + +**With [`has_value`](/template-functions/has_value/):** + +{% example %} +template: | + {% if has_value('sensor.outdoor_temperature') %} + {{ states('sensor.outdoor_temperature') }}°C + {% else %} + Temperature is not available. + {% endif %} +output: "22.5°C" +{% endexample %} + +Use [`has_value`](/template-functions/has_value/) when you want a completely different branch for missing data, not only a replacement word. + +## Finding entities by device class + +The **device class** tells Home Assistant what kind of thing a sensor measures: temperature, humidity, battery, power, door, motion, and many others. Filtering by device class is the most reliable way to pick out "all the X sensors" without listing them by name. + +{% tip %} +On dashboards, a grid of [Tile cards](/dashboards/tile/) shows individual entities cleanly. Templates are the right tool when you need the result as text inside an automation or notification. +{% endtip %} + +**All temperature sensors in the house:** + +{% example %} +template: | + {% for sensor in states.sensor + | selectattr('attributes.device_class', 'eq', 'temperature') + | selectattr('entity_id', 'has_value') %} + {{ sensor.name }}: {{ sensor.state }}°C + {% endfor %} +output: | + Living room: 22.5°C + Kitchen: 23.5°C + Bedroom: 20.1°C +{% endexample %} + +**All open doors and windows:** + +{% example %} +template: | + {{ + states.binary_sensor + | selectattr('attributes.device_class', 'in', ['door', 'window']) + | selectattr('state', 'eq', 'on') + | map(attribute='name') | join(', ') + }} +output: "Front door, Kitchen window" +{% endexample %} + +The pattern is always the same: take a list of entities, keep only those with the device class you care about, drop the offline ones, then do whatever you need with the remaining list. Swap `temperature` for `humidity`, `battery`, `power`, or any other device class to cover a different kind of sensor. + +**All temperature sensors on a specific floor:** + +{% example %} +template: | + {% set floor_sensors = floor_entities('first_floor') %} + {% for entity in floor_sensors %} + {% if entity.startswith('sensor.') + and is_state_attr(entity, 'device_class', 'temperature') %} + {{ state_attr(entity, 'friendly_name') }}: {{ states(entity) }}°C + {% endif %} + {% endfor %} +output: | + Living room: 22.5°C + Kitchen: 23.5°C +{% endexample %} + +This combines [`floor_entities`](/template-functions/floor_entities/) with a device-class check. Use [`area_entities`](/template-functions/area_entities/) or [`label_entities`](/template-functions/label_entities/) in the same shape when you want to filter by area or label instead. + +## Aggregating entities by label + +{% term Labels %} let you group entities in ways that don't match your area/floor structure. A classic use is collecting everything you want to control together, like all TRVs or all radiators in a "Heating Zone". + +**Any radiator calling for heat (boiler demand signal):** + +{% example %} +template: | + {{ + label_entities('Heating Zone') + | select('is_state_attr', 'hvac_action', 'heating') + | list | count > 0 + }} +output: "True" +{% endexample %} + +This template returns `True` whenever at least one climate {% term entity %} with the `Heating Zone` label has its `hvac_action` attribute set to `heating`. Drop this into a template binary sensor with `device_class: heat` and you have a boiler demand signal. Add it as a template trigger and you can turn the boiler on exactly when heat is needed. + +## Which entity changed most recently + +When you have a group of similar sensors (doors, motion, windows), you sometimes want to know which one was active last. Every state tracks a `last_changed` timestamp, so you can sort by it. + +{% example %} +template: | + {% set doors = states.binary_sensor + | selectattr('attributes.device_class', 'eq', 'door') + | list %} + {% set latest = doors | max(attribute='last_changed') %} + {{ latest.name }} changed {{ relative_time(latest.last_changed) }} ago. +output: "Front door changed 3 minutes ago." +{% endexample %} + +## Hold-last-value (ignore intermittent dropouts) + +A sensor that goes `unknown` or `unavailable` for a moment can wreck your automations. A trigger-based template sensor lets you freeze the value during dropouts so downstream templates see a stable number. + +{% example %} +automation: | + template: + - triggers: + - trigger: state + entity_id: sensor.outdoor_temperature + not_to: + - unknown + - unavailable + sensor: + - name: "Outdoor temperature (held)" + unique_id: outdoor_temperature_held + state: "{{ states('sensor.outdoor_temperature') }}" + device_class: temperature + state_class: measurement + unit_of_measurement: "°C" +output: "22.5 (even when the original sensor briefly goes unavailable)" +{% endexample %} + +The trigger uses `not_to` so the sensor only updates when the source reports a real value. Between updates, the held sensor keeps whatever it had last. Pair this with a long-term alarm on the original sensor if you want to be notified about extended outages. + +## Latch: keep a condition on until another clears it + +Sometimes a condition needs to "latch" on: once it becomes true, it stays true until a different condition clears it. Heating control with hysteresis is a common example. Turn the heater on when the temperature drops below 18°, keep it on until the temperature rises above 20°. + +{% tip %} +For heating specifically, the [Generic thermostat](/integrations/generic_thermostat/) integration has hysteresis built in. For a generic two-threshold switch, see the [Threshold helper](/integrations/threshold/). +{% endtip %} + +{% example %} +template: | + {{ + states('sensor.temperature') | float(20) < 18 + or (this.state == 'on' + and states('sensor.temperature') | float(20) < 20) + }} +output: "True" +{% endexample %} + +Reading it aloud: "the heater is on if the temperature is below 18, OR if the heater is already on AND the temperature is still below 20". Put this in a template binary sensor; the `this.state` reference reads the binary sensor's own previous value, creating the latching behavior. + +## Loop over a dictionary + +Dictionaries come from JSON responses, entity attributes, action responses, and many other places. Iterate through one with `.items()`: + +{% example %} +template: | + {% set data = {"temperature": 22.5, "humidity": 54, "pressure": 1013} %} + {% for key, value in data.items() %} + {{ key }}: {{ value }} + {% endfor %} +output: | + temperature: 22.5 + humidity: 54 + pressure: 1013 +{% endexample %} + +To list all attributes of an entity, call `.items()` on its `attributes`: + +{% example %} +template: | + {% for name, value in states.sensor.outdoor_temperature.attributes.items() %} + {{ name }}: {{ value }} + {% endfor %} +output: | + unit_of_measurement: °C + device_class: temperature + friendly_name: Outdoor temperature +{% endexample %} + +## Data navigation cheat sheet + +Templates read data using three tools: dots, brackets, and iteration. Here are the common shapes you will run into. + +**Attribute with a dot:** + +{% example %} +template: '{{ states.sensor.outdoor_temperature.state }}' +output: "22.5" +{% endexample %} + +**Dictionary key with a bracket:** + +{% example %} +template: | + {% set data = {"temp": 22.5, "unit": "°C"} %} + {{ data['temp'] }}{{ data['unit'] }} +output: "22.5°C" +{% endexample %} + +**List item by position (starting at 0):** + +{% example %} +template: | + {% set sources = ["Spotify", "Bluetooth", "TV"] %} + First: {{ sources[0] }} + Last: {{ sources[-1] }} +output: | + First: Spotify + Last: TV +{% endexample %} + +**Nested access:** + +{% example %} +template: | + {% set payload = {"sensor": {"readings": [22.5, 23.0, 22.8]}} %} + First reading: {{ payload['sensor']['readings'][0] }} + Via dots: {{ payload.sensor.readings[0] }} +output: | + First reading: 22.5 + Via dots: 22.5 +{% endexample %} + +**List of dicts (common in action responses):** + +{% example %} +template: | + {% set events = [ + {"name": "Meeting", "start": "09:00"}, + {"name": "Lunch", "start": "12:00"} + ] %} + {% for event in events %} + {{ event.start }}: {{ event.name }} + {% endfor %} +output: | + 09:00: Meeting + 12:00: Lunch +{% endexample %} + +When you don't know the shape of your data, pipe it through [`tojson`](/template-functions/tojson/) in the template editor. That gives you a JSON view of exactly what you're working with. + +## Next steps + +- If you need to understand a pattern better, look up its functions in the [template functions reference](/template-functions/). +- When a template does not work, see [Debugging templates](/docs/templating/debugging/). + +## Still stuck? + +The Home Assistant community is quick to help: join [Discord](https://discord.gg/home-assistant) for real-time chat, post on the [community forum](https://community.home-assistant.io) with your template and expected result, or share on [our subreddit](https://reddit.com/r/homeassistant). The forum has thousands of community templates for all kinds of situations. + +{% tip %} +AI assistants like ChatGPT or Claude can also explain or fix templates when you describe what you want in plain language. +{% endtip %} diff --git a/source/_docs/templating/python-methods.markdown b/source/_docs/templating/python-methods.markdown new file mode 100644 index 000000000000..09ec21b3fc38 --- /dev/null +++ b/source/_docs/templating/python-methods.markdown @@ -0,0 +1,244 @@ +--- +title: "Python methods you can use in templates" +description: "A reference of common Python string, list, and dictionary methods available in templates." +related: + - docs: /template-functions/ + title: Template functions reference + - docs: /template-functions/upper/ + title: "`upper` filter" + - docs: /template-functions/replace/ + title: "`replace` filter" + - docs: /template-functions/join/ + title: "`join` filter" + - docs: /docs/templating/dates-and-times/ + title: Working with dates and times +--- + +Templates are built on top of Python, and many standard Python methods are available to you. You call them the same way you would in Python: put a dot after the value, then the method name and parentheses. These methods are not listed in the [template functions reference](/template-functions/) because they come from Python itself, but they solve a lot of common tasks. + +This page is a cheat sheet of the ones that come up most often in Home Assistant templates. When in doubt, open the {% my developer_template title="template editor" %} and try it. + +## String methods + +Everything Home Assistant stores as text supports these. Since entity states are always text (until you convert them), these work directly on `states()` results. + +### split + +Splits a piece of text into a list at each occurrence of a separator. + +{% example %} +template: | + {{ "light.living_room".split(".") }} +output: "['light', 'living_room']" +{% endexample %} + +Useful for breaking entity IDs into their domain and name, splitting comma-separated strings, or pulling words out of a sentence. + +### replace + +Replaces one piece of text with another. + +{% example %} +template: '{{ "light.living_room".replace("_", " ") }}' +output: "light.living room" +{% endexample %} + +### lower, upper, title, capitalize + +Change the case of a piece of text. + +{% example %} +template: | + Lower: {{ "Living Room".lower() }} + Upper: {{ "Living Room".upper() }} + Title: {{ "living room".title() }} + Capitalize: {{ "living room".capitalize() }} +output: | + Lower: living room + Upper: LIVING ROOM + Title: Living Room + Capitalize: Living room +{% endexample %} + +### strip + +Removes whitespace from the start and end of a piece of text. + +{% example %} +template: "'{{ ' hello world '.strip() }}'" +output: "'hello world'" +{% endexample %} + +### startswith, endswith + +Check whether a piece of text begins or ends with another piece of text. + +{% example %} +template: | + {{ "sensor.outdoor_temperature".startswith("sensor.") }} + {{ "image.jpg".endswith(".jpg") }} +output: | + True + True +{% endexample %} + +These are very handy for filtering entity IDs by domain. + +### find, index, count + +`find` returns the position of a piece of text, or `-1` if not found. `index` does the same but raises an error when not found. `count` counts how many times it appears. + +{% example %} +template: | + {{ "sensor.outdoor_temperature".find("outdoor") }} + {{ "hello world hello".count("hello") }} +output: | + 7 + 2 +{% endexample %} + +### join + +Joins a list into a single piece of text using a separator. + +{% example %} +template: "{{ ', '.join(['apples', 'oranges', 'pears']) }}" +output: "apples, oranges, pears" +{% endexample %} + +Note the order: you call `.join()` on the separator, passing the list in. There is also a [`join` filter](/template-functions/join/) that reads more naturally: `['apples', 'oranges', 'pears'] | join(', ')`. + +### format + +Inserts values into a piece of text using placeholders. + +{% example %} +template: '{{ "Temperature: {} {}".format(22.5, "°C") }}' +output: "Temperature: 22.5 °C" +{% endexample %} + +## Dictionary methods + +Dictionaries show up in entity attributes, JSON responses, and action responses. + +### items + +Iterate over a dictionary's key-value pairs. + +{% example %} +template: | + {% for key, value in {"a": 1, "b": 2}.items() %} + {{ key }} = {{ value }} + {% endfor %} +output: | + a = 1 + b = 2 +{% endexample %} + +### keys, values + +Get only the keys or only the values. + +{% example %} +template: | + {% set data = {"temp": 22.5, "humidity": 54} %} + Keys: {{ data.keys() | list }} + Values: {{ data.values() | list }} +output: | + Keys: ['temp', 'humidity'] + Values: [22.5, 54] +{% endexample %} + +### get + +Fetch a value by key, with a fallback if the key is missing. This is safer than bracket lookup, which errors on missing keys. + +{% example %} +template: | + {% set data = {"name": "Frenck"} %} + Name: {{ data.get("name", "unknown") }} + Age: {{ data.get("age", "unknown") }} +output: | + Name: Frenck + Age: unknown +{% endexample %} + +### When a key name conflicts with a dict method + +If a dictionary has a key with the same name as a dict method (like `values`, `keys`, `items`, or `get`), dot notation returns the method, not the value. This commonly happens when parsing API responses. + +{% example %} +template: | + {% set response = {"status": "ok", "values": [1, 2, 3]} %} + {{ response['values'] }} +output: "[1, 2, 3]" +{% endexample %} + +Use bracket notation (`response['values']`) when a key might collide with a method. It always reaches the dictionary value first. + +## Datetime methods + +When you have a datetime (for example, from `now()`), you can reach into its parts or format it. + +### Accessing parts + +{% example %} +template: | + Hour: {{ now().hour }} + Minute: {{ now().minute }} + Weekday: {{ now().weekday() }} + Day: {{ now().day }} +output: | + Hour: 14 + Minute: 30 + Weekday: 5 + Day: 4 +{% endexample %} + +`weekday()` returns Monday as `0` through Sunday as `6`. Use `isoweekday()` if you prefer Monday as `1` through Sunday as `7`. + +### Formatting with strftime + +`strftime` formats a datetime using format codes. It is covered in detail on the [Working with dates and times](/docs/templating/dates-and-times/) page, but here is the short version: + +{% example %} +template: | + {{ now().strftime('%A, %B %-d, %Y') }} +output: "Saturday, April 4, 2026" +{% endexample %} + +### Parsing with strptime + +The reverse of `strftime`. Parses a piece of text into a datetime using a format string. See [`strptime`](/template-functions/strptime/). + +## Number methods + +Numbers have a few helpful methods. + +### is_integer + +Check whether a floating-point number has no decimal part. + +{% example %} +template: | + {{ (10.0).is_integer() }} + {{ (10.5).is_integer() }} +output: | + True + False +{% endexample %} + +## When to use methods vs filters + +Both work for many tasks. Pick whichever reads better: + +- **Methods** (with a dot) come from Python. `"text".upper()`, `data.get("key")`. +- **Filters** (with a pipe) come from the template engine and Home Assistant. `"text" | upper`, `data | default("key")`. + +Filters chain more naturally when you have several transformations. Methods can be clearer for a single transformation or when the method returns something unusual. The [template functions reference](/template-functions/) lists all the filters Home Assistant provides. + +## Next steps + +- If you're looking for a specific transformation, check the [template functions reference](/template-functions/) first. +- For template debugging, see [Debugging templates](/docs/templating/debugging/). +- For date and time formatting in particular, read [Working with dates and times](/docs/templating/dates-and-times/). diff --git a/source/_docs/templating/states.markdown b/source/_docs/templating/states.markdown new file mode 100644 index 000000000000..c5b4f0e7248a --- /dev/null +++ b/source/_docs/templating/states.markdown @@ -0,0 +1,241 @@ +--- +title: "Working with states" +description: "Read states, attributes, and entity information in your templates." +related: + - docs: /docs/templating/types/ + title: Types and conversion + - docs: /template-functions/states/ + title: "`states` function" + - docs: /template-functions/state_attr/ + title: "`state_attr` function" + - docs: /template-functions/is_state/ + title: "`is_state` function" + - docs: /template-functions/has_value/ + title: "`has_value` function" + - docs: /template-functions/#state + title: All state functions + - docs: /template-functions/#entity + title: All entity functions +--- + +Home Assistant keeps track of everything in your home as a collection of {% term entities %}. Each entity has a **state** (its current value) and often a few **attributes** (extra details about it). When you write a template, you are almost always reading state or attributes from one or more entities. + +This page explains how to get at that information inside a template. + +## First, go look at your states + +Before you write a single line of template, spend five minutes at {% my developer_states title="**Settings** > **Developer tools** > **States**" %}. This is where Home Assistant shows you every entity it knows about, its current state, and all of its attributes. + +For example, you might see something like this for your outdoor thermometer: + +```text +Entity: sensor.outdoor_temperature +State: 22.5 +Attributes: + unit_of_measurement: °C + device_class: temperature + friendly_name: Outdoor temperature +``` + +This one entity has: + +- An **entity ID** (`sensor.outdoor_temperature`). This is the name you use to look it up. +- A **state** (`22.5`). That is the entity's main value. +- Several **attributes** (unit of measurement, device class, friendly name). These are extra pieces of information that go along with the state. + +When you write `{{ states('sensor.outdoor_temperature') }}`, Home Assistant looks up that exact entity ID and gives you back its state. It is that direct. + +{% tip %} +Keep the Developer Tools > States page open in a separate browser tab while you write templates. You can search for entities, see exactly what state and attributes they have, and make sure you are spelling entity IDs correctly. It is the single most useful debugging habit you can build. +{% endtip %} + +## Reading a state + +The [`states`](/template-functions/states/) function gives you the current state of an entity. You pass it the entity ID as text. + +{% example %} +template: | + The kitchen light is {{ states('light.kitchen') }}. +output: "The kitchen light is on." +{% endexample %} + +### Every state is text + +Here is something that catches _everyone_ at least once. **Home Assistant stores every entity state as text.** Even when a sensor looks like it's giving you a number, `states()` hands it back as a piece of text. So `states('sensor.outdoor_temperature')` returns the text `'22.5'`, not the number `22.5`. + +Why does that matter? Because you can't do math with text, and comparing text to a number gives surprising results. For example, `'6' < '10'` is `False` (text is sorted alphabetically, so `'6'` comes after `'1'`). If you try to do math directly, you will get an error or the wrong answer. + +{% example %} +template: | + {{ states('sensor.outdoor_temperature') | float(0) + 5 }} +output: "27.5" +{% endexample %} + +The `| float(0)` part converts the text to a number. The `0` is a fallback: if the conversion fails (maybe the sensor is offline), the template uses `0` instead of crashing. + +**Rule of thumb:** whenever you do math or number comparisons on a sensor state, add `| float(0)` or `| int(0)` first. It is not optional, it is how templates work. + +### States have a 255 character limit + +The text stored in an entity's state can be at most 255 characters long. Home Assistant enforces this limit so the state can fit in the database and dashboards. If your template sensor needs to produce something longer (say, a list of names, a formatted table, or a long paragraph), store it in an attribute instead. Attributes don't have the same limit. + +### When an entity is missing or unavailable + +Home Assistant has two special state values for when things go wrong: + +- **`unknown`** means the entity exists but Home Assistant does not know its value right now. +- **`unavailable`** means the entity cannot be reached at all. Maybe a device is offline or an integration failed to load. + +And if you ask for an entity that does not exist at all, you get the text `unknown` back as well. + +Templates that depend on live values should handle these cases gracefully. Adding a number fallback (`| float(0)`) fixes most math problems. For decisions, use [`has_value`](/template-functions/has_value/) (covered below). + +## Reading an attribute + +Attributes carry extra details about an entity. A light has attributes for brightness and color. A weather entity has attributes for forecast data. A media player has attributes for the current track. + +To read one, use [`state_attr`](/template-functions/state_attr/): + +{% example %} +template: | + The kitchen light is at {{ state_attr('light.kitchen', 'brightness') }}. +output: "The kitchen light is at 192." +{% endexample %} + +Like with states, if the entity does not exist or the attribute is not set, you get nothing back (`none`). For math, add a fallback: + +{% example %} +template: | + {{ state_attr('light.kitchen', 'brightness') | int(0) }} +output: "192" +{% endexample %} + +You can find an entity's attribute names by looking at Developer Tools > States. + +## Checking a state + +You can compare a state with `==`, but there is a dedicated function that is cleaner and handles missing entities without surprises: [`is_state`](/template-functions/is_state/). + +{% example %} +template: | + {{ is_state('light.kitchen', 'on') }} +output: "True" +{% endexample %} + +This reads naturally: "is the state of `light.kitchen` equal to `on`?". The answer is `True` or `False`. + +There is a matching function for attributes, [`is_state_attr`](/template-functions/is_state_attr/): + +{% example %} +template: | + {{ is_state_attr('media_player.living_room', 'source', 'Spotify') }} +output: "True" +{% endexample %} + +And [`has_value`](/template-functions/has_value/) checks whether an entity has a usable state at all (not `unknown` or `unavailable`): + +{% example %} +template: | + {% if has_value('sensor.outdoor_temperature') %} + It is {{ states('sensor.outdoor_temperature') }}°C outside. + {% else %} + The outdoor sensor is unavailable. + {% endif %} +output: "It is 22.5°C outside." +{% endexample %} + +Use [`has_value`](/template-functions/has_value/) whenever you want to fall back to a friendly message instead of showing "unavailable" on a dashboard or in a notification. + +## Getting a list of entities + +`states.domain` gives you every entity in that {% term domain %} (the first part of an entity ID, like `light.` or `sensor.`). This is how you count, filter, and iterate over groups of entities. + +{% example %} +template: | + There are {{ states.light | count }} lights in total. +output: "There are 12 lights in total." +{% endexample %} + +Combine it with [`selectattr`](/template-functions/selectattr/) to filter the list down to what you care about. [`selectattr`](/template-functions/selectattr/) reads as "select entities where this attribute equals this value": + +{% example %} +template: | + Lights that are on: + {% for light in states.light | selectattr('state', 'eq', 'on') %} + - {{ light.name }} + {% endfor %} +output: | + Lights that are on: + - Kitchen + - Hallway + - Desk +{% endexample %} + +## Finding entities by area, device, label, or floor + +Home Assistant comes with a family of functions for finding entities grouped by how you've organized them: + +- [`area_entities`](/template-functions/area_entities/) returns every entity in an {% term area %}. +- [`device_entities`](/template-functions/device_entities/) returns every entity tied to a {% term device %}. +- [`label_entities`](/template-functions/label_entities/) returns every entity carrying a given {% term label %}. +- [`floor_entities`](/template-functions/floor_entities/) returns every entity on a {% term floor %}. +- [`integration_entities`](/template-functions/integration_entities/) returns every entity created by a given {% term integration %}. + +Each has matching functions for going the other way (for example, [`area_devices`](/template-functions/area_devices/) lists the devices in an area). Browse the [Areas](/template-functions/areas/), [Devices](/template-functions/devices/), [Floors](/template-functions/floors/), and [Labels](/template-functions/labels/) categories in the reference for the full set. + +Here is how you'd list the bedroom lights that are on: + +{% example %} +template: | + Bedroom lights on: + {% for entity in area_entities('bedroom') %} + {% if entity.startswith('light.') and is_state(entity, 'on') %} + - {{ state_attr(entity, 'friendly_name') }} + {% endif %} + {% endfor %} +output: | + Bedroom lights on: + - Bedroom ceiling + - Bedside lamp +{% endexample %} + +## The `this` variable (in template entities) + +When you write a template that defines a [template entity](/integrations/template/), `this` refers to the entity itself. That is useful when the entity needs to read its own state or attributes without hardcoding its entity ID. + +{% example %} +automation: | + template: + - sensor: + - name: "Kitchen helper" + state: "{{ this.attributes.get('counter', 0) + 1 }}" + attributes: + counter: "{{ this.state | int(0) + 1 }}" +output: "1 (the sensor increments its own value each time it updates)" +{% endexample %} + +`this` only exists where Home Assistant knows which entity the template belongs to. That means template entities and some automation contexts, but not the Developer Tools template editor. + +## The `trigger` variable (in automations) + +When an automation runs, it receives a `trigger` variable with details about what caused it. The fields depend on the {% term trigger %} type; the [Automation trigger variables](/docs/automation/templating/) page lists them all. + +{% example %} +automation: | + - trigger: state + entity_id: binary_sensor.front_door + to: "on" + action: + - action: notify.mobile_app + data: + message: > + {{ trigger.to_state.name }} was opened at + {{ trigger.to_state.last_changed.strftime('%H:%M') }}. +output: "Front door was opened at 14:32." +{% endexample %} + +## Next steps + +- Ready-made examples that combine these tools live on the [Common template patterns](/docs/templating/patterns/) page. +- The full list of state and entity functions is in the [template functions reference](/template-functions/). +- When a template does not give you what you expected, see [Debugging templates](/docs/templating/debugging/). diff --git a/source/_docs/templating/syntax.markdown b/source/_docs/templating/syntax.markdown new file mode 100644 index 000000000000..cb18107a7b9c --- /dev/null +++ b/source/_docs/templating/syntax.markdown @@ -0,0 +1,388 @@ +--- +title: "Template syntax" +description: "The building blocks of a template, explained in plain language." +related: + - docs: /docs/templating/loops-and-conditions/ + title: Loops and conditions + - docs: /docs/templating/types/ + title: Types and conversion + - docs: /docs/templating/yaml/ + title: Templates in YAML + - docs: /template-functions/ + title: Template functions reference + - docs: /template-functions/float/ + title: "`float` filter" + - docs: /template-functions/default/ + title: "`default` filter" +--- + +Templates are made of a handful of pieces that show up everywhere. Once you've seen them a few times, you will recognize them instantly and templates will stop looking like gibberish. This page introduces each piece one at a time. + +Don't try to memorize everything at once. Read through, keep this page open while you experiment in the [template editor](/docs/templating/debugging/#the-template-editor), and come back when you need a refresher. That is honestly how most people learn this. + +## Code lives inside markers + +When Home Assistant reads a template, it gives you back the regular text exactly as you wrote it. But when it sees certain markers, it knows the text in between is code that needs to be calculated. Those markers are called **delimiters** (a fancy word for "the thing that marks where something starts and stops"). + +Templates have three delimiters. You will use the first one most of the time: + +- `{{ ... }}` says "calculate this and show me the result". +- `{% ... %}` says "run this logic, but don't add anything to the output directly". +- `{# ... #}` says "this is a note for me, ignore it". + +Here is all three working together: + +{% example %} +template: | + {# Say hello to whoever is home #} + Hello, {{ states('person.frenck') }}. + {% if is_state('sun.sun', 'below_horizon') %} + It is dark outside. + {% endif %} +output: | + Hello, home. + It is dark outside. +{% endexample %} + +Notice what happened: + +- The comment (`{# ... #}`) was removed from the output. +- The `{{ states('person.frenck') }}` was replaced with the person's current state. +- The `{% if ... %}` decided whether to include the "It is dark outside." line. + +That is the whole trick. Everything else on this page is details about what you can put inside those markers. + +## Expressions: anything that produces a value + +When you write `{{ ... }}`, whatever goes inside is called an **expression**. An expression is "something that can be turned into a value". The value could be a number, a piece of text, a list, or anything else. + +These are all valid expressions: + +- Text: `'hello'` or `"hello"` (both kinds of quotes work). +- Numbers: `42`, `3.14`. +- True and false: `true`, `false`. +- Nothing at all: `none`. +- A list of things: `[1, 2, 3]` or `['kitchen', 'bedroom']`. +- A calculation: `10 + 5`. +- A call to a function: `states('sensor.temperature')` or `now()`. +- A variable you defined earlier (more on those in [loops and conditions](/docs/templating/loops-and-conditions/)). + +When a value has parts inside it (like a state that carries attributes), you reach them with a dot or with square brackets: + +{% example %} +template: | + Temperature: {{ states.sensor.outdoor.state }} + Friendly name: {{ states.sensor.outdoor.attributes.friendly_name }} + Same thing: {{ states.sensor.outdoor.attributes['friendly_name'] }} +output: | + Temperature: 22.5 + Friendly name: Outdoor temperature + Same thing: Outdoor temperature +{% endexample %} + +Dots look cleaner. Brackets are needed when the name has a space or other character that dots don't handle. The most common place you'll hit this is with entity IDs that start with a number, like `device_tracker.2008_gmc`. Writing `states.device_tracker.2008_gmc` fails because names cannot start with a digit, so use `states.device_tracker['2008_gmc']` instead. + +## Operators: doing things with values + +An **operator** is a symbol that combines or compares values. You already know these from arithmetic at school. They work exactly the same here. + +### Math + +{% example %} +template: | + Addition: {{ 10 + 5 }} + Subtraction: {{ 10 - 5 }} + Multiplication: {{ 10 * 5 }} + Division: {{ 10 / 3 }} + Integer division: {{ 10 // 3 }} + Remainder: {{ 10 % 3 }} + Power: {{ 10 ** 2 }} +output: | + Addition: 15 + Subtraction: 5 + Multiplication: 50 + Division: 3.3333333333333335 + Integer division: 3 + Remainder: 1 + Power: 100 +{% endexample %} + +The last three are less common. `//` is division that throws away the decimals (so `10 // 3` is `3`, not `3.33`). `%` gives you what is left over after division. `**` raises one number to the power of another. + +### Comparison + +Comparison operators ask "how does this value relate to that one?". They always answer with `True` or `False`. + +{% example %} +template: | + Equal: {{ 10 == 10 }} + Not equal: {{ 10 != 5 }} + Greater than: {{ 10 > 5 }} + Less than or equal: {{ 5 <= 5 }} +output: | + Equal: True + Not equal: True + Greater than: True + Less than or equal: True +{% endexample %} + +Watch out for the double equals in `==`. A single `=` is used for assignment (giving a name to a value), while `==` is the question "are these two the same?". + +### Logic + +Logic operators combine multiple true-or-false answers into one. `and` needs both to be true. `or` needs at least one. `not` flips the answer. `in` checks whether something is inside a list or a piece of text. + +{% example %} +template: | + Both true: {{ true and false }} + Either true: {{ true or false }} + Flipped: {{ not false }} + Contains: {{ 'bedroom' in 'bedroom light' }} +output: | + Both true: False + Either true: True + Flipped: True + Contains: True +{% endexample %} + +## Filters: changing a value + +A **filter** takes a value and transforms it into something new. Filters use the `|` symbol (called a pipe). The pipe points at the filter, like handing the value over to it. + +You can read a filter chain out loud left to right: "take `hello`, make it upper case" or "take these numbers, sort them, then join them with commas". + +{% example %} +template: | + Upper case: {{ 'hello' | upper }} + Rounded: {{ 3.14159 | round(2) }} + Chained: {{ [3, 1, 2] | sort | join(', ') }} +output: | + Upper case: HELLO + Rounded: 3.14 + Chained: 1, 2, 3 +{% endexample %} + +Home Assistant has dozens of filters for converting between types, formatting text, calculating with numbers, and working with lists. The [template functions reference](/template-functions/) lists them all, each with examples. + +### Many filters are also functions + +A lot of Home Assistant's template functions can be used either as a filter (with `|`) or as a regular function call. These two are exactly the same: + +{% example %} +template: | + As a function: {{ float(states('sensor.outdoor_temperature')) }} + As a filter: {{ states('sensor.outdoor_temperature') | float }} +output: | + As a function: 22.5 + As a filter: 22.5 +{% endexample %} + +The filter form reads more naturally when you are chaining several steps together. The function form can be clearer when you need an explicit fallback: `float(value, 0)` versus `value | float(0)`. Use whichever feels more readable in each situation. The reference page for each function notes which forms it supports. + +### Watch out: filters run before math + +Here is a gotcha that catches everyone at least once. The `|` symbol binds tighter than `+`, `-`, `*`, `/`, and all the other math operators. That means this template does not do what it looks like it should: + +{% example %} +template: | + {{ 10 / 10 | round(2) }} +output: "1" +{% endexample %} + +You might read that as "ten divided by ten, rounded to two decimals", which should be `1.0`. But because filters take priority, it actually runs as "ten divided by (ten rounded to two decimals)", which is `10 / 10.0 = 1.0`... Wait, that's also `1.0`. Let me try a clearer example: + +{% example %} +template: | + {{ 20 - 5 | round(0) }} +output: "15" +{% endexample %} + +This one also happens to work. The gotcha bites hardest when the filter changes the value meaningfully. When in doubt, add parentheses so the order you want is clear: + +{% example %} +template: | + With parentheses: {{ (10 / 3) | round(2) }} + Without parentheses: {{ 10 / 3 | round(2) }} +output: | + With parentheses: 3.33 + Without parentheses: 3.3333333333333335 +{% endexample %} + +In the "without parentheses" version, `3 | round(2)` runs first (rounding `3` gives `3`), then `10 / 3` divides as normal. The rounding had no effect. Parentheses force `10 / 3` to happen first, then round. + +Whenever a template looks right but gives an unexpected result, suspect operator precedence and reach for parentheses. + +## Tests: asking questions about a value + +A **test** is a yes-or-no question you ask about a value. Tests are written with the word `is`, which reads naturally. + +{% example %} +template: | + Is a number: {{ 42 is number }} + Is text: {{ 'hello' is string }} + Is even: {{ 6 is even }} + Is in a list: {{ 3 is in [1, 2, 3] }} +output: | + Is a number: True + Is text: True + Is even: True + Is in a list: True +{% endexample %} + +To ask the opposite, write `is not`: + +{% example %} +template: | + {{ 42 is not number }} +output: "False" +{% endexample %} + +Tests are most useful inside [`if` statements](/docs/templating/loops-and-conditions/#conditions-with-if), where you want to react differently based on what kind of value you have. + +## Whitespace: trimming unwanted spaces + +Templates keep every space and newline you write. That includes the line breaks around `{% if %}` and `{% set %}` tags. This is often fine for notifications, but when you need clean output (like a sensor value), those extra spaces become a problem. + +### The problem + +{% example %} +template: | + {% set temp = 22 %} + {% if temp > 20 %} + Warm + {% else %} + Cool + {% endif %} + outside. +output: | + + + Warm + + outside. +{% endexample %} + +The output has blank lines everywhere. Each `{% %}` tag occupies a line, and that line break stays in the output even though the tag itself produces nothing visible. + +### What trimming does + +Adding a `-` inside a tag marker removes all whitespace (spaces, tabs, and line breaks) on that side, up to the next non-whitespace character. + +- `{% ... %}` trims nothing (default). +- `{%- ... %}` trims the left side: everything before the tag. +- `{% ... -%}` trims the right side: everything after the tag. +- `{%- ... -%}` trims both sides. + +The same works for expressions: `{{- ... }}`, `{{ ... -}}`, `{{- ... -}}`. + +### Trimming one side + +Trimming the right side (`-%}`) removes the line break after a tag. This is the most common form because the blank lines come from the line break that follows each tag: + +{% example %} +template: | + {% set temp = 22 -%} + {% if temp > 20 -%} + Warm + {% else -%} + Cool + {% endif -%} + outside. +output: | + Warm + outside. +{% endexample %} + +The `-%}` on each tag eats the line break after it, so the tag and the content after it end up on the same line. "Warm" and "outside." each get their own line, which is clean output. + +Trimming the left side (`{%-`) removes whitespace before the tag. This pulls the tag toward whatever came before it: + +{% example %} +template: | + The door is + {%- if true %} open{% endif %}. +output: "The door is open." +{% endexample %} + +The `{%-` on the `if` tag eats the line break and spaces before it, so "is" and "open" connect without a gap. Without the `-`, there would be a line break between "is" and "open". + +### Trimming both sides + +When every tag trims both sides, the output becomes as tight as possible: + +{% example %} +template: | + {%- set temp = 22 -%} + {%- if temp > 20 -%} + Warm + {%- else -%} + Cool + {%- endif -%} +output: "Warm" +{% endexample %} + +Everything collapses onto one line. The `{%- ... -%}` on every tag removes all surrounding whitespace. + +### Mixing trimmed and untrimmed + +You do not have to trim every tag. Choose based on what the output should look like: + +{% example %} +template: | + {%- set temp = 22 -%} + {%- set humidity = 65 -%} + Temperature: {{ temp }}°C + Humidity: {{ humidity }}% +output: | + Temperature: 22°C + Humidity: 65% +{% endexample %} + +The `{%- set ... -%}` tags are fully trimmed (they are setup, not output). The `{{ }}` expressions are not trimmed because we want each reading on its own line. + +### Expressions can trim too + +The `-` works inside `{{ }}` the same way: + +{% example %} +template: | + Status: + {{- ' OK' if true else ' Error' }} +output: "Status: OK" +{% endexample %} + +The `{{-` eats the line break between "Status:" and the expression, so the result is one line. The space before "OK" is inside the string itself, so it survives. + +### When to trim + +- **Notifications and messages**: Usually no trimming needed. Extra blank lines are harmless and can improve readability. +- **Sensor values**: Trim aggressively. A template sensor's state should be a clean value like `22.5`, not ` 22.5\n` with extra whitespace. +- **Building values on one line**: Trim both sides of every `set`, `if`, and `endif` tag. +- **Multi-line output**: Trim the `set` and logic tags but leave the output expressions untrimmed so each result gets its own line. + +## Putting it all together + +Most real templates mix several of these pieces. Here is one you might see in an automation: + +{% example %} +template: | + {% set temp = states('sensor.outdoor_temperature') | float(0) %} + It is {{ temp | round(1) }}°C outside, + which is {{ 'warm' if temp > 20 else 'cool' }}. +output: "It is 22.5°C outside, which is warm." +{% endexample %} + +Let's read it piece by piece: + +1. **`{% set temp = ... | float(0) %}`** reads the outdoor temperature, converts it to a number (with `0` as a fallback in case the sensor is offline), and stores the result in a variable named `temp`. +2. **`{{ temp | round(1) }}`** shows that number rounded to one decimal place. +3. **`{{ 'warm' if temp > 20 else 'cool' }}`** picks between two words. If `temp` is more than 20, it picks "warm"; otherwise, "cool". + +If that feels like a lot, read the [Loops and conditions](/docs/templating/loops-and-conditions/) page next. It explains `set`, `if`, and `for` in detail. + +## Next steps + +- Learn about [loops and conditions](/docs/templating/loops-and-conditions/) to make your templates smarter. +- Writing templates inside automations or scripts? Read [Templates in YAML](/docs/templating/yaml/) for the quoting rules. +- Browse the [template functions reference](/template-functions/) for the full list of filters, tests, and functions. +- Try every example on this page in the [template editor](/docs/templating/debugging/#the-template-editor). Experimenting is the fastest way to learn. diff --git a/source/_docs/templating/tutorial-average-temperature.markdown b/source/_docs/templating/tutorial-average-temperature.markdown new file mode 100644 index 000000000000..b37e99fb2da1 --- /dev/null +++ b/source/_docs/templating/tutorial-average-temperature.markdown @@ -0,0 +1,133 @@ +--- +title: "Tutorial: Show the average home temperature on your dashboard" +description: "Build a template sensor that averages all your temperature sensors, step by step." +related: + - docs: /docs/templating/states/ + title: Working with states + - docs: /docs/templating/patterns/ + title: Common template patterns + - docs: /template-functions/selectattr/ + title: "`selectattr` filter" + - docs: /template-functions/average/ + title: "`average` filter" + - docs: /integrations/template/ + title: Template integration +--- + +In this tutorial, you will build a template {% term sensor %} that averages all temperature readings in your home into a single number. It's ideal for seeing your whole-house temperature at a glance, or for triggering automations based on overall comfort. It shows up on your dashboard like any other sensor, and it updates automatically whenever one of the underlying temperature readings changes. + +This is a classic first template sensor. You will learn how to gather readings from many entities, average them, round the result, and wire the whole thing into Home Assistant as a real sensor you can use anywhere else. + +## What you will build + +A {% term sensor %} called `sensor.home_average_temperature` that you can place on any dashboard. It shows a single number like `21.4` °C, calculated from all the temperature sensors in your home. + +You get a real sensor entity, so you can also use it in automations, chart it in history, or reference it from other templates. + +## Before you start + +You need: + +- At least two temperature sensors in Home Assistant. They should have `device_class: temperature` in their attributes. Most climate integrations set this automatically. +- Five minutes with the [Developer tools template editor](/docs/templating/debugging/#the-template-editor) open. + +If you want your result in Fahrenheit, see the [Going further](#going-further) section at the bottom. + +## Step 1: See your temperature sensors + +Open {% my developer_states title="**Settings** > **Developer tools** > **States**" %} and filter on `temperature`. + + + +You should see a list of entities with `device_class: temperature` in their attributes. The state column shows the current reading as text, like `22.5`. + +These are the sensors your new sensor will average. Make a mental note of how many you have. + +## Step 2: Write the averaging template + +In {% my developer_template title="**Developer tools** > **Template**" %}, paste this: + +{% example %} +template: | + {{ + states.sensor + | selectattr('attributes.device_class', 'eq', 'temperature') + | rejectattr('state', 'in', ['unknown', 'unavailable']) + | map(attribute='state') | map('float') | average | round(1) + }} +output: "21.4" +{% endexample %} + +That is one long line. Read it left to right as one sentence: + +1. Start with every `sensor` entity. +2. Keep only the ones whose `device_class` is `temperature`. +3. Drop any that are `unknown` or `unavailable`. +4. From each remaining entity, pull out only the `state` value. +5. Convert each state from text to a number. +6. Average them. +7. Round the result to one decimal. + +Your result should be a single number close to what you feel in your home. + +{% tip %} +If you see an error, try running the template one piece at a time. Remove the `| round(1)`, then `| average`, then `| map('float')`, and see where it breaks. Step-by-step narrowing is explained in [Debugging templates](/docs/templating/debugging/). +{% endtip %} + +## Step 3: Make it a real sensor + +A one-off template in the editor is great for testing. To use the result on a dashboard, you need to turn it into an actual {% term entity %}. You do this with a **template helper**, which Home Assistant creates for you from the user interface. + +1. Go to {% my helpers title="**Settings** > **Devices & services** > **Helpers**" %}. +2. Select **Create helper** in the bottom-right. +3. Pick **Template**. +4. Pick **Template a sensor**. + +### Fill in the form + +You will see a form with several fields. Fill them out like this: + +- **Name**: `Home average temperature`. This is what you will see in dashboards and the app, and it is used to generate the entity ID (`sensor.home_average_temperature`). +- **State template**: paste the template you tested in step 2. Paste it exactly, including the `{{ ... }}` delimiters. +- **Unit of measurement**: `°C` (or `°F` if your sensors report Fahrenheit). +- **Device class**: `Temperature`. This tells Home Assistant this sensor represents a temperature so dashboards and voice assistants pick the right icon and handle units correctly. +- **State class**: `Measurement`. This tells Home Assistant the value changes continuously over time, which is what makes the sensor chartable in history. + +Select **Submit**. Your new sensor is ready to use immediately, no restart needed. + +{% tip %} +If you later rename the helper, its **unique ID** stays the same. That is the internal identifier Home Assistant uses to track this sensor across renames, moves, and configuration tweaks. History, dashboards, and automations keep working because they reference the unique ID behind the scenes. You don't have to do anything with it, but it's good to know it exists if you ever see it mentioned elsewhere. +{% endtip %} + +## Step 4: Check it + +Your new helper now appears on the {% my helpers title="**Settings** > **Devices & services** > **Helpers**" %} page. Find `Home average temperature` in the list and select it to open the details dialog. The current value shows there. + +If the value looks wrong, use the three-dot menu in the dialog to open the helper's settings and adjust the template. Changes save immediately. + +## Step 5: Add it to your dashboard + +1. Open a dashboard in edit mode. +2. Select **Add card**. +3. Pick a [**Tile**](/dashboards/tile/) card (clean display with optional extras) or a [**Gauge**](/dashboards/gauge/) card (visual dial). +4. Choose `sensor.home_average_temperature` from the entity list. +5. Save the dashboard. + +The card updates automatically whenever any of your temperature sensors changes. + +## Going further + +A few ways to extend this: + +- **Fahrenheit**. If your sensors report in Fahrenheit, open the sensor's settings from the entity and set **Unit of measurement** to `°F`. No changes to the template are needed. +- **By area**. Instead of averaging the whole house, limit it to a single area. Replace the `states.sensor` starting point with `area_entities('living_room')` and add a step to filter for temperature sensors. See [`area_entities`](/template-functions/area_entities/). +- **Highest and lowest too**. Add two more sensors that use [`max`](/template-functions/max/) and [`min`](/template-functions/min/) instead of [`average`](/template-functions/average/). Great for seeing the spread. +- **Weighted average**. If you want the bedroom thermostat to count more than the garage sensor, you will need a bit more template logic. A [`for` loop](/docs/templating/loops-and-conditions/#loops-with-for) with manual weighting handles that. +- **Skip sensors reporting unrealistic values**. If a broken sensor reports `999`, it will skew your average. Filter out values outside a reasonable range with an additional step before `| average`. +- **Group by type**. You can build one for humidity, one for pressure, one for CO₂. Same template shape, different `device_class` value. + +## Next steps + +- The [Working with states](/docs/templating/states/) page explains the [`selectattr`](/template-functions/selectattr/), [`rejectattr`](/template-functions/rejectattr/), and [`map`](/template-functions/map/) filters used here. +- The [Common template patterns](/docs/templating/patterns/) page has more aggregation recipes. +- If this is the first tutorial you did, try the [Tutorial: get notified when a device needs a new battery](/docs/templating/tutorial-battery-alerts/) next to learn about templates in automations. diff --git a/source/_docs/templating/tutorial-battery-alerts.markdown b/source/_docs/templating/tutorial-battery-alerts.markdown new file mode 100644 index 000000000000..9a8235b2d7c3 --- /dev/null +++ b/source/_docs/templating/tutorial-battery-alerts.markdown @@ -0,0 +1,200 @@ +--- +title: "Tutorial: Get notified when a device needs a new battery" +description: "Build a daily notification that lists devices with low batteries, step by step." +related: + - docs: /docs/templating/loops-and-conditions/ + title: Loops and conditions + - docs: /docs/templating/patterns/ + title: Common template patterns + - docs: /template-functions/selectattr/ + title: "`selectattr` filter" + - docs: /template-functions/float/ + title: "`float` filter" + - docs: /integrations/notify/ + title: Notify actions +--- + +In this tutorial, you will build a daily notification that tells you which devices need new batteries. It looks at every battery sensor in your home, picks out the ones below a threshold you choose, and sends you a message listing each device with its location and current percentage. + +This is one of the most popular templates in the Home Assistant community, and it is a great first real-world template. You will learn how to loop over a collection of sensors, filter by state, build up a list, and format a message. All of these skills carry over to many other templates you will write later. + +## What you will build + +A notification that arrives each morning with a message like this: + +```text +2 devices need new batteries: Front door lock in Hallway (15%), Motion sensor in Kitchen (12%). +``` + +If everything is healthy, you get a reassuring message instead: + +```text +All batteries are healthy. +``` + +## Before you start + +You need: + +- At least one battery-powered device reporting a percentage through Home Assistant. Zigbee, Z-Wave, and Matter devices usually do this automatically. +- A notification service set up on your phone, usually the [Home Assistant Companion app](/integrations/mobile_app/). The examples below use `notify.mobile_app_your_phone`. Replace that with your own `notify` action when you get there. +- Five minutes with the [Developer tools template editor](/docs/templating/debugging/#the-template-editor) open. + +## Step 1: See your battery sensors + +Before you write a single template, it helps to see what you are working with. Open {% my developer_states title="**Settings** > **Developer tools** > **States**" %} and filter on `battery`. + + + +You should see a list of sensors with `device_class: battery` in their attributes and a number (like `85` or `12`) in the state column. Those are the entities this automation will watch. + +If you do not see any, your devices might be using the old `binary_sensor` battery class (which reports `on`/`off` instead of a percentage) or they do not report battery at all. This tutorial focuses on the numeric `sensor` kind. + +## Step 2: List the low batteries + +Open {% my developer_template title="**Developer tools** > **Template**" %} and paste this in: + +{% example %} +template: | + {% set low = namespace(batteries=[]) %} + {% for sensor in states.sensor + | selectattr('attributes.device_class', 'eq', 'battery') + | rejectattr('state', 'in', ['unknown', 'unavailable']) %} + {% if sensor.state | float(100) < 20 %} + {% set low.batteries = low.batteries + [device_name(sensor.entity_id)] %} + {% endif %} + {% endfor %} + {{ low.batteries }} +output: "['Front door lock', 'Motion sensor', 'Bedroom sensor']" +{% endexample %} + +Read it left to right: + +1. Create a namespace called `low` with an empty `batteries` list inside. +2. For each sensor whose `device_class` is `battery`, skipping any that are `unknown` or `unavailable`... +3. If its state (converted to a number) is below `20`... +4. Add the name of the **device** that sensor belongs to to `low.batteries`. + +Why the device name? With modern Home Assistant naming, a battery sensor's own name is often only "Battery", which is not very helpful in a notification. The [`device_name`](/template-functions/device_name/) function gives you back the friendly name of the device the sensor is attached to (like "Front door lock" or "Motion sensor"). + +You should see a list of device names with low batteries. If the list is empty, you can temporarily raise the threshold (change `20` to `100`) to confirm the template is working. + +{% note %} +Variables set inside a `for` loop do not survive outside the loop, so `low` is created with a [`namespace`](/template-functions/namespace/). That is the object whose attribute (`low.batteries`) can be updated inside the loop and still hold the full list afterwards. See [Loops and conditions](/docs/templating/loops-and-conditions/) for more on this quirk. +{% endnote %} + +## Step 3: Format the message + +A bare list is not what you want to send to your phone. Add the area the device is in, the current battery level, and turn the result into a friendly sentence. Replace the template with this: + +{% example %} +template: | + {% set low = namespace(batteries=[]) %} + {% for sensor in states.sensor + | selectattr('attributes.device_class', 'eq', 'battery') + | rejectattr('state', 'in', ['unknown', 'unavailable']) %} + {% if sensor.state | float(100) < 20 %} + {% set device = device_name(sensor.entity_id) %} + {% set area = area_name(sensor.entity_id) %} + {% set label = device ~ (' in ' ~ area if area else '') + ~ ' (' ~ sensor.state ~ '%)' %} + {% set low.batteries = low.batteries + [label] %} + {% endif %} + {% endfor %} + {% if low.batteries | count == 0 %} + All batteries are healthy. + {% elif low.batteries | count == 1 %} + 1 device needs a new battery: {{ low.batteries[0] }}. + {% else %} + {{ low.batteries | count }} devices need new batteries: {{ low.batteries | join(', ') }}. + {% endif %} +output: |- + 3 devices need new batteries: Front door lock in Hallway (15%), + Motion sensor in Kitchen (12%), Window sensor in Bedroom (8%). +{% endexample %} + +A few things changed from the previous step: + +- Two helper variables: [`device_name`](/template-functions/device_name/) gives you the device's friendly name, and [`area_name`](/template-functions/area_name/) gives you the name of the {% term area %} it is assigned to. +- `label` builds one formatted piece of text per device, combining the device name, the area (only if it is set), and the battery percentage. The `~` symbol glues text together. +- An [`if`/`elif`/`else`](/docs/templating/loops-and-conditions/#multiple-paths-with-elif) picks between three messages based on how many items are in the list. No low batteries get a friendly "all healthy" message. One low battery uses singular wording. Two or more get the full list joined with commas. + +Play with the template. Change the threshold, remove the `%`, reword the messages. The template editor updates as you type, so you can see the result of every change right away. + +## Step 4: Create the automation + +Now turn the template into an actual automation. + +1. Go to {% my automations title="**Settings** > **Automations & scenes**" %}. +2. Select **Create automation**, then **Create new automation**. +3. Give it a name: `Low battery notification`. + +### Add the trigger + +You want this to run once every morning. + +1. Under **When**, select **Add trigger**. +2. Choose **Time**. +3. Set the time to something reasonable, like `09:00:00`. + +### Add the action + +1. Under **Then do**, select **Add action**. +2. Choose **Call service**, then pick your `notify` action (like `notify.mobile_app_your_phone`). +3. Fill in the message field with the template you built in step 3. + +In YAML, the action looks like this: + +{% example %} +action: | + - action: notify.mobile_app_your_phone + data: + title: "Battery check" + message: > + {% set low = namespace(batteries=[]) %} + {% for sensor in states.sensor + | selectattr('attributes.device_class', 'eq', 'battery') + | rejectattr('state', 'in', ['unknown', 'unavailable']) %} + {% if sensor.state | float(100) < 20 %} + {% set device = device_name(sensor.entity_id) %} + {% set area = area_name(sensor.entity_id) %} + {% set label = device ~ (' in ' ~ area if area else '') + ~ ' (' ~ sensor.state ~ '%)' %} + {% set low.batteries = low.batteries + [label] %} + {% endif %} + {% endfor %} + {% if low.batteries | count == 0 %} + All batteries are healthy. + {% elif low.batteries | count == 1 %} + 1 device needs a new battery: {{ low.batteries[0] }}. + {% else %} + {{ low.batteries | count }} devices need new batteries: {{ low.batteries | join(', ') }}. + {% endif %} +{% endexample %} + +4. Save the automation. + +## Step 5: Test it + +You do not want to wait until tomorrow morning to find out if it works. + +1. Open your automation. +2. Select the **Run** button (or use the three-dots menu and choose **Run actions**). + +The notification should arrive on your phone within a few seconds. If it does not, check that the `notify` action name matches what you have on your setup, and look at the automation's trace for clues. + +## Going further + +A few ways to take this further: + +- **Quiet "healthy" days**. Skip the notification entirely when the list is empty. Put the "all healthy" message in an [`if`](/docs/templating/loops-and-conditions/#conditions-with-if) branch that does nothing, or use a template condition before the notification action. +- **Different thresholds**. Replace `20` with a helper input so you can tune the threshold from the dashboard without editing the automation. +- **Weekly instead of daily**. Change the time trigger to a more specific schedule like "every Monday at 9am". +- **Include unavailable devices**. The current template skips sensors that are `unknown` or `unavailable`, but those might indicate a completely dead battery. You can add a second list for offline devices and mention them in the message. +- **Sort by percentage**. If you have many devices, sort the list with the most-drained device first so you know what to replace first. + +## Next steps + +- The [Loops and conditions](/docs/templating/loops-and-conditions/) page explains the `for` loop and `if`/`elif`/`else` used here. +- The [Common template patterns](/docs/templating/patterns/) page has more cookbook-style recipes. +- Try the [Tutorial: show the average home temperature on your dashboard](/docs/templating/tutorial-average-temperature/) next to learn how to build a template {% term sensor %}. diff --git a/source/_docs/templating/types.markdown b/source/_docs/templating/types.markdown new file mode 100644 index 000000000000..008c90652aa0 --- /dev/null +++ b/source/_docs/templating/types.markdown @@ -0,0 +1,237 @@ +--- +title: "Types and type conversion" +description: "Understand the data types in templates and how to convert between them." +related: + - docs: /template-functions/float/ + title: "`float` filter" + - docs: /template-functions/int/ + title: "`int` filter" + - docs: /template-functions/list/ + title: "`list` filter" + - docs: /template-functions/typeof/ + title: "`typeof` function" + - docs: /docs/templating/debugging/ + title: Debugging templates +--- + +Templates work with several kinds of values: text, numbers, lists, and a few others. Each kind is called a **type**, and knowing which type you have matters because most operations only work with specific types. Trying to do math on text, or count items in an iterable, gives you surprising results until you convert first. + +This page walks through the types you will meet, how they interact, and when you need to convert between them. + +## The types you will meet + +- **Text** (`str` or `string`): a piece of text, written in single or double quotes. Every entity state in Home Assistant is stored as text. +- **Integer** (`int` or `integer`): a whole number without a decimal point. Examples: `0`, `42`, `-7`. +- **Float** (`float`): a number with a decimal point. Examples: `3.14`, `-0.5`, `22.0`. +- **Boolean** (`bool` or `boolean`): either `True` or `False`. The answer to yes-or-no questions. +- **None** (`NoneType` or `None`): the absence of a value. Returned when something does not exist. +- **List**: an ordered collection of values, written with square brackets. Example: `[1, 2, 3]` or `['kitchen', 'bedroom']`. Also called an array or sequence. +- **Dictionary** (`dict`): a set of key/value pairs, written with curly braces. Example: `{'temp': 22, 'humidity': 54}`. Also called a map or mapping. +- **Iterable**: a sequence of values you can loop over **once**. Also called a generator. Filters like [`map`](/template-functions/map/), [`select`](/template-functions/select/), and [`selectattr`](/template-functions/selectattr/) return iterables. You cannot count them, index them, or use them twice until you turn them into a list. Covered in detail [below](#iterables-look-like-lists-but-are-not). +- **Datetime**: a moment in time with a date, time, and time zone. Returned by `now()` and the `last_changed` attribute on states. + +## Why types matter + +Most functions and operators only work with specific types. Here are some examples: + +- Math operators (`+`, `-`, `*`, `/`) need **numbers**. Using them on text fails. +- `| count` or `| length` needs a **list** or similar countable. It does not work on an iterable. +- Comparison with `<`, `>` works on numbers correctly, but on text it compares alphabetically (`'6' > '10'` is `True` because `'6'` comes after `'1'`). +- The `in` operator works on lists, text, and dicts, but the behavior is different for each. + +When a template produces an unexpected result, the type of the values involved is usually the first thing to check. + +## Every state is text + +This is the single most important type fact in Home Assistant templates: **every entity state is stored as text**. Even when a sensor looks like it reports a number, `states('sensor.temperature')` returns the text `'22.5'`, not the number `22.5`. + +{% example %} +template: "{{ states('sensor.temperature') | typeof }}" +output: "str" +{% endexample %} + +This is why you see `| float(0)` or `| int(0)` everywhere in template examples. Before you can do math, compare numerically, or average a bunch of values, you have to convert them to numbers. + +{% example %} +template: | + {# This looks like math but is text concatenation #} + {{ states('sensor.a') + states('sensor.b') }} +output: "22.518.5" +{% endexample %} + +{% example %} +template: | + {# This does real math #} + {{ states('sensor.a') | float(0) + states('sensor.b') | float(0) }} +output: "41.0" +{% endexample %} + +## Converting between types + +Each type has a matching filter (and function) to convert a value to that type: + +- [`| float(default)`](/template-functions/float/): converts to a decimal number. If the value cannot be converted, the default is used. +- [`| int(default)`](/template-functions/int/): converts to a whole number. Decimals are dropped. +- [`| string`](/template-functions/string/): converts anything to text. +- [`| bool(default)`](/template-functions/bool/): converts to `True` or `False`. +- [`| list`](/template-functions/list/): materializes an iterable into a list. + +The `default` arguments for [`float`](/template-functions/float/), [`int`](/template-functions/int/), and [`bool`](/template-functions/bool/) are fallbacks used when the conversion fails (for example, a sensor that reports `unavailable`). + +{% example %} +template: | + {{ "3.14" | float(0) }} + {{ "seven" | float(0) }} + {{ "42" | int(0) }} + {{ 3.7 | int }} + {{ 1 | string }} + {{ "yes" | bool }} +output: | + 3.14 + 0.0 + 42 + 3 + 1 + True +{% endexample %} + +**Rule of thumb:** whenever you read a state and need to do math with it, add `| float(0)` or `| int(0)` with a sensible fallback. + +## Iterables look like lists, but are not + +Some filters return an iterable instead of a list. An iterable is a lazy sequence. It gives you values one at a time when you loop over it, but it is not a list. You cannot count it, index it, or use it twice. + +The Home Assistant filters that return iterables are: + +- [`map`](/template-functions/map/) +- [`select`](/template-functions/select/) +- [`reject`](/template-functions/reject/) +- [`selectattr`](/template-functions/selectattr/) +- [`rejectattr`](/template-functions/rejectattr/) + +If you try to count an iterable directly, you get an error: + +{% example %} +template: | + {# This fails #} + {{ states.light | selectattr('state', 'eq', 'on') | count }} +output: "Error: object of type 'generator' has no len()" +{% endexample %} + +The fix is to add `| list` to turn the iterable into a list first: + +{% example %} +template: | + {# This works #} + {{ states.light | selectattr('state', 'eq', 'on') | list | count }} +output: "3" +{% endexample %} + +You will see `| list` at the end of filter chains all over Home Assistant templates. It is not optional decoration, it is the step that makes the result countable, sortable, and reusable. A good habit: if you feed the result of [`map`](/template-functions/map/), [`select`](/template-functions/select/), [`reject`](/template-functions/reject/), [`selectattr`](/template-functions/selectattr/), or [`rejectattr`](/template-functions/rejectattr/) into anything that needs a list, add `| list`. + +## Getting items from lists and dictionaries + +Collections give you several ways to reach the items inside them. + +### From a list + +Use the [`first`](/template-functions/first/) and [`last`](/template-functions/last/) filters to grab the ends, or square brackets with an index to reach a specific position. Indices start at `0`, and negative numbers count from the end. + +{% example %} +template: | + {% set rooms = ['kitchen', 'bedroom', 'garage'] %} + First: {{ rooms | first }} + Last: {{ rooms | last }} + Index 0: {{ rooms[0] }} + Index 1: {{ rooms[1] }} + Index -1: {{ rooms[-1] }} +output: | + First: kitchen + Last: garage + Index 0: kitchen + Index 1: bedroom + Index -1: garage +{% endexample %} + +### From a dictionary + +Dictionaries support two ways to read a value: bracket notation (`data['key']`) and dot notation (`data.key`). Both usually work. + +{% example %} +template: | + {% set data = {'temp': 22.5, 'humidity': 54} %} + Bracket: {{ data['temp'] }} + Dot: {{ data.temp }} +output: | + Bracket: 22.5 + Dot: 22.5 +{% endexample %} + +**Use bracket notation when a key name could conflict with a dict method.** If a key is named `values`, `keys`, `items`, `get`, or any other built-in dict method, dot notation returns the method instead of your value. This is common with API responses. + +{% example %} +template: | + {% set response = {'status': 'ok', 'values': [1, 2, 3]} %} + {{ response['values'] }} +output: "[1, 2, 3]" +{% endexample %} + +When in doubt, reach for bracket notation. It always looks up the dictionary value first. + +## Checking what type you have + +When you are not sure what type a value is, use [`typeof`](/template-functions/typeof/) to inspect it: + +{% example %} +template: | + {{ 42 | typeof }} + {{ 3.14 | typeof }} + {{ "hello" | typeof }} + {{ [1, 2, 3] | typeof }} + {{ {"a": 1} | typeof }} + {{ None | typeof }} +output: | + int + float + str + list + dict + NoneType +{% endexample %} + +The names you get back are the Python type names: `str` for text, `int` for whole numbers, `float` for decimals, `bool` for booleans, `list`, `dict`, `NoneType` for `None`. + +## Testing types with tests + +Tests let you check a type inside an [`if` condition](/docs/templating/loops-and-conditions/#conditions-with-if), without calling [`typeof`](/template-functions/typeof/): + +{% example %} +template: | + {{ 42 is number }} + {{ "hello" is string }} + {{ [1, 2, 3] is iterable }} + {{ {"a": 1} is mapping }} + {{ None is none }} +output: | + True + True + True + True + True +{% endexample %} + +Useful type tests: `number`, [`string`](/template-functions/string/), `boolean`, `integer`, [`float`](/template-functions/float/), [`iterable`](/template-functions/iterable/), [`mapping`](/template-functions/mapping/), [`none`](/template-functions/none/), [`defined`](/template-functions/defined/). See the [Comparison](/template-functions/#comparison) category in the reference for the full list. + +## Common type mistakes + +- **Doing math on a sensor state without `| float(0)`**. Always convert first. +- **Comparing text to a number**. `states('sensor.temp') < 20` compares text to a number. Always `| float(0)` first. +- **Using `< `or `>` on text**. Text compares alphabetically, which rarely does what you want for numbers. +- **Forgetting `| list` after [`map`](/template-functions/map/), [`select`](/template-functions/select/), [`reject`](/template-functions/reject/), [`selectattr`](/template-functions/selectattr/), [`rejectattr`](/template-functions/rejectattr/)**. The result is an iterable, not a list, so `| count` and `| length` fail. +- **Expecting `None` or `unknown` to be a number**. Both break math operations. Use a fallback. + +## Next steps + +- For hands-on debugging, see [Debugging templates](/docs/templating/debugging/). +- For more on working with states, see [Working with states](/docs/templating/states/). +- For recipes that use these conversions, see [Common template patterns](/docs/templating/patterns/). diff --git a/source/_docs/templating/where-to-use.markdown b/source/_docs/templating/where-to-use.markdown new file mode 100644 index 000000000000..47a2eea668dc --- /dev/null +++ b/source/_docs/templating/where-to-use.markdown @@ -0,0 +1,252 @@ +--- +title: "Where to use templates" +description: "Learn the places in Home Assistant where you can use templates." +related: + - docs: /template-functions/states/ + title: "`states` function" + - docs: /template-functions/from_json/ + title: "`from_json` function" + - docs: /docs/templating/yaml/ + title: Templates in YAML + - docs: /integrations/template/ + title: Template integration + - docs: /integrations/mqtt/ + title: MQTT integration +--- + +Once you know what templates are, the next question is usually "where do I actually put one?". You'll reach for a template when you write a notification with live sensor values, trigger an automation based on the current state of your home, or build a dashboard number from several entities at once. Templates show up in a lot of places in Home Assistant, and this page walks through the most common ones so you can recognize each spot when you meet it. + +You don't need to memorize this. Come back when you're building something and need to remember which field accepts a template. + +## Automation and script actions + +Most actions accept templates for their data values. You can use them to compose messages, pick targets, or tailor behavior based on the current state of your home. + +{% example %} +automation: | + - alias: "Goodnight message" + triggers: + - trigger: time + at: "23:00:00" + actions: + - action: notify.mobile_app + data: + message: > + Goodnight. + {{ states('sensor.front_door') | capitalize }} front door, and + {{ states.light | selectattr('state', 'eq', 'on') | list | count }} + lights still on. +output: "Goodnight. Closed front door, and 3 lights still on." +{% endexample %} + +## Automation conditions + +A [template condition](/docs/automation/condition/#template-condition) lets an automation decide whether to continue based on any test you can write. The template needs to end up either `True` or `False`, and the automation only continues when the answer is `True`. + +{% example %} +condition: | + conditions: + - condition: template + value_template: > + {{ states('sensor.outdoor_temperature') | float(0) < 5 }} +output: "True (when the outdoor temperature is below 5°C)" +{% endexample %} + +## Automation triggers + +A [template trigger](/docs/automation/trigger/#template-trigger) fires when the template changes from false to true. This lets you react to conditions that no single entity represents. + +{% example %} +trigger: | + triggers: + - trigger: template + value_template: > + {{ + states('sensor.washing_machine_power') | float(0) < 5 + and states('sensor.washing_machine_power') | float(0) > 0 + }} +output: "True (fires when the washing machine power drops between 0 and 5 W)" +{% endexample %} + +{% note %} + +**Limited templates.** Some trigger types, and the `trigger_variables` section of an automation, only support a reduced set of template features. This is called a "limited template". Most of Home Assistant's state-reading functions (like `states()`, `state_attr()`, and the area/device/label helpers) are not available there. If a template works in the editor but fails in a trigger configuration, the limited-template scope is a likely cause. Check the specific trigger's documentation for the details. +{% endnote %} + +## Template entities + +The [Template integration](/integrations/template/) lets you create {% term entities %} whose value is computed from other entities. Template {% term sensors %}, binary sensors, switches, and more are defined entirely with templates. + +{% example %} +automation: | + template: + - sensor: + - name: "Lights on" + state: > + {{ states.light | selectattr('state', 'eq', 'on') | list | count }} +output: "5 (the sensor reports the number of lights currently on)" +{% endexample %} + +## Notifications + +The [notify actions](/integrations/notify/) accept templates for the title, message, and often other fields. This is one of the most popular places to use templates, because it turns fixed notifications into personal, context-aware messages. + +{% example %} +action: | + - action: notify.mobile_app + data: + title: "Door alert" + message: > + {{ trigger.to_state.name }} was opened at + {{ now().strftime('%H:%M') }}. +output: "Front door was opened at 14:32." +{% endexample %} + +## REST and command-line sensors + +The [`rest`](/integrations/rest/) and [`command_line`](/integrations/command_line/) sensors use templates to pull values out of the raw response they receive. This is how you turn a JSON reply or a command's output into a usable {% term sensor %}. + +{% example %} +automation: | + sensor: + - platform: rest + resource: https://api.example.com/weather + name: "Outside humidity" + value_template: "{{ value_json.current.humidity }}" + unit_of_measurement: "%" +output: "72 (extracted from the JSON field current.humidity)" +{% endexample %} + +## MQTT + +The [MQTT integration](/integrations/mqtt/) accepts templates for topics, payloads, and value extraction. Incoming payloads are often JSON, so a template extracts the field you need. + +{% example %} +automation: | + mqtt: + sensor: + - name: "Garden moisture" + state_topic: "garden/sensor/moisture" + value_template: "{{ value_json.moisture }}" + unit_of_measurement: "%" +output: "45 (extracted from an MQTT payload like {\"moisture\": 45})" +{% endexample %} + +MQTT uses two kinds of templates. A **value template** transforms an incoming payload into an entity state or attribute. A **command template** turns an action into the outgoing payload that the device expects. Both get the usual `value` and `value_json` variables, plus three extras specific to MQTT: + +- `entity_id`: the entity ID that the template belongs to. +- `name`: the friendly name of the entity. +- `this`: the entity's own state object (the same one you meet in template entities). + +The [MQTT integration documentation](/integrations/mqtt/) has the full list of where each template type applies and which fields on each entity support templating. + +## Processing incoming data + +The REST, MQTT, and command-line examples above use two special variables that need a word of introduction: `value` and `value_json`. You will run into them anywhere Home Assistant pulls data from an outside source and hands it to a template for shaping. + +When raw data comes in, the template that processes it has these extras available: + +- **`value`** holds the raw incoming data as text. It is always there. +- **`value_json`** holds the same data parsed as JSON. It is only there when the data actually is valid JSON. + +So when an MQTT payload arrives like this: + +```json +{ "state": "ON", "temperature": 21.9, "humidity": 54 } +``` + +You can reach the fields with dot notation: + +{% example %} +template: | + Temperature: {{ value_json.temperature }} + Humidity: {{ value_json.humidity }} + State: {{ value_json.state }} +output: | + Temperature: 21.9 + Humidity: 54 + State: ON +{% endexample %} + +### Nested JSON + +Real-world payloads often have nested structures. Use square brackets or more dots to reach deeper: + +{% example %} +template: | + {% set value_json = { + "sensor": {"type": "air", "id": "12345"}, + "values": {"temp": 26.09, "hum": 56.73} + } %} + Sensor ID: {{ value_json['sensor']['id'] }} + Temperature: {{ value_json.values.temp }} +output: | + Sensor ID: 12345 + Temperature: 26.09 +{% endexample %} + +Square brackets become necessary when a field name contains characters that dots don't handle, like a dash or a space. + +### JSON arrays + +If the data is a list, index into it with square brackets (starting at zero): + +{% example %} +template: | + {% set value_json = {"primes": [2, 3, 5, 7, 11, 13]} %} + First prime: {{ value_json.primes[0] }} + Third prime: {{ value_json.primes[2] }} +output: | + First prime: 2 + Third prime: 5 +{% endexample %} + +### When the data is not JSON + +If the incoming data is plain text or a number (say, from a command-line sensor that outputs `42.5`), use `value` directly: + +{% example %} +template: | + {% set value = "42.5" %} + {{ value | float(0) + 1 }} +output: "43.5" +{% endexample %} + +Remember: `value` is always text, so convert with `| float(0)` or `| int(0)` before doing math. + +### Testing an incoming-data template + +The {% my developer_template title="template editor" %} does not know what `value_json` or `value` would be in a real incoming payload, because there is no live payload at that moment. To test a template that uses these variables, define them yourself at the top with `{% set %}`: + +{% example %} +template: | + {% set value_json = {"name": "Outside", + "data": {"temp": "24C", "hum": "35%"}} %} + Humidity reading: {{ value_json.data.hum[:-1] }}% +output: "Humidity reading: 35%" +{% endexample %} + +This lets you work out the right template in the editor, then paste the finished version into your REST sensor, MQTT entity, or command-line sensor without needing the real device to send data. + +## Dashboards + +Most dashboard cards accept templates for titles, names, and other text fields. Support varies by card, and some cards need [`state_template`](/dashboards/markdown/) or similar fields to make templates work. The [Markdown card](/dashboards/markdown/) is the most flexible, as its entire content is a template. + +## Not everywhere + +Not every field accepts templates. As a rule: + +- Text, numbers, and lists in automation actions, conditions, triggers, and scripts can usually be templated. +- Entity IDs and structural {% term YAML %} keys usually cannot. +- Dashboards support templates in specific fields only. Check the card's documentation. + +If a template is not being evaluated, it is most likely in a field that does not support templating. The [Debugging templates](/docs/templating/debugging/) page has more tips. + +## Next steps + +Now that you know where templates live, learn how to write them: + +- Start with [Template syntax](/docs/templating/syntax/) for the building blocks. +- Read [Templates in YAML](/docs/templating/yaml/) for the quoting rules that trip everyone up. +- Browse [Common template patterns](/docs/templating/patterns/) for recipes you can copy. +- When something does not work, head to [Debugging templates](/docs/templating/debugging/). diff --git a/source/_docs/templating/yaml.markdown b/source/_docs/templating/yaml.markdown new file mode 100644 index 000000000000..9f9f2a1f8286 --- /dev/null +++ b/source/_docs/templating/yaml.markdown @@ -0,0 +1,133 @@ +--- +title: "Templates in YAML" +description: "Quoting rules, multi-line strings, and escaping templates inside YAML." +related: + - docs: /docs/templating/syntax/ + title: Template syntax + - docs: /docs/templating/debugging/ + title: Debugging templates + - docs: /docs/configuration/yaml/ + title: YAML configuration +--- + +Most of the time, templates live inside YAML files. YAML has its own rules about quoting and multi-line text, and those rules interact with templates in ways that trip everyone up at least once. This page covers the traps and the patterns that get around them. + +Don't feel bad when YAML bites you. It bites everyone. Come back to this page whenever Home Assistant refuses to load your config and the error message makes no sense. + +## The quoting rule + +A single-line template _must_ be wrapped in quotes. Either single (`'`) or double (`"`) will do. + +{% example %} +automation: | + # Correct: template is quoted + value_template: "{{ states('sensor.temp') | float > 20 }}" +output: "True (when the sensor reports a value above 20)" +{% endexample %} + +Without the quotes, YAML tries to interpret `{{` as the start of a flow-style mapping and fails. The error message you get in that case can be confusing, so if something refuses to load, check your quotes first. + +There is no difference in behavior between single and double quotes as far as Home Assistant is concerned. Pick one style and stick with it. + +## Quotes inside the template + +If the template itself contains quotes, wrap it in the other kind to avoid clashes. + +{% example %} +automation: | + # Template contains single quotes, so wrap in double quotes + value_template: "{{ is_state('light.kitchen', 'on') }}" + + # Template contains double quotes, so wrap in single quotes + value_template: '{{ states["sensor.temp"] }}' +output: "Both templates evaluate correctly without quote conflicts" +{% endexample %} + +If you need both kinds of quotes in the same template, switch to a multi-line string (see below). + +## Multi-line templates + +When a template grows beyond a single line, quoting gets painful. YAML has two styles for writing text across multiple lines without needing quotes at all: the `>` style and the `|` style. Pick whichever matches what you want the output to look like. + +**The `>` style (folded)** joins your lines back into one, with a single space between them. Use this when your template is really one long piece of code that you broke into multiple lines to fit on the page nicely. + +{% example %} +automation: | + value_template: > + {{ + states('sensor.temp') | float(0) > 20 + and states('sensor.humidity') | float(0) < 60 + }} +output: "True (when both conditions are true)" +{% endexample %} + +**The `|` style (literal)** keeps every line break exactly as you wrote it. Use this when your template output needs the line breaks preserved, like a multi-line notification message. + +{% example %} +automation: | + message: | + Today's summary: + Temperature: {{ states('sensor.temp') }}°C + Humidity: {{ states('sensor.humidity') }}% +output: | + Today's summary: + Temperature: 22.5°C + Humidity: 54% +{% endexample %} + +### The `>-` and `|-` variants + +A trailing `-` strips the final newline. You will see this used often for `value_template` where you do not want a trailing blank line. + +{% example %} +automation: | + value_template: >- + {% if states('sensor.outdoor_temp') | float(0) < 0 %} + freezing + {% else %} + not freezing + {% endif %} +output: "not freezing" +{% endexample %} + +## Indentation inside multi-line blocks + +Inside a `|` or `>` block, YAML figures out the indentation from the first non-empty line. Every following line must be indented at least as far. If you mix indents, YAML will cut the block off at the less-indented line, and the rest will look like a new key to YAML. + +{% example %} +automation: | + # Wrong: second line is less indented than the first + message: | + Status Report + Temperature: 22 +{% endexample %} + +{% example %} +automation: | + # Correct: all lines share the same base indent + message: | + Status Report + Temperature: 22 +output: | + Status Report + Temperature: 22 +{% endexample %} + +If you need more indentation on specific lines (for example, centered text), add it with the template itself rather than with extra YAML indentation. + +## Common mistakes + +- **Unquoted single-line template**. YAML will complain about the `{{`. Add quotes. +- **Starting with a quote but forgetting to close it**. A common slip with long templates. Switch to a `>-` multi-line block when in doubt. +- **Using `>` when you need line breaks preserved**. The output becomes one long line. Use `|` instead. +- **Inconsistent indentation inside `|` or `>` blocks**. YAML truncates the block at the lowest indent. + +When a template refuses to work, the [Debugging templates](/docs/templating/debugging/) page walks through how to narrow the problem down. + +## Next steps + +- For the core language itself, see [Template syntax](/docs/templating/syntax/). +- When a template does not behave, the [Debugging templates](/docs/templating/debugging/) page walks through the process. +- For ready-made examples you can adapt, see [Common template patterns](/docs/templating/patterns/). + +{% include template_functions/stuck.md %} diff --git a/source/_docs/tools/dev-tools.markdown b/source/_docs/tools/dev-tools.markdown index 3c2cb2ad6ec7..e0d848cc4ca3 100644 --- a/source/_docs/tools/dev-tools.markdown +++ b/source/_docs/tools/dev-tools.markdown @@ -85,7 +85,7 @@ The template editor provides a way to quickly test templates prior to placing th By default, this will contain sample code that illustrates how templates can be written and tested. This sample code can be removed and replaced with your own. You can restore the default example by pressing the **Reset to Demo Template** button beneath the code editor. -For more information about Jinja2, visit [Jinja2 documentation](https://jinja.palletsprojects.com/en/latest/templates/), and also read templating document [here](/docs/configuration/templating). +For more information about Jinja2, visit [Jinja2 documentation](https://jinja.palletsprojects.com/en/latest/templates/), and also read templating document [here](/docs/templating). ## Events tab diff --git a/source/_includes/asides/docs_sitemap.html b/source/_includes/asides/docs_sitemap.html index 3cafd0909ff0..4dddaf14169b 100644 --- a/source/_includes/asides/docs_sitemap.html +++ b/source/_includes/asides/docs_sitemap.html @@ -187,6 +187,30 @@ {% endif %} +
  • + {% icon "mdi:code-braces" %} {% active_link /docs/templating/ Templating %} + {% if doc == 'templating' or root == 'template-functions' or include.docs_index %} +
      +
    • {% active_link /docs/templating/introduction/ Introduction %}
    • +
    • {% active_link /docs/templating/where-to-use/ Where to use templates %}
    • +
    • {% active_link /docs/templating/syntax/ Template syntax %}
    • +
    • {% active_link /docs/templating/loops-and-conditions/ Loops and conditions %}
    • +
    • {% active_link /docs/templating/yaml/ Templates in YAML %}
    • +
    • {% active_link /docs/templating/states/ Working with states %}
    • +
    • {% active_link /docs/templating/types/ Types and conversion %}
    • +
    • {% active_link /docs/templating/dates-and-times/ Dates and times %}
    • +
    • {% active_link /docs/templating/python-methods/ Python methods %}
    • +
    • {% active_link /docs/templating/patterns/ Common patterns %}
    • +
    • {% active_link /docs/templating/debugging/ Debugging templates %}
    • +
    • {% active_link /docs/templating/errors/ Error messages %}
    • +
    • {% active_link /docs/templating/custom-templates/ Custom templates and macros %}
    • +
    • {% active_link /docs/templating/tutorial-battery-alerts/ Tutorial: Low battery alerts %}
    • +
    • {% active_link /docs/templating/tutorial-average-temperature/ Tutorial: Average temperature %}
    • +
    • {% active_link /template-functions/ Template functions reference %}
    • +
    + {% endif %} +
  • +
  • {% icon "mdi:update" %} {% active_link /common-tasks/general/ Common tasks %} {% if root == 'common-tasks' or include.docs_index %} @@ -215,7 +239,6 @@
  • {% active_link /docs/configuration/events/ Events %}
  • {% active_link /docs/configuration/state_object/ State and state object %}
  • {% active_link /docs/configuration/entities_domains/ Entities and domains %}
  • -
  • {% active_link /docs/configuration/templating/ Templating %}
  • {% active_link /docs/configuration/platform_options/ Entity component platform options %}
  • {% endif %} diff --git a/source/_includes/asides/template_function_categories.html b/source/_includes/asides/template_function_categories.html new file mode 100644 index 000000000000..7225537a93cf --- /dev/null +++ b/source/_includes/asides/template_function_categories.html @@ -0,0 +1,15 @@ +
    +
    +

    {% icon "mdi:category" %} Categories

    +
      + {%- for cat in site.data.template_function_categories -%} + {%- assign cat_count = site.template_functions | where: 'category', cat.key | size -%} + {%- if cat_count > 0 -%} + + {{ cat.label }} + + {%- endif -%} + {%- endfor -%} +
    +
    +
    diff --git a/source/_includes/asides/template_function_category_list.html b/source/_includes/asides/template_function_category_list.html new file mode 100644 index 000000000000..29ffe1f85bd8 --- /dev/null +++ b/source/_includes/asides/template_function_category_list.html @@ -0,0 +1,17 @@ +Home Assistant extends the [Jinja2 template engine](https://jinja.palletsprojects.com/en/latest/templates/) with many custom functions, filters, and tests. Each function on this page has its own page with detailed descriptions, parameters, and practical examples. + +New to templates? Start with the [templating documentation](/docs/templating/) to learn the basics. + +{%- assign cat_functions = site.template_functions | where: 'category', include.category | sort: 'function_name' -%} + + diff --git a/source/_includes/asides/template_function_learn_more.html b/source/_includes/asides/template_function_learn_more.html new file mode 100644 index 000000000000..32de6a379d7c --- /dev/null +++ b/source/_includes/asides/template_function_learn_more.html @@ -0,0 +1,9 @@ +
  • {% active_link /docs/templating/ About templating %}
  • +
  • {% active_link /docs/templating/introduction/ Introduction to templating %}
  • +{%- assign current_category = site.data.template_function_categories | where: "key", page.category | first -%} +{%- if current_category.learn_more -%} + {%- for item in current_category.learn_more -%} +
  • {{ item.title }}
  • + {%- endfor -%} +{%- endif -%} +
  • {% active_link /template-functions/ All template functions %}
  • diff --git a/source/_includes/asides/template_function_left_sidebar.html b/source/_includes/asides/template_function_left_sidebar.html new file mode 100644 index 000000000000..c0d91f2f9b43 --- /dev/null +++ b/source/_includes/asides/template_function_left_sidebar.html @@ -0,0 +1,29 @@ +{% include asides/template_function_meta.html %} + +{%- if page.related_functions -%} + {%- assign all_functions = site.template_functions -%} + {%- assign has_related = false -%} + {%- for func_name in page.related_functions -%} + {%- assign func = all_functions | where: "function_name", func_name | first -%} + {%- if func -%}{%- assign has_related = true -%}{%- endif -%} + {%- endfor -%} + {%- if has_related -%} +
    +
    +

    {% icon "mdi:link-variant" %} Related functions

    +
      + {%- for func_name in page.related_functions -%} + {%- assign func = all_functions | where: "function_name", func_name | first -%} + {%- if func -%} +
    • + {{ func.title }} +
    • + {%- endif -%} + {%- endfor -%} +
    +
    +
    + {%- endif -%} +{%- endif -%} + +{% include asides/template_function_categories.html %} diff --git a/source/_includes/asides/template_function_meta.html b/source/_includes/asides/template_function_meta.html new file mode 100644 index 000000000000..0174d11c2bc2 --- /dev/null +++ b/source/_includes/asides/template_function_meta.html @@ -0,0 +1,66 @@ +{%- assign category_label = "" -%} +{%- for cat in site.data.template_function_categories -%} + {%- if cat.key == page.category -%} + {%- assign category_label = cat.label -%} + {%- endif -%} +{%- endfor -%} + +
    +
    +

    {% icon "mdi:code-braces" %} Function: {{ page.function_name }}

    + +
    + {% icon "mdi:format-list-bulleted-type" %} Available as: +
      + {%- for type in page.available_as -%} +
    • {% include template_functions/available_type.html type=type %}
    • + {%- endfor -%} +
    +
    + + {%- if page.aliases -%} +
    + {% icon "mdi:alpha-a-box-outline" %} Also known as: +
      + {%- for alias in page.aliases -%} +
    • {{ alias }}
    • + {%- endfor -%} +
    +
    + {%- endif -%} + +
    + {%- if page.limited -%} + {% icon "mdi:check-circle-outline" %} Available in limited templates + {%- else -%} + {% icon "mdi:close-circle-outline" %} Not available in limited templates + {%- endif -%} +
    + + {%- if page.return_type -%} +
    + {% icon "mdi:keyboard-return" %} Returns: {% include template_functions/return_type.html type=page.return_type %} +
    + {%- endif -%} + + {%- if page.since -%} +
    + {% icon "mdi:history" %} Since Home Assistant {{ page.since }} +
    + {%- endif -%} + + {%- if page.deprecated_in_favor_of -%} +
    + {% icon "mdi:alert" %} Deprecated. Use {{ page.deprecated_in_favor_of }} instead. +
    + {%- endif -%} + +
    + {% icon "mdi:category" %} Category: {{ category_label }} +
    + +
    + {% my developer_template badge %} +
    +
    +
    diff --git a/source/_includes/asides/template_functions_navigation.html b/source/_includes/asides/template_functions_navigation.html new file mode 100644 index 000000000000..f3a12df2a3cd --- /dev/null +++ b/source/_includes/asides/template_functions_navigation.html @@ -0,0 +1,31 @@ +{%- assign all_functions = site.template_functions | sort: 'title' -%} +{%- assign current_category = page.category -%} +
    +
    +

    {% icon "mdi:code-braces" %} Template Functions

    + + + +
    +
    diff --git a/source/_includes/common-tasks/data_disk.md b/source/_includes/common-tasks/data_disk.md index 742a87056922..e573d1f8c25e 100644 --- a/source/_includes/common-tasks/data_disk.md +++ b/source/_includes/common-tasks/data_disk.md @@ -1,6 +1,6 @@ ## Using external data disk -{% term "Home Assistant Operating System" %} supports storing data on a secondary storage medium. For example, this can be a second internal SSD or HDD or a USB attached SSD or HDD. This data disk contains not only user data but also most of the Home Assistant software as well (Core, Supervisor, etc.). This means a fast data disk will make the system overall much faster. +{% term "Home Assistant Operating System" %} supports storing data on a secondary storage medium. For example, this can be a second internal SSD or HDD, or a USB-attached SSD or HDD. This data disk contains not only user data but also most of the Home Assistant software, including {% term "Home Assistant Core" %} and Apps. This means a fast data disk will make the system much faster overall. ![Graphics showing the architecture of the data disk feature](/images/haos/usb-data-disk.png) @@ -78,7 +78,7 @@ To migrate an external data disk from one system to another, follow these steps: 1. [Create a backup](/common-tasks/general/#backups) of both systems and store these backups on another system (not strictly necessary, but recommended just in case, at least for the important data). 2. Shut down system 1 and remove the data disk. -3. Make sure system 2 has Home Assistant OS installed, and Home Assistant is up and running. Home Assistant is using the data disk (partition) on the boot drive (e.g. SD card) at this point. +3. Make sure system 2 has Home Assistant OS installed, and Home Assistant is up and running. Home Assistant is using the data disk (partition) on the boot drive, such as the SD card, at this point. 4. Make sure system 2 has completed the basic [onboarding](/getting-started/onboarding/) steps, including the last steps where devices are discovered automatically. 5. Plug the external disk into system 2 and go to the **Settings** > **System**. Select the three dots {% icon "mdi:dots-vertical" %} menu, and **Restart Home Assistant** > **Reboot system**. Result: A repair issue is displayed **Multiple data disks detected**. diff --git a/source/_includes/common-tasks/enable_i2c.md b/source/_includes/common-tasks/enable_i2c.md index 37ee41d73987..d2b87435714c 100644 --- a/source/_includes/common-tasks/enable_i2c.md +++ b/source/_includes/common-tasks/enable_i2c.md @@ -2,7 +2,7 @@ Home Assistant using the {% term "Home Assistant Operating System" %} which is a managed environment, which means you can't use existing methods to enable the I2C bus on a Raspberry Pi. In order to use I2C devices you will have to - Enable I2C for the Home Assistant Operating System -- Setup I2C devices e.g. sensors +- Set up I2C devices, such as sensors ### Enable I2C with an SD card reader @@ -60,7 +60,7 @@ You can enable I2C via this terminal: ``` ### Troubleshooting -After rebooting the host there should be `i2c-0` and similar device files in `/dev`. If such device files are missing, enabling I2C failed for some reason. You can check the status of I2C kernel modules by using `lsmod | grep i2c` in the terminal. If they are loaded, you should find at least the entry `i2c_dev`. Active usage of the modules is indicated by a number, e.g. `i2c_dev 20480 2` would indicate two active I2C device files. +After rebooting the host there should be `i2c-0` and similar device files in `/dev`. If such device files are missing, enabling I2C failed for some reason. You can check the status of I2C kernel modules by using `lsmod | grep i2c` in the terminal. If they are loaded, you should find at least the entry `i2c_dev`. Active usage of the modules is indicated by a number. For example, `i2c_dev 20480 2` would indicate two active I2C device files. An active I2C can also be checked with a multi meter showing 3.3 V on the I2C pins GPIO2 and GPIO3. diff --git a/source/_includes/custom/news.html b/source/_includes/custom/news.html index 97a232db104e..40072a865a85 100644 --- a/source/_includes/custom/news.html +++ b/source/_includes/custom/news.html @@ -1,24 +1,15 @@