Compare commits

..

315 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
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
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
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
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
1171 changed files with 29775 additions and 17642 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

@@ -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

@@ -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

@@ -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

@@ -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

@@ -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,
)
@@ -111,6 +114,7 @@ async def get_model_list(client: anthropic.AsyncAnthropic) -> list[SelectOptionD
"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):
@@ -354,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]
@@ -371,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(
{

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"
@@ -29,6 +30,7 @@ DEFAULT = {
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,
@@ -42,6 +44,18 @@ 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",

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

@@ -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"
@@ -125,5 +129,16 @@
},
"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

@@ -547,7 +547,7 @@ class APCUPSdSensor(APCUPSdEntity, SensorEntity):
try:
self._attr_native_value = dateutil.parser.parse(data)
except (dateutil.parser.ParserError, OverflowError):
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

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

@@ -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}"
}
}
}

View File

@@ -199,7 +199,7 @@ class BrData:
"""Return the temperature, or None."""
try:
return float(self.data.get(TEMPERATURE))
except (ValueError, TypeError):
except ValueError, TypeError:
return None
@property
@@ -207,7 +207,7 @@ class BrData:
"""Return the feeltemperature, or None."""
try:
return float(self.data.get(FEELTEMPERATURE))
except (ValueError, TypeError):
except ValueError, TypeError:
return None
@property
@@ -215,7 +215,7 @@ class BrData:
"""Return the pressure, or None."""
try:
return float(self.data.get(PRESSURE))
except (ValueError, TypeError):
except ValueError, TypeError:
return None
@property
@@ -223,7 +223,7 @@ class BrData:
"""Return the humidity, or None."""
try:
return int(self.data.get(HUMIDITY))
except (ValueError, TypeError):
except ValueError, TypeError:
return None
@property
@@ -231,7 +231,7 @@ class BrData:
"""Return the visibility, or None."""
try:
return int(self.data.get(VISIBILITY))
except (ValueError, TypeError):
except ValueError, TypeError:
return None
@property
@@ -239,7 +239,7 @@ class BrData:
"""Return the windgust, or None."""
try:
return float(self.data.get(WINDGUST))
except (ValueError, TypeError):
except ValueError, TypeError:
return None
@property
@@ -247,7 +247,7 @@ class BrData:
"""Return the windspeed, or None."""
try:
return float(self.data.get(WINDSPEED))
except (ValueError, TypeError):
except ValueError, TypeError:
return None
@property
@@ -255,7 +255,7 @@ class BrData:
"""Return the wind bearing, or None."""
try:
return int(self.data.get(WINDAZIMUTH))
except (ValueError, TypeError):
except ValueError, TypeError:
return None
@property

View File

