Compare commits

..

377 Commits

Author SHA1 Message Date
Ludovic BOUÉ
1cdbe596fe Update snapshots 2026-02-06 17:29:30 +00:00
Ludovic BOUÉ
a9d52bfbe7 Remove feature map attribute from occupancy sensing discovery schema 2026-02-06 17:24:51 +00:00
Ludovic BOUÉ
6eed1f9961 Update snapshots 2026-02-06 17:06:27 +00:00
Ludovic BOUÉ
149607ab17 Refactor strings.json: Remove duplicate unoccupied to occupied delay entries and standardize casing for threshold name 2026-02-06 17:04:42 +00:00
Ludovic BOUÉ
279b5be357 Add assertions for min, max, and unit_of_measurement in occupancy sensor tests 2026-02-06 17:02:19 +00:00
Ludovic BOUÉ
82b93e788b Update snapshots 2026-02-06 16:58:16 +00:00
Ludovic BOUÉ
555813f84f Move PIRUnoccupiedToOccupiedDelay before 2026-02-06 16:58:16 +00:00
Ludovic BOUÉ
ecf1b4e591 Fix occupancy sensor threshold test assertion to match updated mock data 2026-02-06 16:58:15 +00:00
Ludovic BOUÉ
e17a9f12a1 Rename occupancy sensor state and entity IDs for clarity in PIR tests 2026-02-06 16:58:15 +00:00
Ludovic BOUÉ
e8f05f5291 Update snapshots 2026-02-06 16:58:15 +00:00
Ludovic BOUÉ
a5a76e9268 Add mock occupancy sensor JSON fixture 2026-02-06 16:58:15 +00:00
Ludovic BOUÉ
edc3fb47b2 Réorganiser les chaînes pour le délai et le seuil de passage de l'état inoccupé à occupé 2026-02-06 16:58:15 +00:00
Ludovic BOUÉ
f1e514a70a Update homeassistant/components/matter/strings.json
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2026-02-06 17:52:35 +01:00
Ludovic BOUÉ
5632baca5b Merge branch 'dev' into PIRUnoccupiedToOccupiedDelay 2026-02-06 17:08:24 +01:00
Ludovic BOUÉ
78f9bad706 PIRUnoccupiedToOccupiedDelay attribute 2026-02-06 16:00:18 +00:00
Artur Pragacz
f2d4319366 Make bad entity ID detection more lenient (#162425) 2026-02-06 15:58:27 +01:00
epenet
3eb8d64381 Fix invalid yardian snaphots (#162422) 2026-02-06 15:52:58 +01:00
epenet
818ce549d9 Remove duplicate class field in incomfort (#162420) 2026-02-06 15:16:20 +01:00
epenet
db7800d170 Simplify (in-)equality checks (#162416) 2026-02-06 15:11:22 +01:00
epenet
bc1c24efb1 Improve (r)split performance (#162418) 2026-02-06 15:10:30 +01:00
David Recordon
5ad632c34a Add Fan mode support to Control4 integration (#159980) 2026-02-06 14:11:18 +01:00
Sab44
65f95e5c4b Bump librehardwaremonitor-api to version 1.9.1 (#162409) 2026-02-06 14:04:21 +01:00
Joost Lekkerkerker
bb406594d1 Remove double unit of measurement for yardian (#162412) 2026-02-06 14:03:57 +01:00
epenet
b7a7b7bc63 Cleanup unnecessary brackets for except statements (a-h) (#162404) 2026-02-06 13:48:56 +01:00
epenet
1c59d846e3 Cleanup unnecessary brackets for except statements (core) (#162410) 2026-02-06 13:45:59 +01:00
Joost Lekkerkerker
3b40bb7d28 Remove entity id overwrite for ambient station (#162403) 2026-02-06 13:39:51 +01:00
epenet
a171e17097 Cleanup unnecessary brackets for except statements (q-z) (#162408) 2026-02-06 13:31:56 +01:00
epenet
c881d96d2f Cleanup unnecessary brackets for except statements (i-p) (#162405) 2026-02-06 13:23:25 +01:00
Luo Chen
f1a99a2d65 Fix unicode escaping in MCP server tool response (#162319)
Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2026-02-06 13:11:28 +01:00
mettolen
d02adabe5d Add Start session action to Saunum integration (#162177) 2026-02-06 12:47:50 +01:00
Mark Jansen
286730165d Simplify sun condition schema by re-using an existing type (#161894)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2026-02-06 12:27:16 +01:00
epenet
95a58252cf Reformat lambda (tests) (#162383) 2026-02-06 12:24:58 +01:00
epenet
bf6643643b Reformat lambda (core) (#162382) 2026-02-06 12:24:48 +01:00
epenet
8d780d6712 Reformat lambda (t-z) (#162381) 2026-02-06 12:24:37 +01:00
epenet
576c7227c6 Reformat lambda (m-s) (#162379) 2026-02-06 12:24:19 +01:00
epenet
915d375f0a Reformat lambda (a-l) (#162377) 2026-02-06 12:24:12 +01:00
Petro31
e9487a81a7 Update template image platform to new entity framework (#162343) 2026-02-06 12:20:11 +01:00
Andres Ruiz
0a2fe01b66 Add device_info to waterfurnace sensors (#162080)
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-02-06 12:17:27 +01:00
Peter Grauvogel
9de9bde7d8 Add timestamp sensors for highest and lowest price times (#161639) 2026-02-06 12:04:39 +01:00
epenet
fbc91d3d3d Remove str from sensor state class (#161686) 2026-02-06 12:01:23 +01:00
Abílio Costa
47672614df Remove external url from config for local_only users (#161891) 2026-02-06 12:00:07 +01:00
Denis Shulyaka
8c01c4a155 Add gpt-image-1.5 model support (#162208) 2026-02-06 11:57:00 +01:00
Aaron Godfrey
5dc7f8bfe3 Fix conversion of data for todo.* actions (#162366) 2026-02-06 11:52:01 +01:00
epenet
cc01d15d74 Use StrEnum in eq3btsmart (#162387) 2026-02-06 11:51:00 +01:00
epenet
5c980e8d97 Cleanup ternary if expressions (#162394) 2026-02-06 11:50:47 +01:00
epenet
c01e3beb2e Use StrEnum in stt (#162389) 2026-02-06 11:50:35 +01:00
Jan Bouwhuis
a5b16e3694 Remove parentheses for except statements where it is not needed in mqtt integration (#162398) 2026-02-06 11:49:42 +01:00
epenet
866cd52ada Cleanup default None value from dict.get (#162396) 2026-02-06 11:49:02 +01:00
John O'Nolan
2d308aaa20 Add Ghost integration (#162041)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-02-06 11:47:53 +01:00
epenet
0456eb54ee Use StrEnum in unifiprotect (#162390) 2026-02-06 11:42:34 +01:00
epenet
ce6fced6a4 Use StrEnum in intent helper (#162391) 2026-02-06 11:41:58 +01:00
epenet
fc56f52c74 Use StrEnum in modbus (#162388) 2026-02-06 11:41:29 +01:00
dependabot[bot]
f7e65eeece Bump github/codeql-action from 4.32.1 to 4.32.2 (#162369)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-06 11:33:24 +01:00
epenet
8c94de4a9c Use StrEnum in easyenergy (#162386) 2026-02-06 12:15:42 +02:00
epenet
46971c1c82 Use StrEnum in cloud (#162385) 2026-02-06 12:15:09 +02:00
epenet
fb5c3c7eb6 Use StrEnum in uptimerobot tests (#162392) 2026-02-06 12:14:28 +02:00
Anders Ödlund
ea42237444 Add dismiss protection to config flows (#162270)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-02-06 10:59:37 +01:00
Matt Zimmerman
2a76c2678e Add missing config flow strings to SmartTub (#162375)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-06 08:53:00 +01:00
Manu
72b6e5fabe Add remote action exceptions to Xbox (#162347) 2026-02-06 08:27:31 +01:00
Jordan Harvey
f739fc1f55 Update pynintendoparental requirement to version 2.3.2.1 (#162362) 2026-02-06 08:26:51 +01:00
Denis Shulyaka
aecfca5020 Add Claude Opus 4.6 support (#162365) 2026-02-06 08:25:53 +01:00
Matt Zimmerman
f024ae442f Add PARALLEL_UPDATES to SmartTub platform files (#162373)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-06 08:21:33 +01:00
Matt Zimmerman
07a9aad4a4 Bump python-smarttub to 0.0.47 (#162367)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-06 08:14:42 +01:00
Denis Shulyaka
22ab58077e Bump anthropic to 0.78.0 (#162349) 2026-02-05 22:15:16 +01:00
epenet
4b666688c9 Adjust color mode handling in esphome lights (#162294) 2026-02-05 21:04:21 +01:00
Shay Levy
d118332366 Fix Shelly Linkedgo Thermostat status update (#162339) 2026-02-05 20:52:21 +01:00
Michael Potthoff
9f32e0da14 Add type option "first_available" to sensor group in group component (#155525) 2026-02-05 20:26:45 +01:00
Andre Lengwenus
1cef223a06 Bump pypck to 0.9.10 (#162333) 2026-02-05 20:14:04 +01:00
epenet
29da1233f3 Fix missing color_mode attribute in mqtt light (#162311)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-05 18:22:57 +01:00
Arie Catsman
a5b3d22058 Bump pyenphase to 2.4.5 (#162324) 2026-02-05 17:42:45 +01:00
jameson_uk
d37e958a0b Add config entry tests to alexa_devices (#162295) 2026-02-05 13:07:14 +00:00
epenet
0498ac7364 Migrate supported_color_modes to shorthand attribute in zwave_js lights (#162296) 2026-02-05 13:56:20 +01:00
epenet
67bdeb9945 Adjust unknown color mode handling in ZHA lights (#162292) 2026-02-05 12:30:06 +01:00
Oliver
a227307387 Add support for media_stop command for denonavr receivers (#162236) 2026-02-05 12:17:25 +01:00
Oliver
0e0309cabf Add mapping for stopped state to denonavr media player (#162283) 2026-02-05 12:15:14 +01:00
epenet
fd2dfc83c6 Use shorthand attributes in zwave_js lights (#162293) 2026-02-05 12:05:08 +01:00
epenet
9e736891c4 Use shorthand attributes in demo lights (#162282) 2026-02-05 12:02:41 +01:00
Oliver
fbabf0dcb8 Bump denonavr to 1.3.2 (#162271) 2026-02-05 11:59:10 +01:00
Tomás Correia
7128791152 Fix multipart upload to use consistent part sizes for R2/S3 (#162278) 2026-02-05 11:54:02 +01:00
epenet
94456b5bc3 Improve type hints in tradfri lights (#162287) 2026-02-05 11:51:34 +01:00
epenet
2105c6b177 Improve type hints in switchbot lights (#162286) 2026-02-05 11:46:50 +01:00
Robin Lintermann
34156f79e8 Bump pysmarlaapi to 0.13.0 (#162277) 2026-02-05 11:45:29 +01:00
epenet
bb1a2530f5 Improve type hints in nanoleaf lights (#162284) 2026-02-05 11:44:54 +01:00
epenet
06613746f9 Remove unnecessary shorthand attribute init in template (#162279) 2026-02-05 11:41:57 +01:00
epenet
98ca948afe Improve type hints in abode lights (#162281) 2026-02-05 11:35:43 +01:00
Krisjanis Lejejs
fa58fe5f4e Bump hass-nabucasa from 1.12.0 to 1.13.0 (#162274) 2026-02-05 11:03:44 +01:00
Petro31
46f230c487 Clean up unused cover constants (#162225) 2026-02-05 10:46:36 +01:00
epenet
13a987aba3 Cleanup deprecated SUPPORT_ light constants (#162210) 2026-02-05 10:32:32 +01:00
cdnninja
9cef323581 Update Vesync quality-scale to Bronze (#162260) 2026-02-05 09:44:47 +01:00
epenet
7ea7576188 Cleanup legacy support for extracting color modes from light supported features (#162265) 2026-02-05 09:33:22 +01:00
Franck Nijhof
f8abbfd42b Merge branch 'master' into dev 2026-02-05 08:17:24 +00:00
Erik Montnemery
5cd1821bc9 Update redgtech snapshots (#162267) 2026-02-05 09:13:13 +01:00
Norbert Rittel
2ef7f26ffb Improve description of camera.play_stream action (#162264) 2026-02-05 09:07:10 +01:00
Jonathan Sady do Nascimento
184bea49e2 Add redgtech integration (#136947)
Co-authored-by: luan-nvg <luannnvg@gmail.com>
2026-02-05 09:04:14 +01:00
David Bonnes
c853fb2068 Bump evohome-async to 1.1.3 (#162232) 2026-02-05 08:25:30 +01:00
mettolen
79e0a93e48 Upgrade Liebherr integration to Silver (#162178) 2026-02-04 22:24:53 +01:00
Andres Ruiz
3867c1d7d1 Extract waterfurnace sensor names for translation (#162025)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-02-04 21:48:23 +01:00
Thomas55555
b9b6b050cc Bump google_air_quality_api to 3.0.1 (#162233) 2026-02-04 21:46:07 +01:00
Muhammad Hamza Khan
d960736b3d Improve typing in syncthing (#162193)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-02-04 21:43:13 +01:00
Franck Nijhof
3e8923f105 2026.2.0 (#162224) 2026-02-04 20:35:11 +01:00
Franck Nijhof
17cca3e69d Bump version to 2026.2.0 2026-02-04 18:53:49 +00:00
Franck Nijhof
12714c489f Bump version to 2026.2.0b5 2026-02-04 18:45:36 +00:00
Robert Resch
f788d61b4a Revert "Bump intents (#162205)" (#162226) 2026-02-04 18:36:12 +00:00
Simone Chemelli
5c726af00b Fix logic and tests for Alexa Devices utils module (#162223) 2026-02-04 18:36:10 +00:00
Joost Lekkerkerker
d1d207fbb2 Add guard for Apple TV text focus state (#162207) 2026-02-04 18:36:09 +00:00
David Bonnes
6c7f8df7f7 Fix evohome not updating scheduled setpoints in state attrs (#162043) 2026-02-04 18:36:07 +00:00
Kevin Stillhammer
6f8c9b1504 Bump fressnapftracker to 0.2.2 (#161913) 2026-02-04 18:36:06 +00:00
Kevin Stillhammer
4f9aedbc84 Filter out invalid trackers in fressnapf_tracker (#161670) 2026-02-04 18:36:04 +00:00
Joost Lekkerkerker
afa0f572ce Add guard for Apple TV text focus state (#162207) 2026-02-04 19:34:53 +01:00
Simone Chemelli
a6a1b9ddbd Fix logic and tests for Alexa Devices utils module (#162223) 2026-02-04 19:31:57 +01:00
Robert Resch
c1f5b4593f Revert "Bump intents (#162205)" (#162226) 2026-02-04 19:30:05 +01:00
Kevin Stillhammer
f1de4dc1cc Filter out invalid trackers in fressnapf_tracker (#161670) 2026-02-04 17:43:24 +01:00
David Bonnes
4ae0d9a9c6 Fix evohome not updating scheduled setpoints in state attrs (#162043) 2026-02-04 17:37:41 +01:00
David Girón
fcd0b579cf Compress container image with zstd (#160665)
Co-authored-by: Robert Resch <robert@resch.dev>
2026-02-04 17:28:07 +01:00
Franck Nijhof
52fb0343e4 Bump version to 2026.2.0b4 2026-02-04 16:14:23 +00:00
Bram Kragten
1050b4580a Update frontend to 20260128.6 (#162214) 2026-02-04 16:10:08 +00:00
Åke Strandberg
344c42172e Add missing codes for Miele coffe systems (#162206) 2026-02-04 16:10:06 +00:00
Michael Hansen
93cc0fd7f1 Bump intents (#162205) 2026-02-04 16:10:05 +00:00
andreimoraru
05fe636b55 Bump yt-dlp to 2026.02.04 (#162204)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-02-04 16:10:03 +00:00
Marc Mueller
f22467d099 Pin auth0-python to <5.0 (#162203) 2026-02-04 16:10:01 +00:00
TheJulianJES
4bc3899b32 Bump ZHA to 0.0.89 (#162195) 2026-02-04 16:10:00 +00:00
Oliver
fc4d6bf5f1 Bump denonavr to 1.3.1 (#162183) 2026-02-04 16:09:58 +00:00
johanzander
8ed0672a8f Bump growattServer to 1.9.0 (#162179)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 16:09:57 +00:00
Norbert Rittel
282e347a1b Clarify action descriptions in media_player (#162172) 2026-02-04 16:09:55 +00:00
Erik Montnemery
1bfb02b440 Bump python-otbr-api to 2.8.0 (#162167) 2026-02-04 16:09:54 +00:00
Przemko92
71b03bd9ae Bump compit-inext-api to 0.8.0 (#162166) 2026-02-04 16:09:52 +00:00
Przemko92
cbd69822eb Update compit-inext-api to 0.7.0 (#162020) 2026-02-04 16:09:51 +00:00
Denis Shulyaka
db900f4dd2 Anthropic repair deprecated models (#162162)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-04 16:00:14 +00:00
Jonathan Bangert
a707e695bc Bump bleak-esphome to 3.6.0 (#162028) 2026-02-04 16:00:12 +00:00
Liquidmasl
4feceac205 Jellyfin native client controls (#161982) 2026-02-04 16:00:11 +00:00
Petro31
10c20faaca Fix template weather humidity (#161945) 2026-02-04 16:00:09 +00:00
Robert Svensson
abcd512401 Add missing OUI to Axis integration, discovery would abort with unsup… (#161943) 2026-02-04 16:00:07 +00:00
Bram Kragten
dee7a237ee Update frontend to 20260128.6 (#162214) 2026-02-04 16:58:12 +01:00
Åke Strandberg
3975eba12c Add missing codes for Miele coffe systems (#162206) 2026-02-04 15:06:35 +01:00
epenet
ade91ebdab Cleanup deprecated COLOR_MODE light constants (#162197) 2026-02-04 15:00:12 +01:00
Norbert Rittel
1bf194dd0f Clarify action descriptions in media_player (#162172) 2026-02-04 14:58:40 +01:00
Manu
2eca8db8aa Add action exceptions to Xbox integration (#162198) 2026-02-04 14:56:52 +01:00
Robert Svensson
78415bc1ff Add missing OUI to Axis integration, discovery would abort with unsup… (#161943) 2026-02-04 14:43:04 +01:00
Denis Shulyaka
e2469bcd0f Anthropic repair deprecated models (#162162)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-04 14:40:31 +01:00
Michael Hansen
54d64b7da2 Bump intents (#162205) 2026-02-04 14:25:28 +01:00
Erik Montnemery
d548f3d12f Bump python-otbr-api to 2.8.0 (#162167) 2026-02-04 14:23:02 +01:00
Ludovic BOUÉ
3fdaaecd0f PIRUnoccupiedToOccupied attributes 2026-02-04 13:01:13 +00:00
epenet
668995da73 Fix incorrect exception in telegram_bot (#162191) 2026-02-04 13:32:29 +01:00
epenet
9eeae8eac6 Improve typing in telegram_bot (#162190) 2026-02-04 12:15:23 +00:00
andreimoraru
7e7056aa94 Bump yt-dlp to 2026.02.04 (#162204)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-02-04 12:07:40 +00:00
epenet
b633b8d271 Fix test_before_setup IQS check (#162187)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-02-04 12:41:59 +01:00
Marc Mueller
45c7b9ccb8 Pin auth0-python to <5.0 (#162203) 2026-02-04 12:19:49 +01:00
dependabot[bot]
3ad1a57dfc Bump github/codeql-action from 4.32.0 to 4.32.1 (#162118)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-04 10:40:44 +01:00
Brandon Rothweiler
3cbe236a36 Bump py-aosmith to 1.0.16 (#162160) 2026-02-04 10:40:09 +01:00
Przemko92
39816c1e8a Bump compit-inext-api to 0.8.0 (#162166) 2026-02-04 10:39:12 +01:00
johanzander
5587dd43b9 Bump growattServer to 1.9.0 (#162179)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 10:38:18 +01:00
Jonathan Bangert
715d1e4eb8 Bump bleak-esphome to 3.6.0 (#162028) 2026-02-04 10:34:30 +01:00
Oliver
af172fb70d Bump denonavr to 1.3.1 (#162183) 2026-02-04 10:33:44 +01:00
TheJulianJES
8c8bc104eb Bump ZHA to 0.0.89 (#162195) 2026-02-04 10:27:23 +01:00
Liquidmasl
51b20fb5db Adjust radarr constants and strings (#162159) 2026-02-04 10:16:47 +01:00
Kamil Breguła
445ba26667 Enable check for duplicate exception handlers (#162169)
Co-authored-by: mik-laj <12058428+mik-laj@users.noreply.github.com>
Co-authored-by: Josef Zweck <josef@zweck.dev>
2026-02-04 10:14:46 +01:00
epenet
886448f4ba Cleanup deprecated mired handling in light platform (#161777) 2026-02-04 09:52:53 +01:00
Petro31
ede4341ef3 Fix template weather humidity (#161945) 2026-02-04 08:02:09 +01:00
Liquidmasl
fe363f32ec Jellyfin native client controls (#161982) 2026-02-03 20:18:59 +01:00
Kamil Breguła
31562e7571 Remove duplicated exception handler in overkiz (#162171)
Co-authored-by: mik-laj <12058428+mik-laj@users.noreply.github.com>
2026-02-03 20:16:06 +01:00
Kamil Breguła
0bdb51e4ca Remove duplicated exception handler in systemmonitor (#162170)
Co-authored-by: mik-laj <12058428+mik-laj@users.noreply.github.com>
2026-02-03 20:14:37 +01:00
epenet
67a5d7ac21 Move neato service registration (#162146) 2026-02-03 20:05:12 +01:00
epenet
5e7f06c476 Move sharkiq service registration (#162147) 2026-02-03 19:52:54 +01:00
epenet
9a69852296 Move xiaomi_miio service registration (#162148) 2026-02-03 19:48:39 +01:00
Bram Kragten
fdf8edf474 Bump version to 2026.2.0b3 2026-02-03 18:03:54 +01:00
Bram Kragten
47e1a98bee Update frontend to 20260128.5 (#162156) 2026-02-03 18:03:04 +01:00
Joost Lekkerkerker
2d8572b943 Add Heatit virtual brand (#162155) 2026-02-03 18:03:02 +01:00
Joost Lekkerkerker
660cfdbd50 Add Heiman virtual brand (#162152) 2026-02-03 18:03:00 +01:00
Steven Travers
4208595da6 Modify Analytics text on feature labs (#162151) 2026-02-03 18:02:59 +01:00
Paul Bottein
b6b2d2fc6f Update title and description of YAML dashboard repair (#162138) 2026-02-03 18:02:58 +01:00
victorigualada
6c4c632848 Handle chat log attachments in Cloud integration (#162121) 2026-02-03 18:02:57 +01:00
Shay Levy
78cf62176f Fix Shelly xpercent sensor state_class (#162107) 2026-02-03 18:02:56 +01:00
Denis Shulyaka
df971c7a42 Anthropic: Switch default model to Haiku 4.5 (#162093) 2026-02-03 18:02:55 +01:00
mezz64
1fcabb7f2d Bump pyhik to 0.4.2 (#162092)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-02-03 18:02:53 +01:00
Åke Strandberg
9fb60c9ea2 Update Senz temperature sensor (#162016) 2026-02-03 18:02:52 +01:00
J. Diego Rodríguez Royo
9c11a4646f Remove coffee machine's hot water sensor's state class at Home Connect (#161246) 2026-02-03 17:58:47 +01:00
jameson_uk
b036a78776 Remove invalid notification sensors for Alexa devices (#160422)
Co-authored-by: Simone Chemelli <simone.chemelli@gmail.com>
2026-02-03 17:58:45 +01:00
Kamil Breguła
60bb3cb704 Handle missing battery stats in systemmonitor (#158287)
Co-authored-by: mik-laj <12058428+mik-laj@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-02-03 17:58:43 +01:00
Bram Kragten
a722925b8e Update frontend to 20260128.5 (#162156) 2026-02-03 17:57:15 +01:00
Joost Lekkerkerker
419c5de50e Add Heiman virtual brand (#162152) 2026-02-03 17:20:25 +01:00
Joost Lekkerkerker
37faed565e Add Heatit virtual brand (#162155) 2026-02-03 17:19:50 +01:00
Paul Bottein
622953e61f Update title and description of YAML dashboard repair (#162138) 2026-02-03 17:09:23 +01:00
Steven Travers
17926c3f6a Modify Analytics text on feature labs (#162151) 2026-02-03 16:09:34 +01:00
victorigualada
48d85170c2 Handle chat log attachments in Cloud integration (#162121) 2026-02-03 15:54:56 +01:00
epenet
08d179c520 Move ecovacs service registration (#162145) 2026-02-03 15:50:17 +01:00
hanwg
5752387da8 Add entity_id parameter for Telegram bot actions (#159745)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-02-03 15:34:39 +01:00
Sebastiaan Speck
1ebde65f03 Add sound horn and flash lights buttons to Renault (#161976)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2026-02-03 15:18:55 +01:00
Denis Shulyaka
89f536e332 Anthropic: Switch default model to Haiku 4.5 (#162093) 2026-02-03 14:12:21 +01:00
Shay Levy
8784329333 Fix Shelly xpercent sensor state_class (#162107) 2026-02-03 14:11:55 +01:00
Marc Mueller
d73538722d Use Generator and AsyncGenerator for contextmanager typing (#162144) 2026-02-03 13:52:33 +01:00
Brett Adams
d49d3f0a2f Mark test-coverage as done for Teslemetry quality scale (#161958)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-02-03 12:57:14 +01:00
Blaine Cook
8466dd4c2b Add temperature sensor to Huum integration (#161405)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-02-03 12:38:39 +01:00
Brett Adams
6bb1e688c6 Mark reconfiguration-flow as done for Teslemetry (#162139)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-03 12:37:24 +01:00
epenet
9bc1c4c4f3 Simplify reolink method arguments (#162137) 2026-02-03 12:23:33 +01:00
jameson_uk
a554cb8211 Remove invalid notification sensors for Alexa devices (#160422)
Co-authored-by: Simone Chemelli <simone.chemelli@gmail.com>
2026-02-03 11:48:57 +01:00
Liquidmasl
145d38403e Add get_queue and get_movies service calls to Radarr (#160753)
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-02-03 11:30:30 +01:00
Erwin Douna
10d4af5674 Use asyncio.gather pattern in portainer (#160888) 2026-02-03 11:23:34 +01:00
Brett Adams
ed3b4d2de3 Fix oauth debug log bug in Teslemetry (#161652) 2026-02-03 11:16:45 +01:00
epenet
e66d324877 Move openhome service registration (#162127) 2026-02-03 11:15:53 +01:00
epenet
f7f18627a2 Move squeezebox service registration (#162132) 2026-02-03 11:15:28 +01:00
epenet
d18630020f Move songpal service registration (#162131) 2026-02-03 11:15:05 +01:00
epenet
a715ec318c Move snapcast service registration (#162130) 2026-02-03 11:14:39 +01:00
epenet
0ef5a77dc9 Move roon service registration (#162129) 2026-02-03 11:14:06 +01:00
epenet
b43abf83b8 Move roku service registration (#162128) 2026-02-03 11:13:19 +01:00
epenet
84d28db3a7 Move linkplay service registration (#162126) 2026-02-03 11:12:32 +01:00
epenet
74d99fa0be Move denonavr service registration (#162123) 2026-02-03 11:12:04 +01:00
epenet
3ff0320ed8 Move vizio service registration (#162133) 2026-02-03 11:11:19 +01:00
epenet
16cb9e9785 Move kodi service registration (#162125) 2026-02-03 11:10:41 +01:00
epenet
d92279dfcb Move epson service registration (#162124) 2026-02-03 11:10:10 +01:00
Kamil Breguła
4b9d28d0e5 Handle missing battery stats in systemmonitor (#158287)
Co-authored-by: mik-laj <12058428+mik-laj@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-02-03 10:54:31 +01:00
Wendelin
e6a60dfe50 Add option to use frontend PR artifact to frontend integration (#161291)
Co-authored-by: Claude Sonnet 4.5 <noreply@anthropic.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2026-02-03 10:23:25 +01:00
Tom
d219056e9d Add target_humidity_step attribute to climate (#160418) 2026-02-03 09:34:31 +02:00
epenet
6ff6b099b5 Move bring service registration (#162077) 2026-02-03 07:42:03 +01:00
Brett Adams
c5b9699098 Add model_id and sw_version to Teslemetry device info (#161959)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-02 21:58:02 +01:00
mettolen
6937bfdf67 Add number entity to Liebherr integration (#162011) 2026-02-02 21:48:39 +01:00
epenet
39ee3fcfaa Move bond service registration (#162075) 2026-02-02 21:47:30 +01:00
J. Diego Rodríguez Royo
16cdfd05a0 Remove coffee machine's hot water sensor's state class at Home Connect (#161246) 2026-02-02 21:30:43 +01:00
mezz64
f49d4787be Bump pyhik to 0.4.2 (#162092)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-02-02 21:22:42 +01:00
epenet
2076700dc4 Move rainbird service registration (#162089) 2026-02-02 21:02:57 +01:00
Åke Strandberg
76c135913e Update Senz temperature sensor (#162016) 2026-02-02 20:10:46 +01:00
epenet
c3534d5445 Mark tts method type hints as mandatory (#161235) 2026-02-02 19:49:55 +01:00
epenet
fc60b16d65 Mark device_tracker method type hints as mandatory (#161232) 2026-02-02 19:49:26 +01:00
epenet
0443c93f77 Move webostv service registration (#162091) 2026-02-02 20:48:21 +02:00
Bram Kragten
0e770958ac Bump version to 2026.2.0b2 2026-02-02 19:12:33 +01:00
Bram Kragten
2a54c71b6c Update frontend to 20260128.4 (#162096) 2026-02-02 19:11:59 +01:00
Steven Travers
50463291ab Add learn more data for Analytics in labs (#162094) 2026-02-02 19:11:59 +01:00
Andrea Turri
43cc34042a Fix Miele dishwasher PowerDisk filling level sensor not showing up (#162048) 2026-02-02 19:11:58 +01:00
Jan Bouwhuis
a02244ccda Bump incomfort-client to 0.6.12 (#162037) 2026-02-02 19:11:57 +01:00
Adrián Moreno
a739619121 Bump pymeteoclimatic to 0.1.1 (#162029) 2026-02-02 19:11:56 +01:00
Åke Strandberg
5db97a5f1c Improved error checking during startup of SENZ (#162026) 2026-02-02 19:11:54 +01:00
Josef Zweck
804ba9c9cc Remove file description dependency in onedrive (#162012) 2026-02-02 19:11:53 +01:00
Filip Bårdsnes Tomren
5ecbcea946 Update ical requirement version to 12.1.3 (#162010) 2026-02-02 19:11:52 +01:00
hanwg
11be2b6289 Fix parse_mode for Telegram bot actions (#162006) 2026-02-02 19:11:51 +01:00
cdnninja
eefae0307b Add integration type of hub to vesync (#162004) 2026-02-02 19:11:50 +01:00
Matthias Alphart
d397ee28ea Fix KNX fan unique_id for switch-only fans (#162002) 2026-02-02 19:11:49 +01:00
starkillerOG
02c821128e Bump reolink-aio to 0.18.2 (#161998) 2026-02-02 19:11:48 +01:00
Shay Levy
71dc15d45f Fix Shelly CoIoT repair issue (#161973)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-02 19:11:47 +01:00
Raphael Hehl
1078387b22 Bump uiprotect to version 10.1.0 (#161967)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-02-02 19:11:46 +01:00
tronikos
35fab27d15 Bump opower to 0.17.0 (#161962) 2026-02-02 19:11:45 +01:00
Yuxin Wang
915dc7a908 Mark datetime sensors as unknown when parsing fails (#161952) 2026-02-02 19:11:44 +01:00
mvn23
e5a9738983 Fix OpenTherm Gateway button availability (#161933) 2026-02-02 19:11:43 +01:00
mvn23
2ff73219a2 Bump pyotgw to 2.2.3 (#161928) 2026-02-02 19:11:42 +01:00
epenet
5dc1270ed1 Fix mired warning in template light (#161923) 2026-02-02 19:11:41 +01:00
J. Diego Rodríguez Royo
9e95ad5a85 Restore the Home Connect program option entities (#156401)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-02-02 19:11:40 +01:00
Bram Kragten
f97cf0e446 Update frontend to 20260128.4 (#162096) 2026-02-02 19:03:38 +01:00
epenet
bd4fa0d5c2 Move reolink service registration (#162085) 2026-02-02 19:02:09 +01:00
Steven Travers
f60d367184 Add learn more data for Analytics in labs (#162094) 2026-02-02 17:22:01 +01:00
epenet
6e231f2ec5 Move husqvarna_automower service registration (#162087) 2026-02-02 17:08:41 +01:00
epenet
13ba2d2e47 Move litterrobot service registration (#162088) 2026-02-02 17:08:07 +01:00
epenet
ba4a163e24 Move roborock service registration (#162090) 2026-02-02 17:07:44 +01:00
epenet
b7db8684db Move elgato service registration (#162086) 2026-02-02 17:05:48 +01:00
Ludovic BOUÉ
a7595dc468 Rename Matter Inovelli VTM31-SN fixture (#162076) 2026-02-02 15:49:11 +01:00
epenet
d2c8c3565b Move blink service registration (#162078) 2026-02-02 14:49:05 +01:00
Ludovic BOUÉ
422d1031f4 Rename Matter Mock air purifier fixture file (#161937) 2026-02-02 14:43:27 +01:00
Viktor Andersson
c9a79cf100 Update Electricity Maps translations (#162074)
Co-authored-by: Josef Zweck <josef@zweck.dev>
2026-02-02 13:54:07 +01:00
epenet
c42d47a619 Rename service registration function in growatt_server (#162073) 2026-02-02 13:32:33 +01:00
Ludovic BOUÉ
a26f871d32 Rename Matter Mock devices (#161949) 2026-02-02 12:59:32 +01:00
Gage Benne
d481c1bcc5 Improve accuracy of blood glucose conversion factor (#161644) 2026-02-02 12:19:16 +01:00
dependabot[bot]
379e3596b4 Bump dawidd6/action-download-artifact from 12 to 14 (#162058)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-02 10:55:15 +01:00
Jan Bouwhuis
423a7cdbba Bump incomfort-client to 0.6.12 (#162037) 2026-02-02 10:10:11 +01:00
Henning Kerstan
841fa48186 Replace hass.data[DATA_ENOCEAN] by config_entry.runtime_data (#161997) 2026-02-02 09:50:49 +01:00
Andres Ruiz
61e35157e3 Bump waterfurnace to 1.5.1 (#162042) 2026-02-02 08:59:53 +01:00
epenet
87f655f56d Move alarmdecoder service registration (#162063) 2026-02-02 08:59:47 +01:00
epenet
692b8d0722 Move agent_dvr service registration (#162062) 2026-02-02 08:59:35 +01:00
Luke Lashley
5f9f623c3f Bump python-roborock to 4.12.0 (#162054) 2026-02-01 20:28:35 -08:00
Przemko92
e595b6cd90 Update compit-inext-api to 0.7.0 (#162020) 2026-02-02 02:28:36 +01:00
Andrea Turri
a748eebf3e Fix Miele dishwasher PowerDisk filling level sensor not showing up (#162048) 2026-02-02 02:18:02 +01:00
Adrián Moreno
6bdd544867 Bump pymeteoclimatic to 0.1.1 (#162029) 2026-02-02 00:44:29 +01:00
Luke Lashley
705eadf8ce Add the ability to select region for Roborock (#160898) 2026-02-01 11:50:34 -08:00
Josef Zweck
b7c6e4eafc Remove file description dependency in onedrive (#162012) 2026-02-01 19:43:56 +01:00
Åke Strandberg
f4aba286fe Improved error checking during startup of SENZ (#162026) 2026-02-01 19:42:27 +01:00
Yuxin Wang
5fa4f6de11 Mark datetime sensors as unknown when parsing fails (#161952) 2026-02-01 17:41:01 +01:00
Justus
db1f045c42 bump iometer to v0.4.0 (#162027) 2026-02-01 17:32:03 +01:00
Erwin Douna
eaba4817bd Optimize attribute lookup in DSMR Reader (#161994) 2026-02-01 15:26:00 +01:00
Erwin Douna
96cb2247df Remove unneeded NotImplementedError in Volvlo entity (#161990) 2026-02-01 15:25:23 +01:00
Matthias Alphart
99fa7a1f52 Fix KNX fan unique_id for switch-only fans (#162002) 2026-02-01 12:53:19 +01:00
Filip Bårdsnes Tomren
e0ba928296 Update ical requirement version to 12.1.3 (#162010) 2026-02-01 12:43:34 +01:00
Tomasz
16fd5e8f1f Move initial_color to CalendarEntityDescription (#161831)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2026-02-01 11:01:05 +00:00
Brett Adams
201e95a417 Complete config-flow-test-coverage quality in Teslemetry (#161955)
Co-authored-by: Claude <noreply@anthropic.com>
2026-02-01 10:54:35 +01:00
dafal
dc01592991 Bthome encryption downgrade (#159646)
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-02-01 09:40:47 +02:00
hanwg
c5fb2bd566 Fix parse_mode for Telegram bot actions (#162006) 2026-02-01 08:37:23 +01:00
cdnninja
d03d996155 Add integration type of hub to vesync (#162004) 2026-02-01 08:33:04 +01:00
starkillerOG
9618412a44 Bump reolink-aio to 0.18.2 (#161998) 2026-02-01 07:49:55 +01:00
Erwin Douna
967e97661f Add reauth to Proxmox (#161944) 2026-01-31 22:42:33 +01:00
Erwin Douna
b757312fe0 Remove unused variables in SMA (#161989) 2026-01-31 20:40:28 +01:00
Erwin Douna
2ed8ec0bdf Add reconfigure to Proxmox (#161941)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2026-01-31 20:21:55 +01:00
epenet
97f6e3741a Fix mired warning in template light (#161923) 2026-01-31 17:30:41 +01:00
Colin
c2d3244d26 openevse: Turn on strict typing (#161957)
Co-authored-by: Josef Zweck <josef@zweck.dev>
2026-01-31 16:56:17 +01:00
Shay Levy
eafeba792d Fix Shelly CoIoT repair issue (#161973)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-31 16:33:31 +02:00
Norbert Rittel
c9318b6fbf Clarify action description for input_button helper (#161963) 2026-01-31 15:16:36 +01:00
epenet
99be382abf Remove outdated device registry cleanup in generic_hygrostat (#161859) 2026-01-31 15:15:19 +01:00
epenet
7cfcfca210 Remove outdated device registry cleanup in generic_thermostat (#161861) 2026-01-31 15:14:57 +01:00
epenet
f29daccb19 Remove outdated device registry cleanup in history_stats (#161862) 2026-01-31 15:14:42 +01:00
epenet
be869fce6c Remove outdated device registry cleanup in mold_indicator (#161864) 2026-01-31 15:14:26 +01:00
epenet
7bb0414a39 Remove outdated device registry cleanup in statistics (#161865) 2026-01-31 15:14:09 +01:00
epenet
3f8807d063 Remove outdated device registry cleanup in threshold (#161866) 2026-01-31 15:13:54 +01:00
mettolen
67642e6246 Add reauthentication flow to Liebherr integration (#161902)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-31 15:12:52 +01:00
mvn23
0d215597f3 Fix OpenTherm Gateway button availability (#161933) 2026-01-31 15:06:21 +01:00
mvn23
f41bd2b582 Bump pyotgw to 2.2.3 (#161928) 2026-01-31 15:03:56 +01:00
Norbert Rittel
5c9ec1911b Clarify action descriptions for input_boolean (#161924) 2026-01-31 15:03:08 +01:00
J. Diego Rodríguez Royo
1a0b7fe984 Restore the Home Connect program option entities (#156401)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-01-31 12:32:18 +01:00
Erwin Douna
26ee25d7bb Pattern fix for Proxmox config flow (#161946) 2026-01-31 11:41:41 +01:00
Norbert Rittel
aabf52d3cf Rename "service" to "action", use common state for "High" (#161940) 2026-01-31 11:40:55 +01:00
Erwin Douna
99fcb46a7e Add parallel updates to Portainer (#161947) 2026-01-31 11:40:25 +01:00
Raphael Hehl
6580c5e5bf Bump uiprotect to version 10.1.0 (#161967)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-01-31 11:39:20 +01:00
tronikos
63e7d4dc08 Bump opower to 0.17.0 (#161962) 2026-01-31 11:38:43 +01:00
Sid
cc6900d846 Bump eheimdigital to 1.6.0 (#161961) 2026-01-31 11:38:14 +01:00
Brett Adams
ca2ad22884 Rename drive inverter unavailable state in Teslemetry (#161960)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-31 11:36:12 +01:00
Armin Ghofrani
40944f0f2d Enable prompt caching for Anthropic conversation integration (#158957)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-01-30 23:32:47 +03:00
uptimeZERO_
91a3e488b1 Bump media source upload limit from 10mb to 20mb (#161436) 2026-01-30 13:07:37 +01:00
Magnus Øverli
9a1f517e6e Convert flexit_bacnet fireplace mode to climate preset- Rename 'Boost… (#155760)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-01-30 12:59:10 +01:00
Simone Chemelli
c82c614bb9 Handle hostname resolution for Shelly repair issue (#161914) 2026-01-30 12:26:48 +01:00
Norbert Rittel
20914dce67 Improve action descriptions of camera (#161876) 2026-01-30 12:08:49 +01:00
Paul Bottein
5fc407d2f3 Update frontend to 20260128.3 (#161918) 2026-01-30 11:51:53 +01:00
Marc Mueller
c7444d38a1 Remove pydantic v1 mypy plugin (#161901) 2026-01-30 11:19:06 +01:00
puddly
81f6136bda Bump ZHA to 0.0.88 (#161904) 2026-01-30 11:18:38 +01:00
Steve Easley
862d0ea49e Bump JVC Projector dependency to 2.0.1 (#161898) 2026-01-30 11:17:14 +01:00
hanwg
f2fdfed241 Update translations for Telegram bot (#161903) 2026-01-30 11:13:46 +01:00
David Recordon
15640049cb Fix Control4 HVAC state-to-action mapping (#161916) 2026-01-30 10:59:39 +01:00
dependabot[bot]
5c163434f8 Bump actions/cache from 5.0.2 to 5.0.3 (#161906)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-01-30 10:47:02 +01:00
Sebastiaan Speck
e54c2ea55e Ensure Renault buttons are supported by the vehicle (#161893)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2026-01-30 09:58:50 +01:00
Kevin Stillhammer
1ec42693ab Bump fressnapftracker to 0.2.2 (#161913) 2026-01-30 09:32:13 +01:00
epenet
672864ae4f Remove outdated device registry cleanup in trend (#161867) 2026-01-30 08:07:53 +01:00
Artur Pragacz
e54d7e42cb Add subscription pattern for conversation intents (#158456) 2026-01-30 07:19:57 +01:00
Jan Bouwhuis
5d63fce015 Re-add Claude code to devcontainer via native install script (#161807) 2026-01-29 23:35:59 -05:00
Paul Bottein
190fe10eed Allow lovelace path for dashboard in yaml and fix yaml dashboard migration (#161816) 2026-01-29 17:19:37 -05:00
Bram Kragten
ef410c1e2a Update frontend to 20260128.2 (#161881) 2026-01-29 23:02:59 +01:00
Artur Pragacz
5a712398e7 Fix validation of actions config in intent_script (#158266) 2026-01-29 22:12:46 +01:00
Thomas55555
b1be3fe0da Introduce common string for data description of verify_ssl (#160703) 2026-01-29 20:27:37 +00:00
Brett Adams
97a7ab011b Add quality scale to Teslemetry (#159589) 2026-01-29 20:23:09 +00:00
SamareshSingh
694a3050b9 Add device_class inheritance to min_max sensor (#157602)
Signed-off-by: Samaresh Sahoo <ssamaresh01@gmail.com>
Co-authored-by: Samaresh Kumar Singh <ssam18@users.noreply.github.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-01-29 21:15:41 +01:00
Erwin Douna
8164e65188 Fix small typo in Portainer strings (#161889) 2026-01-29 20:58:07 +01:00
Marc Mueller
9af0d1eed4 Update fritzconnection to 1.15.1 (#161887) 2026-01-29 20:57:52 +01:00
Jan Bouwhuis
72e6ca55ba Fix use of ambiguous units for reactive power and energy (#161810) 2026-01-29 20:34:09 +01:00
Jeremiah Paige
0fb62a7e97 Add wsdot code-owner (#160807) 2026-01-29 19:52:41 +01:00
Erwin Douna
930eb70a8b Add prune images service to Portainer (#161009)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-01-29 19:39:17 +01:00
Norbert Rittel
462104fa68 Clarify action descriptions for input numbers (#161847) 2026-01-29 18:43:26 +01:00
mettolen
d0c77d8a7e Delete unused Liebherr snapshot (#161879) 2026-01-29 17:38:56 +01:00
Björn Dalfors
606780b20f Bump nibe to 2.22.0 (#161873) 2026-01-29 17:06:38 +01:00
Tucker Kern
8f465cf2ca Remove deprecated Snapcast group entities and custom grouping services (#160945) 2026-01-29 16:44:50 +01:00
epenet
4e29476dd9 Cleanup deprecated YAML import from datadog (#161870) 2026-01-29 15:33:14 +01:00
epenet
b4328083be Fix incorrect entity_description class in radarr (#161856) 2026-01-29 15:09:06 +01:00
epenet
72ba59f559 Remove outdated device registry cleanup in utility_meter (#161868) 2026-01-29 15:01:41 +01:00
epenet
826168b601 Remove outdated device registry cleanup in integration (#161863) 2026-01-29 15:01:22 +01:00
Sebastiaan Speck
66f181992c Bump renault-api to 0.5.3 (#161857) 2026-01-29 14:02:22 +01:00
epenet
336ef4c37b Remove outdated device registry cleanup in derivative (#161858) 2026-01-29 13:55:49 +01:00
mettolen
72e7bf7f9c Add new Liebherr integration (#161197)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-29 13:49:09 +01:00
Gage Benne
acbdbc9be7 Bump pydexcom to 0.5.1 (#161549) 2026-01-29 12:47:05 +01:00
Steve Easley
3551382f8d Add additional JVC Projector entities (#161134) 2026-01-29 12:45:19 +01:00
Mattia Monga
95014d7e6d Make viaggiatreno work by fixing some critical bugs (#160093)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-01-29 12:41:47 +01:00
Retha Runolfsson
dfe1990484 Add service for switchbot keypad vision (#160659)
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-01-29 12:23:38 +01:00
epenet
15ff5d0f74 Modernize tasmota light tests (#161830) 2026-01-29 12:05:03 +01:00
epenet
1407f61a9c Modernize abode light tests (#161829) 2026-01-29 12:01:32 +01:00
epenet
6107b794d6 Modernize hue light tests (#161828) 2026-01-29 12:01:07 +01:00
epenet
7ab8ceab7e Modernize zha light tests (#161826) 2026-01-29 12:00:52 +01:00
epenet
a4db6a9ebc Modernize template light tests (#161833) 2026-01-29 11:59:55 +01:00
Colin
12a2650b6b Add quality scale to openesve (#161651) 2026-01-29 11:55:54 +01:00
Markus Jacobsen
23da7ecedd Bump mozart_api to 5.3.1.108.2 (#161846) 2026-01-29 11:54:11 +01:00
wollew
8d9e7b0b26 Do not use base class of pyvlx in velux light platform (#161837) 2026-01-29 11:52:22 +01:00
epenet
9664047345 Modernize homekit_controller light tests (#161844) 2026-01-29 11:51:59 +01:00
epenet
804fbf9cef Modernize govee_light_local light tests (#161845) 2026-01-29 11:51:22 +01:00
epenet
e10fe074c9 Cleanup deprecated color_temp support in lifx (#161848) 2026-01-29 11:50:53 +01:00
Norbert Rittel
7b0e21da74 Fix action descriptions of alarm_control_panel (#161852) 2026-01-29 11:50:22 +01:00
epenet
29e142cf1e Modernize matter light tests (#161850) 2026-01-29 11:49:51 +01:00
epenet
6b765ebabb Modernize tradfri light tests (#161849) 2026-01-29 11:49:18 +01:00
epenet
899aa62697 Modernize knx light tests (#161851) 2026-01-29 11:42:18 +01:00
dependabot[bot]
a11efba405 Bump docker/login-action from 3.6.0 to 3.7.0 (#161825) 2026-01-29 07:43:41 +01:00
Manu
78280dfc5a Fix string in Namecheap DynamicDNS integration (#161821) 2026-01-29 03:10:09 +01:00
Glenn de Haan
4220bab08a Improve quality scale to gold HDFury integration (#161800) 2026-01-29 00:25:00 +01:00
Marc Mueller
f7dcf8de15 Switch back to mypy 1.19.1 (#161817) 2026-01-29 00:12:46 +01:00
Aaron Godfrey
7e32b50fee Update todoist-api-python to 3.1.0 (#161811)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-29 00:00:53 +01:00
Robert Resch
c875b75272 Use Python 3.14 as default one (#161426)
Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
Co-authored-by: Franck Nijhof <git@frenck.dev>
2026-01-28 23:48:27 +01:00
John Hillery
7368b9ca1d Add sensor for energy remaining to tessie integration (#161796) 2026-01-28 23:41:29 +01:00
Michael Jones
493e8c1a22 Append ID to flood monitoring station name in EAFM (#161794) 2026-01-28 22:18:35 +00:00
Michael Hansen
1b16b24550 Bump intents to 2026.1.28 (#161813) 2026-01-28 23:14:36 +01:00
Franck Nijhof
7637300632 Bump version to 2026.3.0dev0 (#161809) 2026-01-28 23:12:34 +01:00
victorigualada
bdbce57217 Use OpenAI schema dataclasses for cloud stream responses (#161663) 2026-01-28 20:59:03 +01:00
1274 changed files with 33353 additions and 18283 deletions

View File

@@ -10,12 +10,12 @@ on:
env:
BUILD_TYPE: core
DEFAULT_PYTHON: "3.13"
DEFAULT_PYTHON: "3.14.2"
PIP_TIMEOUT: 60
UV_HTTP_TIMEOUT: 60
UV_SYSTEM_PYTHON: "true"
# Base image version from https://github.com/home-assistant/docker
BASE_IMAGE_VERSION: "2025.12.0"
BASE_IMAGE_VERSION: "2026.01.0"
ARCHITECTURES: '["amd64", "aarch64"]'
jobs:
@@ -100,7 +100,7 @@ jobs:
- name: Download nightly wheels of frontend
if: needs.init.outputs.channel == 'dev'
uses: dawidd6/action-download-artifact@0bd50d53a6d7fb5cb921e607957e9cc12b4ce392 # v12
uses: dawidd6/action-download-artifact@5c98f0b039f36ef966fdb7dfa9779262785ecb05 # v14
with:
github_token: ${{secrets.GITHUB_TOKEN}}
repo: home-assistant/frontend
@@ -111,7 +111,7 @@ jobs:
- name: Download nightly wheels of intents
if: needs.init.outputs.channel == 'dev'
uses: dawidd6/action-download-artifact@0bd50d53a6d7fb5cb921e607957e9cc12b4ce392 # v12
uses: dawidd6/action-download-artifact@5c98f0b039f36ef966fdb7dfa9779262785ecb05 # v14
with:
github_token: ${{secrets.GITHUB_TOKEN}}
repo: OHF-Voice/intents-package
@@ -184,7 +184,7 @@ jobs:
echo "${{ github.sha }};${{ github.ref }};${{ github.event_name }};${{ github.actor }}" > rootfs/OFFICIAL_IMAGE
- name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -235,6 +235,7 @@ jobs:
build-args: |
BUILD_FROM=${{ steps.vars.outputs.base_image }}
tags: ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant:${{ needs.init.outputs.version }}
outputs: type=image,push=true,compression=zstd,compression-level=9,force-compression=true,oci-mediatypes=true
labels: |
io.hass.arch=${{ matrix.arch }}
io.hass.version=${{ needs.init.outputs.version }}
@@ -287,7 +288,7 @@ jobs:
fi
- name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -358,13 +359,13 @@ jobs:
- name: Login to DockerHub
if: matrix.registry == 'docker.io/homeassistant'
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
username: ${{ secrets.DOCKERHUB_USERNAME }}
password: ${{ secrets.DOCKERHUB_TOKEN }}
- name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}
@@ -522,7 +523,7 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Login to GitHub Container Registry
uses: docker/login-action@5e57cd118135c172c3672efd75eb46360885c0ef # v3.6.0
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
registry: ghcr.io
username: ${{ github.repository_owner }}

View File

@@ -40,9 +40,9 @@ env:
CACHE_VERSION: 2
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 1
HA_SHORT_VERSION: "2026.2"
DEFAULT_PYTHON: "3.13.11"
ALL_PYTHON_VERSIONS: "['3.13.11', '3.14.2']"
HA_SHORT_VERSION: "2026.3"
DEFAULT_PYTHON: "3.14.2"
ALL_PYTHON_VERSIONS: "['3.14.2']"
# 10.3 is the oldest supported version
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
# 10.6 is the current long-term-support
@@ -310,7 +310,7 @@ jobs:
env.HA_SHORT_VERSION }}-$(date -u '+%Y-%m-%dT%H:%M:%s')" >> $GITHUB_OUTPUT
- name: Restore base Python virtual environment
id: cache-venv
uses: &actions-cache actions/cache@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
uses: &actions-cache actions/cache@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: venv
key: &key-python-venv >-
@@ -374,7 +374,7 @@ jobs:
fi
- name: Save apt cache
if: steps.cache-apt-check.outputs.cache-hit != 'true'
uses: &actions-cache-save actions/cache/save@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
uses: &actions-cache-save actions/cache/save@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: *path-apt-cache
key: *key-apt-cache
@@ -425,7 +425,7 @@ jobs:
steps:
- &cache-restore-apt
name: Restore apt cache
uses: &actions-cache-restore actions/cache/restore@8b402f58fbc84540c8b491a91e594a4576fec3d7 # v5.0.2
uses: &actions-cache-restore actions/cache/restore@cdf6c1fa76f9f475f3d7449005a359c84ca0f306 # v5.0.3
with:
path: *path-apt-cache
fail-on-cache-miss: true

View File

@@ -24,11 +24,11 @@ jobs:
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Initialize CodeQL
uses: github/codeql-action/init@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
uses: github/codeql-action/init@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
with:
languages: python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@b20883b0cd1f46c72ae0ba6d1090936928f9fa30 # v4.32.0
uses: github/codeql-action/analyze@45cbd0c69e560cd9e7cd7f8c32362050c9b7ded2 # v4.32.2
with:
category: "/language:python"

View File

@@ -10,7 +10,7 @@ on:
- "**strings.json"
env:
DEFAULT_PYTHON: "3.13"
DEFAULT_PYTHON: "3.14.2"
jobs:
upload:

View File

@@ -17,7 +17,7 @@ on:
- "script/gen_requirements_all.py"
env:
DEFAULT_PYTHON: "3.13"
DEFAULT_PYTHON: "3.14.2"
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name}}

View File

@@ -1 +1 @@
3.13
3.14

View File

@@ -221,6 +221,7 @@ homeassistant.components.generic_hygrostat.*
homeassistant.components.generic_thermostat.*
homeassistant.components.geo_location.*
homeassistant.components.geocaching.*
homeassistant.components.ghost.*
homeassistant.components.gios.*
homeassistant.components.github.*
homeassistant.components.glances.*
@@ -389,6 +390,7 @@ homeassistant.components.onkyo.*
homeassistant.components.open_meteo.*
homeassistant.components.open_router.*
homeassistant.components.openai_conversation.*
homeassistant.components.openevse.*
homeassistant.components.openexchangerates.*
homeassistant.components.opensky.*
homeassistant.components.openuv.*
@@ -434,6 +436,7 @@ homeassistant.components.raspberry_pi.*
homeassistant.components.rdw.*
homeassistant.components.recollect_waste.*
homeassistant.components.recorder.*
homeassistant.components.redgtech.*
homeassistant.components.remember_the_milk.*
homeassistant.components.remote.*
homeassistant.components.remote_calendar.*

8
CODEOWNERS generated
View File

@@ -595,6 +595,8 @@ build.json @home-assistant/supervisor
/tests/components/geonetnz_quakes/ @exxamalte
/homeassistant/components/geonetnz_volcano/ @exxamalte
/tests/components/geonetnz_volcano/ @exxamalte
/homeassistant/components/ghost/ @johnonolan
/tests/components/ghost/ @johnonolan
/homeassistant/components/gios/ @bieniu
/tests/components/gios/ @bieniu
/homeassistant/components/github/ @timmo001 @ludeeus
@@ -921,6 +923,8 @@ build.json @home-assistant/supervisor
/tests/components/libre_hardware_monitor/ @Sab44
/homeassistant/components/lidarr/ @tkdrob
/tests/components/lidarr/ @tkdrob
/homeassistant/components/liebherr/ @mettolen
/tests/components/liebherr/ @mettolen
/homeassistant/components/lifx/ @Djelibeybi
/tests/components/lifx/ @Djelibeybi
/homeassistant/components/light/ @home-assistant/core
@@ -1353,6 +1357,8 @@ build.json @home-assistant/supervisor
/tests/components/recorder/ @home-assistant/core
/homeassistant/components/recovery_mode/ @home-assistant/core
/tests/components/recovery_mode/ @home-assistant/core
/homeassistant/components/redgtech/ @jonhsady @luan-nvg
/tests/components/redgtech/ @jonhsady @luan-nvg
/homeassistant/components/refoss/ @ashionky
/tests/components/refoss/ @ashionky
/homeassistant/components/rehlko/ @bdraco @peterager
@@ -1878,6 +1884,8 @@ build.json @home-assistant/supervisor
/tests/components/worldclock/ @fabaff
/homeassistant/components/ws66i/ @ssaenger
/tests/components/ws66i/ @ssaenger
/homeassistant/components/wsdot/ @ucodery
/tests/components/wsdot/ @ucodery
/homeassistant/components/wyoming/ @synesthesiam
/tests/components/wyoming/ @synesthesiam
/homeassistant/components/xbox/ @hunterjm @tr4nt0r

View File

@@ -52,6 +52,9 @@ RUN --mount=type=bind,source=requirements.txt,target=requirements.txt \
--mount=type=bind,source=requirements_test_pre_commit.txt,target=requirements_test_pre_commit.txt \
uv pip install -r requirements.txt -r requirements_test.txt
# Claude Code native install
RUN curl -fsSL https://claude.ai/install.sh | bash
WORKDIR /workspaces
# Set the default shell to bash instead of sh

View File

@@ -0,0 +1,5 @@
{
"domain": "heatit",
"name": "Heatit",
"iot_standards": ["zwave"]
}

View File

@@ -0,0 +1,5 @@
{
"domain": "heiman",
"name": "Heiman",
"iot_standards": ["matter", "zigbee"]
}

View File

@@ -64,7 +64,7 @@ class AbodeFlowHandler(ConfigFlow, domain=DOMAIN):
else:
errors = {"base": "cannot_connect"}
except (ConnectTimeout, HTTPError):
except ConnectTimeout, HTTPError:
errors = {"base": "cannot_connect"}
if errors:

View File

@@ -99,7 +99,7 @@ class AbodeLight(AbodeDevice, LightEntity):
return _hs
@property
def color_mode(self) -> ColorMode | None:
def color_mode(self) -> ColorMode:
"""Return the color mode of the light."""
if self._device.is_dimmable and self._device.is_color_capable:
if self.hs_color is not None:
@@ -110,7 +110,7 @@ class AbodeLight(AbodeDevice, LightEntity):
return ColorMode.ONOFF
@property
def supported_color_modes(self) -> set[ColorMode] | None:
def supported_color_modes(self) -> set[ColorMode]:
"""Flag supported color modes."""
if self._device.is_dimmable and self._device.is_color_capable:
return {ColorMode.COLOR_TEMP, ColorMode.HS}

View File

@@ -43,7 +43,7 @@ class AccuWeatherFlowHandler(ConfigFlow, domain=DOMAIN):
longitude=user_input[CONF_LONGITUDE],
)
await accuweather.async_get_location()
except (ApiError, ClientConnectorError, TimeoutError, ClientError):
except ApiError, ClientConnectorError, TimeoutError, ClientError:
errors["base"] = "cannot_connect"
except InvalidApiKeyError:
errors[CONF_API_KEY] = "invalid_api_key"
@@ -104,7 +104,7 @@ class AccuWeatherFlowHandler(ConfigFlow, domain=DOMAIN):
longitude=self._longitude,
)
await accuweather.async_get_location()
except (ApiError, ClientConnectorError, TimeoutError, ClientError):
except ApiError, ClientConnectorError, TimeoutError, ClientError:
errors["base"] = "cannot_connect"
except InvalidApiKeyError:
errors["base"] = "invalid_api_key"

View File

@@ -29,30 +29,42 @@ SWITCHES: tuple[ActronAirSwitchEntityDescription, ...] = (
key="away_mode",
translation_key="away_mode",
is_on_fn=lambda coordinator: coordinator.data.user_aircon_settings.away_mode,
set_fn=lambda coordinator,
enabled: coordinator.data.user_aircon_settings.set_away_mode(enabled),
set_fn=lambda coordinator, enabled: (
coordinator.data.user_aircon_settings.set_away_mode(enabled)
),
),
ActronAirSwitchEntityDescription(
key="continuous_fan",
translation_key="continuous_fan",
is_on_fn=lambda coordinator: coordinator.data.user_aircon_settings.continuous_fan_enabled,
set_fn=lambda coordinator,
enabled: coordinator.data.user_aircon_settings.set_continuous_mode(enabled),
is_on_fn=lambda coordinator: (
coordinator.data.user_aircon_settings.continuous_fan_enabled
),
set_fn=lambda coordinator, enabled: (
coordinator.data.user_aircon_settings.set_continuous_mode(enabled)
),
),
ActronAirSwitchEntityDescription(
key="quiet_mode",
translation_key="quiet_mode",
is_on_fn=lambda coordinator: coordinator.data.user_aircon_settings.quiet_mode_enabled,
set_fn=lambda coordinator,
enabled: coordinator.data.user_aircon_settings.set_quiet_mode(enabled),
is_on_fn=lambda coordinator: (
coordinator.data.user_aircon_settings.quiet_mode_enabled
),
set_fn=lambda coordinator, enabled: (
coordinator.data.user_aircon_settings.set_quiet_mode(enabled)
),
),
ActronAirSwitchEntityDescription(
key="turbo_mode",
translation_key="turbo_mode",
is_on_fn=lambda coordinator: coordinator.data.user_aircon_settings.turbo_enabled,
set_fn=lambda coordinator,
enabled: coordinator.data.user_aircon_settings.set_turbo_mode(enabled),
is_supported_fn=lambda coordinator: coordinator.data.user_aircon_settings.turbo_supported,
is_on_fn=lambda coordinator: (
coordinator.data.user_aircon_settings.turbo_enabled
),
set_fn=lambda coordinator, enabled: (
coordinator.data.user_aircon_settings.set_turbo_mode(enabled)
),
is_supported_fn=lambda coordinator: (
coordinator.data.user_aircon_settings.turbo_supported
),
),
)

View File

@@ -7,10 +7,12 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers import config_validation as cv, device_registry as dr
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.typing import ConfigType
from .const import DOMAIN, SERVER_URL
from .services import async_setup_services
ATTRIBUTION = "ispyconnect.com"
DEFAULT_BRAND = "Agent DVR by ispyconnect.com"
@@ -19,6 +21,14 @@ PLATFORMS = [Platform.ALARM_CONTROL_PANEL, Platform.CAMERA]
AgentDVRConfigEntry = ConfigEntry[Agent]
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the component."""
async_setup_services(hass)
return True
async def async_setup_entry(
hass: HomeAssistant, config_entry: AgentDVRConfigEntry

View File

@@ -9,10 +9,7 @@ from homeassistant.components.camera import CameraEntityFeature
from homeassistant.components.mjpeg import MjpegCamera, filter_urllib3_logging
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import (
AddConfigEntryEntitiesCallback,
async_get_current_platform,
)
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import AgentDVRConfigEntry
from .const import ATTRIBUTION, CAMERA_SCAN_INTERVAL_SECS, DOMAIN
@@ -21,20 +18,6 @@ SCAN_INTERVAL = timedelta(seconds=CAMERA_SCAN_INTERVAL_SECS)
_LOGGER = logging.getLogger(__name__)
_DEV_EN_ALT = "enable_alerts"
_DEV_DS_ALT = "disable_alerts"
_DEV_EN_REC = "start_recording"
_DEV_DS_REC = "stop_recording"
_DEV_SNAP = "snapshot"
CAMERA_SERVICES = {
_DEV_EN_ALT: "async_enable_alerts",
_DEV_DS_ALT: "async_disable_alerts",
_DEV_EN_REC: "async_start_recording",
_DEV_DS_REC: "async_stop_recording",
_DEV_SNAP: "async_snapshot",
}
async def async_setup_entry(
hass: HomeAssistant,
@@ -57,10 +40,6 @@ async def async_setup_entry(
async_add_entities(cameras)
platform = async_get_current_platform()
for service, method in CAMERA_SERVICES.items():
platform.async_register_entity_service(service, None, method)
class AgentCamera(MjpegCamera):
"""Representation of an Agent Device Stream."""

View File

@@ -0,0 +1,38 @@
"""Services for Agent DVR."""
from __future__ import annotations
from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import service
from .const import DOMAIN
_DEV_EN_ALT = "enable_alerts"
_DEV_DS_ALT = "disable_alerts"
_DEV_EN_REC = "start_recording"
_DEV_DS_REC = "stop_recording"
_DEV_SNAP = "snapshot"
CAMERA_SERVICES = {
_DEV_EN_ALT: "async_enable_alerts",
_DEV_DS_ALT: "async_disable_alerts",
_DEV_EN_REC: "async_start_recording",
_DEV_DS_REC: "async_stop_recording",
_DEV_SNAP: "async_snapshot",
}
@callback
def async_setup_services(hass: HomeAssistant) -> None:
"""Home Assistant services."""
for service_name, method in CAMERA_SERVICES.items():
service.async_register_platform_entity_service(
hass,
DOMAIN,
service_name,
entity_domain=CAMERA_DOMAIN,
schema=None,
func=method,
)

View File

@@ -133,8 +133,9 @@ CONTROL_ENTITIES: tuple[AirGradientSelectEntityDescription, ...] = (
value_fn=lambda config: _get_value(
config.co2_automatic_baseline_calibration_days, ABC_DAYS
),
set_value_fn=lambda client,
value: client.set_co2_automatic_baseline_calibration(int(value)),
set_value_fn=lambda client, value: (
client.set_co2_automatic_baseline_calibration(int(value))
),
),
)

View File

@@ -85,7 +85,7 @@ class AirobotButton(AirobotEntity, ButtonEntity):
"""Handle the button press."""
try:
await self.entity_description.press_fn(self.coordinator)
except (AirobotConnectionError, AirobotTimeoutError):
except AirobotConnectionError, AirobotTimeoutError:
# Connection errors during reboot are expected as device restarts
pass
except AirobotError as err:

View File

@@ -114,7 +114,7 @@ class AirOSConfigFlow(ConfigFlow, domain=DOMAIN):
AirOSDeviceConnectionError,
):
self.errors["base"] = "cannot_connect"
except (AirOSConnectionAuthenticationError, AirOSDataMissingError):
except AirOSConnectionAuthenticationError, AirOSDataMissingError:
self.errors["base"] = "invalid_auth"
except AirOSKeyDataMissingError:
self.errors["base"] = "key_data_missing"

View File

@@ -130,7 +130,7 @@ class AirVisualFlowHandler(ConfigFlow, domain=DOMAIN):
try:
await coro
except (InvalidKeyError, KeyExpiredError, UnauthorizedError):
except InvalidKeyError, KeyExpiredError, UnauthorizedError:
errors[CONF_API_KEY] = "invalid_api_key"
except NotFoundError:
errors[CONF_CITY] = "location_not_found"

View File

@@ -100,7 +100,7 @@ class AirZoneCloudConfigFlow(ConfigFlow, domain=DOMAIN):
try:
await self.airzone.login()
except (AirzoneCloudError, LoginError):
except AirzoneCloudError, LoginError:
errors["base"] = "cannot_connect"
else:
return await self.async_step_inst_pick()

View File

@@ -18,12 +18,15 @@ from homeassistant.const import (
Platform,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.typing import ConfigType
from .const import (
CONF_DEVICE_BAUD,
CONF_DEVICE_PATH,
DOMAIN,
PROTOCOL_SERIAL,
PROTOCOL_SOCKET,
SIGNAL_PANEL_MESSAGE,
@@ -32,9 +35,11 @@ from .const import (
SIGNAL_ZONE_FAULT,
SIGNAL_ZONE_RESTORE,
)
from .services import async_setup_services
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
PLATFORMS = [
Platform.ALARM_CONTROL_PANEL,
Platform.BINARY_SENSOR,
@@ -54,6 +59,12 @@ class AlarmDecoderData:
restart: bool
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the component."""
async_setup_services(hass)
return True
async def async_setup_entry(
hass: HomeAssistant, entry: AlarmDecoderConfigEntry
) -> bool:

View File

@@ -2,17 +2,13 @@
from __future__ import annotations
import voluptuous as vol
from homeassistant.components.alarm_control_panel import (
AlarmControlPanelEntity,
AlarmControlPanelEntityFeature,
AlarmControlPanelState,
CodeFormat,
)
from homeassistant.const import ATTR_CODE
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
@@ -27,11 +23,6 @@ from .const import (
)
from .entity import AlarmDecoderEntity
SERVICE_ALARM_TOGGLE_CHIME = "alarm_toggle_chime"
SERVICE_ALARM_KEYPRESS = "alarm_keypress"
ATTR_KEYPRESS = "keypress"
async def async_setup_entry(
hass: HomeAssistant,
@@ -50,23 +41,6 @@ async def async_setup_entry(
)
async_add_entities([entity])
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
SERVICE_ALARM_TOGGLE_CHIME,
{
vol.Required(ATTR_CODE): cv.string,
},
"alarm_toggle_chime",
)
platform.async_register_entity_service(
SERVICE_ALARM_KEYPRESS,
{
vol.Required(ATTR_KEYPRESS): cv.string,
},
"alarm_keypress",
)
class AlarmDecoderAlarmPanel(AlarmDecoderEntity, AlarmControlPanelEntity):
"""Representation of an AlarmDecoder-based alarm panel."""

View File

@@ -0,0 +1,46 @@
"""Support for AlarmDecoder-based alarm control panels (Honeywell/DSC)."""
from __future__ import annotations
import voluptuous as vol
from homeassistant.components.alarm_control_panel import (
DOMAIN as ALARM_CONTROL_PANEL_DOMAIN,
)
from homeassistant.const import ATTR_CODE
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, service
from .const import DOMAIN
SERVICE_ALARM_TOGGLE_CHIME = "alarm_toggle_chime"
SERVICE_ALARM_KEYPRESS = "alarm_keypress"
ATTR_KEYPRESS = "keypress"
@callback
def async_setup_services(hass: HomeAssistant) -> None:
"""Home Assistant services."""
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_ALARM_TOGGLE_CHIME,
entity_domain=ALARM_CONTROL_PANEL_DOMAIN,
schema={
vol.Required(ATTR_CODE): cv.string,
},
func="alarm_toggle_chime",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_ALARM_KEYPRESS,
entity_domain=ALARM_CONTROL_PANEL_DOMAIN,
schema={
vol.Required(ATTR_KEYPRESS): cv.string,
},
func="alarm_keypress",
)

View File

@@ -123,7 +123,7 @@ class Auth:
allow_redirects=True,
)
except (TimeoutError, aiohttp.ClientError):
except TimeoutError, aiohttp.ClientError:
_LOGGER.error("Timeout calling LWA to get auth token")
return None

View File

@@ -358,7 +358,7 @@ async def async_send_changereport_message(
"""
try:
token = await config.async_get_access_token()
except (RequireRelink, NoTokenAvailable):
except RequireRelink, NoTokenAvailable:
await config.set_authorized(False)
_LOGGER.error(
"Error when sending ChangeReport to Alexa, could not get access token"
@@ -392,7 +392,7 @@ async def async_send_changereport_message(
allow_redirects=True,
)
except (TimeoutError, aiohttp.ClientError):
except TimeoutError, aiohttp.ClientError:
_LOGGER.error("Timeout sending report to Alexa for %s", alexa_entity.entity_id)
return
@@ -549,7 +549,7 @@ async def async_send_doorbell_event_message(
allow_redirects=True,
)
except (TimeoutError, aiohttp.ClientError):
except TimeoutError, aiohttp.ClientError:
_LOGGER.error("Timeout sending report to Alexa for %s", alexa_entity.entity_id)
return

View File

@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["aioamazondevices"],
"quality_scale": "platinum",
"requirements": ["aioamazondevices==11.0.2"]
"requirements": ["aioamazondevices==11.1.1"]
}

View File

@@ -28,6 +28,7 @@ from homeassistant.helpers.typing import StateType
from .const import CATEGORY_NOTIFICATIONS, CATEGORY_SENSORS
from .coordinator import AmazonConfigEntry
from .entity import AmazonEntity
from .utils import async_remove_unsupported_notification_sensors
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
@@ -105,6 +106,9 @@ async def async_setup_entry(
coordinator = entry.runtime_data
# Remove notification sensors from unsupported devices
await async_remove_unsupported_notification_sensors(hass, coordinator)
known_devices: set[str] = set()
def _check_device() -> None:
@@ -122,6 +126,7 @@ async def async_setup_entry(
AmazonSensorEntity(coordinator, serial_num, notification_desc)
for notification_desc in NOTIFICATIONS
for serial_num in new_devices
if coordinator.data[serial_num].notifications_supported
]
async_add_entities(sensors_list + notifications_list)

View File

@@ -90,6 +90,9 @@
"cannot_retrieve_data_with_error": {
"message": "Error retrieving data: {error}"
},
"config_entry_not_found": {
"message": "Config entry not found: {device_id}"
},
"device_serial_number_missing": {
"message": "Device serial number missing: {device_id}"
},

View File

@@ -59,13 +59,15 @@ async def async_setup_entry(
coordinator = entry.runtime_data
# Replace unique id for "DND" switch and remove from Speaker Group
await async_update_unique_id(
hass, coordinator, SWITCH_DOMAIN, "do_not_disturb", "dnd"
)
# DND keys
old_key = "do_not_disturb"
new_key = "dnd"
# Remove DND switch from virtual groups
await async_remove_dnd_from_virtual_group(hass, coordinator)
# Remove old DND switch from virtual groups
await async_remove_dnd_from_virtual_group(hass, coordinator, old_key)
# Replace unique id for DND switch
await async_update_unique_id(hass, coordinator, SWITCH_DOMAIN, old_key, new_key)
known_devices: set[str] = set()

View File

@@ -5,8 +5,14 @@ from functools import wraps
from typing import Any, Concatenate
from aioamazondevices.const.devices import SPEAKER_GROUP_FAMILY
from aioamazondevices.const.schedules import (
NOTIFICATION_ALARM,
NOTIFICATION_REMINDER,
NOTIFICATION_TIMER,
)
from aioamazondevices.exceptions import CannotConnect, CannotRetrieveData
from homeassistant.components.sensor import DOMAIN as SENSOR_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
@@ -48,7 +54,7 @@ def alexa_api_call[_T: AmazonEntity, **_P](
async def async_update_unique_id(
hass: HomeAssistant,
coordinator: AmazonDevicesCoordinator,
domain: str,
platform: str,
old_key: str,
new_key: str,
) -> None:
@@ -57,7 +63,9 @@ async def async_update_unique_id(
for serial_num in coordinator.data:
unique_id = f"{serial_num}-{old_key}"
if entity_id := entity_registry.async_get_entity_id(domain, DOMAIN, unique_id):
if entity_id := entity_registry.async_get_entity_id(
DOMAIN, platform, unique_id
):
_LOGGER.debug("Updating unique_id for %s", entity_id)
new_unique_id = unique_id.replace(old_key, new_key)
@@ -68,12 +76,13 @@ async def async_update_unique_id(
async def async_remove_dnd_from_virtual_group(
hass: HomeAssistant,
coordinator: AmazonDevicesCoordinator,
key: str,
) -> None:
"""Remove entity DND from virtual group."""
entity_registry = er.async_get(hass)
for serial_num in coordinator.data:
unique_id = f"{serial_num}-do_not_disturb"
unique_id = f"{serial_num}-{key}"
entity_id = entity_registry.async_get_entity_id(
DOMAIN, SWITCH_DOMAIN, unique_id
)
@@ -81,3 +90,27 @@ async def async_remove_dnd_from_virtual_group(
if entity_id and is_group:
entity_registry.async_remove(entity_id)
_LOGGER.debug("Removed DND switch from virtual group %s", entity_id)
async def async_remove_unsupported_notification_sensors(
hass: HomeAssistant,
coordinator: AmazonDevicesCoordinator,
) -> None:
"""Remove notification sensors from unsupported devices."""
entity_registry = er.async_get(hass)
for serial_num in coordinator.data:
for notification_key in (
NOTIFICATION_ALARM,
NOTIFICATION_REMINDER,
NOTIFICATION_TIMER,
):
unique_id = f"{serial_num}-{notification_key}"
entity_id = entity_registry.async_get_entity_id(
DOMAIN, SENSOR_DOMAIN, unique_id=unique_id
)
is_unsupported = not coordinator.data[serial_num].notifications_supported
if entity_id and is_unsupported:
entity_registry.async_remove(entity_id)
_LOGGER.debug("Removed unsupported notification sensor %s", entity_id)

View File

@@ -77,9 +77,11 @@ class AmbientNetworkConfigFlow(ConfigFlow, domain=DOMAIN):
# Filter out indoor stations
self._stations = dict(
filter(
lambda item: not item[1]
.get(API_STATION_INFO, {})
.get(API_STATION_INDOOR, False),
lambda item: (
not item[1]
.get(API_STATION_INFO, {})
.get(API_STATION_INDOOR, False)
),
self._stations.items(),
)
)
@@ -113,7 +115,7 @@ class AmbientNetworkConfigFlow(ConfigFlow, domain=DOMAIN):
)
return self.async_show_form(
step_id=CONF_USER, data_schema=schema, errors=errors if errors else {}
step_id=CONF_USER, data_schema=schema, errors=errors or {}
)
async def async_step_station(

View File

@@ -31,7 +31,7 @@ class AmbientStationFlowHandler(ConfigFlow, domain=DOMAIN):
return self.async_show_form(
step_id="user",
data_schema=self.data_schema,
errors=errors if errors else {},
errors=errors or {},
)
async def async_step_user(

View File

@@ -26,10 +26,9 @@ from homeassistant.const import (
UnitOfVolumetricFlux,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import AmbientStation, AmbientStationConfigEntry
from . import AmbientStationConfigEntry
from .const import ATTR_LAST_DATA, TYPE_SOLARRADIATION, TYPE_SOLARRADIATION_LX
from .entity import AmbientWeatherEntity
@@ -683,22 +682,6 @@ async def async_setup_entry(
class AmbientWeatherSensor(AmbientWeatherEntity, SensorEntity):
"""Define an Ambient sensor."""
def __init__(
self,
ambient: AmbientStation,
mac_address: str,
station_name: str,
description: EntityDescription,
) -> None:
"""Initialize the sensor."""
super().__init__(ambient, mac_address, station_name, description)
if description.key == TYPE_SOLARRADIATION_LX:
# Since TYPE_SOLARRADIATION and TYPE_SOLARRADIATION_LX will have the same
# name in the UI, we influence the entity ID of TYPE_SOLARRADIATION_LX here
# to differentiate them:
self.entity_id = f"sensor.{station_name}_solar_rad_lx"
@callback
def update_from_latest_data(self) -> None:
"""Fetch new state data for the sensor."""

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
import asyncio
from collections.abc import AsyncIterator, Callable
from collections.abc import AsyncGenerator, Callable
from contextlib import asynccontextmanager, suppress
from dataclasses import dataclass
from datetime import datetime, timedelta
@@ -202,7 +202,7 @@ class AmcrestChecker(ApiWrapper):
@asynccontextmanager
async def async_stream_command(
self, *args: Any, **kwargs: Any
) -> AsyncIterator[httpx.Response]:
) -> AsyncGenerator[httpx.Response]:
"""amcrest.ApiWrapper.command wrapper to catch errors."""
async with (
self._async_command_wrapper(),
@@ -211,7 +211,7 @@ class AmcrestChecker(ApiWrapper):
yield ret
@asynccontextmanager
async def _async_command_wrapper(self) -> AsyncIterator[None]:
async def _async_command_wrapper(self) -> AsyncGenerator[None]:
try:
yield
except LoginError as ex:

View File

@@ -10,6 +10,7 @@
"preview_features": {
"snapshots": {
"feedback_url": "https://forms.gle/GqvRmgmghSDco8M46",
"learn_more_url": "https://www.home-assistant.io/blog/2026/02/02/about-device-database/",
"report_issue_url": "https://github.com/OHF-Device-Database/device-database/issues/new"
}
},

View File

@@ -1,7 +1,7 @@
{
"preview_features": {
"snapshots": {
"description": "This free, open source device database of the Open Home Foundation helps users find useful information about smart home devices used in real installations.\n\nYou can help build it by anonymously sharing data about your devices. Only device-specific details (like model or manufacturer) are shared — never personally identifying information (like the names you assign).\n\nLearn more about the device database and how we process your data in our [Data Use Statement](https://www.openhomefoundation.org/device-database-data-use-statement), which you accept by opting in.",
"description": "We're creating the [Open Home Foundation Device Database](https://www.home-assistant.io/blog/2026/02/02/about-device-database/): a free, open source community-powered resource to help users find practical information about how smart home devices perform in real installations.\n\nYou can help us build it by opting in to share anonymized data about your devices. This data will only ever include device-specific details (like model or manufacturer) never personally identifying information (like the names you assign).\n\nFind out how we process your data (should you choose to contribute) in our [Data Use Statement](https://www.openhomefoundation.org/device-database-data-use-statement).",
"disable_confirmation": "Your data will no longer be shared with the Open Home Foundation's device database.",
"enable_confirmation": "This feature is still in development and may change. The device database is being refined based on user feedback and is not yet complete.",
"name": "Device database"

View File

@@ -93,7 +93,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
self._abort_if_unique_id_configured(updates={CONF_HOST: self.host})
try:
return await self._async_start_pair()
except (CannotConnect, ConnectionClosed):
except CannotConnect, ConnectionClosed:
errors["base"] = "cannot_connect"
else:
user_input = {}
@@ -135,7 +135,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
# Attempt to pair again.
try:
return await self._async_start_pair()
except (CannotConnect, ConnectionClosed):
except CannotConnect, ConnectionClosed:
# Device doesn't respond to the specified host. Abort.
# If we are in the user flow we could go back to the user step to allow
# them to enter a new IP address but we cannot do that for the zeroconf
@@ -203,7 +203,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input is not None:
try:
return await self._async_start_pair()
except (CannotConnect, ConnectionClosed):
except CannotConnect, ConnectionClosed:
# Device became network unreachable after discovery.
# Abort and let discovery find it again later.
return self.async_abort(reason="cannot_connect")
@@ -229,7 +229,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input is not None:
try:
return await self._async_start_pair()
except (CannotConnect, ConnectionClosed):
except CannotConnect, ConnectionClosed:
# Device is network unreachable. Abort.
errors["base"] = "cannot_connect"
return self.async_show_form(
@@ -264,7 +264,7 @@ class AndroidTVRemoteOptionsFlowHandler(OptionsFlowWithReload):
@callback
def _save_config(self, data: dict[str, Any]) -> ConfigFlowResult:
"""Save the updated options."""
new_data = {k: v for k, v in data.items() if k not in [CONF_APPS]}
new_data = {k: v for k, v in data.items() if k != CONF_APPS}
if self._apps:
new_data[CONF_APPS] = self._apps

View File

@@ -73,7 +73,7 @@ async def validate_account(auth: MSOB2CAuth, account_number: str) -> str | MSOB2
_aw = AnglianWater(authenticator=auth)
try:
await _aw.validate_smart_meter(account_number)
except (InvalidAccountIdError, SmartMeterUnavailableError):
except InvalidAccountIdError, SmartMeterUnavailableError:
return "smart_meter_unavailable"
return auth

View File

@@ -14,10 +14,18 @@ from homeassistant.helpers import (
config_validation as cv,
device_registry as dr,
entity_registry as er,
issue_registry as ir,
)
from homeassistant.helpers.typing import ConfigType
from .const import DEFAULT_CONVERSATION_NAME, DOMAIN, LOGGER
from .const import (
CONF_CHAT_MODEL,
DATA_REPAIR_DEFER_RELOAD,
DEFAULT_CONVERSATION_NAME,
DEPRECATED_MODELS,
DOMAIN,
LOGGER,
)
PLATFORMS = (Platform.AI_TASK, Platform.CONVERSATION)
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
@@ -27,6 +35,7 @@ type AnthropicConfigEntry = ConfigEntry[anthropic.AsyncClient]
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Anthropic."""
hass.data.setdefault(DOMAIN, {}).setdefault(DATA_REPAIR_DEFER_RELOAD, set())
await async_migrate_integration(hass)
return True
@@ -50,6 +59,22 @@ async def async_setup_entry(hass: HomeAssistant, entry: AnthropicConfigEntry) ->
entry.async_on_unload(entry.add_update_listener(async_update_options))
for subentry in entry.subentries.values():
if (model := subentry.data.get(CONF_CHAT_MODEL)) and model.startswith(
tuple(DEPRECATED_MODELS)
):
ir.async_create_issue(
hass,
DOMAIN,
"model_deprecated",
is_fixable=True,
is_persistent=False,
learn_more_url="https://platform.claude.com/docs/en/about-claude/model-deprecations",
severity=ir.IssueSeverity.WARNING,
translation_key="model_deprecated",
)
break
return True
@@ -62,6 +87,11 @@ async def async_update_options(
hass: HomeAssistant, entry: AnthropicConfigEntry
) -> None:
"""Update options."""
defer_reload_entries: set[str] = hass.data.setdefault(DOMAIN, {}).setdefault(
DATA_REPAIR_DEFER_RELOAD, set()
)
if entry.entry_id in defer_reload_entries:
return
await hass.config_entries.async_reload(entry.entry_id)

View File

@@ -36,6 +36,7 @@ from homeassistant.helpers.selector import (
SelectOptionDict,
SelectSelector,
SelectSelectorConfig,
SelectSelectorMode,
TemplateSelector,
)
from homeassistant.helpers.typing import VolDictType
@@ -47,6 +48,7 @@ from .const import (
CONF_RECOMMENDED,
CONF_TEMPERATURE,
CONF_THINKING_BUDGET,
CONF_THINKING_EFFORT,
CONF_WEB_SEARCH,
CONF_WEB_SEARCH_CITY,
CONF_WEB_SEARCH_COUNTRY,
@@ -58,6 +60,7 @@ from .const import (
DEFAULT_AI_TASK_NAME,
DEFAULT_CONVERSATION_NAME,
DOMAIN,
NON_ADAPTIVE_THINKING_MODELS,
NON_THINKING_MODELS,
WEB_SEARCH_UNSUPPORTED_MODELS,
)
@@ -92,6 +95,41 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
await client.models.list(timeout=10.0)
async def get_model_list(client: anthropic.AsyncAnthropic) -> list[SelectOptionDict]:
"""Get list of available models."""
try:
models = (await client.models.list()).data
except anthropic.AnthropicError:
models = []
_LOGGER.debug("Available models: %s", models)
model_options: list[SelectOptionDict] = []
short_form = re.compile(r"[^\d]-\d$")
for model_info in models:
# Resolve alias from versioned model name:
model_alias = (
model_info.id[:-9]
if model_info.id
not in (
"claude-3-haiku-20240307",
"claude-3-5-haiku-20241022",
"claude-3-opus-20240229",
)
and model_info.id[-2:-1] != "-"
else model_info.id
)
if short_form.search(model_alias):
model_alias += "-0"
if model_alias.endswith(("haiku", "opus", "sonnet")):
model_alias += "-latest"
model_options.append(
SelectOptionDict(
label=model_info.display_name,
value=model_alias,
)
)
return model_options
class AnthropicConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Anthropic."""
@@ -320,7 +358,9 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
model = self.options[CONF_CHAT_MODEL]
if not model.startswith(tuple(NON_THINKING_MODELS)):
if not model.startswith(tuple(NON_THINKING_MODELS)) and model.startswith(
tuple(NON_ADAPTIVE_THINKING_MODELS)
):
step_schema[
vol.Optional(
CONF_THINKING_BUDGET, default=DEFAULT[CONF_THINKING_BUDGET]
@@ -337,6 +377,22 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
else:
self.options.pop(CONF_THINKING_BUDGET, None)
if not model.startswith(tuple(NON_ADAPTIVE_THINKING_MODELS)):
step_schema[
vol.Optional(
CONF_THINKING_EFFORT,
default=DEFAULT[CONF_THINKING_EFFORT],
)
] = SelectSelector(
SelectSelectorConfig(
options=["none", "low", "medium", "high", "max"],
translation_key=CONF_THINKING_EFFORT,
mode=SelectSelectorMode.DROPDOWN,
)
)
else:
self.options.pop(CONF_THINKING_EFFORT, None)
if not model.startswith(tuple(WEB_SEARCH_UNSUPPORTED_MODELS)):
step_schema.update(
{
@@ -401,38 +457,13 @@ class ConversationSubentryFlowHandler(ConfigSubentryFlow):
async def _get_model_list(self) -> list[SelectOptionDict]:
"""Get list of available models."""
try:
client = await self.hass.async_add_executor_job(
partial(
anthropic.AsyncAnthropic,
api_key=self._get_entry().data[CONF_API_KEY],
)
client = await self.hass.async_add_executor_job(
partial(
anthropic.AsyncAnthropic,
api_key=self._get_entry().data[CONF_API_KEY],
)
models = (await client.models.list()).data
except anthropic.AnthropicError:
models = []
_LOGGER.debug("Available models: %s", models)
model_options: list[SelectOptionDict] = []
short_form = re.compile(r"[^\d]-\d$")
for model_info in models:
# Resolve alias from versioned model name:
model_alias = (
model_info.id[:-9]
if model_info.id
not in ("claude-3-haiku-20240307", "claude-3-opus-20240229")
else model_info.id
)
if short_form.search(model_alias):
model_alias += "-0"
if model_alias.endswith(("haiku", "opus", "sonnet")):
model_alias += "-latest"
model_options.append(
SelectOptionDict(
label=model_info.display_name,
value=model_alias,
)
)
return model_options
)
return await get_model_list(client)
async def _get_location_data(self) -> dict[str, str]:
"""Get approximate location data of the user."""

View File

@@ -14,6 +14,7 @@ CONF_CHAT_MODEL = "chat_model"
CONF_MAX_TOKENS = "max_tokens"
CONF_TEMPERATURE = "temperature"
CONF_THINKING_BUDGET = "thinking_budget"
CONF_THINKING_EFFORT = "thinking_effort"
CONF_WEB_SEARCH = "web_search"
CONF_WEB_SEARCH_USER_LOCATION = "user_location"
CONF_WEB_SEARCH_MAX_USES = "web_search_max_uses"
@@ -22,11 +23,14 @@ CONF_WEB_SEARCH_REGION = "region"
CONF_WEB_SEARCH_COUNTRY = "country"
CONF_WEB_SEARCH_TIMEZONE = "timezone"
DATA_REPAIR_DEFER_RELOAD = "repair_defer_reload"
DEFAULT = {
CONF_CHAT_MODEL: "claude-3-5-haiku-latest",
CONF_CHAT_MODEL: "claude-haiku-4-5",
CONF_MAX_TOKENS: 3000,
CONF_TEMPERATURE: 1.0,
CONF_THINKING_BUDGET: 0,
CONF_THINKING_EFFORT: "low",
CONF_WEB_SEARCH: False,
CONF_WEB_SEARCH_USER_LOCATION: False,
CONF_WEB_SEARCH_MAX_USES: 5,
@@ -40,9 +44,28 @@ NON_THINKING_MODELS = [
"claude-3-haiku",
]
NON_ADAPTIVE_THINKING_MODELS = [
"claude-opus-4-5",
"claude-sonnet-4-5",
"claude-haiku-4-5",
"claude-opus-4-1",
"claude-opus-4-0",
"claude-opus-4-20250514",
"claude-sonnet-4-0",
"claude-sonnet-4-20250514",
"claude-3",
]
WEB_SEARCH_UNSUPPORTED_MODELS = [
"claude-3-haiku",
"claude-3-opus",
"claude-3-5-sonnet-20240620",
"claude-3-5-sonnet-20241022",
]
DEPRECATED_MODELS = [
"claude-3-5-haiku",
"claude-3-7-sonnet",
"claude-3-5-sonnet",
"claude-3-opus",
]

View File

@@ -23,6 +23,7 @@ from anthropic.types import (
MessageDeltaUsage,
MessageParam,
MessageStreamEvent,
OutputConfigParam,
RawContentBlockDeltaEvent,
RawContentBlockStartEvent,
RawContentBlockStopEvent,
@@ -41,6 +42,7 @@ from anthropic.types import (
TextDelta,
ThinkingBlock,
ThinkingBlockParam,
ThinkingConfigAdaptiveParam,
ThinkingConfigDisabledParam,
ThinkingConfigEnabledParam,
ThinkingDelta,
@@ -78,6 +80,7 @@ from .const import (
CONF_MAX_TOKENS,
CONF_TEMPERATURE,
CONF_THINKING_BUDGET,
CONF_THINKING_EFFORT,
CONF_WEB_SEARCH,
CONF_WEB_SEARCH_CITY,
CONF_WEB_SEARCH_COUNTRY,
@@ -89,6 +92,7 @@ from .const import (
DOMAIN,
LOGGER,
MIN_THINKING_BUDGET,
NON_ADAPTIVE_THINKING_MODELS,
NON_THINKING_MODELS,
)
@@ -600,6 +604,16 @@ class AnthropicBaseLLMEntity(Entity):
system = chat_log.content[0]
if not isinstance(system, conversation.SystemContent):
raise TypeError("First message must be a system message")
# System prompt with caching enabled
system_prompt: list[TextBlockParam] = [
TextBlockParam(
type="text",
text=system.content,
cache_control={"type": "ephemeral"},
)
]
messages = _convert_content(chat_log.content[1:])
model = options.get(CONF_CHAT_MODEL, DEFAULT[CONF_CHAT_MODEL])
@@ -608,25 +622,38 @@ class AnthropicBaseLLMEntity(Entity):
model=model,
messages=messages,
max_tokens=options.get(CONF_MAX_TOKENS, DEFAULT[CONF_MAX_TOKENS]),
system=system.content,
system=system_prompt,
stream=True,
)
thinking_budget = options.get(
CONF_THINKING_BUDGET, DEFAULT[CONF_THINKING_BUDGET]
)
if (
not model.startswith(tuple(NON_THINKING_MODELS))
and thinking_budget >= MIN_THINKING_BUDGET
):
model_args["thinking"] = ThinkingConfigEnabledParam(
type="enabled", budget_tokens=thinking_budget
if not model.startswith(tuple(NON_ADAPTIVE_THINKING_MODELS)):
thinking_effort = options.get(
CONF_THINKING_EFFORT, DEFAULT[CONF_THINKING_EFFORT]
)
if thinking_effort != "none":
model_args["thinking"] = ThinkingConfigAdaptiveParam(type="adaptive")
model_args["output_config"] = OutputConfigParam(effort=thinking_effort)
else:
model_args["thinking"] = ThinkingConfigDisabledParam(type="disabled")
model_args["temperature"] = options.get(
CONF_TEMPERATURE, DEFAULT[CONF_TEMPERATURE]
)
else:
model_args["thinking"] = ThinkingConfigDisabledParam(type="disabled")
model_args["temperature"] = options.get(
CONF_TEMPERATURE, DEFAULT[CONF_TEMPERATURE]
thinking_budget = options.get(
CONF_THINKING_BUDGET, DEFAULT[CONF_THINKING_BUDGET]
)
if (
not model.startswith(tuple(NON_THINKING_MODELS))
and thinking_budget >= MIN_THINKING_BUDGET
):
model_args["thinking"] = ThinkingConfigEnabledParam(
type="enabled", budget_tokens=thinking_budget
)
else:
model_args["thinking"] = ThinkingConfigDisabledParam(type="disabled")
model_args["temperature"] = options.get(
CONF_TEMPERATURE, DEFAULT[CONF_TEMPERATURE]
)
tools: list[ToolUnionParam] = []
if chat_log.llm_api:
@@ -695,10 +722,6 @@ class AnthropicBaseLLMEntity(Entity):
type="auto",
)
if isinstance(model_args["system"], str):
model_args["system"] = [
TextBlockParam(type="text", text=model_args["system"])
]
model_args["system"].append( # type: ignore[union-attr]
TextBlockParam(
type="text",

View File

@@ -8,5 +8,5 @@
"documentation": "https://www.home-assistant.io/integrations/anthropic",
"integration_type": "service",
"iot_class": "cloud_polling",
"requirements": ["anthropic==0.75.0"]
"requirements": ["anthropic==0.78.0"]
}

View File

@@ -0,0 +1,275 @@
"""Issue repair flow for Anthropic."""
from __future__ import annotations
from collections.abc import Iterator
from typing import cast
import voluptuous as vol
from homeassistant import data_entry_flow
from homeassistant.components.repairs import RepairsFlow
from homeassistant.config_entries import ConfigEntry, ConfigEntryState, ConfigSubentry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.selector import SelectSelector, SelectSelectorConfig
from .config_flow import get_model_list
from .const import (
CONF_CHAT_MODEL,
DATA_REPAIR_DEFER_RELOAD,
DEFAULT,
DEPRECATED_MODELS,
DOMAIN,
)
class ModelDeprecatedRepairFlow(RepairsFlow):
"""Handler for an issue fixing flow."""
_subentry_iter: Iterator[tuple[str, str]] | None
_current_entry_id: str | None
_current_subentry_id: str | None
_reload_pending: set[str]
_pending_updates: dict[str, dict[str, str]]
def __init__(self) -> None:
"""Initialize the flow."""
super().__init__()
self._subentry_iter = None
self._current_entry_id = None
self._current_subentry_id = None
self._reload_pending = set()
self._pending_updates = {}
async def async_step_init(
self, user_input: dict[str, str] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the first step of a fix flow."""
previous_entry_id: str | None = None
if user_input is not None:
previous_entry_id = self._async_update_current_subentry(user_input)
self._clear_current_target()
target = await self._async_next_target()
next_entry_id = target[0].entry_id if target else None
if previous_entry_id and previous_entry_id != next_entry_id:
await self._async_apply_pending_updates(previous_entry_id)
if target is None:
await self._async_apply_all_pending_updates()
return self.async_create_entry(data={})
entry, subentry, model = target
client = entry.runtime_data
model_list = [
model_option
for model_option in await get_model_list(client)
if not model_option["value"].startswith(tuple(DEPRECATED_MODELS))
]
if "opus" in model:
suggested_model = "claude-opus-4-5"
elif "haiku" in model:
suggested_model = "claude-haiku-4-5"
elif "sonnet" in model:
suggested_model = "claude-sonnet-4-5"
else:
suggested_model = cast(str, DEFAULT[CONF_CHAT_MODEL])
schema = vol.Schema(
{
vol.Required(
CONF_CHAT_MODEL,
default=suggested_model,
): SelectSelector(
SelectSelectorConfig(options=model_list, custom_value=True)
),
}
)
return self.async_show_form(
step_id="init",
data_schema=schema,
description_placeholders={
"entry_name": entry.title,
"model": model,
"subentry_name": subentry.title,
"subentry_type": self._format_subentry_type(subentry.subentry_type),
},
)
def _iter_deprecated_subentries(self) -> Iterator[tuple[str, str]]:
"""Yield entry/subentry pairs that use deprecated models."""
for entry in self.hass.config_entries.async_entries(DOMAIN):
if entry.state is not ConfigEntryState.LOADED:
continue
for subentry in entry.subentries.values():
model = subentry.data.get(CONF_CHAT_MODEL)
if model and model.startswith(tuple(DEPRECATED_MODELS)):
yield entry.entry_id, subentry.subentry_id
async def _async_next_target(
self,
) -> tuple[ConfigEntry, ConfigSubentry, str] | None:
"""Return the next deprecated subentry target."""
if self._subentry_iter is None:
self._subentry_iter = self._iter_deprecated_subentries()
while True:
try:
entry_id, subentry_id = next(self._subentry_iter)
except StopIteration:
return None
entry = self.hass.config_entries.async_get_entry(entry_id)
if entry is None:
continue
subentry = entry.subentries.get(subentry_id)
if subentry is None:
continue
model = self._pending_model(entry_id, subentry_id)
if model is None:
model = subentry.data.get(CONF_CHAT_MODEL)
if not model or not model.startswith(tuple(DEPRECATED_MODELS)):
continue
self._current_entry_id = entry_id
self._current_subentry_id = subentry_id
return entry, subentry, model
def _async_update_current_subentry(self, user_input: dict[str, str]) -> str | None:
"""Update the currently selected subentry."""
if not self._current_entry_id or not self._current_subentry_id:
return None
entry = self.hass.config_entries.async_get_entry(self._current_entry_id)
if entry is None:
return None
subentry = entry.subentries.get(self._current_subentry_id)
if subentry is None:
return None
updated_data = {
**subentry.data,
CONF_CHAT_MODEL: user_input[CONF_CHAT_MODEL],
}
if updated_data == subentry.data:
return entry.entry_id
self._queue_pending_update(
entry.entry_id,
subentry.subentry_id,
updated_data[CONF_CHAT_MODEL],
)
return entry.entry_id
def _clear_current_target(self) -> None:
"""Clear current target tracking."""
self._current_entry_id = None
self._current_subentry_id = None
def _format_subentry_type(self, subentry_type: str) -> str:
"""Return a user-friendly subentry type label."""
if subentry_type == "conversation":
return "Conversation agent"
if subentry_type in ("ai_task", "ai_task_data"):
return "AI task"
return subentry_type
def _queue_pending_update(
self, entry_id: str, subentry_id: str, model: str
) -> None:
"""Store a pending model update for a subentry."""
self._pending_updates.setdefault(entry_id, {})[subentry_id] = model
def _pending_model(self, entry_id: str, subentry_id: str) -> str | None:
"""Return a pending model update if one exists."""
return self._pending_updates.get(entry_id, {}).get(subentry_id)
def _mark_entry_for_reload(self, entry_id: str) -> None:
"""Prevent reload until repairs are complete for the entry."""
self._reload_pending.add(entry_id)
defer_reload_entries: set[str] = self.hass.data.setdefault(
DOMAIN, {}
).setdefault(DATA_REPAIR_DEFER_RELOAD, set())
defer_reload_entries.add(entry_id)
async def _async_reload_entry(self, entry_id: str) -> None:
"""Reload an entry once all repairs are completed."""
if entry_id not in self._reload_pending:
return
entry = self.hass.config_entries.async_get_entry(entry_id)
if entry is not None and entry.state is not ConfigEntryState.LOADED:
self._clear_defer_reload(entry_id)
self._reload_pending.discard(entry_id)
return
if entry is not None:
await self.hass.config_entries.async_reload(entry_id)
self._clear_defer_reload(entry_id)
self._reload_pending.discard(entry_id)
def _clear_defer_reload(self, entry_id: str) -> None:
"""Remove entry from the deferred reload set."""
defer_reload_entries: set[str] = self.hass.data.setdefault(
DOMAIN, {}
).setdefault(DATA_REPAIR_DEFER_RELOAD, set())
defer_reload_entries.discard(entry_id)
async def _async_apply_pending_updates(self, entry_id: str) -> None:
"""Apply pending subentry updates for a single entry."""
updates = self._pending_updates.pop(entry_id, None)
if not updates:
return
entry = self.hass.config_entries.async_get_entry(entry_id)
if entry is None or entry.state is not ConfigEntryState.LOADED:
return
changed = False
for subentry_id, model in updates.items():
subentry = entry.subentries.get(subentry_id)
if subentry is None:
continue
updated_data = {
**subentry.data,
CONF_CHAT_MODEL: model,
}
if updated_data == subentry.data:
continue
if not changed:
self._mark_entry_for_reload(entry_id)
changed = True
self.hass.config_entries.async_update_subentry(
entry,
subentry,
data=updated_data,
)
if not changed:
return
await self._async_reload_entry(entry_id)
async def _async_apply_all_pending_updates(self) -> None:
"""Apply all pending updates across entries."""
for entry_id in list(self._pending_updates):
await self._async_apply_pending_updates(entry_id)
async def async_create_fix_flow(
hass: HomeAssistant,
issue_id: str,
data: dict[str, str | int | float | None] | None,
) -> RepairsFlow:
"""Create flow."""
if issue_id == "model_deprecated":
return ModelDeprecatedRepairFlow()
raise HomeAssistantError("Unknown issue ID")

View File

@@ -47,12 +47,14 @@
"model": {
"data": {
"thinking_budget": "[%key:component::anthropic::config_subentries::conversation::step::model::data::thinking_budget%]",
"thinking_effort": "[%key:component::anthropic::config_subentries::conversation::step::model::data::thinking_effort%]",
"user_location": "[%key:component::anthropic::config_subentries::conversation::step::model::data::user_location%]",
"web_search": "[%key:component::anthropic::config_subentries::conversation::step::model::data::web_search%]",
"web_search_max_uses": "[%key:component::anthropic::config_subentries::conversation::step::model::data::web_search_max_uses%]"
},
"data_description": {
"thinking_budget": "[%key:component::anthropic::config_subentries::conversation::step::model::data_description::thinking_budget%]",
"thinking_effort": "[%key:component::anthropic::config_subentries::conversation::step::model::data_description::thinking_effort%]",
"user_location": "[%key:component::anthropic::config_subentries::conversation::step::model::data_description::user_location%]",
"web_search": "[%key:component::anthropic::config_subentries::conversation::step::model::data_description::web_search%]",
"web_search_max_uses": "[%key:component::anthropic::config_subentries::conversation::step::model::data_description::web_search_max_uses%]"
@@ -95,12 +97,14 @@
"model": {
"data": {
"thinking_budget": "Thinking budget",
"thinking_effort": "Thinking effort",
"user_location": "Include home location",
"web_search": "Enable web search",
"web_search_max_uses": "Maximum web searches"
},
"data_description": {
"thinking_budget": "The number of tokens the model can use to think about the response out of the total maximum number of tokens. Set to 1024 or greater to enable extended thinking.",
"thinking_effort": "Control how many tokens Claude uses when responding, trading off between response thoroughness and token efficiency",
"user_location": "Localize search results based on home location",
"web_search": "The web search tool gives Claude direct access to real-time web content, allowing it to answer questions with up-to-date information beyond its knowledge cutoff",
"web_search_max_uses": "Limit the number of searches performed per response"
@@ -109,5 +113,32 @@
}
}
}
},
"issues": {
"model_deprecated": {
"fix_flow": {
"step": {
"init": {
"data": {
"chat_model": "[%key:common::generic::model%]"
},
"description": "You are updating {subentry_name} ({subentry_type}) in {entry_name}. The current model {model} is deprecated. Select a supported model to continue.",
"title": "Update model"
}
}
},
"title": "Model deprecated"
}
},
"selector": {
"thinking_effort": {
"options": {
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
"max": "Max",
"medium": "[%key:common::state::medium%]",
"none": "None"
}
}
}
}

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/aosmith",
"integration_type": "hub",
"iot_class": "cloud_polling",
"requirements": ["py-aosmith==1.0.15"]
"requirements": ["py-aosmith==1.0.16"]
}

View File

@@ -50,7 +50,7 @@ class ConfigFlowHandler(ConfigFlow, domain=DOMAIN):
try:
async with asyncio.timeout(CONNECTION_TIMEOUT):
data = APCUPSdData(await aioapcaccess.request_status(host, port))
except (OSError, asyncio.IncompleteReadError, TimeoutError):
except OSError, asyncio.IncompleteReadError, TimeoutError:
errors = {"base": "cannot_connect"}
return self.async_show_form(
step_id="user", data_schema=_SCHEMA, errors=errors
@@ -77,7 +77,7 @@ class ConfigFlowHandler(ConfigFlow, domain=DOMAIN):
try:
async with asyncio.timeout(CONNECTION_TIMEOUT):
data = APCUPSdData(await aioapcaccess.request_status(host, port))
except (OSError, asyncio.IncompleteReadError, TimeoutError):
except OSError, asyncio.IncompleteReadError, TimeoutError:
errors = {"base": "cannot_connect"}
return self.async_show_form(
step_id="reconfigure", data_schema=_SCHEMA, errors=errors

View File

@@ -540,7 +540,17 @@ class APCUPSdSensor(APCUPSdEntity, SensorEntity):
data = self.coordinator.data[key]
if self.entity_description.device_class == SensorDeviceClass.TIMESTAMP:
self._attr_native_value = dateutil.parser.parse(data)
# The date could be "N/A" for certain fields (e.g., XOFFBATT), indicating there is no value yet.
if data == "N/A":
self._attr_native_value = None
return
try:
self._attr_native_value = dateutil.parser.parse(data)
except dateutil.parser.ParserError, OverflowError:
# If parsing fails we should mark it as unknown, with a log for further debugging.
_LOGGER.warning('Failed to parse date for %s: "%s"', key, data)
self._attr_native_value = None
return
self._attr_native_value, inferred_unit = infer_unit(data)

View File

@@ -2,15 +2,16 @@
from __future__ import annotations
from pyatv.const import KeyboardFocusState
from pyatv.const import FeatureName, FeatureState, KeyboardFocusState
from pyatv.interface import AppleTV, KeyboardListener
from homeassistant.components.binary_sensor import BinarySensorEntity
from homeassistant.const import CONF_NAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import AppleTvConfigEntry
from . import SIGNAL_CONNECTED, AppleTvConfigEntry
from .entity import AppleTVEntity
@@ -21,10 +22,22 @@ async def async_setup_entry(
) -> None:
"""Load Apple TV binary sensor based on a config entry."""
# apple_tv config entries always have a unique id
assert config_entry.unique_id is not None
name: str = config_entry.data[CONF_NAME]
manager = config_entry.runtime_data
async_add_entities([AppleTVKeyboardFocused(name, config_entry.unique_id, manager)])
cb: CALLBACK_TYPE
def setup_entities(atv: AppleTV) -> None:
if atv.features.in_state(FeatureState.Available, FeatureName.TextFocusState):
assert config_entry.unique_id is not None
name: str = config_entry.data[CONF_NAME]
async_add_entities(
[AppleTVKeyboardFocused(name, config_entry.unique_id, manager)]
)
cb()
cb = async_dispatcher_connect(
hass, f"{SIGNAL_CONNECTED}_{config_entry.unique_id}", setup_entities
)
config_entry.async_on_unload(cb)
class AppleTVKeyboardFocused(AppleTVEntity, BinarySensorEntity, KeyboardListener):

View File

@@ -181,9 +181,9 @@ async def async_import_client_credential(
CONF_DOMAIN: domain,
CONF_CLIENT_ID: credential.client_id,
CONF_CLIENT_SECRET: credential.client_secret,
CONF_AUTH_DOMAIN: auth_domain if auth_domain else domain,
CONF_AUTH_DOMAIN: auth_domain or domain,
}
item[CONF_NAME] = credential.name if credential.name else DEFAULT_IMPORT_NAME
item[CONF_NAME] = credential.name or DEFAULT_IMPORT_NAME
await hass.data[DATA_COMPONENT].async_import_item(item)

View File

@@ -168,7 +168,7 @@ class AprilaireCoordinator(BaseDataUpdateCoordinatorProtocol):
name = data.get(Attribute.NAME) if data else None
return name if name else "Aprilaire"
return name or "Aprilaire"
def get_hw_version(self, data: dict[str, Any]) -> str:
"""Get the hardware version."""

View File

@@ -41,7 +41,7 @@ class APsystemsLocalAPIFlow(ConfigFlow, domain=DOMAIN):
)
try:
device_info = await api.get_device_info()
except (TimeoutError, ClientConnectionError):
except TimeoutError, ClientConnectionError:
errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(device_info.deviceId)

View File

@@ -64,7 +64,7 @@ class ApSystemsDataCoordinator(DataUpdateCoordinator[ApSystemsSensorData]):
async def _async_setup(self) -> None:
try:
device_info = await self.api.get_device_info()
except (ConnectionError, TimeoutError):
except ConnectionError, TimeoutError:
raise UpdateFailed from None
self.api.max_power = device_info.maxPower
self.api.min_power = device_info.minPower

View File

@@ -49,7 +49,7 @@ class ApSystemsMaxOutputNumber(ApSystemsEntity, NumberEntity):
"""Set the state with the value fetched from the inverter."""
try:
status = await self._api.get_max_power()
except (TimeoutError, ClientConnectorError):
except TimeoutError, ClientConnectorError:
self._attr_available = False
else:
self._attr_available = True

View File

@@ -43,7 +43,7 @@ class ApSystemsInverterSwitch(ApSystemsEntity, SwitchEntity):
"""Update switch status and availability."""
try:
status = await self._api.get_device_power_status()
except (TimeoutError, ClientConnectionError, InverterReturnedError):
except TimeoutError, ClientConnectionError, InverterReturnedError:
self._attr_available = False
else:
self._attr_available = True

View File

@@ -56,7 +56,7 @@ class AquaCellConfigFlow(ConfigFlow, domain=DOMAIN):
refresh_token = await api.authenticate(
user_input[CONF_EMAIL], user_input[CONF_PASSWORD]
)
except (ApiException, TimeoutError):
except ApiException, TimeoutError:
errors["base"] = "cannot_connect"
except AuthenticationFailed:
errors["base"] = "invalid_auth"

View File

@@ -94,7 +94,7 @@ def _retry[_SharpAquosTVDeviceT: SharpAquosTVDevice, **_P](
try:
func(obj, *args, **kwargs)
break
except (OSError, TypeError, ValueError):
except OSError, TypeError, ValueError:
update_retries -= 1
if update_retries == 0:
obj.set_state(MediaPlayerState.OFF)

View File

@@ -201,5 +201,5 @@ class ArwnSensor(SensorEntity):
ev: dict[str, Any] = {}
ev.update(event)
self._attr_extra_state_attributes = ev
self._attr_native_value = ev.get(self._state_key, None)
self._attr_native_value = ev.get(self._state_key)
self.async_write_ha_state()

View File

@@ -969,7 +969,7 @@ class PipelineRun:
metadata,
self._speech_to_text_stream(audio_stream=stream, stt_vad=stt_vad),
)
except (asyncio.CancelledError, TimeoutError):
except asyncio.CancelledError, TimeoutError:
raise # expected
except hass_nabucasa.auth.Unauthenticated as src_error:
raise SpeechToTextError(

View File

@@ -189,7 +189,7 @@ class AsusWrtFlowHandler(ConfigFlow, domain=DOMAIN):
try:
await api.async_connect()
except (AsusRouterError, OSError):
except AsusRouterError, OSError:
_LOGGER.error(
"Error connecting to the AsusWrt router at %s using protocol %s",
host,

View File

@@ -51,5 +51,5 @@ class AtagConfigFlow(ConfigFlow, domain=DOMAIN):
return self.async_show_form(
step_id="user",
data_schema=vol.Schema(DATA_SCHEMA),
errors=errors if errors else {},
errors=errors or {},
)

View File

@@ -304,7 +304,7 @@ async def _try_async_validate_config_item(
"""Validate config item."""
try:
return await _async_validate_config_item(hass, config, False, True)
except (vol.Invalid, HomeAssistantError):
except vol.Invalid, HomeAssistantError:
return None

View File

@@ -52,7 +52,7 @@ from .const import (
from .errors import AuthenticationRequired, CannotConnect
from .hub import AxisHub, get_axis_api
AXIS_OUI = {"00:40:8c", "ac:cc:8e", "b8:a4:4f"}
AXIS_OUI = {"00:40:8c", "ac:cc:8e", "b8:a4:4f", "e8:27:25"}
DEFAULT_PORT = 443
DEFAULT_PROTOCOL = "https"
PROTOCOL_CHOICES = ["https", "http"]

View File

@@ -44,7 +44,7 @@ async def async_get_config_entry_diagnostics(
account_data["allowed"], TO_REDACT_ACCOUNT_DATA_ALLOWED
)
except (AttributeError, TypeError, ValueError, KeyError):
except AttributeError, TypeError, ValueError, KeyError:
bucket_info = {"name": "unknown", "id": "unknown"}
account_data = {"error": "Failed to retrieve detailed account information"}

View File

@@ -26,6 +26,7 @@ EXCLUDE_FROM_BACKUP = [
"tmp_backups/*.tar",
"OZW_Log.txt",
"tts/*",
".cache/*",
]
EXCLUDE_DATABASE_FROM_BACKUP = [

View File

@@ -1,6 +1,7 @@
"""Support for Baidu speech service."""
import logging
from typing import Any
from aip import AipSpeech
import voluptuous as vol
@@ -9,6 +10,7 @@ from homeassistant.components.tts import (
CONF_LANG,
PLATFORM_SCHEMA as TTS_PLATFORM_SCHEMA,
Provider,
TtsAudioType,
)
from homeassistant.const import CONF_API_KEY
from homeassistant.helpers import config_validation as cv
@@ -85,17 +87,17 @@ class BaiduTTSProvider(Provider):
}
@property
def default_language(self):
def default_language(self) -> str:
"""Return the default language."""
return self._lang
@property
def supported_languages(self):
def supported_languages(self) -> list[str]:
"""Return a list of supported languages."""
return SUPPORTED_LANGUAGES
@property
def default_options(self):
def default_options(self) -> dict[str, Any]:
"""Return a dict including default options."""
return {
CONF_PERSON: self._speech_conf_data[_OPTIONS[CONF_PERSON]],
@@ -105,11 +107,16 @@ class BaiduTTSProvider(Provider):
}
@property
def supported_options(self):
def supported_options(self) -> list[str]:
"""Return a list of supported options."""
return SUPPORTED_OPTIONS
def get_tts_audio(self, message, language, options):
def get_tts_audio(
self,
message: str,
language: str,
options: dict[str, Any],
) -> TtsAudioType:
"""Load TTS from BaiduTTS."""
aip_speech = AipSpeech(

View File

@@ -145,7 +145,7 @@ class BeoConfigFlowHandler(ConfigFlow, domain=DOMAIN):
async with self._client:
try:
await self._client.get_beolink_self(_request_timeout=3)
except (ClientConnectorError, TimeoutError):
except ClientConnectorError, TimeoutError:
return self.async_abort(reason="invalid_address")
self._model = discovery_info.hostname[:-16].replace("-", " ")

View File

@@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/bang_olufsen",
"integration_type": "device",
"iot_class": "local_push",
"requirements": ["mozart-api==5.3.1.108.0"],
"requirements": ["mozart-api==5.3.1.108.2"],
"zeroconf": ["_bangolufsen._tcp.local."]
}

View File

@@ -8,6 +8,7 @@ from datetime import timedelta
import json
import logging
from typing import TYPE_CHECKING, Any, cast
from uuid import UUID
from aiohttp import ClientConnectorError
from mozart_api import __version__ as MOZART_API_VERSION
@@ -735,7 +736,7 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity):
await self._client.set_active_source(source_id=key)
else:
# Video
await self._client.post_remote_trigger(id=key)
await self._client.post_remote_trigger(id=UUID(key))
async def async_select_sound_mode(self, sound_mode: str) -> None:
"""Select a sound mode."""
@@ -894,7 +895,7 @@ class BeoMediaPlayer(BeoEntity, MediaPlayerEntity):
translation_key="play_media_error",
translation_placeholders={
"media_type": media_type,
"error_message": json.loads(error.body)["message"],
"error_message": json.loads(cast(str, error.body))["message"],
},
) from error

View File

@@ -6,16 +6,9 @@ from typing import Any
from blinkpy.auth import Auth
from blinkpy.blinkpy import Blink
import voluptuous as vol
from homeassistant.components import persistent_notification
from homeassistant.const import (
CONF_FILE_PATH,
CONF_FILENAME,
CONF_NAME,
CONF_PIN,
CONF_SCAN_INTERVAL,
)
from homeassistant.const import CONF_SCAN_INTERVAL
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -27,13 +20,6 @@ from .services import async_setup_services
_LOGGER = logging.getLogger(__name__)
SERVICE_SAVE_VIDEO_SCHEMA = vol.Schema(
{vol.Required(CONF_NAME): cv.string, vol.Required(CONF_FILENAME): cv.string}
)
SERVICE_SEND_PIN_SCHEMA = vol.Schema({vol.Optional(CONF_PIN): cv.string})
SERVICE_SAVE_RECENT_CLIPS_SCHEMA = vol.Schema(
{vol.Required(CONF_NAME): cv.string, vol.Required(CONF_FILE_PATH): cv.string}
)
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)

View File

@@ -9,35 +9,23 @@ from typing import Any
from blinkpy.auth import UnauthorizedError
from blinkpy.camera import BlinkCamera as BlinkCameraAPI
from requests.exceptions import ChunkedEncodingError
import voluptuous as vol
from homeassistant.components.camera import Camera
from homeassistant.const import CONF_FILE_PATH, CONF_FILENAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
HomeAssistantError,
ServiceValidationError,
)
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import (
DEFAULT_BRAND,
DOMAIN,
SERVICE_RECORD,
SERVICE_SAVE_RECENT_CLIPS,
SERVICE_SAVE_VIDEO,
SERVICE_TRIGGER,
)
from .const import DEFAULT_BRAND, DOMAIN
from .coordinator import BlinkConfigEntry, BlinkUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
ATTR_VIDEO_CLIP = "video"
ATTR_IMAGE = "image"
PARALLEL_UPDATES = 1
@@ -56,20 +44,6 @@ async def async_setup_entry(
async_add_entities(entities)
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(SERVICE_RECORD, None, "record")
platform.async_register_entity_service(SERVICE_TRIGGER, None, "trigger_camera")
platform.async_register_entity_service(
SERVICE_SAVE_RECENT_CLIPS,
{vol.Required(CONF_FILE_PATH): cv.string},
"save_recent_clips",
)
platform.async_register_entity_service(
SERVICE_SAVE_VIDEO,
{vol.Required(CONF_FILENAME): cv.string},
"save_video",
)
class BlinkCamera(CoordinatorEntity[BlinkUpdateCoordinator], Camera):
"""An implementation of a Blink Camera."""

View File

@@ -20,11 +20,6 @@ TYPE_TEMPERATURE = "temperature"
TYPE_BATTERY = "battery"
TYPE_WIFI_STRENGTH = "wifi_strength"
SERVICE_RECORD = "record"
SERVICE_TRIGGER = "trigger_camera"
SERVICE_SAVE_VIDEO = "save_video"
SERVICE_SAVE_RECENT_CLIPS = "save_recent_clips"
SERVICE_SEND_PIN = "send_pin"
PLATFORMS = [
Platform.ALARM_CONTROL_PANEL,

View File

@@ -4,13 +4,27 @@ from __future__ import annotations
import voluptuous as vol
from homeassistant.const import ATTR_CONFIG_ENTRY_ID, CONF_PIN
from homeassistant.components.camera import DOMAIN as CAMERA_DOMAIN
from homeassistant.const import (
ATTR_CONFIG_ENTRY_ID,
CONF_FILE_PATH,
CONF_FILENAME,
CONF_PIN,
)
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, issue_registry as ir
from homeassistant.helpers import config_validation as cv, issue_registry as ir, service
from .const import DOMAIN, SERVICE_SEND_PIN
from .const import DOMAIN
SERVICE_RECORD = "record"
SERVICE_TRIGGER = "trigger_camera"
SERVICE_SAVE_VIDEO = "save_video"
SERVICE_SAVE_RECENT_CLIPS = "save_recent_clips"
# Deprecated
SERVICE_SEND_PIN = "send_pin"
SERVICE_SEND_PIN_SCHEMA = vol.Schema(
{
vol.Required(ATTR_CONFIG_ENTRY_ID): vol.All(cv.ensure_list, [cv.string]),
@@ -52,3 +66,36 @@ def async_setup_services(hass: HomeAssistant) -> None:
_send_pin,
schema=SERVICE_SEND_PIN_SCHEMA,
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_RECORD,
entity_domain=CAMERA_DOMAIN,
schema=None,
func="record",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_TRIGGER,
entity_domain=CAMERA_DOMAIN,
schema=None,
func="trigger_camera",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_SAVE_RECENT_CLIPS,
entity_domain=CAMERA_DOMAIN,
schema={vol.Required(CONF_FILE_PATH): cv.string},
func="save_recent_clips",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_SAVE_VIDEO,
entity_domain=CAMERA_DOMAIN,
schema={vol.Required(CONF_FILENAME): cv.string},
func="save_video",
)

View File

@@ -80,18 +80,16 @@ SWITCHES = (
key=PLUG_AND_CHARGE,
translation_key=PLUG_AND_CHARGE,
function=set_plug_and_charge,
turn_on_off_fn=lambda evse_id, connector: (
update_on_value_and_activity(PLUG_AND_CHARGE, evse_id, connector)
turn_on_off_fn=lambda evse_id, connector: update_on_value_and_activity(
PLUG_AND_CHARGE, evse_id, connector
),
),
BlueCurrentSwitchEntityDescription(
key=LINKED_CHARGE_CARDS,
translation_key=LINKED_CHARGE_CARDS,
function=set_linked_charge_cards,
turn_on_off_fn=lambda evse_id, connector: (
update_on_value_and_activity(
PUBLIC_CHARGING, evse_id, connector, reverse_is_on=True
)
turn_on_off_fn=lambda evse_id, connector: update_on_value_and_activity(
PUBLIC_CHARGING, evse_id, connector, reverse_is_on=True
),
),
BlueCurrentSwitchEntityDescription(

View File

@@ -148,8 +148,10 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = (
device_class=BinarySensorDeviceClass.LOCK,
# device class lock: On means unlocked, Off means locked
# Possible values: LOCKED, SECURED, SELECTIVE_LOCKED, UNLOCKED
value_fn=lambda v: v.doors_and_windows.door_lock_state
not in {LockState.LOCKED, LockState.SECURED},
value_fn=lambda v: (
v.doors_and_windows.door_lock_state
not in {LockState.LOCKED, LockState.SECURED}
),
attr_fn=lambda v, u: {
"door_lock_state": v.doors_and_windows.door_lock_state.value
},
@@ -189,9 +191,11 @@ SENSOR_TYPES: tuple[BMWBinarySensorEntityDescription, ...] = (
BMWBinarySensorEntityDescription(
key="is_pre_entry_climatization_enabled",
translation_key="is_pre_entry_climatization_enabled",
value_fn=lambda v: v.charging_profile.is_pre_entry_climatization_enabled
if v.charging_profile
else False,
value_fn=lambda v: (
v.charging_profile.is_pre_entry_climatization_enabled
if v.charging_profile
else False
),
is_available=lambda v: v.has_electric_drivetrain,
),
)

View File

@@ -40,7 +40,9 @@ BUTTON_TYPES: tuple[BMWButtonEntityDescription, ...] = (
BMWButtonEntityDescription(
key="light_flash",
translation_key="light_flash",
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_light_flash(),
remote_function=lambda vehicle: (
vehicle.remote_services.trigger_remote_light_flash()
),
),
BMWButtonEntityDescription(
key="sound_horn",
@@ -50,18 +52,24 @@ BUTTON_TYPES: tuple[BMWButtonEntityDescription, ...] = (
BMWButtonEntityDescription(
key="activate_air_conditioning",
translation_key="activate_air_conditioning",
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning(),
remote_function=lambda vehicle: (
vehicle.remote_services.trigger_remote_air_conditioning()
),
),
BMWButtonEntityDescription(
key="deactivate_air_conditioning",
translation_key="deactivate_air_conditioning",
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_air_conditioning_stop(),
remote_function=lambda vehicle: (
vehicle.remote_services.trigger_remote_air_conditioning_stop()
),
is_available=lambda vehicle: vehicle.is_remote_climate_stop_enabled,
),
BMWButtonEntityDescription(
key="find_vehicle",
translation_key="find_vehicle",
remote_function=lambda vehicle: vehicle.remote_services.trigger_remote_vehicle_finder(),
remote_function=lambda vehicle: (
vehicle.remote_services.trigger_remote_vehicle_finder()
),
),
)

View File

@@ -50,7 +50,9 @@ NUMBER_TYPES: list[BMWSwitchEntityDescription] = [
is_available=lambda v: v.is_remote_climate_stop_enabled,
value_fn=lambda v: v.climate.is_climate_on,
remote_service_on=lambda v: v.remote_services.trigger_remote_air_conditioning(),
remote_service_off=lambda v: v.remote_services.trigger_remote_air_conditioning_stop(),
remote_service_off=lambda v: (
v.remote_services.trigger_remote_air_conditioning_stop()
),
),
BMWSwitchEntityDescription(
key="charging",

View File

@@ -16,14 +16,17 @@ from homeassistant.const import (
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers import config_validation as cv, device_registry as dr
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.entity import SLOW_UPDATE_WARNING
from homeassistant.helpers.typing import ConfigType
from .const import BRIDGE_MAKE, DOMAIN
from .models import BondData
from .services import async_setup_services
from .utils import BondHub
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
PLATFORMS = [
Platform.BUTTON,
Platform.COVER,
@@ -38,6 +41,12 @@ _LOGGER = logging.getLogger(__name__)
type BondConfigEntry = ConfigEntry[BondData]
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the component."""
async_setup_services(hass)
return True
async def async_setup_entry(hass: HomeAssistant, entry: BondConfigEntry) -> bool:
"""Set up Bond from a config entry."""
host = entry.data[CONF_HOST]

View File

@@ -5,10 +5,3 @@ BRIDGE_MAKE = "Olibra"
DOMAIN = "bond"
CONF_BOND_ID: str = "bond_id"
SERVICE_SET_FAN_SPEED_TRACKED_STATE = "set_fan_speed_tracked_state"
SERVICE_SET_POWER_TRACKED_STATE = "set_switch_power_tracked_state"
SERVICE_SET_LIGHT_POWER_TRACKED_STATE = "set_light_power_tracked_state"
SERVICE_SET_LIGHT_BRIGHTNESS_TRACKED_STATE = "set_light_brightness_tracked_state"
ATTR_POWER_STATE = "power_state"

View File

@@ -8,7 +8,6 @@ from typing import Any
from aiohttp.client_exceptions import ClientResponseError
from bond_async import Action, DeviceType, Direction
import voluptuous as vol
from homeassistant.components.fan import (
DIRECTION_FORWARD,
@@ -18,7 +17,6 @@ from homeassistant.components.fan import (
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_platform
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.util.percentage import (
percentage_to_ranged_value,
@@ -27,7 +25,6 @@ from homeassistant.util.percentage import (
from homeassistant.util.scaling import int_states_in_range
from . import BondConfigEntry
from .const import SERVICE_SET_FAN_SPEED_TRACKED_STATE
from .entity import BondEntity
from .models import BondData
from .utils import BondDevice
@@ -44,12 +41,6 @@ async def async_setup_entry(
) -> None:
"""Set up Bond fan devices."""
data = entry.runtime_data
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
SERVICE_SET_FAN_SPEED_TRACKED_STATE,
{vol.Required("speed"): vol.All(vol.Number(scale=0), vol.Range(0, 100))},
"async_set_speed_belief",
)
async_add_entities(
BondFan(data, device)

View File

@@ -7,37 +7,20 @@ from typing import Any
from aiohttp.client_exceptions import ClientResponseError
from bond_async import Action, DeviceType
import voluptuous as vol
from homeassistant.components.light import ATTR_BRIGHTNESS, ColorMode, LightEntity
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import BondConfigEntry
from .const import (
ATTR_POWER_STATE,
SERVICE_SET_LIGHT_BRIGHTNESS_TRACKED_STATE,
SERVICE_SET_LIGHT_POWER_TRACKED_STATE,
)
from .entity import BondEntity
from .models import BondData
from .utils import BondDevice
_LOGGER = logging.getLogger(__name__)
SERVICE_START_INCREASING_BRIGHTNESS = "start_increasing_brightness"
SERVICE_START_DECREASING_BRIGHTNESS = "start_decreasing_brightness"
SERVICE_STOP = "stop"
ENTITY_SERVICES = [
SERVICE_START_INCREASING_BRIGHTNESS,
SERVICE_START_DECREASING_BRIGHTNESS,
SERVICE_STOP,
]
async def async_setup_entry(
hass: HomeAssistant,
@@ -48,14 +31,6 @@ async def async_setup_entry(
data = entry.runtime_data
hub = data.hub
platform = entity_platform.async_get_current_platform()
for service in ENTITY_SERVICES:
platform.async_register_entity_service(
service,
None,
f"async_{service}",
)
fan_lights: list[Entity] = [
BondLight(data, device)
for device in hub.devices
@@ -94,22 +69,6 @@ async def async_setup_entry(
if DeviceType.is_light(device.type)
]
platform.async_register_entity_service(
SERVICE_SET_LIGHT_BRIGHTNESS_TRACKED_STATE,
{
vol.Required(ATTR_BRIGHTNESS): vol.All(
vol.Number(scale=0), vol.Range(0, 255)
)
},
"async_set_brightness_belief",
)
platform.async_register_entity_service(
SERVICE_SET_LIGHT_POWER_TRACKED_STATE,
{vol.Required(ATTR_POWER_STATE): vol.All(cv.boolean)},
"async_set_power_belief",
)
async_add_entities(
fan_lights + fan_up_lights + fan_down_lights + fireplaces + fp_lights + lights,
)

View File

@@ -0,0 +1,101 @@
"""Support for Bond services."""
from __future__ import annotations
import voluptuous as vol
from homeassistant.components.fan import DOMAIN as FAN_DOMAIN
from homeassistant.components.light import ATTR_BRIGHTNESS, DOMAIN as LIGHT_DOMAIN
from homeassistant.components.switch import DOMAIN as SWITCH_DOMAIN
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, service
from .const import DOMAIN
ATTR_POWER_STATE = "power_state"
# Fan
SERVICE_SET_FAN_SPEED_TRACKED_STATE = "set_fan_speed_tracked_state"
# Switch
SERVICE_SET_POWER_TRACKED_STATE = "set_switch_power_tracked_state"
# Light
SERVICE_SET_LIGHT_POWER_TRACKED_STATE = "set_light_power_tracked_state"
SERVICE_SET_LIGHT_BRIGHTNESS_TRACKED_STATE = "set_light_brightness_tracked_state"
SERVICE_START_INCREASING_BRIGHTNESS = "start_increasing_brightness"
SERVICE_START_DECREASING_BRIGHTNESS = "start_decreasing_brightness"
SERVICE_STOP = "stop"
@callback
def async_setup_services(hass: HomeAssistant) -> None:
"""Home Assistant services."""
# Fan entity services
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_SET_FAN_SPEED_TRACKED_STATE,
entity_domain=FAN_DOMAIN,
schema={vol.Required("speed"): vol.All(vol.Number(scale=0), vol.Range(0, 100))},
func="async_set_speed_belief",
)
# Light entity services
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_START_INCREASING_BRIGHTNESS,
entity_domain=LIGHT_DOMAIN,
schema=None,
func="async_start_increasing_brightness",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_START_DECREASING_BRIGHTNESS,
entity_domain=LIGHT_DOMAIN,
schema=None,
func="async_start_decreasing_brightness",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_STOP,
entity_domain=LIGHT_DOMAIN,
schema=None,
func="async_stop",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_SET_LIGHT_BRIGHTNESS_TRACKED_STATE,
entity_domain=LIGHT_DOMAIN,
schema={
vol.Required(ATTR_BRIGHTNESS): vol.All(
vol.Number(scale=0), vol.Range(0, 255)
)
},
func="async_set_brightness_belief",
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_SET_LIGHT_POWER_TRACKED_STATE,
entity_domain=LIGHT_DOMAIN,
schema={vol.Required(ATTR_POWER_STATE): vol.All(cv.boolean)},
func="async_set_power_belief",
)
# Switch entity services
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_SET_POWER_TRACKED_STATE,
entity_domain=SWITCH_DOMAIN,
schema={vol.Required(ATTR_POWER_STATE): cv.boolean},
func="async_set_power_belief",
)

View File

@@ -6,16 +6,13 @@ from typing import Any
from aiohttp.client_exceptions import ClientResponseError
from bond_async import Action, DeviceType
import voluptuous as vol
from homeassistant.components.switch import SwitchEntity
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import BondConfigEntry
from .const import ATTR_POWER_STATE, SERVICE_SET_POWER_TRACKED_STATE
from .entity import BondEntity
@@ -26,12 +23,6 @@ async def async_setup_entry(
) -> None:
"""Set up Bond generic devices."""
data = entry.runtime_data
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
SERVICE_SET_POWER_TRACKED_STATE,
{vol.Required(ATTR_POWER_STATE): cv.boolean},
"async_set_power_belief",
)
async_add_entities(
BondSwitch(data, device)

View File

@@ -200,7 +200,7 @@ class BraviaTVCoordinator(DataUpdateCoordinator[None]):
"device": self.config_entry.title,
},
) from err
except (BraviaConnectionError, BraviaConnectionTimeout, BraviaTurnedOff):
except BraviaConnectionError, BraviaConnectionTimeout, BraviaTurnedOff:
self.is_on = False
self.connected = False
_LOGGER.debug(

View File

@@ -1,14 +1,3 @@
"""Constants for the Bring! integration."""
from typing import Final
DOMAIN = "bring"
ATTR_SENDER: Final = "sender"
ATTR_ITEM_NAME: Final = "item"
ATTR_NOTIFICATION_TYPE: Final = "message"
ATTR_REACTION: Final = "reaction"
ATTR_ACTIVITY: Final = "uuid"
ATTR_RECEIVER: Final = "publicUserUuid"
SERVICE_PUSH_NOTIFICATION = "send_message"
SERVICE_ACTIVITY_STREAM_REACTION = "send_reaction"

View File

@@ -63,9 +63,9 @@ SENSOR_DESCRIPTIONS: tuple[BringSensorEntityDescription, ...] = (
key=BringSensor.LIST_LANGUAGE,
translation_key=BringSensor.LIST_LANGUAGE,
value_fn=(
lambda lst, settings: x.lower()
if (x := list_language(lst.lst.listUuid, settings))
else None
lambda lst, settings: (
x.lower() if (x := list_language(lst.lst.listUuid, settings)) else None
)
),
entity_category=EntityCategory.DIAGNOSTIC,
options=[x.lower() for x in BRING_SUPPORTED_LOCALES],

View File

@@ -1,6 +1,5 @@
"""Actions for Bring! integration."""
import logging
from typing import TYPE_CHECKING
from bring_api import (
@@ -13,22 +12,28 @@ from bring_api import (
import voluptuous as vol
from homeassistant.components.event import ATTR_EVENT_TYPE
from homeassistant.components.todo import DOMAIN as TODO_DOMAIN
from homeassistant.config_entries import ConfigEntryState
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import config_validation as cv, entity_registry as er
from .const import (
ATTR_ACTIVITY,
ATTR_REACTION,
ATTR_RECEIVER,
DOMAIN,
SERVICE_ACTIVITY_STREAM_REACTION,
from homeassistant.helpers import (
config_validation as cv,
entity_registry as er,
service,
)
from .const import DOMAIN
from .coordinator import BringConfigEntry
_LOGGER = logging.getLogger(__name__)
ATTR_ACTIVITY = "uuid"
ATTR_ITEM_NAME = "item"
ATTR_NOTIFICATION_TYPE = "message"
ATTR_REACTION = "reaction"
ATTR_RECEIVER = "publicUserUuid"
SERVICE_PUSH_NOTIFICATION = "send_message"
SERVICE_ACTIVITY_STREAM_REACTION = "send_reaction"
SERVICE_ACTIVITY_STREAM_REACTION_SCHEMA = vol.Schema(
{
@@ -54,6 +59,7 @@ def get_config_entry(hass: HomeAssistant, entry_id: str) -> BringConfigEntry:
return entry
@callback
def async_setup_services(hass: HomeAssistant) -> None:
"""Set up services for Bring! integration."""
@@ -108,3 +114,17 @@ def async_setup_services(hass: HomeAssistant) -> None:
async_send_activity_stream_reaction,
SERVICE_ACTIVITY_STREAM_REACTION_SCHEMA,
)
service.async_register_platform_entity_service(
hass,
DOMAIN,
SERVICE_PUSH_NOTIFICATION,
entity_domain=TODO_DOMAIN,
schema={
vol.Required(ATTR_NOTIFICATION_TYPE): vol.All(
vol.Upper, vol.Coerce(BringNotificationType)
),
vol.Optional(ATTR_ITEM_NAME): cv.string,
},
func="async_send_message",
)

View File

@@ -13,7 +13,6 @@ from bring_api import (
BringNotificationType,
BringRequestException,
)
import voluptuous as vol
from homeassistant.components.todo import (
TodoItem,
@@ -23,15 +22,9 @@ from homeassistant.components.todo import (
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.helpers import config_validation as cv, entity_platform
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import (
ATTR_ITEM_NAME,
ATTR_NOTIFICATION_TYPE,
DOMAIN,
SERVICE_PUSH_NOTIFICATION,
)
from .const import DOMAIN
from .coordinator import BringConfigEntry, BringData, BringDataUpdateCoordinator
from .entity import BringBaseEntity
@@ -63,19 +56,6 @@ async def async_setup_entry(
coordinator.async_add_listener(add_entities)
add_entities()
platform = entity_platform.async_get_current_platform()
platform.async_register_entity_service(
SERVICE_PUSH_NOTIFICATION,
{
vol.Required(ATTR_NOTIFICATION_TYPE): vol.All(
vol.Upper, vol.Coerce(BringNotificationType)
),
vol.Optional(ATTR_ITEM_NAME): cv.string,
},
"async_send_message",
)
class BringTodoListEntity(BringBaseEntity, TodoListEntity):
"""A To-do List representation of the Bring! Shopping List."""

View File

@@ -173,7 +173,7 @@ class BroadlinkDevice[_ApiT: blk.Device = blk.Device]:
request = partial(function, *args, **kwargs)
try:
return await self.hass.async_add_executor_job(request)
except (AuthorizationError, ConnectionClosedError):
except AuthorizationError, ConnectionClosedError:
if not await self.async_auth():
raise
return await self.hass.async_add_executor_job(request)

View File

@@ -337,7 +337,7 @@ class BroadlinkRemote(BroadlinkEntity, RemoteEntity, RestoreEntity):
await asyncio.sleep(1)
try:
code = await device.async_request(device.api.check_data)
except (ReadError, StorageError):
except ReadError, StorageError:
continue
return b64encode(code).decode("utf8")
@@ -413,7 +413,7 @@ class BroadlinkRemote(BroadlinkEntity, RemoteEntity, RestoreEntity):
await asyncio.sleep(1)
try:
code = await device.async_request(device.api.check_data)
except (ReadError, StorageError):
except ReadError, StorageError:
continue
return b64encode(code).decode("utf8")

View File

@@ -127,7 +127,7 @@ class BrotherConfigFlow(ConfigFlow, domain=DOMAIN):
model, serial = await validate_input(self.hass, user_input)
except InvalidHost:
errors[CONF_HOST] = "wrong_host"
except (ConnectionError, TimeoutError):
except ConnectionError, TimeoutError:
errors["base"] = "cannot_connect"
except SnmpError:
errors["base"] = "snmp_error"
@@ -163,7 +163,7 @@ class BrotherConfigFlow(ConfigFlow, domain=DOMAIN):
await self.brother.async_update()
except UnsupportedModelError:
return self.async_abort(reason="unsupported_model")
except (ConnectionError, SnmpError, TimeoutError):
except ConnectionError, SnmpError, TimeoutError:
return self.async_abort(reason="cannot_connect")
# Check if already configured
@@ -211,7 +211,7 @@ class BrotherConfigFlow(ConfigFlow, domain=DOMAIN):
await validate_input(self.hass, user_input, entry.unique_id)
except InvalidHost:
errors[CONF_HOST] = "wrong_host"
except (ConnectionError, TimeoutError):
except ConnectionError, TimeoutError:
errors["base"] = "cannot_connect"
except SnmpError:
errors["base"] = "snmp_error"

View File

@@ -15,7 +15,7 @@ from homeassistant.components.bluetooth import (
)
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers import device_registry as dr, issue_registry as ir
from homeassistant.helpers.device_registry import CONNECTION_BLUETOOTH, DeviceRegistry
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.util.signal_type import SignalType
@@ -36,6 +36,45 @@ PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.EVENT, Platform.SE
_LOGGER = logging.getLogger(__name__)
def get_encryption_issue_id(entry_id: str) -> str:
"""Return the repair issue id for encryption removal."""
return f"encryption_removed_{entry_id}"
def _async_create_encryption_downgrade_issue(
hass: HomeAssistant, entry: BTHomeConfigEntry, issue_id: str
) -> None:
"""Create a repair issue for encryption downgrade."""
_LOGGER.warning(
"BTHome device %s was previously encrypted but is now sending "
"unencrypted data. This could be a spoofing attempt. "
"Data will be ignored until resolved",
entry.title,
)
ir.async_create_issue(
hass,
DOMAIN,
issue_id,
is_fixable=True,
is_persistent=True,
severity=ir.IssueSeverity.WARNING,
translation_key="encryption_removed",
translation_placeholders={"name": entry.title},
data={"entry_id": entry.entry_id},
)
def _async_clear_encryption_downgrade_issue(
hass: HomeAssistant, entry: BTHomeConfigEntry, issue_id: str
) -> None:
"""Clear the encryption downgrade repair issue."""
ir.async_delete_issue(hass, DOMAIN, issue_id)
_LOGGER.info(
"BTHome device %s is now sending encrypted data again. Resuming normal operation",
entry.title,
)
def process_service_info(
hass: HomeAssistant,
entry: BTHomeConfigEntry,
@@ -45,7 +84,26 @@ def process_service_info(
"""Process a BluetoothServiceInfoBleak, running side effects and returning sensor data."""
coordinator = entry.runtime_data
data = coordinator.device_data
issue_registry = ir.async_get(hass)
issue_id = get_encryption_issue_id(entry.entry_id)
update = data.update(service_info)
# Block unencrypted payloads for devices that were previously verified as encrypted.
if entry.data.get(CONF_BINDKEY) and data.downgrade_detected:
if not coordinator.encryption_downgrade_logged:
coordinator.encryption_downgrade_logged = True
if not issue_registry.async_get_issue(DOMAIN, issue_id):
_async_create_encryption_downgrade_issue(hass, entry, issue_id)
return SensorUpdate(title=None, devices={})
if data.bindkey_verified and (
(existing_issue := issue_registry.async_get_issue(DOMAIN, issue_id))
or coordinator.encryption_downgrade_logged
):
coordinator.encryption_downgrade_logged = False
if existing_issue:
_async_clear_encryption_downgrade_issue(hass, entry, issue_id)
discovered_event_classes = coordinator.discovered_event_classes
if entry.data.get(CONF_SLEEPY_DEVICE, False) != data.sleepy_device:
hass.config_entries.async_update_entry(
@@ -150,3 +208,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: BTHomeConfigEntry) -> bo
async def async_unload_entry(hass: HomeAssistant, entry: BTHomeConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_remove_entry(hass: HomeAssistant, entry: BTHomeConfigEntry) -> None:
"""Remove a config entry."""
ir.async_delete_issue(hass, DOMAIN, get_encryption_issue_id(entry.entry_id))

View File

@@ -41,6 +41,8 @@ class BTHomePassiveBluetoothProcessorCoordinator(
self.discovered_event_classes = discovered_event_classes
self.device_data = device_data
self.entry = entry
# Track whether we've already logged the encryption downgrade this session.
self.encryption_downgrade_logged = False
@property
def sleepy_device(self) -> bool:

View File

@@ -81,7 +81,7 @@ def get_event_types_by_event_class(event_class: str) -> set[str]:
but if there is only one button then it will be
button without a number postfix.
"""
return EVENT_TYPES_BY_EVENT_CLASS.get(event_class.split("_")[0], set())
return EVENT_TYPES_BY_EVENT_CLASS.get(event_class.split("_", maxsplit=1)[0], set())
async def async_validate_trigger_config(

View File

@@ -20,5 +20,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/bthome",
"iot_class": "local_push",
"requirements": ["bthome-ble==3.16.0"]
"requirements": ["bthome-ble==3.17.0"]
}

View File

@@ -0,0 +1,65 @@
"""Repairs for the BTHome integration."""
from __future__ import annotations
from typing import Any
from homeassistant import data_entry_flow
from homeassistant.components.repairs import RepairsFlow
from homeassistant.core import HomeAssistant
from homeassistant.helpers import issue_registry as ir
from . import get_encryption_issue_id
from .const import CONF_BINDKEY, DOMAIN
class EncryptionRemovedRepairFlow(RepairsFlow):
"""Handle the repair flow when encryption is disabled."""
def __init__(self, entry_id: str, entry_title: str) -> None:
"""Initialize the repair flow."""
self._entry_id = entry_id
self._entry_title = entry_title
async def async_step_init(
self, user_input: dict[str, Any] | None = None
) -> data_entry_flow.FlowResult:
"""Handle the initial step of the repair flow."""
return await self.async_step_confirm()
async def async_step_confirm(
self, user_input: dict[str, Any] | None = None
) -> data_entry_flow.FlowResult:
"""Handle confirmation, remove the bindkey, and reload the entry."""
if user_input is not None:
entry = self.hass.config_entries.async_get_entry(self._entry_id)
if not entry:
return self.async_abort(reason="entry_removed")
new_data = {k: v for k, v in entry.data.items() if k != CONF_BINDKEY}
self.hass.config_entries.async_update_entry(entry, data=new_data)
ir.async_delete_issue(
self.hass, DOMAIN, get_encryption_issue_id(self._entry_id)
)
await self.hass.config_entries.async_reload(self._entry_id)
return self.async_create_entry(data={})
return self.async_show_form(
step_id="confirm",
description_placeholders={"name": self._entry_title},
)
async def async_create_fix_flow(
hass: HomeAssistant, issue_id: str, data: dict[str, Any] | None
) -> RepairsFlow:
"""Create the repair flow for removing the encryption key."""
if not data or "entry_id" not in data:
raise ValueError("Missing data for repair flow")
entry_id = data["entry_id"]
entry = hass.config_entries.async_get_entry(entry_id)
entry_title = entry.title if entry else "Unknown device"
return EncryptionRemovedRepairFlow(entry_id, entry_title)

View File

@@ -117,5 +117,21 @@
"name": "UV Index"
}
}
},
"issues": {
"encryption_removed": {
"fix_flow": {
"abort": {
"entry_removed": "The device has been removed"
},
"step": {
"confirm": {
"description": "The BTHome device **{name}** was configured with encryption but is now broadcasting unencrypted data. Data from this device is being ignored until this is resolved.\n\nIf you disabled encryption on the device, select **Submit** to remove the encryption key and resume receiving data.\n\nIf you did not disable encryption, someone may be attempting to spoof your device. Do not submit this form and the unencrypted data will continue to be ignored.",
"title": "Remove encryption key for {name}"
}
}
},
"title": "Encryption disabled on {name}"
}
}
}

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