Compare commits

..

184 Commits

Author SHA1 Message Date
Ludovic BOUÉ
1cdbe596fe Update snapshots 2026-02-06 17:29:30 +00:00
Ludovic BOUÉ
a9d52bfbe7 Remove feature map attribute from occupancy sensing discovery schema 2026-02-06 17:24:51 +00:00
Ludovic BOUÉ
6eed1f9961 Update snapshots 2026-02-06 17:06:27 +00:00
Ludovic BOUÉ
149607ab17 Refactor strings.json: Remove duplicate unoccupied to occupied delay entries and standardize casing for threshold name 2026-02-06 17:04:42 +00:00
Ludovic BOUÉ
279b5be357 Add assertions for min, max, and unit_of_measurement in occupancy sensor tests 2026-02-06 17:02:19 +00:00
Ludovic BOUÉ
82b93e788b Update snapshots 2026-02-06 16:58:16 +00:00
Ludovic BOUÉ
555813f84f Move PIRUnoccupiedToOccupiedDelay before 2026-02-06 16:58:16 +00:00
Ludovic BOUÉ
ecf1b4e591 Fix occupancy sensor threshold test assertion to match updated mock data 2026-02-06 16:58:15 +00:00
Ludovic BOUÉ
e17a9f12a1 Rename occupancy sensor state and entity IDs for clarity in PIR tests 2026-02-06 16:58:15 +00:00
Ludovic BOUÉ
e8f05f5291 Update snapshots 2026-02-06 16:58:15 +00:00
Ludovic BOUÉ
a5a76e9268 Add mock occupancy sensor JSON fixture 2026-02-06 16:58:15 +00:00
Ludovic BOUÉ
edc3fb47b2 Réorganiser les chaînes pour le délai et le seuil de passage de l'état inoccupé à occupé 2026-02-06 16:58:15 +00:00
Ludovic BOUÉ
f1e514a70a Update homeassistant/components/matter/strings.json
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2026-02-06 17:52:35 +01:00
Ludovic BOUÉ
5632baca5b Merge branch 'dev' into PIRUnoccupiedToOccupiedDelay 2026-02-06 17:08:24 +01:00
Ludovic BOUÉ
78f9bad706 PIRUnoccupiedToOccupiedDelay attribute 2026-02-06 16:00:18 +00:00
Artur Pragacz
f2d4319366 Make bad entity ID detection more lenient (#162425) 2026-02-06 15:58:27 +01:00
epenet
3eb8d64381 Fix invalid yardian snaphots (#162422) 2026-02-06 15:52:58 +01:00
epenet
818ce549d9 Remove duplicate class field in incomfort (#162420) 2026-02-06 15:16:20 +01:00
epenet
db7800d170 Simplify (in-)equality checks (#162416) 2026-02-06 15:11:22 +01:00
epenet
bc1c24efb1 Improve (r)split performance (#162418) 2026-02-06 15:10:30 +01:00
David Recordon
5ad632c34a Add Fan mode support to Control4 integration (#159980) 2026-02-06 14:11:18 +01:00
Sab44
65f95e5c4b Bump librehardwaremonitor-api to version 1.9.1 (#162409) 2026-02-06 14:04:21 +01:00
Joost Lekkerkerker
bb406594d1 Remove double unit of measurement for yardian (#162412) 2026-02-06 14:03:57 +01:00
epenet
b7a7b7bc63 Cleanup unnecessary brackets for except statements (a-h) (#162404) 2026-02-06 13:48:56 +01:00
epenet
1c59d846e3 Cleanup unnecessary brackets for except statements (core) (#162410) 2026-02-06 13:45:59 +01:00
Joost Lekkerkerker
3b40bb7d28 Remove entity id overwrite for ambient station (#162403) 2026-02-06 13:39:51 +01:00
epenet
a171e17097 Cleanup unnecessary brackets for except statements (q-z) (#162408) 2026-02-06 13:31:56 +01:00
epenet
c881d96d2f Cleanup unnecessary brackets for except statements (i-p) (#162405) 2026-02-06 13:23:25 +01:00
Luo Chen
f1a99a2d65 Fix unicode escaping in MCP server tool response (#162319)
Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2026-02-06 13:11:28 +01:00
mettolen
d02adabe5d Add Start session action to Saunum integration (#162177) 2026-02-06 12:47:50 +01:00
Mark Jansen
286730165d Simplify sun condition schema by re-using an existing type (#161894)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2026-02-06 12:27:16 +01:00
epenet
95a58252cf Reformat lambda (tests) (#162383) 2026-02-06 12:24:58 +01:00
epenet
bf6643643b Reformat lambda (core) (#162382) 2026-02-06 12:24:48 +01:00
epenet
8d780d6712 Reformat lambda (t-z) (#162381) 2026-02-06 12:24:37 +01:00
epenet
576c7227c6 Reformat lambda (m-s) (#162379) 2026-02-06 12:24:19 +01:00
epenet
915d375f0a Reformat lambda (a-l) (#162377) 2026-02-06 12:24:12 +01:00
Petro31
e9487a81a7 Update template image platform to new entity framework (#162343) 2026-02-06 12:20:11 +01:00
Andres Ruiz
0a2fe01b66 Add device_info to waterfurnace sensors (#162080)
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-02-06 12:17:27 +01:00
Peter Grauvogel
9de9bde7d8 Add timestamp sensors for highest and lowest price times (#161639) 2026-02-06 12:04:39 +01:00
epenet
fbc91d3d3d Remove str from sensor state class (#161686) 2026-02-06 12:01:23 +01:00
Abílio Costa
47672614df Remove external url from config for local_only users (#161891) 2026-02-06 12:00:07 +01:00
Denis Shulyaka
8c01c4a155 Add gpt-image-1.5 model support (#162208) 2026-02-06 11:57:00 +01:00
Aaron Godfrey
5dc7f8bfe3 Fix conversion of data for todo.* actions (#162366) 2026-02-06 11:52:01 +01:00
epenet
cc01d15d74 Use StrEnum in eq3btsmart (#162387) 2026-02-06 11:51:00 +01:00
epenet
5c980e8d97 Cleanup ternary if expressions (#162394) 2026-02-06 11:50:47 +01:00
epenet
c01e3beb2e Use StrEnum in stt (#162389) 2026-02-06 11:50:35 +01:00
Jan Bouwhuis
a5b16e3694 Remove parentheses for except statements where it is not needed in mqtt integration (#162398) 2026-02-06 11:49:42 +01:00
epenet
866cd52ada Cleanup default None value from dict.get (#162396) 2026-02-06 11:49:02 +01:00
John O'Nolan
2d308aaa20 Add Ghost integration (#162041)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-02-06 11:47:53 +01:00
epenet
0456eb54ee Use StrEnum in unifiprotect (#162390) 2026-02-06 11:42:34 +01:00
epenet
ce6fced6a4 Use StrEnum in intent helper (#162391) 2026-02-06 11:41:58 +01:00
epenet
fc56f52c74 Use StrEnum in modbus (#162388) 2026-02-06 11:41:29 +01:00
dependabot[bot]
f7e65eeece Bump github/codeql-action from 4.32.1 to 4.32.2 (#162369)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2026-02-06 11:33:24 +01:00
epenet
8c94de4a9c Use StrEnum in easyenergy (#162386) 2026-02-06 12:15:42 +02:00
epenet
46971c1c82 Use StrEnum in cloud (#162385) 2026-02-06 12:15:09 +02:00
epenet
fb5c3c7eb6 Use StrEnum in uptimerobot tests (#162392) 2026-02-06 12:14:28 +02:00
Anders Ödlund
ea42237444 Add dismiss protection to config flows (#162270)
Co-authored-by: J. Nick Koston <nick@home-assistant.io>
2026-02-06 10:59:37 +01:00
Matt Zimmerman
2a76c2678e Add missing config flow strings to SmartTub (#162375)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-06 08:53:00 +01:00
Manu
72b6e5fabe Add remote action exceptions to Xbox (#162347) 2026-02-06 08:27:31 +01:00
Jordan Harvey
f739fc1f55 Update pynintendoparental requirement to version 2.3.2.1 (#162362) 2026-02-06 08:26:51 +01:00
Denis Shulyaka
aecfca5020 Add Claude Opus 4.6 support (#162365) 2026-02-06 08:25:53 +01:00
Matt Zimmerman
f024ae442f Add PARALLEL_UPDATES to SmartTub platform files (#162373)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-06 08:21:33 +01:00
Matt Zimmerman
07a9aad4a4 Bump python-smarttub to 0.0.47 (#162367)
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-06 08:14:42 +01:00
Denis Shulyaka
22ab58077e Bump anthropic to 0.78.0 (#162349) 2026-02-05 22:15:16 +01:00
epenet
4b666688c9 Adjust color mode handling in esphome lights (#162294) 2026-02-05 21:04:21 +01:00
Shay Levy
d118332366 Fix Shelly Linkedgo Thermostat status update (#162339) 2026-02-05 20:52:21 +01:00
Michael Potthoff
9f32e0da14 Add type option "first_available" to sensor group in group component (#155525) 2026-02-05 20:26:45 +01:00
Andre Lengwenus
1cef223a06 Bump pypck to 0.9.10 (#162333) 2026-02-05 20:14:04 +01:00
epenet
29da1233f3 Fix missing color_mode attribute in mqtt light (#162311)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-05 18:22:57 +01:00
Arie Catsman
a5b3d22058 Bump pyenphase to 2.4.5 (#162324) 2026-02-05 17:42:45 +01:00
jameson_uk
d37e958a0b Add config entry tests to alexa_devices (#162295) 2026-02-05 13:07:14 +00:00
epenet
0498ac7364 Migrate supported_color_modes to shorthand attribute in zwave_js lights (#162296) 2026-02-05 13:56:20 +01:00
epenet
67bdeb9945 Adjust unknown color mode handling in ZHA lights (#162292) 2026-02-05 12:30:06 +01:00
Oliver
a227307387 Add support for media_stop command for denonavr receivers (#162236) 2026-02-05 12:17:25 +01:00
Oliver
0e0309cabf Add mapping for stopped state to denonavr media player (#162283) 2026-02-05 12:15:14 +01:00
epenet
fd2dfc83c6 Use shorthand attributes in zwave_js lights (#162293) 2026-02-05 12:05:08 +01:00
epenet
9e736891c4 Use shorthand attributes in demo lights (#162282) 2026-02-05 12:02:41 +01:00
Oliver
fbabf0dcb8 Bump denonavr to 1.3.2 (#162271) 2026-02-05 11:59:10 +01:00
Tomás Correia
7128791152 Fix multipart upload to use consistent part sizes for R2/S3 (#162278) 2026-02-05 11:54:02 +01:00
epenet
94456b5bc3 Improve type hints in tradfri lights (#162287) 2026-02-05 11:51:34 +01:00
epenet
2105c6b177 Improve type hints in switchbot lights (#162286) 2026-02-05 11:46:50 +01:00
Robin Lintermann
34156f79e8 Bump pysmarlaapi to 0.13.0 (#162277) 2026-02-05 11:45:29 +01:00
epenet
bb1a2530f5 Improve type hints in nanoleaf lights (#162284) 2026-02-05 11:44:54 +01:00
epenet
06613746f9 Remove unnecessary shorthand attribute init in template (#162279) 2026-02-05 11:41:57 +01:00
epenet
98ca948afe Improve type hints in abode lights (#162281) 2026-02-05 11:35:43 +01:00
Krisjanis Lejejs
fa58fe5f4e Bump hass-nabucasa from 1.12.0 to 1.13.0 (#162274) 2026-02-05 11:03:44 +01:00
Petro31
46f230c487 Clean up unused cover constants (#162225) 2026-02-05 10:46:36 +01:00
epenet
13a987aba3 Cleanup deprecated SUPPORT_ light constants (#162210) 2026-02-05 10:32:32 +01:00
cdnninja
9cef323581 Update Vesync quality-scale to Bronze (#162260) 2026-02-05 09:44:47 +01:00
epenet
7ea7576188 Cleanup legacy support for extracting color modes from light supported features (#162265) 2026-02-05 09:33:22 +01:00
Franck Nijhof
f8abbfd42b Merge branch 'master' into dev 2026-02-05 08:17:24 +00:00
Erik Montnemery
5cd1821bc9 Update redgtech snapshots (#162267) 2026-02-05 09:13:13 +01:00
Norbert Rittel
2ef7f26ffb Improve description of camera.play_stream action (#162264) 2026-02-05 09:07:10 +01:00
Jonathan Sady do Nascimento
184bea49e2 Add redgtech integration (#136947)
Co-authored-by: luan-nvg <luannnvg@gmail.com>
2026-02-05 09:04:14 +01:00
David Bonnes
c853fb2068 Bump evohome-async to 1.1.3 (#162232) 2026-02-05 08:25:30 +01:00
mettolen
79e0a93e48 Upgrade Liebherr integration to Silver (#162178) 2026-02-04 22:24:53 +01:00
Andres Ruiz
3867c1d7d1 Extract waterfurnace sensor names for translation (#162025)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-02-04 21:48:23 +01:00
Thomas55555
b9b6b050cc Bump google_air_quality_api to 3.0.1 (#162233) 2026-02-04 21:46:07 +01:00
Muhammad Hamza Khan
d960736b3d Improve typing in syncthing (#162193)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-02-04 21:43:13 +01:00
Franck Nijhof
3e8923f105 2026.2.0 (#162224) 2026-02-04 20:35:11 +01:00
Franck Nijhof
17cca3e69d Bump version to 2026.2.0 2026-02-04 18:53:49 +00:00
Franck Nijhof
12714c489f Bump version to 2026.2.0b5 2026-02-04 18:45:36 +00:00
Robert Resch
f788d61b4a Revert "Bump intents (#162205)" (#162226) 2026-02-04 18:36:12 +00:00
Simone Chemelli
5c726af00b Fix logic and tests for Alexa Devices utils module (#162223) 2026-02-04 18:36:10 +00:00
Joost Lekkerkerker
d1d207fbb2 Add guard for Apple TV text focus state (#162207) 2026-02-04 18:36:09 +00:00
David Bonnes
6c7f8df7f7 Fix evohome not updating scheduled setpoints in state attrs (#162043) 2026-02-04 18:36:07 +00:00
Kevin Stillhammer
6f8c9b1504 Bump fressnapftracker to 0.2.2 (#161913) 2026-02-04 18:36:06 +00:00
Kevin Stillhammer
4f9aedbc84 Filter out invalid trackers in fressnapf_tracker (#161670) 2026-02-04 18:36:04 +00:00
Franck Nijhof
52fb0343e4 Bump version to 2026.2.0b4 2026-02-04 16:14:23 +00:00
Bram Kragten
1050b4580a Update frontend to 20260128.6 (#162214) 2026-02-04 16:10:08 +00:00
Åke Strandberg
344c42172e Add missing codes for Miele coffe systems (#162206) 2026-02-04 16:10:06 +00:00
Michael Hansen
93cc0fd7f1 Bump intents (#162205) 2026-02-04 16:10:05 +00:00
andreimoraru
05fe636b55 Bump yt-dlp to 2026.02.04 (#162204)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-02-04 16:10:03 +00:00
Marc Mueller
f22467d099 Pin auth0-python to <5.0 (#162203) 2026-02-04 16:10:01 +00:00
TheJulianJES
4bc3899b32 Bump ZHA to 0.0.89 (#162195) 2026-02-04 16:10:00 +00:00
Oliver
fc4d6bf5f1 Bump denonavr to 1.3.1 (#162183) 2026-02-04 16:09:58 +00:00
johanzander
8ed0672a8f Bump growattServer to 1.9.0 (#162179)
Co-authored-by: Claude Opus 4.5 <noreply@anthropic.com>
2026-02-04 16:09:57 +00:00
Norbert Rittel
282e347a1b Clarify action descriptions in media_player (#162172) 2026-02-04 16:09:55 +00:00
Erik Montnemery
1bfb02b440 Bump python-otbr-api to 2.8.0 (#162167) 2026-02-04 16:09:54 +00:00
Przemko92
71b03bd9ae Bump compit-inext-api to 0.8.0 (#162166) 2026-02-04 16:09:52 +00:00
Przemko92
cbd69822eb Update compit-inext-api to 0.7.0 (#162020) 2026-02-04 16:09:51 +00:00
Denis Shulyaka
db900f4dd2 Anthropic repair deprecated models (#162162)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-04 16:00:14 +00:00
Jonathan Bangert
a707e695bc Bump bleak-esphome to 3.6.0 (#162028) 2026-02-04 16:00:12 +00:00
Liquidmasl
4feceac205 Jellyfin native client controls (#161982) 2026-02-04 16:00:11 +00:00
Petro31
10c20faaca Fix template weather humidity (#161945) 2026-02-04 16:00:09 +00:00
Robert Svensson
abcd512401 Add missing OUI to Axis integration, discovery would abort with unsup… (#161943) 2026-02-04 16:00:07 +00:00
Ludovic BOUÉ
3fdaaecd0f PIRUnoccupiedToOccupied attributes 2026-02-04 13:01:13 +00:00
Bram Kragten
fdf8edf474 Bump version to 2026.2.0b3 2026-02-03 18:03:54 +01:00
Bram Kragten
47e1a98bee Update frontend to 20260128.5 (#162156) 2026-02-03 18:03:04 +01:00
Joost Lekkerkerker
2d8572b943 Add Heatit virtual brand (#162155) 2026-02-03 18:03:02 +01:00
Joost Lekkerkerker
660cfdbd50 Add Heiman virtual brand (#162152) 2026-02-03 18:03:00 +01:00
Steven Travers
4208595da6 Modify Analytics text on feature labs (#162151) 2026-02-03 18:02:59 +01:00
Paul Bottein
b6b2d2fc6f Update title and description of YAML dashboard repair (#162138) 2026-02-03 18:02:58 +01:00
victorigualada
6c4c632848 Handle chat log attachments in Cloud integration (#162121) 2026-02-03 18:02:57 +01:00
Shay Levy
78cf62176f Fix Shelly xpercent sensor state_class (#162107) 2026-02-03 18:02:56 +01:00
Denis Shulyaka
df971c7a42 Anthropic: Switch default model to Haiku 4.5 (#162093) 2026-02-03 18:02:55 +01:00
mezz64
1fcabb7f2d Bump pyhik to 0.4.2 (#162092)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-02-03 18:02:53 +01:00
Åke Strandberg
9fb60c9ea2 Update Senz temperature sensor (#162016) 2026-02-03 18:02:52 +01:00
J. Diego Rodríguez Royo
9c11a4646f Remove coffee machine's hot water sensor's state class at Home Connect (#161246) 2026-02-03 17:58:47 +01:00
jameson_uk
b036a78776 Remove invalid notification sensors for Alexa devices (#160422)
Co-authored-by: Simone Chemelli <simone.chemelli@gmail.com>
2026-02-03 17:58:45 +01:00
Kamil Breguła
60bb3cb704 Handle missing battery stats in systemmonitor (#158287)
Co-authored-by: mik-laj <12058428+mik-laj@users.noreply.github.com>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2026-02-03 17:58:43 +01:00
Bram Kragten
0e770958ac Bump version to 2026.2.0b2 2026-02-02 19:12:33 +01:00
Bram Kragten
2a54c71b6c Update frontend to 20260128.4 (#162096) 2026-02-02 19:11:59 +01:00
Steven Travers
50463291ab Add learn more data for Analytics in labs (#162094) 2026-02-02 19:11:59 +01:00
Andrea Turri
43cc34042a Fix Miele dishwasher PowerDisk filling level sensor not showing up (#162048) 2026-02-02 19:11:58 +01:00
Jan Bouwhuis
a02244ccda Bump incomfort-client to 0.6.12 (#162037) 2026-02-02 19:11:57 +01:00
Adrián Moreno
a739619121 Bump pymeteoclimatic to 0.1.1 (#162029) 2026-02-02 19:11:56 +01:00
Åke Strandberg
5db97a5f1c Improved error checking during startup of SENZ (#162026) 2026-02-02 19:11:54 +01:00
Josef Zweck
804ba9c9cc Remove file description dependency in onedrive (#162012) 2026-02-02 19:11:53 +01:00
Filip Bårdsnes Tomren
5ecbcea946 Update ical requirement version to 12.1.3 (#162010) 2026-02-02 19:11:52 +01:00
hanwg
11be2b6289 Fix parse_mode for Telegram bot actions (#162006) 2026-02-02 19:11:51 +01:00
cdnninja
eefae0307b Add integration type of hub to vesync (#162004) 2026-02-02 19:11:50 +01:00
Matthias Alphart
d397ee28ea Fix KNX fan unique_id for switch-only fans (#162002) 2026-02-02 19:11:49 +01:00
starkillerOG
02c821128e Bump reolink-aio to 0.18.2 (#161998) 2026-02-02 19:11:48 +01:00
Shay Levy
71dc15d45f Fix Shelly CoIoT repair issue (#161973)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-02-02 19:11:47 +01:00
Raphael Hehl
1078387b22 Bump uiprotect to version 10.1.0 (#161967)
Co-authored-by: RaHehl <rahehl@users.noreply.github.com>
2026-02-02 19:11:46 +01:00
tronikos
35fab27d15 Bump opower to 0.17.0 (#161962) 2026-02-02 19:11:45 +01:00
Yuxin Wang
915dc7a908 Mark datetime sensors as unknown when parsing fails (#161952) 2026-02-02 19:11:44 +01:00
mvn23
e5a9738983 Fix OpenTherm Gateway button availability (#161933) 2026-02-02 19:11:43 +01:00
mvn23
2ff73219a2 Bump pyotgw to 2.2.3 (#161928) 2026-02-02 19:11:42 +01:00
epenet
5dc1270ed1 Fix mired warning in template light (#161923) 2026-02-02 19:11:41 +01:00
J. Diego Rodríguez Royo
9e95ad5a85 Restore the Home Connect program option entities (#156401)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2026-02-02 19:11:40 +01:00
Franck Nijhof
9a5d4610f7 Bump version to 2026.2.0b1 2026-01-30 11:45:08 +00:00
Paul Bottein
41c524fce4 Update frontend to 20260128.3 (#161918) 2026-01-30 11:44:54 +00:00
David Recordon
5f9fa95554 Fix Control4 HVAC state-to-action mapping (#161916) 2026-01-30 11:44:51 +00:00
Simone Chemelli
6950be8ea9 Handle hostname resolution for Shelly repair issue (#161914) 2026-01-30 11:44:47 +00:00
puddly
c5a8bf64d0 Bump ZHA to 0.0.88 (#161904) 2026-01-30 11:44:44 +00:00
hanwg
a2b9a6e9df Update translations for Telegram bot (#161903) 2026-01-30 11:44:43 +00:00
Marc Mueller
a0c567f0da Update fritzconnection to 1.15.1 (#161887) 2026-01-30 11:44:40 +00:00
Bram Kragten
c7feafdde6 Update frontend to 20260128.2 (#161881) 2026-01-30 11:44:38 +00:00
Björn Dalfors
e1e74b0aeb Bump nibe to 2.22.0 (#161873) 2026-01-30 11:44:36 +00:00
Sebastiaan Speck
673411ef97 Bump renault-api to 0.5.3 (#161857) 2026-01-30 11:44:34 +00:00
epenet
f7e5af7cb1 Fix incorrect entity_description class in radarr (#161856) 2026-01-30 11:44:32 +00:00
Norbert Rittel
0ee56ce708 Fix action descriptions of alarm_control_panel (#161852) 2026-01-30 11:44:30 +00:00
Manu
f93a176398 Fix string in Namecheap DynamicDNS integration (#161821) 2026-01-30 11:44:28 +00:00
Paul Bottein
cd2394bc12 Allow lovelace path for dashboard in yaml and fix yaml dashboard migration (#161816) 2026-01-30 11:44:26 +00:00
Michael Hansen
5c20b8eaff Bump intents to 2026.1.28 (#161813) 2026-01-30 11:44:25 +00:00
Aaron Godfrey
4bd499d3a6 Update todoist-api-python to 3.1.0 (#161811)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2026-01-30 11:44:23 +00:00
Jan Bouwhuis
8a53b94c5a Fix use of ambiguous units for reactive power and energy (#161810) 2026-01-30 11:44:20 +00:00
victorigualada
d5aff326e3 Use OpenAI schema dataclasses for cloud stream responses (#161663) 2026-01-30 11:44:18 +00:00
Gage Benne
22f66abbe7 Bump pydexcom to 0.5.1 (#161549) 2026-01-30 11:44:16 +00:00
Mattia Monga
f635228b1f Make viaggiatreno work by fixing some critical bugs (#160093)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2026-01-30 11:44:14 +00:00
Artur Pragacz
4c708c143d Fix validation of actions config in intent_script (#158266) 2026-01-30 11:44:12 +00:00
Franck Nijhof
3369459d41 Bump version to 2026.2.0b0 2026-01-28 20:00:19 +00:00
675 changed files with 8449 additions and 3151 deletions

View File

@@ -1,118 +0,0 @@
name: "Image builder"
description: "Build a Docker image"
inputs:
base-image:
description: "Base image to use for the build"
required: true
# example: 'ghcr.io/home-assistant/amd64-homeassistant-base:2024.6.0'
tags:
description: "Tag(s) for the built image (can be multiline for multiple tags)"
required: true
# example: 'ghcr.io/home-assistant/amd64-homeassistant:2026.2.0' or multiline for multiple tags
arch:
description: "Architecture for the build (used for default labels)"
required: true
# example: 'amd64'
version:
description: "Version for the build (used for default labels)"
required: true
# example: '2026.2.0'
dockerfile:
description: "Path to the Dockerfile to build"
required: true
# example: './Dockerfile'
cosign-base-identity:
description: "Certificate identity regexp for base image verification"
required: true
# example: 'https://github.com/home-assistant/docker/.*'
additional-labels:
description: "Additional labels to add to the built image (merged with default labels)"
required: false
default: ""
# example: 'custom.label=value'
push:
description: "Whether to push the image to the registry"
required: false
default: "true"
# example: 'true' or 'false'
runs:
using: "composite"
steps:
- name: Install Cosign
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
with:
cosign-release: "v2.5.3"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Verify base image signature
shell: bash
run: |
cosign verify \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp "${{ inputs.cosign-base-identity }}" \
"${{ inputs.base-image }}"
- name: Verify cache image signature
id: cache
continue-on-error: true
shell: bash
run: |
cosign verify \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp "https://github.com/home-assistant/core/.*" \
"ghcr.io/home-assistant/${{ inputs.arch }}-homeassistant:latest"
- name: Prepare labels
id: labels
shell: bash
run: |
# Generate creation timestamp
CREATED=$(date --rfc-3339=seconds --utc)
# Build default labels array
LABELS=(
"io.hass.arch=${{ inputs.arch }}"
"io.hass.version=${{ inputs.version }}"
"org.opencontainers.image.created=${CREATED}"
"org.opencontainers.image.version=${{ inputs.version }}"
)
# Append additional labels if provided
if [ -n "${{ inputs.additional-labels }}" ]; then
while IFS= read -r label; do
[ -n "$label" ] && LABELS+=("$label")
done <<< "${{ inputs.additional-labels }}"
fi
# Output the combined labels using EOF delimiter for multiline
{
echo 'result<<EOF'
printf '%s\n' "${LABELS[@]}"
echo 'EOF'
} >> "$GITHUB_OUTPUT"
- name: Build base image
id: build
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
context: .
file: ${{ inputs.dockerfile }}
platforms: ${{ steps.vars.outputs.platform }}
push: ${{ inputs.push }}
cache-from: ${{ steps.cache.outcome == 'success' && format('ghcr.io/home-assistant/{0}-homeassistant:latest', inputs.arch) || '' }}
build-args: |
BUILD_FROM=${{ inputs.base-image }}
tags: ${{ inputs.tags }}
outputs: type=image,compression=zstd,compression-level=9,force-compression=true,oci-mediatypes=true
labels: ${{ steps.labels.outputs.result }}
- name: Sign image
if: ${{ inputs.push == 'true' }}
shell: bash
run: |
# Sign each tag
while IFS= read -r tag; do
[ -n "$tag" ] && cosign sign --yes "${tag}@${{ steps.build.outputs.digest }}"
done <<< "${{ inputs.tags }}"

View File

@@ -1,68 +0,0 @@
name: "Machine image builder"
description: "Build or copy a machine-specific Docker image"
inputs:
machine:
description: "Machine name"
required: true
# example: 'raspberrypi4-64'
version:
description: "Version for the build"
required: true
# example: '2026.2.0'
arch:
description: "Architecture for the build"
required: true
# example: 'aarch64'
runs:
using: "composite"
steps:
- name: Prepare build variables
id: vars
shell: bash
run: |
echo "base_image=ghcr.io/home-assistant/${{ inputs.arch }}-homeassistant:${{ inputs.version }}" >> "$GITHUB_OUTPUT"
# Build tags array with version-specific tag
TAGS=(
"ghcr.io/home-assistant/${{ inputs.machine }}-homeassistant:${{ inputs.version }}"
)
# Add general tag based on version
if [[ "${{ inputs.version }}" =~ d ]]; then
TAGS+=("ghcr.io/home-assistant/${{ inputs.machine }}-homeassistant:dev")
elif [[ "${{ inputs.version }}" =~ b ]]; then
TAGS+=("ghcr.io/home-assistant/${{ inputs.machine }}-homeassistant:beta")
else
TAGS+=("ghcr.io/home-assistant/${{ inputs.machine }}-homeassistant:stable")
fi
# Output tags using EOF delimiter for multiline
{
echo 'tags<<EOF'
printf '%s\n' "${TAGS[@]}"
echo 'EOF'
} >> "$GITHUB_OUTPUT"
LABELS=(
"io.hass.type=core"
"io.hass.machine=${{ inputs.machine }}"
"org.opencontainers.image.source=https://github.com/home-assistant/core"
)
# Output the labels using EOF delimiter for multiline
{
echo 'labels<<EOF'
printf '%s\n' "${LABELS[@]}"
echo 'EOF'
} >> "$GITHUB_OUTPUT"
- name: Build machine image
uses: ./.github/actions/builder/generic
with:
base-image: ${{ steps.vars.outputs.base_image }}
tags: ${{ steps.vars.outputs.tags }}
arch: ${{ inputs.arch }}
version: ${{ inputs.version }}
dockerfile: machine/${{ inputs.machine }}
cosign-base-identity: "https://github.com/home-assistant/core/.*"
additional-labels: ${{ steps.vars.outputs.labels }}

View File

@@ -190,53 +190,103 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- &install_cosign
name: Install Cosign
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
with:
cosign-release: "v2.5.3"
- name: Set up Docker Buildx
uses: docker/setup-buildx-action@8d2750c68a42422c14e847fe6c8ac0403b4cbd6f # v3.12.0
- name: Build variables
id: vars
shell: bash
run: |
echo "base_image=ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant-base:${{ env.BASE_IMAGE_VERSION }}" >> "$GITHUB_OUTPUT"
echo "cache_image=ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant:latest" >> "$GITHUB_OUTPUT"
echo "created=$(date --rfc-3339=seconds --utc)" >> "$GITHUB_OUTPUT"
- name: Verify base image signature
run: |
cosign verify \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp "https://github.com/home-assistant/docker/.*" \
"${{ steps.vars.outputs.base_image }}"
- name: Verify cache image signature
id: cache
continue-on-error: true
run: |
cosign verify \
--certificate-oidc-issuer https://token.actions.githubusercontent.com \
--certificate-identity-regexp "https://github.com/home-assistant/core/.*" \
"${{ steps.vars.outputs.cache_image }}"
- name: Build base image
uses: ./.github/actions/builder/generic
id: build
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
base-image: ${{ steps.vars.outputs.base_image }}
context: .
file: ./Dockerfile
platforms: ${{ steps.vars.outputs.platform }}
push: true
cache-from: ${{ steps.cache.outcome == 'success' && steps.vars.outputs.cache_image || '' }}
build-args: |
BUILD_FROM=${{ steps.vars.outputs.base_image }}
tags: ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant:${{ needs.init.outputs.version }}
arch: ${{ matrix.arch }}
version: ${{ needs.init.outputs.version }}
dockerfile: ./Dockerfile
cosign-base-identity: "https://github.com/home-assistant/docker/.*"
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 }}
org.opencontainers.image.created=${{ steps.vars.outputs.created }}
org.opencontainers.image.version=${{ needs.init.outputs.version }}
- name: Sign image
run: |
cosign sign --yes "ghcr.io/home-assistant/${{ matrix.arch }}-homeassistant:${{ needs.init.outputs.version }}@${{ steps.build.outputs.digest }}"
build_machine:
name: Build ${{ matrix.machine.name }} machine core image
name: Build ${{ matrix.machine }} machine core image
if: github.repository_owner == 'home-assistant'
needs: ["init", "build_base"]
runs-on: ${{ matrix.machine.arch == 'amd64' && 'ubuntu-latest' || 'ubuntu-24.04-arm' }}
runs-on: ubuntu-latest
permissions:
contents: read
packages: write
id-token: write
strategy:
fail-fast: false
matrix:
machine:
- { name: generic-x86-64, arch: amd64 }
- { name: intel-nuc, arch: amd64 }
- { name: qemux86-64, arch: amd64 }
- { name: khadas-vim3, arch: aarch64 }
- { name: odroid-c2, arch: aarch64 }
- { name: odroid-c4, arch: aarch64 }
- { name: odroid-m1, arch: aarch64 }
- { name: odroid-n2, arch: aarch64 }
- { name: qemuarm-64, arch: aarch64 }
- { name: raspberrypi3-64, arch: aarch64 }
- { name: raspberrypi4-64, arch: aarch64 }
- { name: raspberrypi5-64, arch: aarch64 }
- { name: yellow, arch: aarch64 }
- { name: green, arch: aarch64 }
- generic-x86-64
- intel-nuc
- khadas-vim3
- odroid-c2
- odroid-c4
- odroid-m1
- odroid-n2
- qemuarm-64
- qemux86-64
- raspberrypi3-64
- raspberrypi4-64
- raspberrypi5-64
- yellow
- green
steps:
- name: Checkout the repository
uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2
- name: Set build additional args
run: |
# Create general tags
if [[ "${{ needs.init.outputs.version }}" =~ d ]]; then
echo "BUILD_ARGS=--additional-tag dev" >> $GITHUB_ENV
elif [[ "${{ needs.init.outputs.version }}" =~ b ]]; then
echo "BUILD_ARGS=--additional-tag beta" >> $GITHUB_ENV
else
echo "BUILD_ARGS=--additional-tag stable" >> $GITHUB_ENV
fi
- name: Login to GitHub Container Registry
uses: docker/login-action@c94ce9fb468520275223c153574b00df6fe4bcc9 # v3.7.0
with:
@@ -244,12 +294,15 @@ jobs:
username: ${{ github.repository_owner }}
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build machine image
uses: ./.github/actions/builder/machine
# home-assistant/builder doesn't support sha pinning
- name: Build base image
uses: home-assistant/builder@2025.11.0
with:
machine: ${{ matrix.machine.name }}
version: ${{ needs.init.outputs.version }}
arch: ${{ matrix.machine.arch }}
args: |
$BUILD_ARGS \
--target /data/machine \
--cosign \
--machine "${{ needs.init.outputs.version }}=${{ matrix.machine }}"
publish_ha:
name: Publish version files
@@ -302,10 +355,7 @@ jobs:
matrix:
registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"]
steps:
- name: Install Cosign
uses: sigstore/cosign-installer@faadad0cce49287aee09b3a48701e75088a2c6ad # v4.0.0
with:
cosign-release: "v2.5.3"
- *install_cosign
- name: Login to DockerHub
if: matrix.registry == 'docker.io/homeassistant'

View File

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

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.*
@@ -435,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.*

4
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
@@ -1355,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

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

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

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

@@ -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,
)
@@ -622,21 +626,34 @@ class AnthropicBaseLLMEntity(Entity):
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:

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

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

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

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

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

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

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

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

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

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

@@ -58,14 +58,14 @@
"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"
}
},

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

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

View File

@@ -126,5 +126,5 @@ class ComedHourlyPricingSensor(SensorEntity):
except (TimeoutError, aiohttp.ClientError) as err:
_LOGGER.error("Could not get data from ComEd API: %s", err)
except (ValueError, KeyError):
except ValueError, KeyError:
_LOGGER.warning("Could not update status for %s", self.name)

View File

@@ -186,7 +186,7 @@ class CompensationSensor(SensorEntity):
y_value = self._poly(x_value)
self._attr_native_value = round(y_value, self._precision)
except (ValueError, TypeError):
except ValueError, TypeError:
self._attr_native_value = None
if self._source_attribute:
_LOGGER.warning(

View File

@@ -421,7 +421,7 @@ def config_entries_flow_subscribe(
config_entries.SOURCE_USER,
)
]
except (ValueError, TypeError):
except ValueError, TypeError:
# If we can't serialize, we'll filter out unserializable flows
serialized_flows = []
for flw in hass.config_entries.flow.async_progress():
@@ -434,7 +434,7 @@ def config_entries_flow_subscribe(
serialized_flows.append(
json_bytes({"type": None, "flow_id": flw["flow_id"], "flow": flw})
)
except (ValueError, TypeError):
except ValueError, TypeError:
_LOGGER.error(
"Unable to serialize to JSON. Bad data found at %s",
format_unserializable_data(

View File

@@ -38,6 +38,8 @@ CONTROL4_CURRENT_TEMPERATURE = "TEMPERATURE_F"
CONTROL4_HUMIDITY = "HUMIDITY"
CONTROL4_COOL_SETPOINT = "COOL_SETPOINT_F"
CONTROL4_HEAT_SETPOINT = "HEAT_SETPOINT_F"
CONTROL4_FAN_MODE = "FAN_MODE"
CONTROL4_FAN_MODES_LIST = "FAN_MODES_LIST"
VARIABLES_OF_INTEREST = {
CONTROL4_HVAC_STATE,
@@ -46,6 +48,8 @@ VARIABLES_OF_INTEREST = {
CONTROL4_HUMIDITY,
CONTROL4_COOL_SETPOINT,
CONTROL4_HEAT_SETPOINT,
CONTROL4_FAN_MODE,
CONTROL4_FAN_MODES_LIST,
}
# Map Control4 HVAC modes to Home Assistant
@@ -153,12 +157,7 @@ class Control4Climate(Control4Entity, ClimateEntity):
_attr_has_entity_name = True
_attr_temperature_unit = UnitOfTemperature.FAHRENHEIT
_attr_supported_features = (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
| ClimateEntityFeature.TURN_ON
| ClimateEntityFeature.TURN_OFF
)
_attr_translation_key = "thermostat"
_attr_hvac_modes = [HVACMode.OFF, HVACMode.HEAT, HVACMode.COOL, HVACMode.HEAT_COOL]
def __init__(
@@ -201,6 +200,19 @@ class Control4Climate(Control4Entity, ClimateEntity):
"""Return the thermostat data from the coordinator."""
return self.coordinator.data.get(self._idx)
@property
def supported_features(self) -> ClimateEntityFeature:
"""Return the list of supported features."""
features = (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
| ClimateEntityFeature.TURN_ON
| ClimateEntityFeature.TURN_OFF
)
if self.fan_modes:
features |= ClimateEntityFeature.FAN_MODE
return features
@property
def current_temperature(self) -> float | None:
"""Return the current temperature."""
@@ -275,6 +287,28 @@ class Control4Climate(Control4Entity, ClimateEntity):
return data.get(CONTROL4_HEAT_SETPOINT)
return None
@property
def fan_mode(self) -> str | None:
"""Return the current fan mode."""
data = self._thermostat_data
if data is None:
return None
c4_fan_mode = data.get(CONTROL4_FAN_MODE)
if c4_fan_mode is None:
return None
return c4_fan_mode.lower()
@property
def fan_modes(self) -> list[str] | None:
"""Return the list of available fan modes."""
data = self._thermostat_data
if data is None:
return None
modes = data.get(CONTROL4_FAN_MODES_LIST)
if not modes:
return None
return [m.strip().lower() for m in modes.split(",") if m.strip()]
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new target HVAC mode."""
c4_hvac_mode = HA_TO_C4_HVAC_MODE[hvac_mode]
@@ -303,3 +337,9 @@ class Control4Climate(Control4Entity, ClimateEntity):
await c4_climate.setHeatSetpointF(temp)
await self.coordinator.async_request_refresh()
async def async_set_fan_mode(self, fan_mode: str) -> None:
"""Set new target fan mode."""
c4_climate = self._create_api_object()
await c4_climate.setFanMode(fan_mode.title())
await self.coordinator.async_request_refresh()

View File

@@ -74,7 +74,7 @@ class Control4ConfigFlow(ConfigFlow, domain=DOMAIN):
director_bearer_token = (
await account.getDirectorBearerToken(controller_unique_id)
)["token"]
except (BadCredentials, Unauthorized):
except BadCredentials, Unauthorized:
errors["base"] = "invalid_auth"
return errors, data, description_placeholders
except NotFound:
@@ -97,7 +97,7 @@ class Control4ConfigFlow(ConfigFlow, domain=DOMAIN):
except Unauthorized:
errors["base"] = "director_auth_failed"
return errors, data, description_placeholders
except (ClientError, TimeoutError):
except ClientError, TimeoutError:
errors["base"] = "cannot_connect"
description_placeholders["host"] = host
return errors, data, description_placeholders

View File

@@ -0,0 +1,15 @@
{
"entity": {
"climate": {
"thermostat": {
"state_attributes": {
"fan_mode": {
"state": {
"circulate": "mdi:fan-clock"
}
}
}
}
}
}
}

View File

@@ -21,6 +21,19 @@
}
}
},
"entity": {
"climate": {
"thermostat": {
"state_attributes": {
"fan_mode": {
"state": {
"circulate": "Circulate"
}
}
}
}
}
},
"options": {
"step": {
"init": {

View File

@@ -48,9 +48,11 @@ SENSOR_DESCRIPTIONS: tuple[CookidooSensorEntityDescription, ...] = (
key=CookidooSensor.SUBSCRIPTION,
translation_key=CookidooSensor.SUBSCRIPTION,
value_fn=(
lambda data: SUBSCRIPTION_MAP[data.subscription.type]
if data.subscription
else SUBSCRIPTION_MAP["NONE"]
lambda data: (
SUBSCRIPTION_MAP[data.subscription.type]
if data.subscription
else SUBSCRIPTION_MAP["NONE"]
)
),
entity_category=EntityCategory.DIAGNOSTIC,
options=list(SUBSCRIPTION_MAP.values()),
@@ -60,9 +62,11 @@ SENSOR_DESCRIPTIONS: tuple[CookidooSensorEntityDescription, ...] = (
key=CookidooSensor.EXPIRES,
translation_key=CookidooSensor.EXPIRES,
value_fn=(
lambda data: dt_util.parse_datetime(data.subscription.expires)
if data.subscription
else None
lambda data: (
dt_util.parse_datetime(data.subscription.expires)
if data.subscription
else None
)
),
entity_category=EntityCategory.DIAGNOSTIC,
device_class=SensorDeviceClass.TIMESTAMP,

View File

@@ -93,7 +93,7 @@ class FlowHandler(ConfigFlow, domain=DOMAIN):
password=password,
ssl_context=client_context_no_verify(),
)
except (TimeoutError, ClientError):
except TimeoutError, ClientError:
self.host = None
return self.async_show_form(
step_id="user",

View File

@@ -142,7 +142,7 @@ async def validate_datadog_connection(
try:
client = DogStatsd(user_input[CONF_HOST], user_input[CONF_PORT])
await hass.async_add_executor_job(client.increment, "connection_test")
except (OSError, ValueError):
except OSError, ValueError:
return False
else:
return True

View File

@@ -100,7 +100,7 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN):
async with asyncio.timeout(10):
self.bridges = await deconz_discovery(session)
except (TimeoutError, ResponseError):
except TimeoutError, ResponseError:
self.bridges = []
if LOGGER.isEnabledFor(logging.DEBUG):
@@ -158,7 +158,7 @@ class DeconzFlowHandler(ConfigFlow, domain=DOMAIN):
except LinkButtonNotPressed:
errors["base"] = "linking_not_possible"
except (ResponseError, RequestError, TimeoutError):
except ResponseError, RequestError, TimeoutError:
errors["base"] = "no_key"
else:

View File

@@ -87,7 +87,7 @@ class DelugeFlowHandler(ConfigFlow, domain=DOMAIN):
)
try:
await self.hass.async_add_executor_job(api.connect)
except (ConnectionRefusedError, TimeoutError, SSLError):
except ConnectionRefusedError, TimeoutError, SSLError:
return "cannot_connect"
except Exception as ex:
_LOGGER.exception("Unexpected error")

View File

@@ -46,7 +46,6 @@ async def async_setup_entry(
async_add_entities(
[
DemoLight(
available=True,
effect_list=LIGHT_EFFECT_LIST,
effect=LIGHT_EFFECT_LIST[0],
translation_key="bed_light",
@@ -55,21 +54,18 @@ async def async_setup_entry(
unique_id="light_1",
),
DemoLight(
available=True,
ct=LIGHT_TEMPS[1],
device_name="Ceiling Lights",
state=True,
unique_id="light_2",
),
DemoLight(
available=True,
hs_color=LIGHT_COLORS[1],
device_name="Kitchen Lights",
state=True,
unique_id="light_3",
),
DemoLight(
available=True,
ct=LIGHT_TEMPS[1],
device_name="Office RGBW Lights",
rgbw_color=(255, 0, 0, 255),
@@ -78,7 +74,6 @@ async def async_setup_entry(
unique_id="light_4",
),
DemoLight(
available=True,
device_name="Living Room RGBWW Lights",
rgbww_color=(255, 0, 0, 255, 0),
state=True,
@@ -86,7 +81,6 @@ async def async_setup_entry(
unique_id="light_5",
),
DemoLight(
available=True,
device_name="Entrance Color + White Lights",
hs_color=LIGHT_COLORS[1],
state=True,
@@ -112,7 +106,6 @@ class DemoLight(LightEntity):
unique_id: str,
device_name: str,
state: bool,
available: bool = False,
brightness: int = 180,
ct: int | None = None,
effect_list: list[str] | None = None,
@@ -125,128 +118,72 @@ class DemoLight(LightEntity):
) -> None:
"""Initialize the light."""
self._attr_translation_key = translation_key
self._available = True
self._brightness = brightness
self._ct = ct or random.choice(LIGHT_TEMPS)
self._effect = effect
self._effect_list = effect_list
self._hs_color = hs_color
self._rgbw_color = rgbw_color
self._rgbww_color = rgbww_color
self._state = state
self._unique_id = unique_id
self._attr_brightness = brightness
self._attr_color_temp_kelvin = ct or random.choice(LIGHT_TEMPS)
self._attr_effect = effect
self._attr_effect_list = effect_list
self._attr_hs_color = hs_color
self._attr_rgbw_color = rgbw_color
self._attr_rgbww_color = rgbww_color
self._attr_is_on = state
self._attr_unique_id = unique_id
if hs_color:
self._color_mode = ColorMode.HS
self._attr_color_mode = ColorMode.HS
elif rgbw_color:
self._color_mode = ColorMode.RGBW
self._attr_color_mode = ColorMode.RGBW
elif rgbww_color:
self._color_mode = ColorMode.RGBWW
self._attr_color_mode = ColorMode.RGBWW
else:
self._color_mode = ColorMode.COLOR_TEMP
self._attr_color_mode = ColorMode.COLOR_TEMP
if not supported_color_modes:
supported_color_modes = SUPPORT_DEMO
self._color_modes = supported_color_modes
if self._effect_list is not None:
self._attr_supported_color_modes = supported_color_modes
if self._attr_effect_list is not None:
self._attr_supported_features |= LightEntityFeature.EFFECT
self._attr_device_info = DeviceInfo(
identifiers={
# Serial numbers are unique identifiers within a specific domain
(DOMAIN, self.unique_id)
(DOMAIN, unique_id)
},
name=device_name,
)
@property
def unique_id(self) -> str:
"""Return unique ID for light."""
return self._unique_id
@property
def available(self) -> bool:
"""Return availability."""
# This demo light is always available, but well-behaving components
# should implement this to inform Home Assistant accordingly.
return self._available
@property
def brightness(self) -> int:
"""Return the brightness of this light between 0..255."""
return self._brightness
@property
def color_mode(self) -> ColorMode | None:
"""Return the color mode of the light."""
return self._color_mode
@property
def hs_color(self) -> tuple[int, int] | None:
"""Return the hs color value."""
return self._hs_color
@property
def rgbw_color(self) -> tuple[int, int, int, int] | None:
"""Return the rgbw color value."""
return self._rgbw_color
@property
def rgbww_color(self) -> tuple[int, int, int, int, int] | None:
"""Return the rgbww color value."""
return self._rgbww_color
@property
def color_temp_kelvin(self) -> int | None:
"""Return the color temperature value in Kelvin."""
return self._ct
@property
def effect_list(self) -> list[str] | None:
"""Return the list of supported effects."""
return self._effect_list
@property
def effect(self) -> str | None:
"""Return the current effect."""
return self._effect
@property
def is_on(self) -> bool:
"""Return true if light is on."""
return self._state
@property
def supported_color_modes(self) -> set[ColorMode]:
"""Flag supported color modes."""
return self._color_modes
return True
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the light on."""
self._state = True
self._attr_is_on = True
if ATTR_BRIGHTNESS in kwargs:
self._brightness = kwargs[ATTR_BRIGHTNESS]
self._attr_brightness = kwargs[ATTR_BRIGHTNESS]
if ATTR_COLOR_TEMP_KELVIN in kwargs:
self._color_mode = ColorMode.COLOR_TEMP
self._ct = kwargs[ATTR_COLOR_TEMP_KELVIN]
self._attr_color_mode = ColorMode.COLOR_TEMP
self._attr_color_temp_kelvin = kwargs[ATTR_COLOR_TEMP_KELVIN]
if ATTR_EFFECT in kwargs:
self._effect = kwargs[ATTR_EFFECT]
self._attr_effect = kwargs[ATTR_EFFECT]
if ATTR_HS_COLOR in kwargs:
self._color_mode = ColorMode.HS
self._hs_color = kwargs[ATTR_HS_COLOR]
self._attr_color_mode = ColorMode.HS
self._attr_hs_color = kwargs[ATTR_HS_COLOR]
if ATTR_RGBW_COLOR in kwargs:
self._color_mode = ColorMode.RGBW
self._rgbw_color = kwargs[ATTR_RGBW_COLOR]
self._attr_color_mode = ColorMode.RGBW
self._attr_rgbw_color = kwargs[ATTR_RGBW_COLOR]
if ATTR_RGBWW_COLOR in kwargs:
self._color_mode = ColorMode.RGBWW
self._rgbww_color = kwargs[ATTR_RGBWW_COLOR]
self._attr_color_mode = ColorMode.RGBWW
self._attr_rgbww_color = kwargs[ATTR_RGBWW_COLOR]
if ATTR_WHITE in kwargs:
self._color_mode = ColorMode.WHITE
self._brightness = kwargs[ATTR_WHITE]
self._attr_color_mode = ColorMode.WHITE
self._attr_brightness = kwargs[ATTR_WHITE]
# As we have disabled polling, we need to inform
# Home Assistant about updates in our state ourselves.
@@ -254,7 +191,7 @@ class DemoLight(LightEntity):
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn the light off."""
self._state = False
self._attr_is_on = False
# As we have disabled polling, we need to inform
# Home Assistant about updates in our state ourselves.

View File

@@ -199,7 +199,7 @@ class DenonAvrFlowHandler(ConfigFlow, domain=DOMAIN):
try:
success = await connect_denonavr.async_connect_receiver()
except (AvrNetworkError, AvrTimoutError):
except AvrNetworkError, AvrTimoutError:
success = False
if not success:
return self.async_abort(reason="cannot_connect")

View File

@@ -7,7 +7,7 @@
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["denonavr"],
"requirements": ["denonavr==1.3.1"],
"requirements": ["denonavr==1.3.2"],
"ssdp": [
{
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",

View File

@@ -17,6 +17,7 @@ from denonavr.const import (
STATE_ON,
STATE_PAUSED,
STATE_PLAYING,
STATE_STOPPED,
)
from denonavr.exceptions import (
AvrCommandError,
@@ -69,6 +70,7 @@ SUPPORT_MEDIA_MODES = (
| MediaPlayerEntityFeature.NEXT_TRACK
| MediaPlayerEntityFeature.VOLUME_SET
| MediaPlayerEntityFeature.PLAY
| MediaPlayerEntityFeature.STOP
)
SCAN_INTERVAL = timedelta(seconds=10)
@@ -96,6 +98,7 @@ DENON_STATE_MAPPING = {
STATE_OFF: MediaPlayerState.OFF,
STATE_PLAYING: MediaPlayerState.PLAYING,
STATE_PAUSED: MediaPlayerState.PAUSED,
STATE_STOPPED: MediaPlayerState.IDLE,
}
@@ -404,6 +407,11 @@ class DenonDevice(MediaPlayerEntity):
"""Send pause command."""
await self._receiver.async_pause()
@async_log_errors
async def async_media_stop(self) -> None:
"""Send stop command."""
await self._receiver.async_stop()
@async_log_errors
async def async_media_previous_track(self) -> None:
"""Send previous track command."""

View File

@@ -104,7 +104,7 @@ PLATFORM_SCHEMA = SENSOR_PLATFORM_SCHEMA.extend(
def _is_decimal_state(state: str) -> bool:
try:
Decimal(state)
except (InvalidOperation, TypeError):
except InvalidOperation, TypeError:
return False
else:
return True
@@ -220,8 +220,8 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
if max_sub_interval is None or max_sub_interval.total_seconds() == 0
else max_sub_interval
)
self._cancel_max_sub_interval_exceeded_callback: CALLBACK_TYPE = (
lambda *args: None
self._cancel_max_sub_interval_exceeded_callback: CALLBACK_TYPE = lambda *args: (
None
)
def _derive_and_set_attributes_from_state(self, source_state: State | None) -> None:
@@ -306,7 +306,7 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
Decimal(restored_data.native_value), # type: ignore[arg-type]
self._round_digits,
)
except (InvalidOperation, TypeError):
except InvalidOperation, TypeError:
self._attr_native_value = None
async def async_added_to_hass(self) -> None:

View File

@@ -86,11 +86,7 @@ class DevialetMediaPlayerEntity(
self._attr_media_position_updated_at = (
self.coordinator.client.position_updated_at
)
self._attr_media_title = (
self.coordinator.client.media_title
if self.coordinator.client.media_title
else self.source
)
self._attr_media_title = self.coordinator.client.media_title or self.source
self.async_write_ha_state()
@property

View File

@@ -311,7 +311,7 @@ async def _async_get_device_automation_capabilities(
try:
capabilities = await getattr(platform, function_name)(hass, automation)
except (EntityNotFound, InvalidDeviceAutomationConfig):
except EntityNotFound, InvalidDeviceAutomationConfig:
return {}
capabilities = capabilities.copy()

View File

@@ -895,7 +895,7 @@ class Device(RestoreEntity):
try:
self.gps = float(gps[0]), float(gps[1])
self.gps_accuracy = gps_accuracy or 0
except (ValueError, TypeError, IndexError):
except ValueError, TypeError, IndexError:
self.gps = None
self.gps_accuracy = 0
LOGGER.warning("Could not parse gps value for %s: %s", self.dev_id, gps)

View File

@@ -87,7 +87,7 @@ async def _async_try_connect(token: str) -> tuple[str | None, nextcord.AppInfo |
info = await discord_bot.application_info()
except nextcord.LoginFailure:
return "invalid_auth", None
except (ClientConnectorError, nextcord.HTTPException, nextcord.NotFound):
except ClientConnectorError, nextcord.HTTPException, nextcord.NotFound:
return "cannot_connect", None
except Exception:
_LOGGER.exception("Unexpected exception")

View File

@@ -72,7 +72,7 @@ class DiscovergyConfigFlow(ConfigFlow, domain=DOMAIN):
httpx_client=get_async_client(self.hass),
authentication=BasicAuth(),
).meters()
except (discovergyError.HTTPError, discovergyError.DiscovergyClientError):
except discovergyError.HTTPError, discovergyError.DiscovergyClientError:
errors["base"] = "cannot_connect"
except discovergyError.InvalidLogin:
errors["base"] = "invalid_auth"

View File

@@ -260,7 +260,7 @@ class DlnaDmrEntity(MediaPlayerEntity):
try:
bootid_str = info.ssdp_headers[ssdp.ATTR_SSDP_BOOTID]
bootid: int | None = int(bootid_str, 10)
except (KeyError, ValueError):
except KeyError, ValueError:
bootid = None
if change == ssdp.SsdpChange.UPDATE:

View File

@@ -234,7 +234,7 @@ class DmsDeviceSource:
try:
bootid_str = info.ssdp_headers[ssdp.ATTR_SSDP_BOOTID]
bootid: int | None = int(bootid_str, 10)
except (KeyError, ValueError):
except KeyError, ValueError:
bootid = None
if change == ssdp.SsdpChange.UPDATE:
@@ -245,7 +245,7 @@ class DmsDeviceSource:
try:
next_bootid_str = info.ssdp_headers[ssdp.ATTR_SSDP_NEXTBOOTID]
self._bootid = int(next_bootid_str, 10)
except (KeyError, ValueError):
except KeyError, ValueError:
pass
# Nothing left to do until ssdp:alive comes through
return
@@ -567,7 +567,7 @@ class DmsDeviceSource:
# can_play is False).
try:
child_count = int(item.child_count)
except (AttributeError, TypeError, ValueError):
except AttributeError, TypeError, ValueError:
child_count = 0
can_expand = (
bool(children) or child_count > 0 or isinstance(item, didl_lite.Container)

View File

@@ -36,8 +36,10 @@ BINARY_SENSOR_DESCRIPTIONS = (
key="security_locked",
translation_key="deadbolt",
device_class=BinarySensorDeviceClass.LOCK,
is_on=lambda state: state.unlock_status
not in (UnlockStatus.SECURITY_LOCKED, UnlockStatus.UNLOCKED_SECURITY_LOCKED),
is_on=lambda state: (
state.unlock_status
not in (UnlockStatus.SECURITY_LOCKED, UnlockStatus.UNLOCKED_SECURITY_LOCKED)
),
),
)

View File

@@ -40,7 +40,7 @@ class Dremel3DPrinterConfigFlow(ConfigFlow, domain=DOMAIN):
try:
api = await self.hass.async_add_executor_job(Dremel3DPrinter, host)
except (ConnectTimeout, HTTPError, JSONDecodeError):
except ConnectTimeout, HTTPError, JSONDecodeError:
errors = {"base": "cannot_connect"}
except Exception: # noqa: BLE001
LOGGER.exception("An unknown error has occurred")

View File

@@ -120,7 +120,7 @@ class DSMRConnection:
try:
transport, protocol = await asyncio.create_task(reader_factory())
except (serial.SerialException, OSError):
except serial.SerialException, OSError:
LOGGER.exception("Error connecting to DSMR")
return False

View File

@@ -837,7 +837,7 @@ async def async_setup_entry(
# throttle reconnect attempts
await asyncio.sleep(DEFAULT_RECONNECT_INTERVAL)
except (serial.SerialException, OSError):
except serial.SerialException, OSError:
# Log any error while establishing connection and drop to retry
# connection wait
LOGGER.exception("Error connecting to DSMR")

View File

@@ -44,7 +44,7 @@ class DukeEnergyConfigFlow(ConfigFlow, domain=DOMAIN):
auth = await api.authenticate()
except ClientResponseError as e:
errors["base"] = "invalid_auth" if e.status == 404 else "cannot_connect"
except (ClientError, TimeoutError):
except ClientError, TimeoutError:
errors["base"] = "cannot_connect"
except Exception:
_LOGGER.exception("Unexpected exception")

View File

@@ -214,7 +214,7 @@ class DukeEnergyCoordinator(DataUpdateCoordinator[None]):
# Make sure we don't go back too far
if end_step < start:
break
except (TimeoutError, ClientError):
except TimeoutError, ClientError:
# ClientError is raised when there is no more data for the range
break

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
from datetime import date, datetime
from enum import Enum
from enum import StrEnum
from functools import partial
from typing import Final
@@ -47,7 +47,7 @@ SERVICE_SCHEMA: Final = vol.Schema(
)
class PriceType(str, Enum):
class PriceType(StrEnum):
"""Type of price."""
ENERGY_USAGE = "energy_usage"

View File

@@ -68,7 +68,7 @@ def setup(hass: HomeAssistant, config: ConfigType) -> bool:
try:
ebusdpy.init(server_address)
except (TimeoutError, OSError):
except TimeoutError, OSError:
return False
hass.data[EBUSD_DATA] = EbusdData(server_address, circuit)
sensor_config = {

View File

@@ -619,9 +619,7 @@ class Thermostat(ClimateEntity):
return [
{
"id": device.id,
"name_by_user": device.name_by_user
if device.name_by_user
else device.name,
"name_by_user": device.name_by_user or device.name,
}
for device in device_registry.devices.values()
for sensor_info in sensors_info
@@ -924,7 +922,7 @@ class Thermostat(ClimateEntity):
sensor_names = self._sensors_in_preset_mode(preset_mode)
return sorted(
[
device.name_by_user if device.name_by_user else device.name
device.name_by_user or device.name
for device in device_registry.devices.values()
for sensor_name in sensor_names
if device.name == sensor_name

View File

@@ -212,7 +212,7 @@ def _process_forecast(json):
if json["windSpeed"] != ECOBEE_STATE_UNKNOWN:
forecast[ATTR_FORECAST_NATIVE_WIND_SPEED] = int(json["windSpeed"])
except (ValueError, IndexError, KeyError):
except ValueError, IndexError, KeyError:
return None
if forecast:

View File

@@ -164,7 +164,7 @@ class EcovacsActiveMapSelectEntity(
self._option_to_id.clear()
for map_info in event.maps:
name = map_info.name if map_info.name else map_info.id
name = map_info.name or map_info.id
self._id_to_option[map_info.id] = name
self._option_to_id[name] = map_info.id

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