@@ -506,6 +506,8 @@ def is_offset_reached(
class CalendarEntityDescription(EntityDescription, frozen_or_thawed=True):
"""A class that describes calendar entities."""
initial_color: str | None = None
class CalendarEntity(Entity):
"""Base class for calendar event entities."""
@@ -516,12 +518,16 @@ class CalendarEntity(Entity):
_alarm_unsubs: list[CALLBACK_TYPE] | None = None
_attr_initial_color: str | None = None
_attr_initial_color: str | None
@property
def initial_color(self) -> str | None:
"""Return the initial color for the calendar entity."""
return self._attr_initial_color
if hasattr(self, "_attr_initial_color"):
return self._attr_initial_color
if hasattr(self, "entity_description"):
return self.entity_description.initial_color
return None
def get_initial_entity_options(self) -> er.EntityOptionsType | None:
"""Return initial entity options."""
@@ -685,7 +691,7 @@ class CalendarEventView(http.HomeAssistantView):
try:
start_date = dt_util.parse_datetime(start)
end_date = dt_util.parse_datetime(end)
except (ValueError, AttributeError):
except ValueError, AttributeError:
return web.Response(status=HTTPStatus.BAD_REQUEST)
if start_date is None or end_date is None:
return web.Response(status=HTTPStatus.BAD_REQUEST)

View File

@@ -50,22 +50,22 @@
"selector": {},
"services": {
"disable_motion_detection": {
"description": "Disables the motion detection.",
"description": "Disables the motion detection of a camera.",
"name": "Disable motion detection"
},
"enable_motion_detection": {
"description": "Enables the motion detection.",
"description": "Enables the motion detection of a camera.",
"name": "Enable motion detection"
},
"play_stream": {
"description": "Plays the camera stream on a supported media player.",
"description": "Plays a camera stream on a supported media player.",
"fields": {
"format": {
"description": "Stream format supported by the media player.",
"name": "Format"
},
"media_player": {
"description": "Media players to stream to.",
"description": "Media player to stream to.",
"name": "Media player"
}
},
@@ -100,11 +100,11 @@
"name": "Take snapshot"
},
"turn_off": {
"description": "Turns off the camera.",
"description": "Turns off a camera.",
"name": "[%key:common::action::turn_off%]"
},
"turn_on": {
"description": "Turns on the camera.",
"description": "Turns on a camera.",
"name": "[%key:common::action::turn_on%]"
}
},

View File

@@ -71,7 +71,7 @@ class CanaryConfigFlow(ConfigFlow, domain=DOMAIN):
await self.hass.async_add_executor_job(
validate_input, self.hass, user_input
)
except (ConnectTimeout, HTTPError):
except ConnectTimeout, HTTPError:
errors["base"] = "cannot_connect"
except Exception:
_LOGGER.exception("Unexpected exception")

View File

@@ -49,6 +49,7 @@ from .const import ( # noqa: F401
ATTR_SWING_HORIZONTAL_MODES,
ATTR_SWING_MODE,
ATTR_SWING_MODES,
ATTR_TARGET_HUMIDITY_STEP,
ATTR_TARGET_TEMP_HIGH,
ATTR_TARGET_TEMP_LOW,
ATTR_TARGET_TEMP_STEP,
@@ -234,6 +235,7 @@ CACHED_PROPERTIES_WITH_ATTR_ = {
"max_temp",
"min_humidity",
"max_humidity",
"target_humidity_step",
}
@@ -249,6 +251,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
ATTR_MAX_TEMP,
ATTR_MIN_HUMIDITY,
ATTR_MAX_HUMIDITY,
ATTR_TARGET_HUMIDITY_STEP,
ATTR_TARGET_TEMP_STEP,
ATTR_PRESET_MODES,
}
@@ -275,6 +278,7 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
_attr_swing_horizontal_mode: str | None
_attr_swing_horizontal_modes: list[str] | None
_attr_target_humidity: float | None = None
_attr_target_humidity_step: int | None = None
_attr_target_temperature_high: float | None
_attr_target_temperature_low: float | None
_attr_target_temperature_step: float | None = None
@@ -323,6 +327,9 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
data[ATTR_MIN_HUMIDITY] = self.min_humidity
data[ATTR_MAX_HUMIDITY] = self.max_humidity
if self.target_humidity_step is not None:
data[ATTR_TARGET_HUMIDITY_STEP] = self.target_humidity_step
if ClimateEntityFeature.FAN_MODE in supported_features:
data[ATTR_FAN_MODES] = self.fan_modes
@@ -728,6 +735,11 @@ class ClimateEntity(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
"""Return the maximum humidity."""
return self._attr_max_humidity
@cached_property
def target_humidity_step(self) -> int | None:
"""Return the supported step of humidity."""
return self._attr_target_humidity_step
async def async_service_humidity_set(
entity: ClimateEntity, service_call: ServiceCall

View File

@@ -114,6 +114,7 @@ ATTR_SWING_MODES = "swing_modes"
ATTR_SWING_MODE = "swing_mode"
ATTR_SWING_HORIZONTAL_MODE = "swing_horizontal_mode"
ATTR_SWING_HORIZONTAL_MODES = "swing_horizontal_modes"
ATTR_TARGET_HUMIDITY_STEP = "target_humidity_step"
ATTR_TARGET_TEMP_HIGH = "target_temp_high"
ATTR_TARGET_TEMP_LOW = "target_temp_low"
ATTR_TARGET_TEMP_STEP = "target_temp_step"

View File

@@ -373,7 +373,7 @@ class CloudAlexaConfig(alexa_config.AbstractConfig):
if self.should_report_state:
try:
await self.async_enable_proactive_mode()
except (alexa_errors.NoTokenAvailable, alexa_errors.RequireRelink):
except alexa_errors.NoTokenAvailable, alexa_errors.RequireRelink:
await self.set_authorized(False)
else:
await self.async_disable_proactive_mode()

View File

@@ -187,7 +187,7 @@ class CloudClient(Interface):
err,
)
async_call_later(self._hass, 30, enable_alexa_job)
except (alexa_errors.NoTokenAvailable, alexa_errors.RequireRelink):
except alexa_errors.NoTokenAvailable, alexa_errors.RequireRelink:
pass
enable_alexa_job = HassJob(enable_alexa, cancel_on_shutdown=True)

View File

@@ -2,7 +2,7 @@
import base64
from collections.abc import AsyncGenerator, Callable, Iterable
from enum import Enum
from enum import StrEnum
import json
import logging
import re
@@ -59,7 +59,7 @@ _LOGGER = logging.getLogger(__name__)
_MAX_TOOL_ITERATIONS = 10
class ResponseItemType(str, Enum):
class ResponseItemType(StrEnum):
"""Response item types."""
FUNCTION_CALL = "function_call"

View File

@@ -779,7 +779,7 @@ async def websocket_update_prefs(
msg["id"], "alexa_timeout", "Timeout validating Alexa access token."
)
return
except (alexa_errors.NoTokenAvailable, alexa_errors.RequireRelink):
except alexa_errors.NoTokenAvailable, alexa_errors.RequireRelink:
connection.send_error(
msg["id"],
"alexa_relink",

View File

@@ -13,6 +13,6 @@
"integration_type": "system",
"iot_class": "cloud_push",
"loggers": ["acme", "hass_nabucasa", "snitun"],
"requirements": ["hass-nabucasa==1.12.0", "openai==2.15.0"],
"requirements": ["hass-nabucasa==1.13.0", "openai==2.15.0"],
"single_config_entry": true
}

View File

@@ -196,44 +196,46 @@ class R2BackupAgent(BackupAgent):
)
upload_id = multipart_upload["UploadId"]
try:
parts = []
parts: list[dict[str, Any]] = []
part_number = 1
buffer_size = 0 # bytes
buffer: list[bytes] = []
buffer = bytearray() # bytes buffer to store the data
stream = await open_stream()
async for chunk in stream:
buffer_size += len(chunk)
buffer.append(chunk)
buffer.extend(chunk)
# upload parts of exactly MULTIPART_MIN_PART_SIZE_BYTES to ensure
# all non-trailing parts have the same size (required by S3/R2)
while len(buffer) >= MULTIPART_MIN_PART_SIZE_BYTES:
part_data = bytes(buffer[:MULTIPART_MIN_PART_SIZE_BYTES])
del buffer[:MULTIPART_MIN_PART_SIZE_BYTES]
# If buffer size meets minimum part size, upload it as a part
if buffer_size >= MULTIPART_MIN_PART_SIZE_BYTES:
_LOGGER.debug(
"Uploading part number %d, size %d", part_number, buffer_size
"Uploading part number %d, size %d",
part_number,
len(part_data),
)
part = await self._client.upload_part(
Bucket=self._bucket,
Key=self._with_prefix(tar_filename),
PartNumber=part_number,
UploadId=upload_id,
Body=b"".join(buffer),
Body=part_data,
)
parts.append({"PartNumber": part_number, "ETag": part["ETag"]})
part_number += 1
buffer_size = 0
buffer = []
# Upload the final buffer as the last part (no minimum size requirement)
if buffer:
_LOGGER.debug(
"Uploading final part number %d, size %d", part_number, buffer_size
"Uploading final part number %d, size %d", part_number, len(buffer)
)
part = await self._client.upload_part(
Bucket=self._bucket,
Key=self._with_prefix(tar_filename),
PartNumber=part_number,
UploadId=upload_id,
Body=b"".join(buffer),
Body=bytes(buffer),
)
parts.append({"PartNumber": part_number, "ETag": part["ETag"]})

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