Compare commits

..

109 Commits

Author SHA1 Message Date
Josef Zweck 73cdf7e067 Revert "Add pyserial-asyncio and pyserial-asyncio-fast to deprecated packages" (#174110) 2026-06-17 09:18:44 +02:00
Franck Nijhof 4e2cfecd96 Filter out closed sites in Amber Electric config flow (#174084) 2026-06-17 09:18:19 +02:00
Åke Strandberg 4625f7de27 Aqvify has reached gold tier (#174018) 2026-06-17 09:11:42 +02:00
Brett Adams 53a1db405c Improve test coverage of Teslemetry offline polling (#174108)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-17 08:47:08 +02:00
Paul Bottein ff7262d36f Fix Yoto quality scale comments (#174088) 2026-06-17 08:43:08 +02:00
tronikos 54feb95b76 Gemini: Update TTS model to gemini-3.1 and adjust configuration options (#174094) 2026-06-17 08:42:00 +02:00
epenet d9e2b49c0c Fix incorrect use of entity component constants in template (#172532) 2026-06-17 07:55:57 +02:00
renovate[bot] 4f9051464d Update cryptography to 48.0.1 (#174096) 2026-06-17 07:34:00 +02:00
Paulus Schoutsen 87894fd623 Activate venv before running python commands (#174093) 2026-06-17 07:32:22 +02:00
Franck Nijhof 34a70a9210 Clean up deprecated solar_rising entity from sun integration (#174079) 2026-06-17 06:44:16 +02:00
Paulus Schoutsen c9fb6a13fb Remove stale requirements_test_all.txt reference (#174095) 2026-06-17 05:08:20 +02:00
Franck Nijhof 1601b5151c Bump opower to 0.18.5 (#174080) 2026-06-16 14:20:30 -07:00
Franck Nijhof da0e23093d Cast system version to string for simplisafe device model (#174081) 2026-06-16 22:05:31 +02:00
Paul Bottein 7863468a34 Enable strict typing for Yoto (#174068) 2026-06-16 22:02:48 +02:00
Erik Montnemery 4ff5ee0520 Fix trigger first all race (#174078) 2026-06-16 22:00:03 +02:00
Franck Nijhof 6d8e3ab0c9 Retry webdav setup on connection errors (#174077) 2026-06-16 21:49:44 +02:00
epenet faa3a4ddef Add new enum for Density units (#172551) 2026-06-16 21:18:13 +02:00
Erik Montnemery 9cd7ea97e9 Improve condition history manager (#174069) 2026-06-16 21:17:34 +02:00
Paul Bottein 6012ec97b3 Add diagnostics to Yoto (#174070) 2026-06-16 21:09:36 +02:00
Jan Bouwhuis c58b281eda Remove term "Advanced" in IMAP translation strings (#174074) 2026-06-16 21:07:08 +02:00
jasonjhofmann 05001e581a Add network MAC connection to Renson devices (#173677)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 21:00:35 +02:00
jasonjhofmann 20dbfd19e2 Add network MAC connection to Electra Smart devices (#173678)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:59:54 +02:00
jasonjhofmann 179cb6e385 Add network MAC connection to Vilfo router (#173680)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:59:20 +02:00
Ariel Ebersberger 163fe9f20c Fix flaky cover device_condition test by ignoring asyncio slow-callback warnings (#173876)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 20:58:38 +02:00
some-random-climber f7d8bb112f Use dt_util.utcnow in reolink tests (#174022)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2026-06-16 20:57:29 +02:00
Josef Zweck c973bd90b2 Add pyserial-asyncio and pyserial-asyncio-fast to deprecated packages (#174013) 2026-06-16 20:57:20 +02:00
Michael Hansen 92e947ac28 Fix punctuation in voice aliases (#173945)
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
2026-06-16 20:56:44 +02:00
Joost Lekkerkerker a514683efa Remove unnecessary source state attribute in Sonos (#173995) 2026-06-16 20:56:20 +02:00
some-random-climber 41fe4f4f69 Use dt_util.utcnow in yolink (#174027) 2026-06-16 20:54:02 +02:00
some-random-climber e613f2b1e7 Use dt_util.utcnow in mcp tests (#174019) 2026-06-16 20:51:18 +02:00
some-random-climber 5c4f48a069 Use dt_util.utcnow in nest tests (#174021) 2026-06-16 20:50:56 +02:00
some-random-climber 219455ab4b Use dt_util.utcnow in sensoterra (#174023) 2026-06-16 20:50:01 +02:00
some-random-climber 75815fbc15 Use dt_util.utcnow in integration (#174024) 2026-06-16 20:49:36 +02:00
some-random-climber 33d9249d34 Use dt_util.utcnow in reolink (#174025) 2026-06-16 20:49:10 +02:00
some-random-climber 7cefe94467 Use dt_util.utcnow in starlink (#174026) 2026-06-16 20:48:38 +02:00
Markus Jacobsen c95ea00479 Replace "advanced" wording for Beolink actions in Bang & Olufsen (#174062) 2026-06-16 20:47:34 +02:00
Przemko92 730b6065ff Upgrade compit-inext-api to 0.9.1 (#173955) 2026-06-16 20:46:32 +02:00
Abílio Costa 1589ad2c6a Don't use infrared entity id as unique id for LG Infrared (#174072) 2026-06-16 20:44:12 +02:00
Tom d0df0de267 Rename airOS advanced settings (#174066) 2026-06-16 19:41:10 +02:00
Franck Nijhof aec09fadd4 Avoid allocating the exclude attributes set for every recorded state (#173690) 2026-06-16 18:34:46 +02:00
Franck Nijhof e2d68fcf58 Skip setting unused legacy columns when recording states and events (#173691) 2026-06-16 18:34:20 +02:00
Manu 90fe38c0f2 Replace Advanced settings in Habitica integration (#174064) 2026-06-16 17:55:40 +02:00
Michael Hansen 65c2aaf22f Revert "Update hassil to 3.8.1" (#174061) 2026-06-16 17:43:02 +02:00
Åke Strandberg a691de352c Add volume and temperature sensors to aqvify (#174007) 2026-06-16 17:38:12 +02:00
renovate[bot] 4203781fa5 Update hassil to 3.8.1 (#173957)
Co-authored-by: renovate[bot] <29139614+renovate[bot]@users.noreply.github.com>
2026-06-16 10:08:16 -05:00
Erik Montnemery e6b3a97162 Avoid flooding the recorder when priming condition history (#174020) 2026-06-16 17:06:34 +02:00
Erik Montnemery 6ad8ad5715 Call state change listeners immediately instead of deferring them to the event loop (#173974) 2026-06-16 16:50:32 +02:00
Åke Strandberg a45867b896 Use username as config entry title in aqvify (#174008) 2026-06-16 14:30:25 +02:00
Franck Nijhof 000e075a8e Add missing subentry flow translations in scrape (#174006) 2026-06-16 14:26:12 +02:00
Franck Nijhof 0899d016b9 Add missing flow form field translations in ecobee (#174002) 2026-06-16 14:25:40 +02:00
Franck Nijhof 3375f2ed76 Add missing flow form field translation in otp (#173994) 2026-06-16 14:25:29 +02:00
Franck Nijhof 3f5778e71b Add missing flow form field translations in tractive (#174005) 2026-06-16 14:10:47 +02:00
Franck Nijhof 86c39694d3 Add missing flow form field translation in iskra (#174004) 2026-06-16 13:48:58 +02:00
Franck Nijhof a53a6644c0 Fix flow form field translations in modem_callerid (#173999) 2026-06-16 13:47:01 +02:00
Franck Nijhof 18fdfacf45 Fix flow form field translation key in sia (#173998) 2026-06-16 13:46:27 +02:00
Franck Nijhof bd9bd29f2c Add missing flow form field translation in airvisual (#174000) 2026-06-16 13:46:19 +02:00
Franck Nijhof 334c6614cc Fix flow form field translations in local_calendar (#173997) 2026-06-16 13:43:22 +02:00
Franck Nijhof aa772f6ecd Add missing flow form field translation in honeywell (#173996) 2026-06-16 13:41:18 +02:00
Franck Nijhof 87169921ae Fix flow form field translations in hlk_sw16 (#173993) 2026-06-16 13:39:40 +02:00
Franck Nijhof 16338b8b6b Fix flow form field translation keys in here_travel_time (#173992) 2026-06-16 13:38:50 +02:00
Åke Strandberg 519da3c9c9 Add aqvify devices dynamically (#173534) 2026-06-16 13:37:42 +02:00
Åke Strandberg 6f34718c1f Bump pyaqvify to 0.0.11 (#173989) 2026-06-16 13:37:02 +02:00
Tim Laing e4287bb43c Bump PyiCloud to 2.6.5 (#173928) 2026-06-16 13:05:50 +02:00
Mike O'Driscoll d724ebac2a casper_glow: add bluetooth reachability diagnostics (#173921) 2026-06-16 13:02:46 +02:00
Raphael Hehl dc480051db Use console name in UniFi Network discovery title (#173931) 2026-06-16 12:57:51 +02:00
Franck Nijhof 63b6ced9c4 Bump evolutionhttp to 0.0.19 (#173911) 2026-06-16 12:46:21 +02:00
Franck Nijhof 34e9b3ff1e Bump lunatone-rest-api-client to 0.9.2 (#173918) 2026-06-16 12:45:19 +02:00
Robert Resch 210746525e Fix missing full sha as hidden field in requirements check aw (#173900) 2026-06-16 11:05:08 +02:00
Robert Resch 0134e99366 Token views should behave the same (#173500) 2026-06-16 10:46:18 +02:00
Oscar Calvo 06de89d6a3 Fix CCM15 temperature unit to follow the device's C/F setting (#173788) 2026-06-16 10:41:58 +02:00
Paul Bottein 4c267617f8 Publish numeric sensor device classes as generated sensor.json (#173919) 2026-06-16 11:41:27 +03:00
Franck Nijhof a82f1a7a1d Bump pyfireservicerota to 0.0.49 (#173935) 2026-06-16 10:36:34 +02:00
Franck Nijhof d234f65dd9 Bump heatmiserV3 to 2.0.6 (#173913)
Co-authored-by: Erwin Douna <e.douna@gmail.com>
2026-06-16 10:36:03 +02:00
John Pettitt 30148980e1 Add API_GEN_4 support to Subaru integration (#173956) 2026-06-16 10:32:02 +02:00
Franck Nijhof 1fa9a3353c Bump eufylife-ble-client to 0.1.10 (#173934) 2026-06-16 10:30:52 +02:00
Raphael Hehl 2dbbd70085 Use console name in UniFi Protect discovery title (#173966) 2026-06-16 10:25:50 +02:00
Franck Nijhof 73903b0bfc Bump pysesame2 to 1.0.2 (#173904) 2026-06-16 10:20:23 +02:00
Franck Nijhof b09f54ce3b Bump foobot_async to 1.0.1 (#173905) 2026-06-16 10:19:38 +02:00
Franck Nijhof 6d9e41da07 Bump pencompy to 0.0.4 (#173906) 2026-06-16 10:17:46 +02:00
Franck Nijhof f5600a602f Bump webexpythonsdk to 2.0.6 (#173916) 2026-06-16 09:50:37 +02:00
Franck Nijhof d83cd941a7 Bump hole to 0.9.2 (#173936) 2026-06-16 09:37:58 +02:00
Franck Nijhof 2120cad533 Bump pdunehd to 1.3.3 (#173907)
Co-authored-by: Josef Zweck <josef@zweck.dev>
2026-06-16 09:34:44 +02:00
dependabot[bot] fb4e72af77 Bump home-assistant/builder from 2026.03.2 to 2026.06.0 (#173963)
Signed-off-by: dependabot[bot] <support@github.com>
2026-06-16 09:22:22 +02:00
Paul Bottein badd4130b6 Bump yoto-api to 4.3.0 (#173910) 2026-06-16 09:18:10 +02:00
Franck Nijhof 7a4ca4dcfd Bump roombapy to 1.9.1 (#173922) 2026-06-16 09:14:40 +02:00
Franck Nijhof 9b47a0d440 Bump atenpdu to 0.3.6 (#173932) 2026-06-16 09:10:16 +02:00
Franck Nijhof 4b99e81a8a Bump influxdb to 5.3.2 (#173891) 2026-06-16 08:54:20 +02:00
Franck Nijhof 62e5238f43 Bump tellcore-py to 1.1.3 (#173894) 2026-06-16 08:53:50 +02:00
Franck Nijhof 149c884a89 Bump DoorBirdPy to 3.0.12 (#173923) 2026-06-16 08:52:50 +02:00
Franck Nijhof 71ca453c42 Bump pyhomematic to 0.1.78 (#173925) 2026-06-16 08:52:27 +02:00
Franck Nijhof aad6080307 Bump omnilogic to 0.4.9 (#173938) 2026-06-16 08:51:40 +02:00
Franck Nijhof 2db2e0b0cf Bump aioairq to 0.4.8 (#173940) 2026-06-16 08:50:50 +02:00
Franck Nijhof 3fc36ab6f9 Bump messagebird to 1.2.1 (#173942) 2026-06-16 08:49:56 +02:00
Denis Shulyaka 0fad24393c Fix docs-data-update IQS for Anthropic (#173947) 2026-06-16 08:21:09 +02:00
Raphael Hehl a992a58367 Use console name in UniFi Access discovery title (#173962) 2026-06-16 08:20:29 +02:00
jasonjhofmann f0cefe2f2e Add network MAC connection to Rain Bird controller (#173672)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 08:15:33 +02:00
jasonjhofmann 40264992a2 Add network MAC connection to AnthemAV main zone device (#173682)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 08:12:52 +02:00
jasonjhofmann c29aebd60e Add network MAC connection to PlayStation 4 devices (#173681)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 08:11:25 +02:00
jasonjhofmann 36b74d6f05 Add network MAC connection to iAlarm device (#173676)
Co-authored-by: jasonjhofmann <16144532+jasonjhofmann@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 08:02:49 +02:00
jasonjhofmann 2c626fa8f0 Add network MAC connection to Rabbit Air devices (#173684)
Co-authored-by: jasonjhofmann <16144532+jasonjhofmann@users.noreply.github.com>
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 08:01:22 +02:00
jasonjhofmann cab0d015f6 Add network MAC connection to Aprilaire devices (#173675)
Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-16 08:00:13 +02:00
Erik Montnemery c544f95979 Prime condition durations from history (#173426) 2026-06-16 07:53:41 +02:00
renovate[bot] 2189d0ae74 Update infrared-protocols to 6.0.1 (#173958) 2026-06-16 07:53:22 +02:00
Raphael Hehl 9e96a06aff Bump unifi-discovery to 1.5.0 (#173927) 2026-06-16 00:06:01 +02:00
Franck Nijhof d16e0e9867 Bump greeclimate to 2.1.4 (#173924) 2026-06-15 22:53:59 +02:00
Franck Nijhof 2209996919 Bump pyipma to 3.0.10 (#173943) 2026-06-15 22:09:00 +02:00
Franck Nijhof d88767155b Bump pykrakenapi to 0.1.9 (#173933) 2026-06-15 21:47:44 +02:00
Franck Nijhof 334d02077f Bump pypck to 0.9.13 (#173914) 2026-06-15 21:46:57 +02:00
Franck Nijhof 2b7e9289d2 Bump librouteros to 3.2.1 (#173937) 2026-06-15 21:41:42 +02:00
201 changed files with 4509 additions and 926 deletions
+2 -2
View File
@@ -193,7 +193,7 @@ jobs:
echo "${GITHUB_SHA};${GITHUB_REF};${GITHUB_EVENT_NAME};${GITHUB_ACTOR}" > rootfs/OFFICIAL_IMAGE
- name: Build base image
uses: home-assistant/builder/actions/build-image@62a1597b84b3461abad9816d9cd92862a2b542c3 # 2026.03.2
uses: home-assistant/builder/actions/build-image@4de35182ce1e329181bffcbcc84d33db5e2c7e10 # 2026.06.0
with:
arch: ${{ matrix.arch }}
build-args: |
@@ -264,7 +264,7 @@ jobs:
fi
- name: Build machine image
uses: home-assistant/builder/actions/build-image@62a1597b84b3461abad9816d9cd92862a2b542c3 # 2026.03.2
uses: home-assistant/builder/actions/build-image@4de35182ce1e329181bffcbcc84d33db5e2c7e10 # 2026.06.0
with:
arch: ${{ matrix.arch }}
build-args: |
+5 -4
View File
@@ -1,4 +1,4 @@
# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"328ce915f8b178bbcfcb1b69f397ac996456a4ab50490805b5b3bdd26cbf58fe","body_hash":"0665c72fe4b0a14b3a0b396dc293ce78235771fe15ebc894662fece33b189135","compiler_version":"v0.79.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}}
# gh-aw-metadata: {"schema_version":"v4","frontmatter_hash":"7b142e96e0f8b454cdcc9c0c25070cf9a52c44d83a6b1fbc3ad6725b6567337c","body_hash":"3894ded07d5934ac5f29d160ffb1f9115cf72b6da8a7e453a4d4f69e8641a48e","compiler_version":"v0.79.6","strict":true,"agent_id":"copilot","engine_versions":{"copilot":"1.0.60"}}
# gh-aw-manifest: {"version":1,"secrets":["COPILOT_GITHUB_TOKEN","GH_AW_GITHUB_MCP_SERVER_TOKEN","GH_AW_GITHUB_TOKEN","GITHUB_TOKEN"],"actions":[{"repo":"actions/checkout","sha":"df4cb1c069e1874edd31b4311f1884172cec0e10","version":"v6.0.3"},{"repo":"actions/download-artifact","sha":"3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c","version":"v8.0.1"},{"repo":"actions/github-script","sha":"3a2844b7e9c422d3c10d287c895573f7108da1b3","version":"v9.0.0"},{"repo":"actions/setup-node","sha":"48b55a011bda9f5d6aeb4c2d9c7362e8dae4041e","version":"v6.4.0"},{"repo":"actions/upload-artifact","sha":"043fb46d1a93c77aae656e7c1c64a875d1fc6a0a","version":"v7.0.1"},{"repo":"github/gh-aw-actions/setup","sha":"v0.79.6","version":"v0.79.6"}],"containers":[{"image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2","digest":"sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6","pinned_image":"ghcr.io/github/gh-aw-firewall/agent:0.27.2@sha256:f88e5b17b6b7a600117bc121114d6ce2155c88c983c0c939c5df884f730fa1d6"},{"image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2","digest":"sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4","pinned_image":"ghcr.io/github/gh-aw-firewall/api-proxy:0.27.2@sha256:ee39841d980878ebbb87592903b06d31a1af500c71525c9616f7e8e2a27041a4"},{"image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2","digest":"sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591","pinned_image":"ghcr.io/github/gh-aw-firewall/squid:0.27.2@sha256:2e3a717e5f19a654cd9a2263beb52012b56bcb68562ec5ae2e42f9d156b49591"},{"image":"ghcr.io/github/gh-aw-mcpg:v0.3.25","digest":"sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa","pinned_image":"ghcr.io/github/gh-aw-mcpg:v0.3.25@sha256:c10331ad17668ef89f38f5e356678788a40b0cd5fef96e8f92e1d9c1de47cbaa"},{"image":"ghcr.io/github/github-mcp-server:v1.1.2","digest":"sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c","pinned_image":"ghcr.io/github/github-mcp-server:v1.1.2@sha256:30197479d8036c7811892bc07e06f9a05c9ef3cdd79bc59f256d50647f95788c"}]}
# ___ _ _
# / _ \ | | (_)
@@ -1500,11 +1500,12 @@ jobs:
echo "Artifact has no head_sha; running the agent."
exit 0
fi
# Recover the commit recorded in the most recent requirements-check comment.
# Recover the commit recorded in the most recent requirements-check
# comment from the "Checked at commit" link
PRIOR=$(gh api --paginate "repos/${GITHUB_REPOSITORY}/issues/${PR}/comments" \
--jq '.[] | select(.body | contains("<!-- requirements-check -->")) | .body' \
| grep -oE '<!-- requirements-check-sha: [0-9a-f]{7,40} -->' \
| grep -oE '[0-9a-f]{7,40}' | tail -1 || true)
| grep -oiE '/commit/[0-9a-f]{40}' \
| grep -oiE '[0-9a-f]{40}' | tail -1 || true)
if [ -z "${PRIOR}" ]; then
echo "No previous comment with a recorded commit; running the agent."
exit 0
+6 -7
View File
@@ -51,11 +51,12 @@ jobs:
echo "Artifact has no head_sha; running the agent."
exit 0
fi
# Recover the commit recorded in the most recent requirements-check comment.
# Recover the commit recorded in the most recent requirements-check
# comment from the "Checked at commit" link
PRIOR=$(gh api --paginate "repos/${GITHUB_REPOSITORY}/issues/${PR}/comments" \
--jq '.[] | select(.body | contains("<!-- requirements-check -->")) | .body' \
| grep -oE '<!-- requirements-check-sha: [0-9a-f]{7,40} -->' \
| grep -oE '[0-9a-f]{7,40}' | tail -1 || true)
| grep -oiE '/commit/[0-9a-f]{40}' \
| grep -oiE '[0-9a-f]{40}' | tail -1 || true)
if [ -z "${PRIOR}" ]; then
echo "No previous comment with a recorded commit; running the agent."
exit 0
@@ -188,10 +189,8 @@ Then stop. Do not improvise a verdict.
Replace every placeholder with the resolved value and emit
`rendered_comment` via `add_comment`. Preserve the leading
`<!-- requirements-check -->` marker and the
`<!-- requirements-check-sha: … -->` marker that follows it — the next
run reads the recorded commit from it to decide whether anything changed.
The PR target is already wired; do not pass `item_number`.
`<!-- requirements-check -->` marker. The PR target is already wired;
do not pass `item_number`.
## Check instructions
+1 -1
View File
@@ -102,7 +102,7 @@ repos:
pass_filenames: false
language: script
types: [text]
files: ^(homeassistant/.+/(icons|manifest|strings)\.json|homeassistant/.+/(conditions|quality_scale|services|triggers)\.yaml|homeassistant/brands/.*\.json|script/hassfest/(?!metadata|mypy_config).+\.py|requirements.+\.txt)$
files: ^(homeassistant/.+/(icons|manifest|strings)\.json|homeassistant/.+/(conditions|quality_scale|services|triggers)\.yaml|homeassistant/brands/.*\.json|script/hassfest/(?!metadata|mypy_config).+\.py|homeassistant/components/sensor/const\.py|requirements.+\.txt)$
- id: hassfest-metadata
name: hassfest-metadata
entry: script/run-in-env.sh python3 -m script.hassfest -p metadata,docker
+1
View File
@@ -642,6 +642,7 @@ homeassistant.components.xbox.*
homeassistant.components.xiaomi_ble.*
homeassistant.components.yale_smart_alarm.*
homeassistant.components.yalexs_ble.*
homeassistant.components.yoto.*
homeassistant.components.youtube.*
homeassistant.components.zeroconf.*
homeassistant.components.zinvolt.*
+6 -6
View File
@@ -30,7 +30,7 @@ from homeassistant.exceptions import (
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DEFAULT_SSL, DEFAULT_VERIFY_SSL, DOMAIN, SECTION_ADVANCED_SETTINGS
from .const import DEFAULT_SSL, DEFAULT_VERIFY_SSL, DOMAIN, SECTION_ADDITIONAL_SETTINGS
from .coordinator import (
AirOSConfigEntry,
AirOSDataUpdateCoordinator,
@@ -55,14 +55,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> boo
# By default airOS 8 comes with self-signed SSL certificates,
# with no option in the web UI to change or upload a custom certificate.
session = async_get_clientsession(
hass, verify_ssl=entry.data[SECTION_ADVANCED_SETTINGS][CONF_VERIFY_SSL]
hass, verify_ssl=entry.data[SECTION_ADDITIONAL_SETTINGS][CONF_VERIFY_SSL]
)
conn_data = {
CONF_HOST: entry.data[CONF_HOST],
CONF_USERNAME: entry.data[CONF_USERNAME],
CONF_PASSWORD: entry.data[CONF_PASSWORD],
"use_ssl": entry.data[SECTION_ADVANCED_SETTINGS][CONF_SSL],
"use_ssl": entry.data[SECTION_ADDITIONAL_SETTINGS][CONF_SSL],
"session": session,
}
@@ -116,15 +116,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> boo
async def async_migrate_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> bool:
"""Migrate old config entry."""
# 1.1 Migrate config_entry to add advanced ssl settings
# 1.1 Migrate config_entry to add additional ssl settings
if entry.version == 1 and entry.minor_version == 1:
new_minor_version = 2
new_data = {**entry.data}
advanced_data = {
additional_data = {
CONF_SSL: DEFAULT_SSL,
CONF_VERIFY_SSL: DEFAULT_VERIFY_SSL,
}
new_data[SECTION_ADVANCED_SETTINGS] = advanced_data
new_data[SECTION_ADDITIONAL_SETTINGS] = additional_data
hass.config_entries.async_update_entry(
entry,
@@ -52,7 +52,7 @@ from .const import (
HOSTNAME,
IP_ADDRESS,
MAC_ADDRESS,
SECTION_ADVANCED_SETTINGS,
SECTION_ADDITIONAL_SETTINGS,
)
_LOGGER = logging.getLogger(__name__)
@@ -66,7 +66,7 @@ STEP_DISCOVERY_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_USERNAME, default=DEFAULT_USERNAME): str,
vol.Required(CONF_PASSWORD): str,
vol.Required(SECTION_ADVANCED_SETTINGS): section(
vol.Required(SECTION_ADDITIONAL_SETTINGS): section(
vol.Schema(
{
vol.Required(CONF_SSL, default=DEFAULT_SSL): bool,
@@ -134,7 +134,7 @@ class AirOSConfigFlow(ConfigFlow, domain=DOMAIN):
# with no option in the web UI to change or upload a custom certificate.
session = async_get_clientsession(
self.hass,
verify_ssl=config_data[SECTION_ADVANCED_SETTINGS][CONF_VERIFY_SSL],
verify_ssl=config_data[SECTION_ADDITIONAL_SETTINGS][CONF_VERIFY_SSL],
)
try:
@@ -143,7 +143,7 @@ class AirOSConfigFlow(ConfigFlow, domain=DOMAIN):
username=config_data[CONF_USERNAME],
password=config_data[CONF_PASSWORD],
session=session,
use_ssl=config_data[SECTION_ADVANCED_SETTINGS][CONF_SSL],
use_ssl=config_data[SECTION_ADDITIONAL_SETTINGS][CONF_SSL],
)
except (
@@ -234,18 +234,18 @@ class AirOSConfigFlow(ConfigFlow, domain=DOMAIN):
autocomplete="current-password",
)
),
vol.Required(SECTION_ADVANCED_SETTINGS): section(
vol.Required(SECTION_ADDITIONAL_SETTINGS): section(
vol.Schema(
{
vol.Required(
CONF_SSL,
default=current_data[SECTION_ADVANCED_SETTINGS][
default=current_data[SECTION_ADDITIONAL_SETTINGS][
CONF_SSL
],
): bool,
vol.Required(
CONF_VERIFY_SSL,
default=current_data[SECTION_ADVANCED_SETTINGS][
default=current_data[SECTION_ADDITIONAL_SETTINGS][
CONF_VERIFY_SSL
],
): bool,
+1 -1
View File
@@ -12,7 +12,7 @@ MANUFACTURER = "Ubiquiti"
DEFAULT_VERIFY_SSL = False
DEFAULT_SSL = True
SECTION_ADVANCED_SETTINGS = "advanced_settings"
SECTION_ADDITIONAL_SETTINGS = "additional_settings"
# Discovery related
DEFAULT_USERNAME = "ubnt"
+2 -2
View File
@@ -4,7 +4,7 @@ from homeassistant.const import CONF_HOST, CONF_SSL
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, MANUFACTURER, SECTION_ADVANCED_SETTINGS
from .const import DOMAIN, MANUFACTURER, SECTION_ADDITIONAL_SETTINGS
from .coordinator import AirOSDataUpdateCoordinator
@@ -20,7 +20,7 @@ class AirOSEntity(CoordinatorEntity[AirOSDataUpdateCoordinator]):
airos_data = self.coordinator.data
url_schema = (
"https"
if coordinator.config_entry.data[SECTION_ADVANCED_SETTINGS][CONF_SSL]
if coordinator.config_entry.data[SECTION_ADDITIONAL_SETTINGS][CONF_SSL]
else "http"
)
+12 -12
View File
@@ -33,16 +33,16 @@
},
"description": "Enter the username and password for {device_name}",
"sections": {
"advanced_settings": {
"additional_settings": {
"data": {
"ssl": "[%key:component::airos::config::step::manual::sections::advanced_settings::data::ssl%]",
"ssl": "[%key:component::airos::config::step::manual::sections::additional_settings::data::ssl%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
},
"data_description": {
"ssl": "[%key:component::airos::config::step::manual::sections::advanced_settings::data_description::ssl%]",
"verify_ssl": "[%key:component::airos::config::step::manual::sections::advanced_settings::data_description::verify_ssl%]"
"ssl": "[%key:component::airos::config::step::manual::sections::additional_settings::data_description::ssl%]",
"verify_ssl": "[%key:component::airos::config::step::manual::sections::additional_settings::data_description::verify_ssl%]"
},
"name": "[%key:component::airos::config::step::manual::sections::advanced_settings::name%]"
"name": "[%key:component::airos::config::step::manual::sections::additional_settings::name%]"
}
}
},
@@ -58,7 +58,7 @@
"username": "Administrator username for the airOS device, normally 'ubnt'"
},
"sections": {
"advanced_settings": {
"additional_settings": {
"data": {
"ssl": "Use HTTPS",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
@@ -67,7 +67,7 @@
"ssl": "Whether the connection should be encrypted (required for most devices)",
"verify_ssl": "Whether the certificate should be verified when using HTTPS. This should be off for self-signed certificates"
},
"name": "Advanced settings"
"name": "Additional settings"
}
}
},
@@ -87,16 +87,16 @@
"password": "[%key:component::airos::config::step::manual::data_description::password%]"
},
"sections": {
"advanced_settings": {
"additional_settings": {
"data": {
"ssl": "[%key:component::airos::config::step::manual::sections::advanced_settings::data::ssl%]",
"ssl": "[%key:component::airos::config::step::manual::sections::additional_settings::data::ssl%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
},
"data_description": {
"ssl": "[%key:component::airos::config::step::manual::sections::advanced_settings::data_description::ssl%]",
"verify_ssl": "[%key:component::airos::config::step::manual::sections::advanced_settings::data_description::verify_ssl%]"
"ssl": "[%key:component::airos::config::step::manual::sections::additional_settings::data_description::ssl%]",
"verify_ssl": "[%key:component::airos::config::step::manual::sections::additional_settings::data_description::verify_ssl%]"
},
"name": "[%key:component::airos::config::step::manual::sections::advanced_settings::name%]"
"name": "[%key:component::airos::config::step::manual::sections::additional_settings::name%]"
}
}
},
+1 -1
View File
@@ -7,7 +7,7 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["aioairq"],
"requirements": ["aioairq==0.4.7"],
"requirements": ["aioairq==0.4.8"],
"zeroconf": [
{
"properties": {
@@ -37,6 +37,9 @@
"title": "Re-authenticate AirVisual"
},
"user": {
"data": {
"type": "Integration type"
},
"description": "Pick what type of AirVisual data you want to monitor.",
"title": "Configure AirVisual"
}
@@ -34,11 +34,13 @@ def generate_site_selector_name(site: Site) -> str:
def filter_sites(sites: list[Site]) -> list[Site]:
"""Deduplicates the list of sites."""
"""Filter out closed sites and deduplicate the list of sites."""
filtered: list[Site] = []
filtered_nmi: set[str] = set()
for site in sorted(sites, key=lambda site: site.status):
if site.status == SiteStatus.CLOSED:
continue
if site.status == SiteStatus.ACTIVE or site.nmi not in filtered_nmi:
filtered.append(site)
filtered_nmi.add(site.nmi)
@@ -12,7 +12,7 @@ from homeassistant.components.media_player import (
)
from homeassistant.const import CONF_MAC, CONF_MODEL
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -87,9 +87,12 @@ class AnthemAVR(MediaPlayerEntity):
via_device=(DOMAIN, mac_address),
)
else:
# Zone 1 is the physical receiver that owns the network MAC; higher
# zones are via_device children and carry no connection.
self._attr_unique_id = mac_address
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, mac_address)},
connections={(CONNECTION_NETWORK_MAC, mac_address)},
name=name,
manufacturer=MANUFACTURER,
model=model,
@@ -52,10 +52,7 @@ rules:
status: exempt
comment: |
Service integration, no discovery.
docs-data-update:
status: exempt
comment: |
No data updates.
docs-data-update: done
docs-examples: done
docs-known-limitations: done
docs-supported-devices: done
@@ -193,6 +193,7 @@ class AprilaireCoordinator(BaseDataUpdateCoordinatorProtocol):
device_info = DeviceInfo(
identifiers={(DOMAIN, self.unique_id)},
connections={(dr.CONNECTION_NETWORK_MAC, data[Attribute.MAC_ADDRESS])},
name=self.create_device_name(data),
manufacturer="Aprilaire",
)
@@ -59,7 +59,9 @@ class AqvifyConfigFlow(ConfigFlow, domain=DOMAIN):
self._get_reconfigure_entry(), data_updates=user_input
)
self._abort_if_unique_id_configured()
return self.async_create_entry(title="Aqvify", data=user_input)
return self.async_create_entry(
title=account_data.name or "Aqvify", data=user_input
)
return self.async_show_form(
step_id="user",
@@ -152,3 +152,10 @@ class AqvifyCoordinator(DataUpdateCoordinator[AqvifyCoordinatorData]):
devices=devices,
device_data=device_data,
)
def async_add_devices(self, added_devices: set[str]) -> tuple[set[str], set[str]]:
"""Return newly discovered device keys and the full current device set."""
current_devices = set(self.data.devices.devices)
new_devices: set[str] = current_devices - added_devices
return (new_devices, current_devices)
@@ -7,6 +7,6 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["pyaqvify"],
"quality_scale": "silver",
"requirements": ["pyaqvify==0.0.10"]
"quality_scale": "gold",
"requirements": ["pyaqvify==0.0.11"]
}
@@ -53,28 +53,42 @@ rules:
test-coverage: done
# Gold
devices: todo
diagnostics: todo
discovery-update-info: todo
discovery: todo
docs-data-update: todo
docs-examples: todo
docs-known-limitations: todo
docs-supported-devices: todo
docs-supported-functions: todo
docs-troubleshooting: todo
docs-use-cases: todo
dynamic-devices: todo
entity-category: todo
entity-device-class: todo
entity-disabled-by-default: todo
entity-translations: todo
exception-translations: todo
devices: done
diagnostics: done
discovery-update-info:
status: exempt
comment: |
Discovery not possible, as device is connected via 4G only. No LAN connection.
discovery:
status: exempt
comment: |
Discovery not possible, as device is connected via 4G only. No LAN connection.
docs-data-update: done
docs-examples: done
docs-known-limitations:
status: done
comment: |
No known limitations
docs-supported-devices: done
docs-supported-functions: done
docs-troubleshooting: done
docs-use-cases: done
dynamic-devices: done
entity-category:
status: done
comment: |
None of current sensors should be set as diagnostic
entity-device-class: done
entity-disabled-by-default: done
entity-translations: done
exception-translations: done
icon-translations: done
reconfiguration-flow: todo
repair-issues: todo
stale-devices: todo
reconfiguration-flow: done
repair-issues:
status: exempt
comment: |
No repair issues are created.
stale-devices: done
# Platinum
async-dependency: todo
inject-websession: todo
+35 -6
View File
@@ -13,7 +13,7 @@ from homeassistant.components.sensor import (
SensorStateClass,
StateType,
)
from homeassistant.const import UnitOfLength
from homeassistant.const import UnitOfLength, UnitOfTemperature, UnitOfVolume
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -50,6 +50,23 @@ ENTITIES: tuple[AqvifySensorEntityDescription, ...] = (
suggested_display_precision=2,
value_fn=lambda value: value.water_level,
),
AqvifySensorEntityDescription(
key="volume",
native_unit_of_measurement=UnitOfVolume.LITERS,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.VOLUME_STORAGE,
suggested_display_precision=0,
value_fn=lambda value: value.volume,
),
AqvifySensorEntityDescription(
key="temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
device_class=SensorDeviceClass.TEMPERATURE,
suggested_display_precision=1,
value_fn=lambda value: value.temperature,
entity_registry_enabled_default=False,
),
)
@@ -59,11 +76,23 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Aqvify sensor entities from a config entry."""
async_add_entities(
AqvifySensor(entry.runtime_data, description, device_key)
for description in ENTITIES
for device_key in entry.runtime_data.data.devices.devices
)
coordinator = entry.runtime_data
added_devices: set[str] = set()
def _async_add_new_devices() -> None:
nonlocal added_devices
new_devices_set, current_devices = coordinator.async_add_devices(added_devices)
added_devices = current_devices
async_add_entities(
AqvifySensor(coordinator, description, device_key)
for description in ENTITIES
for device_key in new_devices_set
)
entry.async_on_unload(coordinator.async_add_listener(_async_add_new_devices))
_async_add_new_devices()
class AqvifySensor(AqvifyBaseEntity, SensorEntity):
@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/aten_pe",
"iot_class": "local_polling",
"quality_scale": "legacy",
"requirements": ["atenpdu==0.3.2"]
"requirements": ["atenpdu==0.3.6"]
}
@@ -1,6 +1,6 @@
{
"common": {
"jid_options_description": "Advanced grouping options, where devices' unique Beolink IDs (Called JIDs) are used directly. JIDs can be found in the state attributes of the media player entity.",
"jid_options_description": "Additional grouping options, where devices' unique Beolink IDs (Called JIDs) are used directly. JIDs can be found in the state attributes of the media player entity.",
"jid_options_name": "JID options",
"key_press": "Press",
"key_release": "Release",
@@ -123,7 +123,14 @@ class _BrandsBaseView(HomeAssistantView):
)
if not authenticated:
if hdrs.AUTHORIZATION in request.headers:
# A failed request that carried an Authorization header is a real
# Bearer auth attempt — return 401 and let the ban middleware count
# it as a wrong login.
raise web.HTTPUnauthorized
# No Authorization header: most likely a benign signed-URL / query-
# token request whose token has expired (e.g. a browser tab left
# open that re-fetches resources later). Return 403 so it doesn't
# register as a wrong login and ban the user's own IP.
raise web.HTTPForbidden
async def _serve_from_custom_integration(
@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/bryant_evolution",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["evolutionhttp==0.0.18"]
"requirements": ["evolutionhttp==0.0.19"]
}
+1 -1
View File
@@ -460,7 +460,7 @@ class EventTrigger(Trigger):
listener = TargetCalendarEventListener(
self._hass, target_selection, self._event_type, offset, run_action
)
return listener.async_setup()
return await listener.async_setup()
class EventStartedTrigger(EventTrigger):
+10 -4
View File
@@ -785,7 +785,9 @@ class CameraView(HomeAssistantView):
async def get(self, request: web.Request, entity_id: str) -> web.StreamResponse:
"""Start a GET request."""
if (camera := self.component.get_entity(entity_id)) is None:
raise web.HTTPNotFound
raise (
web.HTTPNotFound if request[KEY_AUTHENTICATED] else web.HTTPUnauthorized
)
authenticated = (
request[KEY_AUTHENTICATED]
@@ -793,11 +795,15 @@ class CameraView(HomeAssistantView):
)
if not authenticated:
# Attempt with invalid bearer token, raise unauthorized
# so ban middleware can handle it.
if hdrs.AUTHORIZATION in request.headers:
# A failed request that carried an Authorization header is a real
# Bearer auth attempt — return 401 and let the ban middleware count
# it as a wrong login.
raise web.HTTPUnauthorized
# Invalid sigAuth or camera access token
# No Authorization header: most likely a benign signed-URL / query-
# token request whose token has expired (e.g. a browser tab left
# open that re-fetches resources later). Return 403 so it doesn't
# register as a wrong login and ban the user's own IP.
raise web.HTTPForbidden
if not camera.is_on:
@@ -3,6 +3,7 @@
from pycasperglow import CasperGlow
from homeassistant.components import bluetooth
from homeassistant.components.bluetooth import BluetoothReachabilityIntent
from homeassistant.const import CONF_ADDRESS, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
@@ -27,7 +28,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: CasperGlowConfigEntry) -
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="device_not_found",
translation_placeholders={"address": address},
translation_placeholders={
"address": address,
"reason": bluetooth.async_address_reachability_diagnostics(
hass, address.upper(), BluetoothReachabilityIntent.CONNECTION
),
},
)
glow = CasperGlow(ble_device)
@@ -56,7 +56,7 @@
"message": "An error occurred while communicating with the Casper Glow: {error}"
},
"device_not_found": {
"message": "Could not find Casper Glow device with address {address}"
"message": "Could not find Casper Glow device with address {address}: {reason}"
}
}
}
+7 -1
View File
@@ -49,7 +49,6 @@ async def async_setup_entry(
class CCM15Climate(CoordinatorEntity[CCM15Coordinator], ClimateEntity):
"""Climate device for CCM15 coordinator."""
_attr_temperature_unit = UnitOfTemperature.CELSIUS
_attr_has_entity_name = True
_attr_target_temperature_step = PRECISION_WHOLE
_attr_hvac_modes = [
@@ -93,6 +92,13 @@ class CCM15Climate(CoordinatorEntity[CCM15Coordinator], ClimateEntity):
"""Return device data."""
return self.coordinator.get_ac_data(self._ac_index)
@property
def temperature_unit(self) -> str:
"""Return the unit of measurement reported by the device."""
if (data := self.data) is not None and not data.is_celsius:
return UnitOfTemperature.FAHRENHEIT
return UnitOfTemperature.CELSIUS
@property
def current_temperature(self) -> int | None:
"""Return current temperature."""
@@ -6,5 +6,5 @@
"iot_class": "cloud_push",
"loggers": ["webexpythonsdk"],
"quality_scale": "legacy",
"requirements": ["webexpythonsdk==2.0.1"]
"requirements": ["webexpythonsdk==2.0.6"]
}
+11 -16
View File
@@ -3,7 +3,7 @@
import logging
from typing import Any
from compit_inext_api import Param, Parameter
from compit_inext_api import Parameter
from compit_inext_api.consts import (
CompitFanMode,
CompitHVACMode,
@@ -150,7 +150,7 @@ class CompitClimate(CoordinatorEntity[CompitDataUpdateCoordinator], ClimateEntit
value = self.get_parameter_value(CompitParameter.CURRENT_TEMPERATURE)
if value is None:
return None
return float(value.value)
return float(value)
@property
def target_temperature(self) -> float | None:
@@ -158,7 +158,7 @@ class CompitClimate(CoordinatorEntity[CompitDataUpdateCoordinator], ClimateEntit
value = self.get_parameter_value(CompitParameter.SET_TARGET_TEMPERATURE)
if value is None:
return None
return float(value.value)
return float(value)
@cached_property
def preset_modes(self) -> list[str] | None:
@@ -195,27 +195,24 @@ class CompitClimate(CoordinatorEntity[CompitDataUpdateCoordinator], ClimateEntit
"""Return the current preset mode."""
preset_mode = self.get_parameter_value(CompitParameter.PRESET_MODE)
if preset_mode:
compit_preset_mode = CompitPresetMode(preset_mode.value)
return COMPIT_PRESET_MAP.get(compit_preset_mode)
if preset_mode is not None:
return COMPIT_PRESET_MAP.get(CompitPresetMode(preset_mode))
return None
@property
def fan_mode(self) -> str | None:
"""Return the current fan mode."""
fan_mode = self.get_parameter_value(CompitParameter.FAN_MODE)
if fan_mode:
compit_fan_mode = CompitFanMode(fan_mode.value)
return COMPIT_FANSPEED_MAP.get(compit_fan_mode)
if fan_mode is not None:
return COMPIT_FANSPEED_MAP.get(CompitFanMode(fan_mode))
return None
@property
def hvac_mode(self) -> HVACMode | None:
"""Return the current HVAC mode."""
hvac_mode = self.get_parameter_value(CompitParameter.HVAC_MODE)
if hvac_mode:
compit_hvac_mode = CompitHVACMode(hvac_mode.value)
return COMPIT_MODE_MAP.get(compit_hvac_mode)
if hvac_mode is not None:
return COMPIT_MODE_MAP.get(CompitHVACMode(hvac_mode))
return None
async def async_set_temperature(self, **kwargs: Any) -> None:
@@ -258,8 +255,6 @@ class CompitClimate(CoordinatorEntity[CompitDataUpdateCoordinator], ClimateEntit
)
self.async_write_ha_state()
def get_parameter_value(self, parameter: CompitParameter) -> Param | None:
def get_parameter_value(self, parameter: CompitParameter) -> str | float | None:
"""Get the parameter value from the device state."""
return self.coordinator.connector.get_device_parameter(
self.device_id, parameter
)
return self.coordinator.connector.get_current_value(self.device_id, parameter)
@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["compit"],
"quality_scale": "bronze",
"requirements": ["compit-inext-api==0.8.0"]
"requirements": ["compit-inext-api==0.9.1"]
}
@@ -852,7 +852,7 @@ class DefaultAgent(ConversationEntity):
)
# Build filtered slot list
text_lower = text.strip().lower()
text_lower = remove_punctuation(text).strip().lower()
return TextSlotList(
name="name",
values=[
@@ -889,7 +889,8 @@ class DefaultAgent(ConversationEntity):
for name in intent.async_get_entity_aliases(
self.hass, entity_entry, state=state
):
yield (name, name, context)
# Strip punctuation so aliases match the cleaned input text.
yield (remove_punctuation(name).strip(), name, context)
def _recognize_strict(
self,
@@ -1162,7 +1163,7 @@ class DefaultAgent(ConversationEntity):
areas = ar.async_get(self.hass)
area_names = []
for area in areas.async_list_areas():
area_names.append((area.name, area.name))
area_names.append((remove_punctuation(area.name).strip(), area.name))
if not area.aliases:
continue
@@ -1171,13 +1172,13 @@ class DefaultAgent(ConversationEntity):
if not alias:
continue
area_names.append((alias, alias))
area_names.append((remove_punctuation(alias).strip(), alias))
# Expose all floors.
floors = fr.async_get(self.hass)
floor_names = []
for floor in floors.async_list_floors():
floor_names.append((floor.name, floor.name))
floor_names.append((remove_punctuation(floor.name).strip(), floor.name))
if not floor.aliases:
continue
@@ -1186,7 +1187,7 @@ class DefaultAgent(ConversationEntity):
if not alias:
continue
floor_names.append((alias, floor.name))
floor_names.append((remove_punctuation(alias).strip(), floor.name))
# Build trie
self._exposed_names_trie = Trie()
@@ -8,7 +8,7 @@
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["doorbirdpy"],
"requirements": ["DoorBirdPy==3.0.11"],
"requirements": ["DoorBirdPy==3.0.12"],
"zeroconf": [
{
"properties": {
@@ -7,5 +7,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["pdunehd"],
"requirements": ["pdunehd==1.3.2"]
"requirements": ["pdunehd==1.3.3"]
}
+3 -1
View File
@@ -30,7 +30,9 @@
},
"user": {
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]"
"api_key": "[%key:common::config_flow::data::api_key%]",
"password": "[%key:common::config_flow::data::password%]",
"username": "[%key:common::config_flow::data::username%]"
},
"description": "Please enter the API key obtained from ecobee.com."
}
@@ -25,7 +25,7 @@ from homeassistant.components.climate import (
from homeassistant.const import ATTR_TEMPERATURE, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import ElectraSmartConfigEntry
@@ -145,6 +145,7 @@ class ElectraClimateEntity(ClimateEntity):
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, self._electra_ac_device.mac)},
connections={(CONNECTION_NETWORK_MAC, self._electra_ac_device.mac)},
name=device.name,
model=self._electra_ac_device.model,
manufacturer=self._electra_ac_device.manufactor,
@@ -2,6 +2,12 @@
"domain": "eufylife_ble",
"name": "EufyLife",
"bluetooth": [
{
"local_name": "eufy T9120"
},
{
"local_name": "eufy T9130"
},
{
"local_name": "eufy T9140"
},
@@ -16,6 +22,9 @@
},
{
"local_name": "eufy T9149"
},
{
"local_name": "eufy T9150"
}
],
"codeowners": ["@bdr99"],
@@ -24,5 +33,5 @@
"documentation": "https://www.home-assistant.io/integrations/eufylife_ble",
"integration_type": "device",
"iot_class": "local_push",
"requirements": ["eufylife-ble-client==0.1.8"]
"requirements": ["eufylife-ble-client==0.1.10"]
}
@@ -7,5 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["pyfireservicerota"],
"requirements": ["pyfireservicerota==0.0.46"]
"requirements": ["pyfireservicerota==0.0.49"]
}
@@ -6,5 +6,5 @@
"iot_class": "cloud_polling",
"loggers": ["foobot_async"],
"quality_scale": "legacy",
"requirements": ["foobot_async==1.0.0"]
"requirements": ["foobot_async==1.0.1"]
}
@@ -434,49 +434,56 @@ async def google_generative_ai_config_option_schema(
description={"suggested_value": options.get(CONF_TEMPERATURE)},
default=RECOMMENDED_TEMPERATURE,
): NumberSelector(NumberSelectorConfig(min=0, max=2, step=0.05)),
vol.Optional(
CONF_TOP_P,
description={"suggested_value": options.get(CONF_TOP_P)},
default=RECOMMENDED_TOP_P,
): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)),
vol.Optional(
CONF_TOP_K,
description={"suggested_value": options.get(CONF_TOP_K)},
default=RECOMMENDED_TOP_K,
): int,
vol.Optional(
CONF_MAX_TOKENS,
description={"suggested_value": options.get(CONF_MAX_TOKENS)},
default=RECOMMENDED_MAX_TOKENS,
): int,
vol.Optional(
CONF_HARASSMENT_BLOCK_THRESHOLD,
description={
"suggested_value": options.get(CONF_HARASSMENT_BLOCK_THRESHOLD)
},
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
): harm_block_thresholds_selector,
vol.Optional(
CONF_HATE_BLOCK_THRESHOLD,
description={"suggested_value": options.get(CONF_HATE_BLOCK_THRESHOLD)},
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
): harm_block_thresholds_selector,
vol.Optional(
CONF_SEXUAL_BLOCK_THRESHOLD,
description={
"suggested_value": options.get(CONF_SEXUAL_BLOCK_THRESHOLD)
},
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
): harm_block_thresholds_selector,
vol.Optional(
CONF_DANGEROUS_BLOCK_THRESHOLD,
description={
"suggested_value": options.get(CONF_DANGEROUS_BLOCK_THRESHOLD)
},
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
): harm_block_thresholds_selector,
}
)
if subentry_type != "tts":
schema.update(
{
vol.Optional(
CONF_TOP_P,
description={"suggested_value": options.get(CONF_TOP_P)},
default=RECOMMENDED_TOP_P,
): NumberSelector(NumberSelectorConfig(min=0, max=1, step=0.05)),
vol.Optional(
CONF_TOP_K,
description={"suggested_value": options.get(CONF_TOP_K)},
default=RECOMMENDED_TOP_K,
): int,
vol.Optional(
CONF_MAX_TOKENS,
description={"suggested_value": options.get(CONF_MAX_TOKENS)},
default=RECOMMENDED_MAX_TOKENS,
): int,
vol.Optional(
CONF_HARASSMENT_BLOCK_THRESHOLD,
description={
"suggested_value": options.get(CONF_HARASSMENT_BLOCK_THRESHOLD)
},
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
): harm_block_thresholds_selector,
vol.Optional(
CONF_HATE_BLOCK_THRESHOLD,
description={
"suggested_value": options.get(CONF_HATE_BLOCK_THRESHOLD)
},
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
): harm_block_thresholds_selector,
vol.Optional(
CONF_SEXUAL_BLOCK_THRESHOLD,
description={
"suggested_value": options.get(CONF_SEXUAL_BLOCK_THRESHOLD)
},
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
): harm_block_thresholds_selector,
vol.Optional(
CONF_DANGEROUS_BLOCK_THRESHOLD,
description={
"suggested_value": options.get(CONF_DANGEROUS_BLOCK_THRESHOLD)
},
default=RECOMMENDED_HARM_BLOCK_THRESHOLD,
): harm_block_thresholds_selector,
}
)
if subentry_type == "conversation":
schema.update(
{
@@ -21,7 +21,7 @@ CONF_RECOMMENDED = "recommended"
CONF_CHAT_MODEL = "chat_model"
RECOMMENDED_CHAT_MODEL = "models/gemini-3.1-flash-lite"
RECOMMENDED_STT_MODEL = RECOMMENDED_CHAT_MODEL
RECOMMENDED_TTS_MODEL = "models/gemini-2.5-flash-preview-tts"
RECOMMENDED_TTS_MODEL = "models/gemini-3.1-flash-tts-preview"
RECOMMENDED_IMAGE_MODEL = "models/gemini-2.5-flash-image"
CONF_TEMPERATURE = "temperature"
RECOMMENDED_TEMPERATURE = 1.0
@@ -18,7 +18,13 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import CONF_CHAT_MODEL, LOGGER, RECOMMENDED_TTS_MODEL
from .const import (
CONF_CHAT_MODEL,
CONF_TEMPERATURE,
LOGGER,
RECOMMENDED_TEMPERATURE,
RECOMMENDED_TTS_MODEL,
)
from .entity import GoogleGenerativeAILLMBaseEntity
from .helpers import convert_to_wav
@@ -191,7 +197,10 @@ class GoogleGenerativeAITextToSpeechEntity(
self, message: str, language: str, options: dict[str, Any]
) -> TtsAudioType:
"""Load tts audio file from the engine."""
config = self.create_generate_content_config()
config = types.GenerateContentConfig()
config.temperature = self.subentry.data.get(
CONF_TEMPERATURE, RECOMMENDED_TEMPERATURE
)
config.response_modalities = ["AUDIO"]
config.speech_config = types.SpeechConfig(
voice_config=types.VoiceConfig(
+1 -1
View File
@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/gree",
"iot_class": "local_polling",
"loggers": ["greeclimate"],
"requirements": ["greeclimate==2.1.1"]
"requirements": ["greeclimate==2.1.4"]
}
@@ -17,7 +17,7 @@
"date_description": "The to-do's due date.",
"date_name": "Due date",
"developer_options_description": "Additional features available in developer mode.",
"developer_options_name": "Advanced settings",
"developer_options_name": "Developer options",
"every_x_description": "The number of intervals (days, weeks, months, or years) after which the daily repeats, based on the chosen repetition interval. A value of 0 makes the daily inactive ('Grey Daily').",
"every_x_name": "Repeat every X",
"frequency_daily_description": "The repetition interval of a daily.",
@@ -6,5 +6,5 @@
"iot_class": "local_polling",
"loggers": ["heatmiserV3"],
"quality_scale": "legacy",
"requirements": ["heatmiserV3==2.0.4"]
"requirements": ["heatmiserV3==2.0.6"]
}
@@ -15,7 +15,7 @@
},
"title": "[%key:component::here_travel_time::config::step::destination_menu::title%]"
},
"destination_entity_id": {
"destination_entity": {
"data": {
"destination_entity_id": "Destination using an entity"
},
@@ -34,7 +34,7 @@
},
"title": "[%key:component::here_travel_time::config::step::origin_menu::title%]"
},
"origin_entity_id": {
"origin_entity": {
"data": {
"origin_entity_id": "Origin using an entity"
},
@@ -12,8 +12,7 @@
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]",
"password": "[%key:common::config_flow::data::password%]",
"username": "[%key:common::config_flow::data::username%]"
"port": "[%key:common::config_flow::data::port%]"
},
"data_description": {
"host": "The hostname or IP address of your Hi-Link HLK-SW-16 device."
@@ -6,5 +6,5 @@
"iot_class": "local_push",
"loggers": ["pyhomematic"],
"quality_scale": "legacy",
"requirements": ["pyhomematic==0.1.77"]
"requirements": ["pyhomematic==0.1.78"]
}
@@ -10,7 +10,8 @@
"step": {
"reauth_confirm": {
"data": {
"password": "[%key:common::config_flow::data::password%]"
"password": "[%key:common::config_flow::data::password%]",
"username": "[%key:common::config_flow::data::username%]"
},
"description": "The Honeywell integration needs to re-authenticate your account",
"title": "[%key:common::config_flow::title::reauth%]"
@@ -8,5 +8,5 @@
"iot_class": "local_polling",
"loggers": ["python_qube_heatpump"],
"quality_scale": "bronze",
"requirements": ["python-qube-heatpump==1.11.1"]
"requirements": ["python-qube-heatpump==1.11.0"]
}
@@ -6,7 +6,7 @@ from homeassistant.components.alarm_control_panel import (
AlarmControlPanelState,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -41,6 +41,7 @@ class IAlarmPanel(
super().__init__(coordinator)
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, coordinator.mac)},
connections={(CONNECTION_NETWORK_MAC, coordinator.mac)},
manufacturer="Antifurto365 - Meian",
name="iAlarm",
)
@@ -8,5 +8,5 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["keyrings.alt", "pyicloud"],
"requirements": ["pyicloud==2.4.1"]
"requirements": ["pyicloud==2.6.5"]
}
+10 -4
View File
@@ -326,7 +326,9 @@ class ImageView(HomeAssistantView):
) -> ImageEntity:
"""Authenticate request and return image entity."""
if (image_entity := self.component.get_entity(entity_id)) is None:
raise web.HTTPNotFound
raise (
web.HTTPNotFound if request[KEY_AUTHENTICATED] else web.HTTPUnauthorized
)
authenticated = (
request[KEY_AUTHENTICATED]
@@ -334,11 +336,15 @@ class ImageView(HomeAssistantView):
)
if not authenticated:
# Attempt with invalid bearer token, raise unauthorized
# so ban middleware can handle it.
if hdrs.AUTHORIZATION in request.headers:
# A failed request that carried an Authorization header is a real
# Bearer auth attempt — return 401 and let the ban middleware count
# it as a wrong login.
raise web.HTTPUnauthorized
# Invalid sigAuth or image entity access token
# No Authorization header: most likely a benign signed-URL / query-
# token request whose token has expired (e.g. a browser tab left
# open that re-fetches resources later). Return 403 so it doesn't
# register as a wrong login and ban the user's own IP.
raise web.HTTPForbidden
return image_entity
+1 -1
View File
@@ -32,7 +32,7 @@
"port": "[%key:common::config_flow::data::port%]",
"search": "IMAP search",
"server": "Server",
"ssl_cipher_list": "SSL cipher list (Advanced)",
"ssl_cipher_list": "SSL cipher list",
"username": "[%key:common::config_flow::data::username%]",
"verify_ssl": "[%key:common::config_flow::data::verify_ssl%]"
},
@@ -7,6 +7,6 @@
"documentation": "https://www.home-assistant.io/integrations/influxdb",
"iot_class": "local_push",
"loggers": ["influxdb", "influxdb_client"],
"requirements": ["influxdb==5.3.1", "influxdb-client==1.50.0"],
"requirements": ["influxdb==5.3.2", "influxdb-client==1.50.0"],
"single_config_entry": true
}
@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/infrared",
"integration_type": "entity",
"quality_scale": "internal",
"requirements": ["infrared-protocols==6.0.0"]
"requirements": ["infrared-protocols==6.0.1"]
}
@@ -2,7 +2,7 @@
from abc import ABC, abstractmethod
from dataclasses import dataclass
from datetime import UTC, datetime, timedelta
from datetime import datetime, timedelta
from decimal import Decimal, InvalidOperation
from enum import Enum
import logging
@@ -49,6 +49,7 @@ from homeassistant.helpers.event import (
async_track_state_report_event,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import dt as dt_util
from .const import (
CONF_MAX_SUB_INTERVAL,
@@ -339,8 +340,7 @@ class IntegrationSensor(RestoreSensor):
else max_sub_interval
)
self._max_sub_interval_exceeded_callback: CALLBACK_TYPE = lambda *args: None
# pylint: disable-next=home-assistant-enforce-utcnow
self._last_integration_time: datetime = datetime.now(tz=UTC)
self._last_integration_time: datetime = dt_util.utcnow()
self._last_integration_trigger = _IntegrationTrigger.StateEvent
self._attr_suggested_display_precision = round_digits or 2
@@ -499,8 +499,7 @@ class IntegrationSensor(RestoreSensor):
old_timestamp, new_timestamp, old_state, new_state
)
self._last_integration_trigger = _IntegrationTrigger.StateEvent
# pylint: disable-next=home-assistant-enforce-utcnow
self._last_integration_time = datetime.now(tz=UTC)
self._last_integration_time = dt_util.utcnow()
finally:
# When max_sub_interval exceeds without state change the source is assumed
# constant with the last known state (new_state).
@@ -608,8 +607,7 @@ class IntegrationSensor(RestoreSensor):
self._update_integral(area)
self.async_write_ha_state()
# pylint: disable-next=home-assistant-enforce-utcnow
self._last_integration_time = datetime.now(tz=UTC)
self._last_integration_time = dt_util.utcnow()
self._last_integration_trigger = _IntegrationTrigger.TimeElapsed
self._schedule_max_sub_interval_exceeded_if_state_is_numeric(
+1 -1
View File
@@ -7,5 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["geopy", "pyipma"],
"requirements": ["pyipma==3.0.9"]
"requirements": ["pyipma==3.0.10"]
}
+2 -1
View File
@@ -31,7 +31,8 @@
},
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]"
"host": "[%key:common::config_flow::data::host%]",
"protocol": "Protocol"
},
"data_description": {
"host": "Hostname or IP address of your Iskra device."
@@ -7,5 +7,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"loggers": ["krakenex", "pykrakenapi"],
"requirements": ["krakenex==2.2.2", "pykrakenapi==0.1.8"]
"requirements": ["krakenex==2.2.2", "pykrakenapi==0.1.9"]
}
+1 -1
View File
@@ -10,5 +10,5 @@
"iot_class": "local_polling",
"loggers": ["pypck"],
"quality_scale": "silver",
"requirements": ["pypck==0.9.11", "lcn-frontend==0.2.9"]
"requirements": ["pypck==0.9.13", "lcn-frontend==0.2.9"]
}
+1 -1
View File
@@ -175,7 +175,7 @@ class LcnLedLogicSensor(LcnEntity, SensorEntity):
async def async_update(self) -> None:
"""Update the state of the entity."""
self._attr_available = (
await self.device_connection.request_status_led_and_logic_ops(
await self.device_connection.request_status_leds_and_logic_ops(
SCAN_INTERVAL.seconds
)
is not None
@@ -1,11 +1,15 @@
"""LG IR Remote integration for Home Assistant."""
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
PLATFORMS = [Platform.BUTTON, Platform.EVENT, Platform.MEDIA_PLAYER]
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up LG IR from a config entry."""
@@ -16,3 +20,14 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a LG IR config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_migrate_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Migrate old config entry."""
if entry.version == 1:
# v1 used the infrared entity_id in the entry's unique_id, which is
# not stable and was removed in v2.
_LOGGER.debug("Migrating config entry from version 1 to 2")
hass.config_entries.async_update_entry(entry, unique_id=None, version=2)
return True
@@ -1,6 +1,6 @@
"""Config flow for LG IR integration."""
from typing import Any
from typing import TYPE_CHECKING, Any
import voluptuous as vol
@@ -35,7 +35,7 @@ DEVICE_TYPE_NAMES: dict[LGDeviceType, str] = {
class LgIrConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle config flow for LG IR."""
VERSION = 1
VERSION = 2
async def async_step_user(
self, user_input: dict[str, Any] | None = None
@@ -49,24 +49,39 @@ class LgIrConfigFlow(ConfigFlow, domain=DOMAIN):
errors: dict[str, str] = {}
if user_input is not None:
if entity_id := user_input.get(CONF_INFRARED_ENTITY_ID) or user_input.get(
CONF_INFRARED_RECEIVER_ENTITY_ID
):
emitter_id = user_input.get(CONF_INFRARED_ENTITY_ID)
receiver_id = user_input.get(CONF_INFRARED_RECEIVER_ENTITY_ID)
if emitter_id or receiver_id:
device_type = user_input[CONF_DEVICE_TYPE]
await self.async_set_unique_id(f"lg_ir_{device_type}_{entity_id}")
self._abort_if_unique_id_configured()
if emitter_id:
self._async_abort_entries_match(
{
CONF_DEVICE_TYPE: device_type,
CONF_INFRARED_ENTITY_ID: emitter_id,
}
)
if receiver_id:
self._async_abort_entries_match(
{
CONF_DEVICE_TYPE: device_type,
CONF_INFRARED_RECEIVER_ENTITY_ID: receiver_id,
}
)
# Get entity name for the title
title_entity_id = emitter_id or receiver_id
if TYPE_CHECKING:
assert title_entity_id is not None
ent_reg = er.async_get(self.hass)
entry = ent_reg.async_get(entity_id)
entity_name = (
entry.name or entry.original_name or entity_id
entry = ent_reg.async_get(title_entity_id)
title_entity_name = (
entry.name or entry.original_name or title_entity_id
if entry
else entity_id
else title_entity_id
)
device_type_name = DEVICE_TYPE_NAMES[LGDeviceType(device_type)]
title = f"LG {device_type_name} via {entity_name}"
title = f"LG {device_type_name} via {title_entity_name}"
return self.async_create_entry(title=title, data=user_input)
@@ -7,7 +7,10 @@
"invalid_ics_file": "There was a problem reading the calendar information. See the error log for additional details."
},
"step": {
"import": {
"import_ics_file": {
"data": {
"ics_file": "ICS file"
},
"description": "You can import events in iCal format (.ics file)."
},
"user": {
@@ -7,7 +7,7 @@
"integration_type": "hub",
"iot_class": "local_polling",
"quality_scale": "silver",
"requirements": ["lunatone-rest-api-client==0.9.1"],
"requirements": ["lunatone-rest-api-client==0.9.2"],
"zeroconf": [
{
"properties": {
@@ -16,7 +16,7 @@ from typing import Any, Final, Required, TypedDict, final
from urllib.parse import quote, urlparse
import aiohttp
from aiohttp import web
from aiohttp import hdrs, web
from aiohttp.hdrs import CACHE_CONTROL, CONTENT_TYPE
from aiohttp.typedefs import LooseHeaders
from propcache.api import cached_property
@@ -1271,12 +1271,9 @@ class MediaPlayerImageView(HomeAssistantView):
) -> web.Response:
"""Start a get request."""
if (player := self.component.get_entity(entity_id)) is None:
status = (
HTTPStatus.NOT_FOUND
if request[KEY_AUTHENTICATED]
else HTTPStatus.UNAUTHORIZED
raise (
web.HTTPNotFound if request[KEY_AUTHENTICATED] else web.HTTPUnauthorized
)
return web.Response(status=status)
assert isinstance(player, MediaPlayerEntity)
authenticated = (
@@ -1285,7 +1282,16 @@ class MediaPlayerImageView(HomeAssistantView):
)
if not authenticated:
return web.Response(status=HTTPStatus.UNAUTHORIZED)
if hdrs.AUTHORIZATION in request.headers:
# A failed request that carried an Authorization header is a real
# Bearer auth attempt — return 401 and let the ban middleware count
# it as a wrong login.
raise web.HTTPUnauthorized
# No Authorization header: most likely a benign signed-URL / query-
# token request whose token has expired (e.g. a browser tab left
# open that re-fetches resources later). Return 403 so it doesn't
# register as a wrong login and ban the user's own IP.
raise web.HTTPForbidden
if media_content_type and media_content_id:
media_image_id = request.query.get("media_image_id")
@@ -6,5 +6,5 @@
"iot_class": "cloud_push",
"loggers": ["messagebird"],
"quality_scale": "legacy",
"requirements": ["messagebird==1.2.0"]
"requirements": ["messagebird==1.2.1"]
}
@@ -7,5 +7,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["librouteros"],
"requirements": ["librouteros==3.2.0"]
"requirements": ["librouteros==3.2.1"]
}
@@ -14,8 +14,7 @@
},
"user": {
"data": {
"name": "[%key:common::config_flow::data::name%]",
"port": "[%key:common::config_flow::data::port%]"
"device": "[%key:common::config_flow::data::device%]"
},
"description": "This is an integration for landline calls using a CX93001 voice modem. This can retrieve caller ID information with an option to reject an incoming call."
}
@@ -7,6 +7,6 @@
"integration_type": "hub",
"iot_class": "cloud_polling",
"loggers": ["config", "omnilogic"],
"requirements": ["omnilogic==0.4.5"],
"requirements": ["omnilogic==0.4.9"],
"single_config_entry": true
}
@@ -9,5 +9,5 @@
"iot_class": "cloud_polling",
"loggers": ["opower"],
"quality_scale": "platinum",
"requirements": ["opower==0.18.4"]
"requirements": ["opower==0.18.5"]
}
+2 -1
View File
@@ -11,7 +11,8 @@
"step": {
"confirm": {
"data": {
"code": "Verification code (OTP)"
"code": "Verification code (OTP)",
"qr_code": "QR code"
},
"data_description": {
"code": "The six-digit code currently displayed in your authentication app."
@@ -6,5 +6,5 @@
"iot_class": "local_polling",
"loggers": ["pencompy"],
"quality_scale": "legacy",
"requirements": ["pencompy==0.0.3"]
"requirements": ["pencompy==0.0.4"]
}
@@ -7,5 +7,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["hole"],
"requirements": ["hole==0.9.0"]
"requirements": ["hole==0.9.2"]
}
@@ -352,6 +352,8 @@ class PS4Device(MediaPlayerEntity):
for device in d_registry.devices.get_devices_for_config_entry_id(
self._entry_id
):
# Rebuilt from the existing device entry, which already carries
# the network MAC connection added by the live-status branch.
self._attr_device_info = DeviceInfo(
identifiers=device.identifiers,
manufacturer=device.manufacturer,
@@ -365,7 +367,9 @@ class PS4Device(MediaPlayerEntity):
_sw_version = status["system-version"]
_sw_version = _sw_version[1:4]
sw_version = f"{_sw_version[0]}.{_sw_version[1:]}"
# status["host-id"] is the console's network MAC address.
self._attr_device_info = DeviceInfo(
connections={(dr.CONNECTION_NETWORK_MAC, status["host-id"])},
identifiers={(DOMAIN, status["host-id"])},
manufacturer="Sony Interactive Entertainment Inc.",
model="PlayStation 4",
+2 -1
View File
@@ -6,7 +6,7 @@ from typing import Any
from rabbitair import Model
from homeassistant.const import CONF_MAC
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
@@ -36,6 +36,7 @@ class RabbitAirBaseEntity(CoordinatorEntity[RabbitAirDataUpdateCoordinator]):
self._attr_unique_id = entry.unique_id
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, entry.data[CONF_MAC])},
connections={(CONNECTION_NETWORK_MAC, entry.data[CONF_MAC])},
manufacturer="Rabbit Air",
model=MODELS.get(coordinator.data.model),
name=entry.title,
@@ -13,9 +13,10 @@ from pyrainbird.async_client import (
)
from pyrainbird.data import ModelAndVersion, Schedule
from homeassistant.const import CONF_MAC
from homeassistant.core import HomeAssistant
from homeassistant.helpers.debounce import Debouncer
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN, MANUFACTURER, TIMEOUT_SECONDS
@@ -104,13 +105,18 @@ class RainbirdUpdateCoordinator(DataUpdateCoordinator[RainbirdDeviceState]):
"""Return information about the device."""
if self._unique_id is None:
return None
return DeviceInfo(
device_info = DeviceInfo(
name=self.device_name,
identifiers={(DOMAIN, self._unique_id)},
manufacturer=MANUFACTURER,
model=self._model_info.model_name,
sw_version=f"{self._model_info.major}.{self._model_info.minor}",
)
# The unique id is the formatted MAC for current config entries, but was
# historically the serial number, so derive the connection from the MAC.
if mac_address := self.config_entry.data.get(CONF_MAC):
device_info["connections"] = {(CONNECTION_NETWORK_MAC, mac_address)}
return device_info
async def _async_update_data(self) -> RainbirdDeviceState:
"""Fetch data from Rain Bird device."""
+19 -15
View File
@@ -308,17 +308,17 @@ class Events(Base):
def from_event(event: Event) -> Events:
"""Create an event database object from a native event."""
context = event.context
# The unused legacy columns (event_type, event_data, time_fired,
# context_id, context_user_id, context_parent_id) are nullable with no
# default, so they are intentionally left unset here. Assigning them
# None would still insert NULL, but each assignment goes through
# SQLAlchemy's instrumented attribute machinery, which is a measurable
# cost when run for every recorded event.
return Events(
event_type=None,
event_data=None,
origin_idx=event.origin.idx,
time_fired=None,
time_fired_ts=event.time_fired_timestamp,
context_id=None,
context_id_bin=ulid_to_bytes_or_none(context.id),
context_user_id=None,
context_user_id_bin=uuid_hex_to_bytes_or_none(context.user_id),
context_parent_id=None,
context_parent_id_bin=ulid_to_bytes_or_none(context.parent_id),
)
@@ -491,19 +491,18 @@ class States(Base):
else:
last_reported_ts = state.last_reported_timestamp
context = event.context
# The unused legacy columns (entity_id, attributes, context_id,
# context_user_id, context_parent_id, last_updated, last_changed) are
# nullable with no default, so they are intentionally left unset here.
# Assigning them None would still insert NULL, but each assignment goes
# through SQLAlchemy's instrumented attribute machinery, which is a
# measurable cost when run for every recorded state change.
return States(
state=state_value,
entity_id=None,
attributes=None,
context_id=None,
context_id_bin=ulid_to_bytes_or_none(context.id),
context_user_id=None,
context_user_id_bin=uuid_hex_to_bytes_or_none(context.user_id),
context_parent_id=None,
context_parent_id_bin=ulid_to_bytes_or_none(context.parent_id),
origin_idx=event.origin.idx,
last_updated=None,
last_changed=None,
last_updated_ts=last_updated_ts,
last_changed_ts=last_changed_ts,
last_reported_ts=last_reported_ts,
@@ -560,8 +559,13 @@ class StateAttributes(Base):
# None state means the state was removed from the state machine
if (state := event.data["new_state"]) is None:
return b"{}"
if state_info := state.state_info:
unrecorded_attributes = state_info["unrecorded_attributes"]
if (state_info := state.state_info) and (
unrecorded_attributes := state_info["unrecorded_attributes"]
):
# The entity has unrecorded attributes, so a combined exclude set
# has to be built. The common case (no unrecorded attributes) falls
# through to the shared constant below without allocating a set per
# recorded state change.
exclude_attrs = {
*ALL_DOMAIN_EXCLUDE_ATTRS,
*unrecorded_attributes,
+6 -7
View File
@@ -8,7 +8,7 @@ from renson_endura_delta.field_enum import (
)
from renson_endura_delta.renson import RensonVentilation
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
@@ -24,10 +24,11 @@ class RensonEntity(CoordinatorEntity[RensonCoordinator]):
"""Initialize the Renson entity."""
super().__init__(coordinator)
mac = api.get_field_value(coordinator.data, MAC_ADDRESS.name)
self._attr_device_info = DeviceInfo(
identifiers={
(DOMAIN, api.get_field_value(coordinator.data, MAC_ADDRESS.name))
},
identifiers={(DOMAIN, mac)},
connections={(CONNECTION_NETWORK_MAC, mac)},
manufacturer="Renson",
model=api.get_field_value(coordinator.data, DEVICE_NAME_FIELD.name),
name="Ventilation",
@@ -41,6 +42,4 @@ class RensonEntity(CoordinatorEntity[RensonCoordinator]):
self.api = api
self._attr_unique_id = (
api.get_field_value(coordinator.data, MAC_ADDRESS.name) + f"{name}"
)
self._attr_unique_id = f"{mac}{name}"
+3 -2
View File
@@ -1,7 +1,7 @@
"""Reolink integration for HomeAssistant."""
from collections.abc import Callable
from datetime import UTC, datetime, timedelta
from datetime import timedelta
import logging
from random import uniform
from time import time
@@ -26,6 +26,7 @@ from homeassistant.helpers import (
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.typing import ConfigType
from homeassistant.util import dt as dt_util
from .const import (
BATTERY_PASSIVE_WAKE_UPDATE_INTERVAL,
@@ -192,7 +193,7 @@ async def async_setup_entry(
hass.config_entries.async_update_entry(config_entry, data=data)
# If camera WAN blocked, firmware check fails and takes long, do not prevent setup
now = datetime.now(UTC) # pylint: disable=home-assistant-enforce-utcnow
now = dt_util.utcnow()
check_time = timedelta(seconds=check_time_sec)
delta_midnight = now - now.replace(hour=0, minute=0, second=0, microsecond=0)
firmware_check_delay = check_time - delta_midnight
@@ -25,7 +25,7 @@
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["paho_mqtt", "roombapy"],
"requirements": ["roombapy==1.9.0"],
"requirements": ["roombapy==1.9.1"],
"zeroconf": [
{
"name": "irobot-*",
@@ -58,6 +58,38 @@
"user": "Add sensor"
},
"step": {
"reconfigure": {
"data": {
"index": "[%key:component::scrape::config_subentries::entity::step::user::data::index%]",
"select": "[%key:component::scrape::config_subentries::entity::step::user::data::select%]"
},
"data_description": {
"index": "[%key:component::scrape::config_subentries::entity::step::user::data_description::index%]",
"select": "[%key:component::scrape::config_subentries::entity::step::user::data_description::select%]"
},
"sections": {
"advanced": {
"data": {
"attribute": "[%key:component::scrape::config_subentries::entity::step::user::sections::advanced::data::attribute%]",
"availability": "[%key:component::scrape::config_subentries::entity::step::user::sections::advanced::data::availability%]",
"device_class": "[%key:component::scrape::config_subentries::entity::step::user::sections::advanced::data::device_class%]",
"state_class": "[%key:component::scrape::config_subentries::entity::step::user::sections::advanced::data::state_class%]",
"unit_of_measurement": "[%key:component::scrape::config_subentries::entity::step::user::sections::advanced::data::unit_of_measurement%]",
"value_template": "[%key:component::scrape::config_subentries::entity::step::user::sections::advanced::data::value_template%]"
},
"data_description": {
"attribute": "[%key:component::scrape::config_subentries::entity::step::user::sections::advanced::data_description::attribute%]",
"availability": "[%key:component::scrape::config_subentries::entity::step::user::sections::advanced::data_description::availability%]",
"device_class": "[%key:component::scrape::config_subentries::entity::step::user::sections::advanced::data_description::device_class%]",
"state_class": "[%key:component::scrape::config_subentries::entity::step::user::sections::advanced::data_description::state_class%]",
"unit_of_measurement": "[%key:component::scrape::config_subentries::entity::step::user::sections::advanced::data_description::unit_of_measurement%]",
"value_template": "[%key:component::scrape::config_subentries::entity::step::user::sections::advanced::data_description::value_template%]"
},
"description": "[%key:component::scrape::config_subentries::entity::step::user::sections::advanced::description%]",
"name": "[%key:component::scrape::config_subentries::entity::step::user::sections::advanced::name%]"
}
}
},
"user": {
"data": {
"index": "Index",
@@ -1,6 +1,6 @@
"""Sensoterra devices."""
from datetime import UTC, datetime, timedelta
from datetime import timedelta
from enum import StrEnum, auto
from sensoterra.probe import Probe, Sensor
@@ -22,6 +22,7 @@ from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from homeassistant.util import dt as dt_util
from .const import CONFIGURATION_URL, DOMAIN, SENSOR_EXPIRATION_DAYS
from .coordinator import SensoterraConfigEntry, SensoterraCoordinator
@@ -165,5 +166,5 @@ class SensoterraEntity(CoordinatorEntity[SensoterraCoordinator], SensorEntity):
return False
# Expire sensor if no update within the last few days.
expiration = datetime.now(UTC) - timedelta(days=SENSOR_EXPIRATION_DAYS) # pylint: disable=home-assistant-enforce-utcnow
expiration = dt_util.utcnow() - timedelta(days=SENSOR_EXPIRATION_DAYS)
return sensor.timestamp >= expiration
@@ -6,5 +6,5 @@
"iot_class": "cloud_polling",
"loggers": ["pysesame2"],
"quality_scale": "legacy",
"requirements": ["pysesame2==1.0.1"]
"requirements": ["pysesame2==1.0.2"]
}
+1 -1
View File
@@ -13,7 +13,7 @@
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"step": {
"additional_account": {
"add_account": {
"data": {
"account": "[%key:component::sia::config::step::user::data::account%]",
"additional_account": "[%key:component::sia::config::step::user::data::additional_account%]",
@@ -247,7 +247,7 @@ def _async_register_base_station(
config_entry_id=entry.entry_id,
identifiers={(DOMAIN, str(system.system_id))},
manufacturer="SimpliSafe",
model=system.version,
model=str(system.version),
name=system.address,
)
@@ -21,7 +21,6 @@ from sonos_websocket.exception import SonosWebsocketError
from homeassistant.components import media_source, spotify
from homeassistant.components.media_player import (
ATTR_INPUT_SOURCE,
ATTR_MEDIA_ALBUM_NAME,
ATTR_MEDIA_ANNOUNCE,
ATTR_MEDIA_ARTIST,
@@ -779,9 +778,6 @@ class SonosMediaPlayerEntity(SonosEntity, MediaPlayerEntity):
if self.media.queue_size:
attributes["queue_size"] = self.media.queue_size
if self.source:
attributes[ATTR_INPUT_SOURCE] = self.source
return attributes
async def async_get_browse_image(
+2 -2
View File
@@ -9,6 +9,7 @@ from homeassistant.components.time import TimeEntity, TimeEntityDescription
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util import dt as dt_util
from .coordinator import StarlinkConfigEntry, StarlinkData, StarlinkUpdateCoordinator
from .entity import StarlinkEntity
@@ -63,8 +64,7 @@ def _utc_minutes_to_time(utc_minutes: int, timezone: tzinfo) -> time:
hour -= 24
minute = utc_minutes % 60
try:
# pylint: disable-next=home-assistant-enforce-utcnow
utc = datetime.now(UTC).replace(
utc = dt_util.utcnow().replace(
hour=hour, minute=minute, second=0, microsecond=0
)
except ValueError as exc:
+1
View File
@@ -29,6 +29,7 @@ VEHICLE_STATUS = "vehicle_status"
API_GEN_1 = "g1"
API_GEN_2 = "g2"
API_GEN_3 = "g3"
API_GEN_4 = "g4"
MANUFACTURER = "Subaru"
PLATFORMS = [
+3 -2
View File
@@ -24,6 +24,7 @@ from . import get_device_info
from .const import (
API_GEN_2,
API_GEN_3,
API_GEN_4,
VEHICLE_API_GEN,
VEHICLE_HAS_EV,
VEHICLE_STATUS,
@@ -153,10 +154,10 @@ def create_vehicle_sensors(
sensor_descriptions_to_add = []
sensor_descriptions_to_add.extend(SAFETY_SENSORS)
if vehicle_info[VEHICLE_API_GEN] in [API_GEN_2, API_GEN_3]:
if vehicle_info[VEHICLE_API_GEN] in [API_GEN_2, API_GEN_3, API_GEN_4]:
sensor_descriptions_to_add.extend(API_GEN_2_SENSORS)
if vehicle_info[VEHICLE_API_GEN] == API_GEN_3:
if vehicle_info[VEHICLE_API_GEN] in [API_GEN_3, API_GEN_4]:
sensor_descriptions_to_add.extend(API_GEN_3_SENSORS)
if vehicle_info[VEHICLE_HAS_EV]:
+8 -1
View File
@@ -5,7 +5,7 @@ import logging
from homeassistant.config_entries import SOURCE_IMPORT
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType
@@ -50,6 +50,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: SunConfigEntry) -> bool:
"""Set up from a config entry."""
# Remove deprecated solar_rising sensor entity (removed in 2026.1)
ent_reg = er.async_get(hass)
if entity_id := ent_reg.async_get_entity_id(
Platform.SENSOR, DOMAIN, f"{entry.entry_id}-solar_rising"
):
ent_reg.async_remove(entity_id)
sun = Sun(hass)
component = EntityComponent[Sun](_LOGGER, DOMAIN, hass)
await component.async_add_entities([sun])
@@ -6,5 +6,5 @@
"iot_class": "assumed_state",
"loggers": ["tellcore"],
"quality_scale": "legacy",
"requirements": ["tellcore-net==0.4", "tellcore-py==1.1.2"]
"requirements": ["tellcore-net==0.4", "tellcore-py==1.1.3"]
}
+16 -10
View File
@@ -1,15 +1,12 @@
"""Support for Template fans."""
from enum import StrEnum
import logging
from typing import TYPE_CHECKING, Any
import voluptuous as vol
from homeassistant.components.fan import (
ATTR_DIRECTION,
ATTR_OSCILLATING,
ATTR_PERCENTAGE,
ATTR_PRESET_MODE,
DIRECTION_FORWARD,
DIRECTION_REVERSE,
DOMAIN as FAN_DOMAIN,
@@ -100,6 +97,15 @@ FAN_CONFIG_ENTRY_SCHEMA = FAN_COMMON_SCHEMA.extend(
)
class FanScriptVariable(StrEnum):
"""Variables for scripts."""
DIRECTION = "direction"
OSCILLATING = "oscillating"
PERCENTAGE = "percentage"
PRESET_MODE = "preset_mode"
async def async_setup_platform(
hass: HomeAssistant,
config: ConfigType,
@@ -235,8 +241,8 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
await self.async_run_script(
self._action_scripts[CONF_ON_ACTION],
run_variables={
ATTR_PERCENTAGE: percentage,
ATTR_PRESET_MODE: preset_mode,
FanScriptVariable.PERCENTAGE: percentage,
FanScriptVariable.PRESET_MODE: preset_mode,
},
context=self._context,
)
@@ -267,7 +273,7 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
if script := self._action_scripts.get(CONF_SET_PERCENTAGE_ACTION):
await self.async_run_script(
script,
run_variables={ATTR_PERCENTAGE: self._attr_percentage},
run_variables={FanScriptVariable.PERCENTAGE: self._attr_percentage},
context=self._context,
)
@@ -284,7 +290,7 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
if script := self._action_scripts.get(CONF_SET_PRESET_MODE_ACTION):
await self.async_run_script(
script,
run_variables={ATTR_PRESET_MODE: self._attr_preset_mode},
run_variables={FanScriptVariable.PRESET_MODE: self._attr_preset_mode},
context=self._context,
)
@@ -302,7 +308,7 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
) is not None:
await self.async_run_script(
script,
run_variables={ATTR_OSCILLATING: self.oscillating},
run_variables={FanScriptVariable.OSCILLATING: self.oscillating},
context=self._context,
)
@@ -318,7 +324,7 @@ class AbstractTemplateFan(AbstractTemplateEntity, FanEntity):
) is not None:
await self.async_run_script(
script,
run_variables={ATTR_DIRECTION: direction},
run_variables={FanScriptVariable.DIRECTION: direction},
context=self._context,
)
if CONF_DIRECTION not in self._templates:
+1 -2
View File
@@ -5,7 +5,6 @@ from typing import TYPE_CHECKING, Any
import voluptuous as vol
from homeassistant.components.number import (
ATTR_VALUE,
DEFAULT_MAX_VALUE,
DEFAULT_MIN_VALUE,
DEFAULT_STEP,
@@ -161,7 +160,7 @@ class AbstractTemplateNumber(AbstractTemplateEntity, NumberEntity):
if set_value := self._action_scripts.get(CONF_SET_VALUE):
await self.async_run_script(
set_value,
run_variables={ATTR_VALUE: value},
run_variables={"value": value},
context=self._context,
)
+3 -5
View File
@@ -6,8 +6,6 @@ from typing import TYPE_CHECKING, Any
import voluptuous as vol
from homeassistant.components.select import (
ATTR_OPTION,
ATTR_OPTIONS,
DOMAIN as SELECT_DOMAIN,
ENTITY_ID_FORMAT,
SelectEntity,
@@ -48,7 +46,7 @@ SCRIPT_FIELDS = (CONF_SELECT_OPTION,)
SELECT_COMMON_SCHEMA = vol.Schema(
{
vol.Required(ATTR_OPTIONS): cv.template,
vol.Required(CONF_OPTIONS): cv.template,
vol.Optional(CONF_SELECT_OPTION): cv.SCRIPT_SCHEMA,
vol.Optional(CONF_STATE): cv.template,
}
@@ -147,7 +145,7 @@ class AbstractTemplateSelect(AbstractTemplateEntity, SelectEntity):
if select_option := self._action_scripts.get(CONF_SELECT_OPTION):
await self.async_run_script(
select_option,
run_variables={ATTR_OPTION: option},
run_variables={"option": option},
context=self._context,
)
@@ -175,7 +173,7 @@ class TriggerSelectEntity(TriggerEntity, AbstractTemplateSelect):
"""Select entity based on trigger data."""
domain = SELECT_DOMAIN
extra_template_keys_complex = (ATTR_OPTIONS,)
extra_template_keys_complex = (CONF_OPTIONS,)
def __init__(
self,
+5 -5
View File
@@ -9,7 +9,6 @@ from typing import Any
import voluptuous as vol
from homeassistant.components.sensor import (
ATTR_LAST_RESET,
CONF_STATE_CLASS,
DEVICE_CLASSES_SCHEMA,
DOMAIN as SENSOR_DOMAIN,
@@ -50,13 +49,14 @@ from .schemas import (
from .template_entity import TemplateEntity
from .trigger_entity import TriggerEntity
CONF_LAST_RESET = "last_reset"
DEFAULT_NAME = "Template Sensor"
def validate_last_reset(val):
"""Run extra validation checks."""
if (
val.get(ATTR_LAST_RESET) is not None
val.get(CONF_LAST_RESET) is not None
and val.get(CONF_STATE_CLASS) != SensorStateClass.TOTAL
):
raise vol.Invalid(
@@ -78,7 +78,7 @@ SENSOR_COMMON_SCHEMA = vol.Schema(
SENSOR_YAML_SCHEMA = vol.All(
vol.Schema(
{
vol.Optional(ATTR_LAST_RESET): cv.template,
vol.Optional(CONF_LAST_RESET): cv.template,
}
)
.extend(SENSOR_COMMON_SCHEMA.schema)
@@ -204,10 +204,10 @@ class AbstractTemplateSensor(AbstractTemplateEntity, RestoreSensor):
self._validate_state,
)
self.setup_template(
ATTR_LAST_RESET,
CONF_LAST_RESET,
"_attr_last_reset",
validate_datetime(
self, ATTR_LAST_RESET, SensorDeviceClass.TIMESTAMP, require_tzinfo=False
self, CONF_LAST_RESET, SensorDeviceClass.TIMESTAMP, require_tzinfo=False
),
)
+1 -2
View File
@@ -7,7 +7,6 @@ from typing import TYPE_CHECKING, Any
import voluptuous as vol
from homeassistant.components.vacuum import (
ATTR_FAN_SPEED,
DOMAIN as VACUUM_DOMAIN,
SERVICE_CLEAN_SPOT,
SERVICE_LOCATE,
@@ -389,7 +388,7 @@ class AbstractTemplateVacuum(AbstractTemplateEntity, StateVacuumEntity):
if script := self._action_scripts.get(SERVICE_SET_FAN_SPEED):
await self.async_run_script(
script, run_variables={ATTR_FAN_SPEED: fan_speed}, context=self._context
script, run_variables={"fan_speed": fan_speed}, context=self._context
)

Some files were not shown because too many files have changed in this diff Show More