Compare commits

...

223 Commits

Author SHA1 Message Date
Franck Nijhof
3e6473d130 2025.5.3 (#145516) 2025-05-23 17:09:32 +02:00
Franck Nijhof
9a183bc16a Bump version to 2025.5.3 2025-05-23 14:03:47 +00:00
Robert Resch
e540247c14 Bump deebot-client to 13.2.1 (#145492) 2025-05-23 14:03:02 +00:00
rappenze
0aef8b58d8 Bump pyfibaro to 0.8.3 (#145488) 2025-05-23 14:03:01 +00:00
tronikos
f0501f917b Fix strings related to Google search tool in Google AI (#145480) 2025-05-23 14:02:59 +00:00
tronikos
97004e13cb Make Gemma models work in Google AI (#145479)
* Make Gemma models work in Google AI

* move one line to be improve readability
2025-05-23 14:02:58 +00:00
tronikos
f867a0af24 Bump opower to 0.12.1 (#145464) 2025-05-23 14:02:57 +00:00
Joost Lekkerkerker
d3b3839ffa Bump pysmartthings to 3.2.3 (#145444) 2025-05-23 14:02:56 +00:00
starkillerOG
1a227d6a10 Reolink fix device migration (#145443) 2025-05-23 14:02:54 +00:00
Joost Lekkerkerker
fc8c403a3a Bump yt-dlp to 2025.05.22 (#145441) 2025-05-23 14:02:53 +00:00
Josef Zweck
c1bf596eba Mark backflush binary sensor not supported for GS3 MP in lamarzocco (#145406) 2025-05-23 14:02:52 +00:00
Michael
63f69a9e3d Bump py-synologydsm-api to 2.7.2 (#145403)
bump py-synologydsm-api to 2.7.2
2025-05-23 14:02:51 +00:00
Josef Zweck
e13b014b6f Bump pylamarzocco to 2.0.4 (#145402) 2025-05-23 14:02:49 +00:00
c0ffeeca7
be0d4d926c OTBR: remove links to obsolete multiprotocol docs (#145394) 2025-05-23 14:02:48 +00:00
Raj Laud
2403fff81f Bump pysqueezebox to v0.12.1 (#145384) 2025-05-23 14:02:47 +00:00
Andy
8c475787cc Fix: Revert Ecovacs mower total_stats_area unit to square meters (#145380) 2025-05-23 14:02:45 +00:00
peteS-UK
d9fe1edd82 Add initial coordinator refresh for players in Squeezebox (#145347)
* initial

* add test for new player
2025-05-23 14:02:44 +00:00
Michael
f5cf64700a Fix limit of shown backups on Synology DSM location (#145342) 2025-05-23 14:02:43 +00:00
Josef Zweck
777b04d7a5 Handle more exceptions in azure_storage (#145320) 2025-05-23 14:02:41 +00:00
Josef Zweck
9fc78ed4e2 Add cloud as after_dependency to onedrive (#145301) 2025-05-23 14:02:40 +00:00
Matthew FitzGerald-Chamberlain
d03af549d4 Bump pyaprilaire to 0.9.0 (#145260) 2025-05-23 14:02:39 +00:00
G Johansson
d91f01243c Bump holidays to 0.73 (#145238) 2025-05-23 14:02:38 +00:00
Martin Hjelmare
5094208db6 Fix Z-Wave config entry unique id after NVM restore (#145221)
* Fix Z-Wave config entry unique id after NVM restore

* Remove stale comment
2025-05-23 14:01:37 +00:00
Simone Chemelli
006f66a841 Bump aiocomelit to 0.12.3 (#145209) 2025-05-23 14:01:36 +00:00
Maikel Punie
64b7d77840 Bump velbusaio to 2025.5.0 (#145198) 2025-05-23 14:01:35 +00:00
Martin Hjelmare
abf6a809b8 Fix Z-Wave unique id update during controller migration (#145185) 2025-05-23 14:01:33 +00:00
Martin Hjelmare
1b7dd205c7 Improve Z-Wave config flow tests (#144871)
* Improve Z-Wave config flow tests

* Fix test

* Use identify check for result type
2025-05-23 14:01:32 +00:00
Keilin Bickar
3e00366a61 Bump sense-energy to 0.13.8 (#145156) 2025-05-23 13:50:34 +00:00
karwosts
a17275b559 Fix history_stats with sliding window that ends before now (#145117) 2025-05-23 13:50:33 +00:00
Jan-Philipp Benecke
9534a919ce Add missing device condition translations to lock component (#145104) 2025-05-23 13:45:44 +00:00
Joost Lekkerkerker
422dbfef88 Map auto to heat_cool for thermostat in SmartThings (#145098) 2025-05-23 13:45:43 +00:00
Robert Resch
8e44684a61 Fix proberly Ecovacs mower area sensors (#145078) 2025-05-23 13:45:41 +00:00
Manu
642e7fd487 Bump aiontfy to 0.5.2 (#145044) 2025-05-23 13:45:40 +00:00
peteS-UK
9bb9132e7b Fix album and artist returning "None" rather than None for Squeezebox media player. (#144971)
* fix

* snapshot update

* cast type
2025-05-23 13:45:39 +00:00
J. Nick Koston
41be82f167 Bump ESPHome stable BLE version to 2025.5.0 (#144857) 2025-05-23 13:45:37 +00:00
Marc Hörsken
47140e14d9 Postpone update in WMSPro after service call (#144836)
* Reduce stress on WMS WebControl pro with higher scan interval

Avoid delays and connection issues due to overloaded hub.
Fixes #133832 and #134413

* Schedule an entity state update after performing an action

Avoid delaying immediate status updates, e.g. on/off changes.

* Replace scheduled state updates with delayed action completion

Suggested-by: joostlek
2025-05-23 13:45:36 +00:00
TheOneValen
926502b0f1 Allow image send with read-only access (matrix notify) (#144819) 2025-05-23 13:45:35 +00:00
disforw
78351ff7a7 Fix QNAP fail to load (#144675)
* Update coordinator.py

* Update coordinator.py

@peternash

* Update coordinator.py

* Update coordinator.py

* Update coordinator.py

* Update coordinator.py
2025-05-23 13:45:33 +00:00
wuede
c333726867 Netatmo: do not fail on schedule updates (#142933)
* do not fail on schedule updates

* add test to check that the store data remains unchanged
2025-05-23 13:45:32 +00:00
Franck Nijhof
f66feabaaf 2025.5.2 (#145072)
Co-authored-by: Shay Levy <levyshay1@gmail.com>
Co-authored-by: Allen Porter <allen.porter@gmail.com>
Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: TimL <tl@smlight.tech>
Co-authored-by: Seweryn Zeman <seweryn.zeman@jazzy.pro>
Co-authored-by: hahn-th <15319212+hahn-th@users.noreply.github.com>
Co-authored-by: Luke Lashley <conway220@gmail.com>
Co-authored-by: starkillerOG <starkiller.og@gmail.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Arie Catsman <120491684+catsmanac@users.noreply.github.com>
Co-authored-by: Josef Zweck <josef@zweck.dev>
Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
Co-authored-by: Ruben van Dijk <15885455+RubenNL@users.noreply.github.com>
Co-authored-by: G Johansson <goran.johansson@shiftit.se>
Co-authored-by: Simone Chemelli <simone.chemelli@gmail.com>
Co-authored-by: Thomas55555 <59625598+Thomas55555@users.noreply.github.com>
Co-authored-by: Øyvind Matheson Wergeland <oyvind@wergeland.org>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
Co-authored-by: Brett Adams <Bre77@users.noreply.github.com>
Co-authored-by: rjblake <richard.blake@gmail.com>
Co-authored-by: Daniel Hjelseth Høyer <github@dahoiv.net>
Co-authored-by: Matthias Alphart <farmio@alphart.net>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: Robert Resch <robert@resch.dev>
Co-authored-by: Odd Stråbø <oddstr13@openshell.no>
Co-authored-by: puddly <32534428+puddly@users.noreply.github.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
fix privacy mode availability for NVR IPC cams (#144569)
fix enphase_envoy diagnostics home endpoint name (#144634)
Close Octoprint aiohttp session on unload (#144670)
Fix strings typo for Comelit (#144672)
Fix wrong state in Husqvarna Automower (#144684)
Fix Netgear handeling of missing MAC in device registry (#144722)
Fix blocking call in azure storage (#144803)
Fix Z-Wave unique id after controller reset (#144813)
Fix blocking call in azure_storage config flow (#144818)
Fix wall connector states in Teslemetry (#144855)
Fix Reolink setup when ONVIF push is unsupported (#144869)
Fix some Home Connect translation strings (#144905)
Fix unknown Pure AQI in Sensibo (#144924)
Fix Home Assistant Yellow config entry data (#144948)
Fix ESPHome entities unavailable if deep sleep enabled after entry setup (#144970)
fix from ZHA event `unique_id` (#145006)
Fix climate idle state for Comelit (#145059)
Fix fan AC mode in SmartThings AC (#145064)
Fix Ecovacs mower area sensors (#145071)
2025-05-16 23:08:52 +02:00
Franck Nijhof
0ef098a9f3 Pin rpds-py to 0.24.0 (#145074) 2025-05-16 20:40:02 +00:00
Franck Nijhof
02b028add3 Bump version to 2025.5.2 2025-05-16 19:31:36 +00:00
Robert Resch
34455f9743 Fix Ecovacs mower area sensors (#145071) 2025-05-16 19:31:15 +00:00
Joost Lekkerkerker
8c4eec231f Don't create entities for Smartthings smarttags (#145066) 2025-05-16 19:31:14 +00:00
Joost Lekkerkerker
621a14d7cc Fix fan AC mode in SmartThings AC (#145064) 2025-05-16 19:31:12 +00:00
Joost Lekkerkerker
4906e78a5c Only set suggested area for new SmartThings devices (#145063) 2025-05-16 19:31:11 +00:00
Bram Kragten
146e440d59 Update frontend to 20250516.0 (#145062) 2025-05-16 19:31:10 +00:00
Joost Lekkerkerker
e2ede3ed19 Map SmartThings auto mode correctly (#145061) 2025-05-16 19:31:09 +00:00
Simone Chemelli
b76ac68fb1 Fix climate idle state for Comelit (#145059) 2025-05-16 19:31:07 +00:00
Joost Lekkerkerker
0691ad9362 Set SmartThings oven setpoint to unknown if its 1 Fahrenheit (#145038) 2025-05-16 19:31:06 +00:00
Joost Lekkerkerker
715f116954 Bump pySmartThings to 3.2.2 (#145033) 2025-05-16 19:31:05 +00:00
puddly
9f0db98745 Strip _CLIENT suffix from ZHA event unique_id (#145006) 2025-05-16 19:31:03 +00:00
Odd Stråbø
0ba55c31e8 Fix ESPHome entities unavailable if deep sleep enabled after entry setup (#144970) 2025-05-16 19:31:02 +00:00
Robert Resch
19b7cfbd4a Bump deebot-client to 13.2.0 (#144957) 2025-05-16 19:31:01 +00:00
Erik Montnemery
a9520888cf Fix Home Assistant Yellow config entry data (#144948) 2025-05-16 19:31:00 +00:00
Matthias Alphart
f086f4a955 Ignore Fronius Gen24 firmware 1.35.4-1 SSL verification issue for new setups (#144940) 2025-05-16 19:30:59 +00:00
G Johansson
a657964c25 Fix unknown Pure AQI in Sensibo (#144924)
* Fix unknown Pure AQI in Sensibo

* Fix mypy
2025-05-16 19:30:57 +00:00
Daniel Hjelseth Høyer
543104b36c Update mill library 0.12.5 (#144911)
* Update mill library 0.12.5

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

* Update mill library 0.12.5

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>

---------

Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>
2025-05-16 19:30:56 +00:00
Daniel Hjelseth Høyer
bf1d2069e4 Update Tibber lib 0.31.2 (#144908)
Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>
2025-05-16 19:30:55 +00:00
rjblake
e5e1c9fb05 Fix some Home Connect translation strings (#144905)
* Update strings.json

Corrected program names:
changed "Pre_rinse" to "Pre-Rinse"
changed "Kurz 60°C" to "Speed 60°C"

Both match the Home Connect app; although the UK documentation refers to "Speed 60°C" as "Quick 60°C"

* Adjust casing

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-05-16 19:30:53 +00:00
starkillerOG
4c4be88323 Fix Reolink setup when ONVIF push is unsupported (#144869)
* Fix setup when ONVIF push is not supported

* fix styling
2025-05-16 19:30:52 +00:00
Brett Adams
5a83627dc5 Fix wall connector states in Teslemetry (#144855)
* Fix wall connector

* Update snapshot
2025-05-16 19:30:51 +00:00
Allen Porter
3123a7b168 Bump ical to 9.2.4 (#144852) 2025-05-16 19:30:50 +00:00
Luke Lashley
8161ce6ea8 Bump python-snoo to 0.6.6 (#144849) 2025-05-16 19:30:49 +00:00
Josef Zweck
d9cbd1b65f Bump pylamarzocco to 2.0.3 (#144825) 2025-05-16 19:30:47 +00:00
Josef Zweck
b7c07209b8 Fix blocking call in azure_storage config flow (#144818)
* Fix blocking call in azure_storage config flow

* Fix blocking call in azure_storage config_flow as well

* move session getting to event flow
2025-05-16 19:30:46 +00:00
Martin Hjelmare
6c3a4f17f0 Fix Z-Wave unique id after controller reset (#144813) 2025-05-16 19:30:45 +00:00
Josef Zweck
d82feb807f Fix blocking call in azure storage (#144803) 2025-05-16 19:30:43 +00:00
Jan Bouwhuis
c373fa9296 Do not show an empty component name on MQTT device subentries not as None if it is not set (#144792) 2025-05-16 19:30:42 +00:00
starkillerOG
139b48440f Cleanup wrongly combined Reolink devices (#144771) 2025-05-16 19:30:41 +00:00
Joost Lekkerkerker
9de1d3b143 Fill in Plaato URL via placeholders (#144754) 2025-05-16 19:29:35 +00:00
Martin Hjelmare
b69ebdaecb Repair Z-Wave unknown controller (#144738)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-05-16 19:23:25 +00:00
starkillerOG
f25e50b017 Fix Netgear handeling of missing MAC in device registry (#144722) 2025-05-16 19:23:24 +00:00
Simone Chemelli
a4a7601f9f Bump aiocomelit to 0.12.1 (#144720) 2025-05-16 19:23:22 +00:00
Øyvind Matheson Wergeland
41a503f76f Bump gcal-sync to 7.0.1 (#144718)
Co-authored-by: Allen Porter <allen.porter@gmail.com>
2025-05-16 19:23:21 +00:00
Allen Porter
f1a3d62db2 Bump ical to 9.2.2 (#144713) 2025-05-16 19:23:20 +00:00
Allen Porter
e465276464 Bump voluptuous-openapi to 0.1.0 (#144703) 2025-05-16 19:23:18 +00:00
Thomas55555
47b45444eb Fix wrong state in Husqvarna Automower (#144684) 2025-05-16 19:22:02 +00:00
Simone Chemelli
cf0911cc56 Avoid closing shared session for Comelit (#144682) 2025-05-16 19:22:00 +00:00
Simone Chemelli
da79d5b2e3 Fix strings typo for Comelit (#144672) 2025-05-16 19:21:59 +00:00
G Johansson
358b0c1c17 Bump holidays to 0.72 (#144671) 2025-05-16 19:21:58 +00:00
Ruben van Dijk
543348fe58 Close Octoprint aiohttp session on unload (#144670) 2025-05-16 19:21:57 +00:00
Simon Lamon
0635856761 Bump python-linkplay to v0.2.5 (#144666)
Bump linkplay to 0.2.5
2025-05-16 19:21:55 +00:00
Allen Porter
081afe6034 Bump ical to 9.2.1 (#144642) 2025-05-16 19:21:54 +00:00
Arie Catsman
ca14322227 bump pyenphase to 1.26.1 (#144641) 2025-05-16 19:21:53 +00:00
Josef Zweck
a54816a6e5 Bump pylamarzocco to 2.0.2 (#144635)
Co-authored-by: Shay Levy <levyshay1@gmail.com>
2025-05-16 19:21:51 +00:00
Arie Catsman
27db4e90b5 fix enphase_envoy diagnostics home endpoint name (#144634) 2025-05-16 19:21:50 +00:00
J. Nick Koston
e9cc624d93 Mark inkbird coordinator as not needing connectable (#144584) 2025-05-16 19:19:59 +00:00
starkillerOG
5a95f43992 Bump reolink_aio to 0.13.3 (#144583) 2025-05-16 19:19:58 +00:00
J. Nick Koston
36a35132c0 Bump aiodiscover to 2.7.0 (#144571) 2025-05-16 19:19:57 +00:00
starkillerOG
2fbc75f89b Reolink fix privacy mode availability for NVR IPC cams (#144569)
* Correct "available" for IPC cams

* Check privacy mode when updating
2025-05-16 19:19:56 +00:00
Luke Lashley
48aa6be889 Don't scale Roborock mop Path (#144421)
don't scale mop path
2025-05-16 19:19:55 +00:00
hahn-th
bde04bc47b Doorbell Event is fired just once in homematicip_cloud (#144357)
* fire event if event type if correct

* Fix requested changes
2025-05-16 19:19:53 +00:00
Seweryn Zeman
7d163aa659 Removed unused file_id param from open_ai_conversation request (#143878) 2025-05-16 19:19:52 +00:00
TimL
010b044379 Allow dns hostnames to be retained for SMLIGHT user flow. (#142514)
* Dont overwrite host with local IP

* adjust test for user flow change
2025-05-16 19:19:50 +00:00
Franck Nijhof
00627b82e0 2025.5.1 (#144564) 2025-05-09 17:03:40 +02:00
Franck Nijhof
13aba6201e Bump version to 2025.5.1 2025-05-09 13:29:29 +00:00
starkillerOG
f392e0c1c7 Prevent errors during cleaning of connections/identifiers in device registry (#144558) 2025-05-09 13:28:33 +00:00
starkillerOG
181eca6c82 Reolink clean device registry mac (#144554) 2025-05-09 13:28:32 +00:00
Bram Kragten
196d923ac6 Update frontend to 20250509.0 (#144549) 2025-05-09 13:28:30 +00:00
Josef Zweck
4ad387c967 Fix statistics coordinator subscription for lamarzocco (#144541) 2025-05-09 13:28:29 +00:00
J. Nick Koston
cb475bf153 Bump aiodns to 3.4.0 (#144511) 2025-05-09 13:28:28 +00:00
Michael
47acceea08 Fix removing of smarthome templates on startup of AVM Fritz!SmartHome integration (#144506) 2025-05-09 13:28:26 +00:00
J. Nick Koston
fd6fb7e3bc Bump forecast-solar to 4.2.0 (#144502) 2025-05-09 13:28:25 +00:00
Erik Montnemery
30f7e9b441 Don't encrypt or decrypt unknown files in backup archives (#144495) 2025-05-09 13:28:24 +00:00
Matthias Alphart
a8beec2691 Ignore Fronius Gen24 firmware 1.35.4-1 SSL verification issue (#144463) 2025-05-09 13:28:23 +00:00
Fredrik Erlandsson
23244fb79f Fix point import error (#144462)
* fix import error

* fix failing tests

* Apply suggestions from code review

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-09 13:28:22 +00:00
Martin Hjelmare
e5c56629e2 Fix Z-Wave reset accumulated values button entity category (#144459) 2025-05-09 13:28:20 +00:00
Josef Zweck
a793503c8a Bump pylamarzocco to 2.0.1 (#144454) 2025-05-09 13:28:19 +00:00
DukeChocula
054c7a0adc Add LAP-V102S-AUSR to VeSync (#144437)
Update const.py

Added LAP-V102S-AUSR to Vital 100S
2025-05-09 13:28:18 +00:00
Tamer Wahba
6eb2d1aa7c fix homekit air purifier temperature sensor to convert unit (#144435) 2025-05-09 13:28:16 +00:00
Martin Hjelmare
619fdea5df Fix Z-Wave restore nvm command to wait for driver ready (#144413) 2025-05-09 13:28:15 +00:00
Franck Nijhof
e8bdc7286e 2025.5.0 (#144406) 2025-05-07 19:13:53 +02:00
Franck Nijhof
18f2b120ef Bump version to 2025.5.0 2025-05-07 14:31:26 +00:00
Michael Hansen
43d8345821 Bump intents to 2025.5.7 (#144404) 2025-05-07 14:30:48 +00:00
Franck Nijhof
999e930fc8 Bump version to 2025.5.0b10 2025-05-07 13:04:15 +00:00
Petar Petrov
d4e99efc46 Add more missing device_class translations for template helper (#144399) 2025-05-07 13:04:08 +00:00
Bram Kragten
fb01a0a9f1 Update frontend to 20250507.0 (#144398) 2025-05-07 13:04:07 +00:00
Robert Resch
9556285c59 Bump deebot-client to 13.1.0 (#144397) 2025-05-07 13:04:06 +00:00
Guido Schmitz
2d40b1ec75 Bump devolo_home_control_api to 0.19.0 (#144374) 2025-05-07 13:04:04 +00:00
Thomas55555
7eb690b125 Improve activity logic in Husqvarna Automower (#144057)
* Improve activity logic in Husqvarna Automower

* add test
2025-05-07 13:04:03 +00:00
Thomas55555
a23644debc Fix test in Husqvarna Automower (#144055) 2025-05-07 13:04:02 +00:00
Franck Nijhof
c98ba7f6ba Bump version to 2025.5.0b9 2025-05-07 11:09:32 +00:00
Joost Lekkerkerker
aa2b61f133 Fix variables in MELCloud (#144396) 2025-05-07 11:09:07 +00:00
Joost Lekkerkerker
f85d4afe45 Set SmartThings power energy state class to Total (#144395) 2025-05-07 11:09:06 +00:00
Joost Lekkerkerker
b4ab9177b8 Bump pySmartThings to 3.2.1 (#144393) 2025-05-07 11:09:05 +00:00
Petar Petrov
e7c310ca58 Add missing device_class translations for template helper (#144392) 2025-05-07 11:09:04 +00:00
Joost Lekkerkerker
85a83f2553 Fix SmartThings machine operating state with no options (#144390) 2025-05-07 11:09:02 +00:00
Martin Hjelmare
d2e7baeb38 Fix Z-Wave controller hard reset (#144389) 2025-05-07 11:09:01 +00:00
Barry vd. Heuvel
07b2ce28b1 Bump wh-python to 2025.4.29 for Weheat integration (#144384) 2025-05-07 11:09:00 +00:00
Franck Nijhof
35c90d9bde Bump version to 2025.5.0b8 2025-05-07 07:38:18 +00:00
Raphael Hehl
a9632bd0ff Bump uiprotect to version 7.6.0 (#144369) 2025-05-07 07:38:12 +00:00
epenet
983e134ae9 Bump renault-api to 0.3.1 (#144366)
* Bump renault-api to 0.3.1

* Adjust tests
2025-05-07 07:38:10 +00:00
Jan Bouwhuis
e217532f9e Fix field validation for mqtt subentry options in sections (#144355) 2025-05-07 07:38:09 +00:00
Franck Nijhof
1eeab28eec Bump version to 2025.5.0b7 2025-05-06 19:30:08 +00:00
Bram Kragten
2a3bd45901 Update frontend to 20250506.0 (#144354) 2025-05-06 19:29:59 +00:00
Paulus Schoutsen
d16453a465 Remove some media player intent checks for when paused (#144351) 2025-05-06 19:29:59 +00:00
Jan Bouwhuis
de63dddc96 Ensure all default MQTT subentry option values are saved (#144347)
* Ensure all default MQTT subentry option values are saved

* Apply correct filter
2025-05-06 19:29:58 +00:00
J. Nick Koston
ccffe19611 Bump bluemaestro-ble to 0.4.1 (#144345)
changelog: https://github.com/Bluetooth-Devices/bluemaestro-ble/compare/v0.4.0...v0.4.1

fixes #https://github.com/home-assistant/core/issues/144339
2025-05-06 19:29:57 +00:00
Martin Hjelmare
806bcf47d9 Fix Z-Wave migration flow to unload config entry before unplugging controller (#144343)
* Fix Z-Wave migration unload config entry before unplugging controller

* Remove typo
2025-05-06 19:29:56 +00:00
Franck Nijhof
5ed3f18d70 Bump version to 2025.5.0b6 2025-05-06 13:57:05 +00:00
Martin Hjelmare
7cc142dd59 Fix Z-Wave to reload config entry after migration nvm restore (#144338) 2025-05-06 13:56:58 +00:00
Robert Resch
9150c78901 Add endpoint validation for AWS S3 (#144334) 2025-05-06 13:56:56 +00:00
Stefan Agner
4b7c337dc9 Update Home Assistant base image to 2025.05.0 (#144333) 2025-05-06 13:56:55 +00:00
Robert Resch
1aa79c71cc Rename S3 to AWS_S3 (#144324) 2025-05-06 13:56:54 +00:00
Robert Resch
5f70140e72 Revert "Disable S3 checksums" (#144092) (#144318) 2025-05-06 13:56:52 +00:00
Martin Hjelmare
58f7a8a51e Fix Z-Wave USB discovery to use serial by id path (#144314) 2025-05-06 13:56:51 +00:00
Ivan Lopez Hernandez
a91ae71139 Fixes #140182 by checking file status before sending the prompt. (#144131)
* Added unit tests

* Addressed review comments

* Fixed tests

* PR comments
2025-05-06 13:56:50 +00:00
Cerallin
576b4ef60d Bump xiaomi-ble to 0.38.0 (#143885) 2025-05-06 13:56:48 +00:00
Paulus Schoutsen
918499a85c Bump version to 2025.5.0b5 2025-05-06 02:54:14 +00:00
Jamin
46ef578986 Bump VoIP utils to 0.3.2 (#144298) 2025-05-06 02:54:01 +00:00
Pete Sage
86162eb660 Rehlko adjust timeouts for coordinator polls (#144297) 2025-05-06 02:54:00 +00:00
Jan Bouwhuis
7f7a33b027 Fix mqtt subentry device name is not required but should be (#144289)
Fix mqtt subentry device name is not required
2025-05-06 02:53:59 +00:00
Michael
867df99353 Fix un-/re-load of Feedreader integration (#144285)
fix unload platforms call
2025-05-06 02:53:58 +00:00
Martin Hjelmare
283e9d073b Fix Z-Wave config flow forms (#144279) 2025-05-06 02:53:57 +00:00
Jan Bouwhuis
38f26376a1 Fix default entity name not the device default entity when no name set on MQTT subentry entity (#144263) 2025-05-06 02:53:56 +00:00
Jamin
0322dd0e0f Improve Voip pipeline stability (#137620)
* Improve Voip pipeline stability

It appears the pipeline is being unexpectedly cancelled in some
instances. In order to mitigate this issue hang ups will be detected
using a separate task rather than relying on timeouts in the STT read
method. Also reading STT events will be retried once if it is cancelled.
The pipeline will also catch and log any CancelledErrors to help with
further debugging.

* Update Voip tests

* Remove unnecessary changes

Remove unnecessary logging and cancelled error handling in wyoming STT.

* Remove comment about clearing system prompt

The test no longer checks for clearing the system prompt. Since that
logic exists completely in the assist_satellite component I think it is
reasonable to only test that logic in the unit tests for that component.

* Re-raise cancellation

Re-raise CancelledError if the current task is cancelling in the check hangup task

Co-authored-by: J. Nick Koston <nick@koston.org>

* Re-raise CancelledError in pipeline as well

* Fix formatting issue

* Remove unnecessary logging

* Add MockResultStream import to tests

This was presumably missed while merging

* Cancel check hangup task on disconnect

* Add myself as codeowner for VoIP

* Update CODEOWNERS

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2025-05-06 02:53:55 +00:00
Paulus Schoutsen
3798802557 Bump version to 2025.5.0b4 2025-05-05 14:51:20 -04:00
Paul Bottein
f7833bdbd4 Update frontend to 20250502.1 (#144276) 2025-05-05 14:51:14 -04:00
Josef Zweck
e3a156c9b7 Bump pylamarzocco to 2.0.0 (#144275) 2025-05-05 14:51:13 -04:00
Luke Lashley
6247ec73a3 Bump Roborock Map Parser to 0.1.4 (#144260)
Bump to 0.1.4
2025-05-05 14:51:12 -04:00
Luke Lashley
3feda06e60 Bump python-roborock to 2.18.2 (#144235) 2025-05-05 14:51:11 -04:00
Åke Strandberg
56e895fdd4 Remove program phase sensor from miele vacuum robot (#144257)
* Use device class transation

* Remove program pghses sensor from robot vacuum cleaner
2025-05-05 14:49:10 -04:00
tronikos
541506cbdb Fix Invalid statistic_id for Opower: National Grid (#144243)
Co-authored-by: J. Nick Koston <nick+github@koston.org>
2025-05-05 14:49:09 -04:00
Allen Porter
1f4cda6282 Bump ical to 9.2.0 (#144240) 2025-05-05 14:49:08 -04:00
Allen Porter
6f77d0b0d5 Update local calendar to process calendar events in the executor (#144233) 2025-05-05 14:49:07 -04:00
Allen Porter
7976e1b104 Update remote calendar to do all event handling in an executor (#144232) 2025-05-05 14:49:05 -04:00
Eliz
1c260cfb00 Fix missing head forwarding in ingress (#144231)
* Add support for connect, head and trace in ingress

* added tests

* update the testutil

* fix

* fix empty space

* removed connect

* remove trace
2025-05-05 14:49:04 -04:00
Allen Porter
8424f179e4 Fix Office 365 calendars to be compatible with rfc5545 (#144230) 2025-05-05 14:49:03 -04:00
Pete Sage
00a14a0824 bump aiokem to 0.5.10 (#144203) 2025-05-05 14:49:02 -04:00
Pete Sage
34bec1c50f Avoid delaying HA startup in Rehlko (#144202) 2025-05-05 14:49:01 -04:00
tronikos
1d0c520f64 Use names instead of statistic IDs in the Opower repair issue (#144018)
* Use names instead of statistic IDs in the Opower repair issue

* target_ids
2025-05-05 14:49:00 -04:00
Luca De Petrillo
d51eda40b3 Fix message corruption in picotts component (#141182) 2025-05-05 14:48:59 -04:00
Paulus Schoutsen
2d3259413a Bump version to 2025.5.0b3 2025-05-04 16:11:29 +00:00
Paulus Schoutsen
7a7bd9c621 Fix intent TurnOn creating stack trace for buttons (#144205) 2025-05-04 16:11:22 +00:00
Maciej Bieniek
8ce0b6b4b3 Add missing pollen category to AccuWeather (#144185)
* Add extreme level to pollen map

* Sort

* Sort
2025-05-04 16:11:21 +00:00
hahn-th
63679333cc Bump homematicip to 2.0.1.1 (#144182)
Co-authored-by: Shay Levy <levyshay1@gmail.com>
2025-05-04 16:11:20 +00:00
Marc Mueller
5b12bdca00 Fix licenses check for setuptools (#144181) 2025-05-04 16:11:18 +00:00
Åke Strandberg
99e13278e3 Bump pymiele to 0.4.3 (#144176)
* Use device class transation

* Bump pymiele to 0.4.3

---------

Co-authored-by: Shay Levy <levyshay1@gmail.com>
2025-05-04 16:11:17 +00:00
Paulus Schoutsen
07a03ee10d Point thumbnail TTS media source to right logo (#144162) 2025-05-04 16:11:16 +00:00
J. Nick Koston
fb9f8e3581 Bump zeroconf to 0.147.0 (#144158) 2025-05-04 16:11:15 +00:00
J. Nick Koston
ee125cd9a4 Bump habluetooth to 3.48.2 (#144157) 2025-05-04 16:11:14 +00:00
Josef Zweck
35a1429e2b Switch to common clientsession for lamarzocco (#144137) 2025-05-04 16:11:13 +00:00
J. Nick Koston
89916b38e9 Add tests to ensure ESPHome entity_ids are preserved on upgrade (#144116) 2025-05-04 16:11:10 +00:00
tronikos
c560439545 Skip the update right after the migration in Opower (#144088)
* Wait for the migration to finish in Opower

* Don't call async_block_till_done since this can timeout and seems to meant for tests

* Don't call async_block_till_done since this can timeout and seems to meant for tests
2025-05-04 16:11:10 +00:00
Charlie Rusbridger
7322be2006 Use kodi posters, fall back to thumbnails if unavailable. (#144066) 2025-05-04 16:11:08 +00:00
Florian Sabonchi
e95ed12ba1 Fix check for locked device in AVM Fritz!SmartHome (#141697)
* feat: raise execption on hvac mode while device is locked

* fix: test for setting hvac mode while device is locked.

* feat: update translation

* feat: add separate translations for HVAC and temperature

* fix: test cases

* fix: test cases for test_set_preset_mode_boost

* rev: code review

* rev: exception string

* feat: updated  error message and added helper function

* Update homeassistant/components/fritzbox/strings.json

Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>

* fix: translation key

* remove check_active_or_lock_mode from async_set_preset_mode

---------

Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>
2025-05-04 16:11:07 +00:00
Paulus Schoutsen
16f36912db Bump version to 2025.5.0b2 2025-05-03 08:06:31 -04:00
J. Nick Koston
2a5f031ba5 Bump Bluetooth deps to improve auto recovery process (#144133) 2025-05-03 08:06:23 -04:00
J. Nick Koston
71bb8ae529 Bump bleak-esphome to 2.15.1 (#144129) 2025-05-03 08:06:22 -04:00
J. Nick Koston
f6a94d0661 Bump PyISY to 3.4.1 (#144127) 2025-05-03 08:06:21 -04:00
Marc Hörsken
c2575735ff Update pywmspro to 0.2.2 to make error handling more robust (#144124) 2025-05-03 08:06:20 -04:00
Josef Zweck
7eee5ecd9a Fix intermittent unavailability for lamarzocco brew active sensor (#144120)
* Fix brew active intermittent unavailability for lamarzocco

* Whitespaces
2025-05-03 08:06:19 -04:00
Thomas55555
db0cf9fbf4 Bump aioautomower to 2025.5.1 (#144118) 2025-05-03 08:06:18 -04:00
J. Nick Koston
aded44ee0f Bump aiodns to 3.3.0 (#144115) 2025-05-03 08:06:17 -04:00
Bram Kragten
901926e8e6 Update frontend to 20250502.0 (#144114) 2025-05-03 08:06:16 -04:00
Pete Sage
2e336626ac bump aiokem to 0.5.9 (#144098)
fix: bump aiokem to 0.5.9
2025-05-03 08:06:15 -04:00
Tomáš Bedřich
3ea3d77f4d Disable S3 checksums (#144092)
Disable S3 checksums (#143995)
2025-05-03 08:06:14 -04:00
Jan Bouwhuis
fe8e7b73bf Fix small issues with mqtt translations and improve readability (#144091) 2025-05-03 08:06:13 -04:00
J. Nick Koston
a34065ee2f Only create a single resolver object if there are multiple aiohttp sessions (#144090) 2025-05-03 08:06:12 -04:00
Brett Adams
e1a908c8ac Bump teslemetry-stream to 0.7.7 (#144085) 2025-05-03 08:06:11 -04:00
Ian
628d99886a Bump py-nextbusnext to 2.1.2 (#144081)r
Bump py-nextbusnext version

Fixes #144059
2025-05-03 08:06:09 -04:00
Åke Strandberg
6b10710484 Improve naming of miele freezers and fridges (#144062)
* Use device class transation

* Improve naming of miele freezers and fridges

* Address review

* Address review comment

* Simplify
2025-05-03 08:06:08 -04:00
Andreas Kölsch
8a4f28fa94 Fix brightness calculation when using brightness_step_pct (#143786) 2025-05-03 08:06:07 -04:00
Paulus Schoutsen
e7331633c7 Bump version to 2025.5.0b1 2025-05-01 21:42:07 +00:00
J. Nick Koston
934be08a59 Bump inkbird-ble to 0.16.1 (#144074)
I made a mistake in one of the data lengths as I forgot to add
the length of the id which is 2 bytes. I really wish vendors
would stop putting raw data in this field.

changelog: https://github.com/Bluetooth-Devices/inkbird-ble/compare/v0.16.0...v0.16.1
2025-05-01 21:41:39 +00:00
J. Nick Koston
485522fd76 Avoid DomainData lookup in ESPHome update platform (#144072)
We can get this from entry.runtime_data
2025-05-01 21:41:38 +00:00
J. Nick Koston
eba0daa2e9 Avoid validation of ESPHome MAC when discovered entry is ignored or unchanged (#144071)
fixes #144033
fixes #143991
2025-05-01 21:41:37 +00:00
Åke Strandberg
851779e7ad Use device class transation for door in miele (#144053) 2025-05-01 21:41:36 +00:00
Åke Strandberg
ea9a0f4bf5 Use action property defined in MieleEntity (#144052) 2025-05-01 21:41:35 +00:00
Josef Zweck
1143468eb5 Handle TimeoutError for lamarzocco (#144042) 2025-05-01 21:41:35 +00:00
OzGav
52b0b1e2ab Media Player strings adjust grammar (#144030) 2025-05-01 21:41:34 +00:00
Andrea Turri
23ba652b83 Fix state of fan entity for Miele hobs with extractor when turned off (#144025) 2025-05-01 21:41:33 +00:00
tronikos
43b737c4a2 Pass empty set instead of empty dict to get_last_statistics (#144022) 2025-05-01 21:41:33 +00:00
Josef Zweck
3fbd23b98d Add bluetooth connection availability to diagnostics for lamarzocco (#144012)
* Add bluetooth connection availability to diagnostics for lamarzocco

* make even more detailed
2025-05-01 21:41:32 +00:00
Josef Zweck
0cbeeebd0b Add connect/disconnect callbacks to lamarzocco (#144011) 2025-05-01 21:41:31 +00:00
Jan-Philipp Benecke
99a0679ee9 Default backup encryption to true when updating only location retention (#143997) 2025-05-01 21:41:30 +00:00
Norbert Rittel
e82713b68c Add translations for "energy_distance" and "wind_direction" in random (#143994)
* Add translations for "energy_distance" and "wind_direction" in `random`

* Comma
2025-05-01 21:41:30 +00:00
Paulus Schoutsen
9293afd95a Ensure legacy TTS providers are hidden if entity exists (#143992) 2025-05-01 21:41:29 +00:00
Josef Zweck
9ea3e786f6 Bump pylamarzocco to 2.0.0b7 (#143989) 2025-05-01 21:41:28 +00:00
J. Diego Rodríguez Royo
a8169d2056 Add units of measurement for Home Connect counter entities (#143982) 2025-05-01 21:41:27 +00:00
Megamind
1cd94affd1 Bump pushover-complete to 1.2.0 (#143966)
Co-authored-by: Joostlek <joostlek@outlook.com>
2025-05-01 21:41:26 +00:00
Franck Nijhof
724825d34c Bump version to 2025.5.0b0 2025-04-30 18:59:18 +00:00
290 changed files with 6367 additions and 2630 deletions

8
CODEOWNERS generated
View File

@@ -171,6 +171,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/avea/ @pattyland
/homeassistant/components/awair/ @ahayworth @danielsjf
/tests/components/awair/ @ahayworth @danielsjf
/homeassistant/components/aws_s3/ @tomasbedrich
/tests/components/aws_s3/ @tomasbedrich
/homeassistant/components/axis/ @Kane610
/tests/components/axis/ @Kane610
/homeassistant/components/azure_data_explorer/ @kaareseras
@@ -1318,8 +1320,6 @@ build.json @home-assistant/supervisor
/tests/components/ruuvitag_ble/ @akx
/homeassistant/components/rympro/ @OnFreund @elad-bar @maorcc
/tests/components/rympro/ @OnFreund @elad-bar @maorcc
/homeassistant/components/s3/ @tomasbedrich
/tests/components/s3/ @tomasbedrich
/homeassistant/components/sabnzbd/ @shaiu @jpbede
/tests/components/sabnzbd/ @shaiu @jpbede
/homeassistant/components/saj/ @fredericvl
@@ -1678,8 +1678,8 @@ build.json @home-assistant/supervisor
/tests/components/vlc_telnet/ @rodripf @MartinHjelmare
/homeassistant/components/vodafone_station/ @paoloantinori @chemelli74
/tests/components/vodafone_station/ @paoloantinori @chemelli74
/homeassistant/components/voip/ @balloob @synesthesiam
/tests/components/voip/ @balloob @synesthesiam
/homeassistant/components/voip/ @balloob @synesthesiam @jaminh
/tests/components/voip/ @balloob @synesthesiam @jaminh
/homeassistant/components/volumio/ @OnFreund
/tests/components/volumio/ @OnFreund
/homeassistant/components/volvooncall/ @molobrakos

View File

@@ -1,10 +1,10 @@
image: ghcr.io/home-assistant/{arch}-homeassistant
build_from:
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2025.02.1
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2025.02.1
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2025.02.1
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2025.02.1
i386: ghcr.io/home-assistant/i386-homeassistant-base:2025.02.1
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2025.05.0
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2025.05.0
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2025.05.0
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2025.05.0
i386: ghcr.io/home-assistant/i386-homeassistant-base:2025.05.0
codenotary:
signer: notary@home-assistant.io
base_image: notary@home-assistant.io

View File

@@ -1,5 +1,12 @@
{
"domain": "amazon",
"name": "Amazon",
"integrations": ["alexa", "amazon_polly", "aws", "fire_tv", "route53"]
"integrations": [
"alexa",
"amazon_polly",
"aws",
"aws_s3",
"fire_tv",
"route53"
]
}

View File

@@ -67,6 +67,7 @@ POLLEN_CATEGORY_MAP = {
2: "moderate",
3: "high",
4: "very_high",
5: "extreme",
}
UPDATE_INTERVAL_OBSERVATION = timedelta(minutes=40)
UPDATE_INTERVAL_DAILY_FORECAST = timedelta(hours=6)

View File

@@ -72,6 +72,7 @@
"level": {
"name": "Level",
"state": {
"extreme": "Extreme",
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
"moderate": "Moderate",
@@ -89,6 +90,7 @@
"level": {
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
"state": {
"extreme": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::extreme%]",
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
@@ -123,6 +125,7 @@
"level": {
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
"state": {
"extreme": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::extreme%]",
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
@@ -167,6 +170,7 @@
"level": {
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
"state": {
"extreme": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::extreme%]",
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
@@ -181,6 +185,7 @@
"level": {
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
"state": {
"extreme": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::extreme%]",
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",
@@ -195,6 +200,7 @@
"level": {
"name": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::name%]",
"state": {
"extreme": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::extreme%]",
"high": "[%key:common::state::high%]",
"low": "[%key:common::state::low%]",
"moderate": "[%key:component::accuweather::entity::sensor::grass_pollen::state_attributes::level::state::moderate%]",

View File

@@ -7,5 +7,5 @@
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["pyaprilaire"],
"requirements": ["pyaprilaire==0.8.1"]
"requirements": ["pyaprilaire==0.9.0"]
}

View File

@@ -1,4 +1,4 @@
"""The S3 integration."""
"""The AWS S3 integration."""
from __future__ import annotations

View File

@@ -1,4 +1,4 @@
"""Backup platform for the S3 integration."""
"""Backup platform for the AWS S3 integration."""
from collections.abc import AsyncIterator, Callable, Coroutine
import functools

View File

@@ -1,8 +1,9 @@
"""Config flow for the S3 integration."""
"""Config flow for the AWS S3 integration."""
from __future__ import annotations
from typing import Any
from urllib.parse import urlparse
from aiobotocore.session import AioSession
from botocore.exceptions import ClientError, ConnectionError, ParamValidationError
@@ -17,6 +18,7 @@ from homeassistant.helpers.selector import (
)
from .const import (
AWS_DOMAIN,
CONF_ACCESS_KEY_ID,
CONF_BUCKET,
CONF_ENDPOINT_URL,
@@ -57,28 +59,34 @@ class S3ConfigFlow(ConfigFlow, domain=DOMAIN):
CONF_ENDPOINT_URL: user_input[CONF_ENDPOINT_URL],
}
)
try:
session = AioSession()
async with session.create_client(
"s3",
endpoint_url=user_input.get(CONF_ENDPOINT_URL),
aws_secret_access_key=user_input[CONF_SECRET_ACCESS_KEY],
aws_access_key_id=user_input[CONF_ACCESS_KEY_ID],
) as client:
await client.head_bucket(Bucket=user_input[CONF_BUCKET])
except ClientError:
errors["base"] = "invalid_credentials"
except ParamValidationError as err:
if "Invalid bucket name" in str(err):
errors[CONF_BUCKET] = "invalid_bucket_name"
except ValueError:
if not urlparse(user_input[CONF_ENDPOINT_URL]).hostname.endswith(
AWS_DOMAIN
):
errors[CONF_ENDPOINT_URL] = "invalid_endpoint_url"
except ConnectionError:
errors[CONF_ENDPOINT_URL] = "cannot_connect"
else:
return self.async_create_entry(
title=user_input[CONF_BUCKET], data=user_input
)
try:
session = AioSession()
async with session.create_client(
"s3",
endpoint_url=user_input.get(CONF_ENDPOINT_URL),
aws_secret_access_key=user_input[CONF_SECRET_ACCESS_KEY],
aws_access_key_id=user_input[CONF_ACCESS_KEY_ID],
) as client:
await client.head_bucket(Bucket=user_input[CONF_BUCKET])
except ClientError:
errors["base"] = "invalid_credentials"
except ParamValidationError as err:
if "Invalid bucket name" in str(err):
errors[CONF_BUCKET] = "invalid_bucket_name"
except ValueError:
errors[CONF_ENDPOINT_URL] = "invalid_endpoint_url"
except ConnectionError:
errors[CONF_ENDPOINT_URL] = "cannot_connect"
else:
return self.async_create_entry(
title=user_input[CONF_BUCKET], data=user_input
)
return self.async_show_form(
step_id="user",

View File

@@ -1,18 +1,19 @@
"""Constants for the S3 integration."""
"""Constants for the AWS S3 integration."""
from collections.abc import Callable
from typing import Final
from homeassistant.util.hass_dict import HassKey
DOMAIN: Final = "s3"
DOMAIN: Final = "aws_s3"
CONF_ACCESS_KEY_ID = "access_key_id"
CONF_SECRET_ACCESS_KEY = "secret_access_key"
CONF_ENDPOINT_URL = "endpoint_url"
CONF_BUCKET = "bucket"
DEFAULT_ENDPOINT_URL = "https://s3.eu-central-1.amazonaws.com/"
AWS_DOMAIN = "amazonaws.com"
DEFAULT_ENDPOINT_URL = f"https://s3.eu-central-1.{AWS_DOMAIN}/"
DATA_BACKUP_AGENT_LISTENERS: HassKey[list[Callable[[], None]]] = HassKey(
f"{DOMAIN}.backup_agent_listeners"

View File

@@ -1,9 +1,9 @@
{
"domain": "s3",
"name": "S3",
"domain": "aws_s3",
"name": "AWS S3",
"codeowners": ["@tomasbedrich"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/s3",
"documentation": "https://www.home-assistant.io/integrations/aws_s3",
"integration_type": "service",
"iot_class": "cloud_push",
"loggers": ["aiobotocore"],

View File

@@ -9,19 +9,19 @@
"endpoint_url": "Endpoint URL"
},
"data_description": {
"access_key_id": "Access key ID to connect to S3 API",
"secret_access_key": "Secret access key to connect to S3 API",
"access_key_id": "Access key ID to connect to AWS S3 API",
"secret_access_key": "Secret access key to connect to AWS S3 API",
"bucket": "Bucket must already exist and be writable by the provided credentials.",
"endpoint_url": "Endpoint URL provided to [Boto3 Session]({boto3_docs_url}). Region-specific [AWS S3 endpoints]({aws_s3_docs_url}) are available in their docs."
},
"title": "Add S3 bucket"
"title": "Add AWS S3 bucket"
}
},
"error": {
"cannot_connect": "[%key:component::s3::exceptions::cannot_connect::message%]",
"invalid_bucket_name": "[%key:component::s3::exceptions::invalid_bucket_name::message%]",
"invalid_credentials": "[%key:component::s3::exceptions::invalid_credentials::message%]",
"invalid_endpoint_url": "Invalid endpoint URL"
"cannot_connect": "[%key:component::aws_s3::exceptions::cannot_connect::message%]",
"invalid_bucket_name": "[%key:component::aws_s3::exceptions::invalid_bucket_name::message%]",
"invalid_credentials": "[%key:component::aws_s3::exceptions::invalid_credentials::message%]",
"invalid_endpoint_url": "Invalid endpoint URL. Please make sure it's a valid AWS S3 endpoint URL."
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"

View File

@@ -2,8 +2,8 @@
from aiohttp import ClientTimeout
from azure.core.exceptions import (
AzureError,
ClientAuthenticationError,
HttpResponseError,
ResourceNotFoundError,
)
from azure.core.pipeline.transport._aiohttp import (
@@ -39,11 +39,20 @@ async def async_setup_entry(
session = async_create_clientsession(
hass, timeout=ClientTimeout(connect=10, total=12 * 60 * 60)
)
container_client = ContainerClient(
account_url=f"https://{entry.data[CONF_ACCOUNT_NAME]}.blob.core.windows.net/",
container_name=entry.data[CONF_CONTAINER_NAME],
credential=entry.data[CONF_STORAGE_ACCOUNT_KEY],
transport=AioHttpTransport(session=session),
def create_container_client() -> ContainerClient:
"""Create a ContainerClient."""
return ContainerClient(
account_url=f"https://{entry.data[CONF_ACCOUNT_NAME]}.blob.core.windows.net/",
container_name=entry.data[CONF_CONTAINER_NAME],
credential=entry.data[CONF_STORAGE_ACCOUNT_KEY],
transport=AioHttpTransport(session=session),
)
# has a blocking call to open in cpython
container_client: ContainerClient = await hass.async_add_executor_job(
create_container_client
)
try:
@@ -61,7 +70,7 @@ async def async_setup_entry(
translation_key="invalid_auth",
translation_placeholders={CONF_ACCOUNT_NAME: entry.data[CONF_ACCOUNT_NAME]},
) from err
except HttpResponseError as err:
except AzureError as err:
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="cannot_connect",

View File

@@ -8,7 +8,7 @@ import json
import logging
from typing import Any, Concatenate
from azure.core.exceptions import HttpResponseError
from azure.core.exceptions import AzureError, HttpResponseError, ServiceRequestError
from azure.storage.blob import BlobProperties
from homeassistant.components.backup import (
@@ -80,6 +80,20 @@ def handle_backup_errors[_R, **P](
f"Error during backup operation in {func.__name__}:"
f" Status {err.status_code}, message: {err.message}"
) from err
except ServiceRequestError as err:
raise BackupAgentError(
f"Timeout during backup operation in {func.__name__}"
) from err
except AzureError as err:
_LOGGER.debug(
"Error during backup in %s: %s",
func.__name__,
err,
exc_info=True,
)
raise BackupAgentError(
f"Error during backup operation in {func.__name__}: {err}"
) from err
return wrapper

View File

@@ -27,9 +27,25 @@ _LOGGER = logging.getLogger(__name__)
class AzureStorageConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for azure storage."""
def get_account_url(self, account_name: str) -> str:
"""Get the account URL."""
return f"https://{account_name}.blob.core.windows.net/"
async def get_container_client(
self, account_name: str, container_name: str, storage_account_key: str
) -> ContainerClient:
"""Get the container client.
ContainerClient has a blocking call to open in cpython
"""
session = async_get_clientsession(self.hass)
def create_container_client() -> ContainerClient:
return ContainerClient(
account_url=f"https://{account_name}.blob.core.windows.net/",
container_name=container_name,
credential=storage_account_key,
transport=AioHttpTransport(session=session),
)
return await self.hass.async_add_executor_job(create_container_client)
async def validate_config(
self, container_client: ContainerClient
@@ -58,11 +74,10 @@ class AzureStorageConfigFlow(ConfigFlow, domain=DOMAIN):
self._async_abort_entries_match(
{CONF_ACCOUNT_NAME: user_input[CONF_ACCOUNT_NAME]}
)
container_client = ContainerClient(
account_url=self.get_account_url(user_input[CONF_ACCOUNT_NAME]),
container_client = await self.get_container_client(
account_name=user_input[CONF_ACCOUNT_NAME],
container_name=user_input[CONF_CONTAINER_NAME],
credential=user_input[CONF_STORAGE_ACCOUNT_KEY],
transport=AioHttpTransport(session=async_get_clientsession(self.hass)),
storage_account_key=user_input[CONF_STORAGE_ACCOUNT_KEY],
)
errors = await self.validate_config(container_client)
@@ -99,12 +114,12 @@ class AzureStorageConfigFlow(ConfigFlow, domain=DOMAIN):
reauth_entry = self._get_reauth_entry()
if user_input is not None:
container_client = ContainerClient(
account_url=self.get_account_url(reauth_entry.data[CONF_ACCOUNT_NAME]),
container_client = await self.get_container_client(
account_name=reauth_entry.data[CONF_ACCOUNT_NAME],
container_name=reauth_entry.data[CONF_CONTAINER_NAME],
credential=user_input[CONF_STORAGE_ACCOUNT_KEY],
transport=AioHttpTransport(session=async_get_clientsession(self.hass)),
storage_account_key=user_input[CONF_STORAGE_ACCOUNT_KEY],
)
errors = await self.validate_config(container_client)
if not errors:
return self.async_update_reload_and_abort(
@@ -129,13 +144,10 @@ class AzureStorageConfigFlow(ConfigFlow, domain=DOMAIN):
reconfigure_entry = self._get_reconfigure_entry()
if user_input is not None:
container_client = ContainerClient(
account_url=self.get_account_url(
reconfigure_entry.data[CONF_ACCOUNT_NAME]
),
container_client = await self.get_container_client(
account_name=reconfigure_entry.data[CONF_ACCOUNT_NAME],
container_name=user_input[CONF_CONTAINER_NAME],
credential=user_input[CONF_STORAGE_ACCOUNT_KEY],
transport=AioHttpTransport(session=async_get_clientsession(self.hass)),
storage_account_key=user_input[CONF_STORAGE_ACCOUNT_KEY],
)
errors = await self.validate_config(container_client)
if not errors:

View File

@@ -202,7 +202,7 @@ class BackupConfig:
if agent_id not in self.data.agents:
old_agent_retention = None
self.data.agents[agent_id] = AgentConfig(
protected=agent_config.get("protected", False),
protected=agent_config.get("protected", True),
retention=new_agent_retention,
)
else:

View File

@@ -22,7 +22,7 @@ from . import util
from .agent import BackupAgent
from .const import DATA_MANAGER
from .manager import BackupManager
from .models import BackupNotFound
from .models import AgentBackup, BackupNotFound
@callback
@@ -85,7 +85,15 @@ class DownloadBackupView(HomeAssistantView):
request, headers, backup_id, agent_id, agent, manager
)
return await self._send_backup_with_password(
hass, request, headers, backup_id, agent_id, password, agent, manager
hass,
backup,
request,
headers,
backup_id,
agent_id,
password,
agent,
manager,
)
except BackupNotFound:
return Response(status=HTTPStatus.NOT_FOUND)
@@ -116,6 +124,7 @@ class DownloadBackupView(HomeAssistantView):
async def _send_backup_with_password(
self,
hass: HomeAssistant,
backup: AgentBackup,
request: Request,
headers: dict[istr, str],
backup_id: str,
@@ -144,7 +153,8 @@ class DownloadBackupView(HomeAssistantView):
stream = util.AsyncIteratorWriter(hass)
worker = threading.Thread(
target=util.decrypt_backup, args=[reader, stream, password, on_done, 0, []]
target=util.decrypt_backup,
args=[backup, reader, stream, password, on_done, 0, []],
)
try:
worker.start()

View File

@@ -295,13 +295,26 @@ def validate_password_stream(
raise BackupEmpty
def _get_expected_archives(backup: AgentBackup) -> set[str]:
"""Get the expected archives in the backup."""
expected_archives = set()
if backup.homeassistant_included:
expected_archives.add("homeassistant")
for addon in backup.addons:
expected_archives.add(addon.slug)
for folder in backup.folders:
expected_archives.add(folder.value)
return expected_archives
def decrypt_backup(
backup: AgentBackup,
input_stream: IO[bytes],
output_stream: IO[bytes],
password: str | None,
on_done: Callable[[Exception | None], None],
minimum_size: int,
nonces: list[bytes],
nonces: NonceGenerator,
) -> None:
"""Decrypt a backup."""
error: Exception | None = None
@@ -315,7 +328,7 @@ def decrypt_backup(
fileobj=output_stream, mode="w|", bufsize=BUF_SIZE
) as output_tar,
):
_decrypt_backup(input_tar, output_tar, password)
_decrypt_backup(backup, input_tar, output_tar, password)
except (DecryptError, SecureTarError, tarfile.TarError) as err:
LOGGER.warning("Error decrypting backup: %s", err)
error = err
@@ -333,15 +346,18 @@ def decrypt_backup(
def _decrypt_backup(
backup: AgentBackup,
input_tar: tarfile.TarFile,
output_tar: tarfile.TarFile,
password: str | None,
) -> None:
"""Decrypt a backup."""
expected_archives = _get_expected_archives(backup)
for obj in input_tar:
# We compare with PurePath to avoid issues with different path separators,
# for example when backup.json is added as "./backup.json"
if PurePath(obj.name) == PurePath("backup.json"):
object_path = PurePath(obj.name)
if object_path == PurePath("backup.json"):
# Rewrite the backup.json file to indicate that the backup is decrypted
if not (reader := input_tar.extractfile(obj)):
raise DecryptError
@@ -352,7 +368,13 @@ def _decrypt_backup(
metadata_obj.size = len(updated_metadata_b)
output_tar.addfile(metadata_obj, BytesIO(updated_metadata_b))
continue
if not obj.name.endswith((".tar", ".tgz", ".tar.gz")):
prefix, _, suffix = object_path.name.partition(".")
if suffix not in ("tar", "tgz", "tar.gz"):
LOGGER.debug("Unknown file %s will not be decrypted", obj.name)
output_tar.addfile(obj, input_tar.extractfile(obj))
continue
if prefix not in expected_archives:
LOGGER.debug("Unknown inner tar file %s will not be decrypted", obj.name)
output_tar.addfile(obj, input_tar.extractfile(obj))
continue
istf = SecureTarFile(
@@ -371,12 +393,13 @@ def _decrypt_backup(
def encrypt_backup(
backup: AgentBackup,
input_stream: IO[bytes],
output_stream: IO[bytes],
password: str | None,
on_done: Callable[[Exception | None], None],
minimum_size: int,
nonces: list[bytes],
nonces: NonceGenerator,
) -> None:
"""Encrypt a backup."""
error: Exception | None = None
@@ -390,7 +413,7 @@ def encrypt_backup(
fileobj=output_stream, mode="w|", bufsize=BUF_SIZE
) as output_tar,
):
_encrypt_backup(input_tar, output_tar, password, nonces)
_encrypt_backup(backup, input_tar, output_tar, password, nonces)
except (EncryptError, SecureTarError, tarfile.TarError) as err:
LOGGER.warning("Error encrypting backup: %s", err)
error = err
@@ -408,17 +431,20 @@ def encrypt_backup(
def _encrypt_backup(
backup: AgentBackup,
input_tar: tarfile.TarFile,
output_tar: tarfile.TarFile,
password: str | None,
nonces: list[bytes],
nonces: NonceGenerator,
) -> None:
"""Encrypt a backup."""
inner_tar_idx = 0
expected_archives = _get_expected_archives(backup)
for obj in input_tar:
# We compare with PurePath to avoid issues with different path separators,
# for example when backup.json is added as "./backup.json"
if PurePath(obj.name) == PurePath("backup.json"):
object_path = PurePath(obj.name)
if object_path == PurePath("backup.json"):
# Rewrite the backup.json file to indicate that the backup is encrypted
if not (reader := input_tar.extractfile(obj)):
raise EncryptError
@@ -429,16 +455,21 @@ def _encrypt_backup(
metadata_obj.size = len(updated_metadata_b)
output_tar.addfile(metadata_obj, BytesIO(updated_metadata_b))
continue
if not obj.name.endswith((".tar", ".tgz", ".tar.gz")):
prefix, _, suffix = object_path.name.partition(".")
if suffix not in ("tar", "tgz", "tar.gz"):
LOGGER.debug("Unknown file %s will not be encrypted", obj.name)
output_tar.addfile(obj, input_tar.extractfile(obj))
continue
if prefix not in expected_archives:
LOGGER.debug("Unknown inner tar file %s will not be encrypted", obj.name)
continue
istf = SecureTarFile(
None, # Not used
gzip=False,
key=password_to_key(password) if password is not None else None,
mode="r",
fileobj=input_tar.extractfile(obj),
nonce=nonces[inner_tar_idx],
nonce=nonces.get(inner_tar_idx),
)
inner_tar_idx += 1
with istf.encrypt(obj) as encrypted:
@@ -456,17 +487,33 @@ class _CipherWorkerStatus:
writer: AsyncIteratorWriter
class NonceGenerator:
"""Generate nonces for encryption."""
def __init__(self) -> None:
"""Initialize the generator."""
self._nonces: dict[int, bytes] = {}
def get(self, index: int) -> bytes:
"""Get a nonce for the given index."""
if index not in self._nonces:
# Generate a new nonce for the given index
self._nonces[index] = os.urandom(16)
return self._nonces[index]
class _CipherBackupStreamer:
"""Encrypt or decrypt a backup."""
_cipher_func: Callable[
[
AgentBackup,
IO[bytes],
IO[bytes],
str | None,
Callable[[Exception | None], None],
int,
list[bytes],
NonceGenerator,
],
None,
]
@@ -484,7 +531,7 @@ class _CipherBackupStreamer:
self._hass = hass
self._open_stream = open_stream
self._password = password
self._nonces: list[bytes] = []
self._nonces = NonceGenerator()
def size(self) -> int:
"""Return the maximum size of the decrypted or encrypted backup."""
@@ -508,7 +555,15 @@ class _CipherBackupStreamer:
writer = AsyncIteratorWriter(self._hass)
worker = threading.Thread(
target=self._cipher_func,
args=[reader, writer, self._password, on_done, self.size(), self._nonces],
args=[
self._backup,
reader,
writer,
self._password,
on_done,
self.size(),
self._nonces,
],
)
worker_status = _CipherWorkerStatus(
done=asyncio.Event(), reader=reader, thread=worker, writer=writer
@@ -538,17 +593,6 @@ class DecryptedBackupStreamer(_CipherBackupStreamer):
class EncryptedBackupStreamer(_CipherBackupStreamer):
"""Encrypt a backup."""
def __init__(
self,
hass: HomeAssistant,
backup: AgentBackup,
open_stream: Callable[[], Coroutine[Any, Any, AsyncIterator[bytes]]],
password: str | None,
) -> None:
"""Initialize."""
super().__init__(hass, backup, open_stream, password)
self._nonces = [os.urandom(16) for _ in range(self._num_tar_files())]
_cipher_func = staticmethod(encrypt_backup)
def backup(self) -> AgentBackup:

View File

@@ -12,5 +12,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/bluemaestro",
"iot_class": "local_push",
"requirements": ["bluemaestro-ble==0.4.0"]
"requirements": ["bluemaestro-ble==0.4.1"]
}

View File

@@ -18,9 +18,9 @@
"bleak==0.22.3",
"bleak-retry-connector==3.9.0",
"bluetooth-adapters==0.21.4",
"bluetooth-auto-recovery==1.4.5",
"bluetooth-auto-recovery==1.5.1",
"bluetooth-data-tools==1.28.1",
"dbus-fast==2.43.0",
"habluetooth==3.45.0"
"habluetooth==3.48.2"
]
}

View File

@@ -418,9 +418,11 @@ class CloudTTSEntity(TextToSpeechEntity):
language=language,
voice=options.get(
ATTR_VOICE,
self._voice
if language == self._language
else DEFAULT_VOICES[language],
(
self._voice
if language == self._language
else DEFAULT_VOICES[language]
),
),
gender=options.get(ATTR_GENDER),
),
@@ -435,6 +437,8 @@ class CloudTTSEntity(TextToSpeechEntity):
class CloudProvider(Provider):
"""Home Assistant Cloud speech API provider."""
has_entity = True
def __init__(self, cloud: Cloud[CloudClient]) -> None:
"""Initialize cloud provider."""
self.cloud = cloud

View File

@@ -77,6 +77,5 @@ async def async_unload_entry(hass: HomeAssistant, entry: ComelitConfigEntry) ->
coordinator = entry.runtime_data
if unload_ok := await hass.config_entries.async_unload_platforms(entry, platforms):
await coordinator.api.logout()
await coordinator.api.close()
return unload_ok

View File

@@ -134,11 +134,9 @@ class ComelitClimateEntity(ComelitBridgeBaseEntity, ClimateEntity):
self._attr_current_temperature = values[0] / 10
self._attr_hvac_action = None
if _mode == ClimaComelitMode.OFF:
self._attr_hvac_action = HVACAction.OFF
if not _active:
self._attr_hvac_action = HVACAction.IDLE
if _mode in API_STATUS:
elif _mode in API_STATUS:
self._attr_hvac_action = API_STATUS[_mode]["hvac_action"]
self._attr_hvac_mode = None

View File

@@ -73,7 +73,6 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str,
) from err
finally:
await api.logout()
await api.close()
return {"title": data[CONF_HOST]}

View File

@@ -8,5 +8,5 @@
"iot_class": "local_polling",
"loggers": ["aiocomelit"],
"quality_scale": "bronze",
"requirements": ["aiocomelit==0.12.0"]
"requirements": ["aiocomelit==0.12.3"]
}

View File

@@ -76,7 +76,7 @@
"cannot_authenticate": {
"message": "Error authenticating"
},
"updated_failed": {
"update_failed": {
"message": "Failed to update data: {error}"
}
}

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/conversation",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["hassil==2.2.3", "home-assistant-intents==2025.4.30"]
"requirements": ["hassil==2.2.3", "home-assistant-intents==2025.5.7"]
}

View File

@@ -8,6 +8,6 @@
"integration_type": "hub",
"iot_class": "local_push",
"loggers": ["devolo_home_control_api"],
"requirements": ["devolo-home-control-api==0.18.3"],
"requirements": ["devolo-home-control-api==0.19.0"],
"zeroconf": ["_dvl-deviceapi._tcp.local."]
}

View File

@@ -15,7 +15,7 @@
"quality_scale": "internal",
"requirements": [
"aiodhcpwatcher==1.1.1",
"aiodiscover==2.6.1",
"aiodiscover==2.7.0",
"cached-ipaddress==0.10.0"
]
}

View File

@@ -68,7 +68,7 @@ async def async_validate_hostname(
result = False
with contextlib.suppress(DNSError):
result = bool(
await aiodns.DNSResolver(
await aiodns.DNSResolver( # type: ignore[call-overload]
nameservers=[resolver], udp_port=port, tcp_port=port
).query(hostname, qtype)
)

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/dnsip",
"iot_class": "cloud_polling",
"requirements": ["aiodns==3.2.0"]
"requirements": ["aiodns==3.4.0"]
}

View File

@@ -106,7 +106,7 @@ class WanIpSensor(SensorEntity):
async def async_update(self) -> None:
"""Get the current DNS IP address for hostname."""
try:
response = await self.resolver.query(self.hostname, self.querytype)
response = await self.resolver.query(self.hostname, self.querytype) # type: ignore[call-overload]
except DNSError as err:
_LOGGER.warning("Exception while resolving host: %s", err)
response = None

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/ecovacs",
"iot_class": "cloud_push",
"loggers": ["sleekxmppfs", "sucks", "deebot_client"],
"requirements": ["py-sucks==0.9.10", "deebot-client==13.0.1"]
"requirements": ["py-sucks==0.9.10", "deebot-client==13.2.1"]
}

View File

@@ -6,7 +6,8 @@ from collections.abc import Callable
from dataclasses import dataclass
from typing import Any, Generic
from deebot_client.capabilities import CapabilityEvent, CapabilityLifeSpan
from deebot_client.capabilities import CapabilityEvent, CapabilityLifeSpan, DeviceType
from deebot_client.device import Device
from deebot_client.events import (
BatteryEvent,
ErrorEvent,
@@ -34,7 +35,7 @@ from homeassistant.const import (
UnitOfArea,
UnitOfTime,
)
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
@@ -59,6 +60,15 @@ class EcovacsSensorEntityDescription(
"""Ecovacs sensor entity description."""
value_fn: Callable[[EventT], StateType]
native_unit_of_measurement_fn: Callable[[DeviceType], str | None] | None = None
@callback
def get_area_native_unit_of_measurement(device_type: DeviceType) -> str | None:
"""Get the area native unit of measurement based on device type."""
if device_type is DeviceType.MOWER:
return UnitOfArea.SQUARE_CENTIMETERS
return UnitOfArea.SQUARE_METERS
ENTITY_DESCRIPTIONS: tuple[EcovacsSensorEntityDescription, ...] = (
@@ -68,7 +78,9 @@ ENTITY_DESCRIPTIONS: tuple[EcovacsSensorEntityDescription, ...] = (
capability_fn=lambda caps: caps.stats.clean,
value_fn=lambda e: e.area,
translation_key="stats_area",
native_unit_of_measurement=UnitOfArea.SQUARE_METERS,
device_class=SensorDeviceClass.AREA,
native_unit_of_measurement_fn=get_area_native_unit_of_measurement,
suggested_unit_of_measurement=UnitOfArea.SQUARE_METERS,
),
EcovacsSensorEntityDescription[StatsEvent](
key="stats_time",
@@ -85,6 +97,7 @@ ENTITY_DESCRIPTIONS: tuple[EcovacsSensorEntityDescription, ...] = (
value_fn=lambda e: e.area,
key="total_stats_area",
translation_key="total_stats_area",
device_class=SensorDeviceClass.AREA,
native_unit_of_measurement=UnitOfArea.SQUARE_METERS,
state_class=SensorStateClass.TOTAL_INCREASING,
),
@@ -249,6 +262,27 @@ class EcovacsSensor(
entity_description: EcovacsSensorEntityDescription
def __init__(
self,
device: Device,
capability: CapabilityEvent,
entity_description: EcovacsSensorEntityDescription,
**kwargs: Any,
) -> None:
"""Initialize entity."""
super().__init__(device, capability, entity_description, **kwargs)
if (
entity_description.native_unit_of_measurement_fn
and (
native_unit_of_measurement
:= entity_description.native_unit_of_measurement_fn(
device.capabilities.device_type
)
)
is not None
):
self._attr_native_unit_of_measurement = native_unit_of_measurement
async def async_added_to_hass(self) -> None:
"""Set up the event listeners now that hass is ready."""
await super().async_added_to_hass()

View File

@@ -6,5 +6,5 @@
"iot_class": "local_push",
"loggers": ["sense_energy"],
"quality_scale": "internal",
"requirements": ["sense-energy==0.13.7"]
"requirements": ["sense-energy==0.13.8"]
}

View File

@@ -64,7 +64,7 @@ async def _get_fixture_collection(envoy: Envoy, serial: str) -> dict[str, Any]:
"/ivp/ensemble/generator",
"/ivp/meters",
"/ivp/meters/readings",
"/home,",
"/home",
]
for end_point in end_points:

View File

@@ -7,7 +7,7 @@
"iot_class": "local_polling",
"loggers": ["pyenphase"],
"quality_scale": "platinum",
"requirements": ["pyenphase==1.26.0"],
"requirements": ["pyenphase==1.26.1"],
"zeroconf": [
{
"type": "_enphase-envoy._tcp.local."

View File

@@ -22,5 +22,5 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["eq3btsmart"],
"requirements": ["eq3btsmart==1.4.1", "bleak-esphome==2.14.0"]
"requirements": ["eq3btsmart==1.4.1", "bleak-esphome==2.15.1"]
}

View File

@@ -22,6 +22,7 @@ import voluptuous as vol
from homeassistant.components import zeroconf
from homeassistant.config_entries import (
SOURCE_IGNORE,
SOURCE_REAUTH,
SOURCE_RECONFIGURE,
ConfigEntry,
@@ -31,6 +32,7 @@ from homeassistant.config_entries import (
)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_PORT
from homeassistant.core import callback
from homeassistant.data_entry_flow import AbortFlow
from homeassistant.helpers.device_registry import format_mac
from homeassistant.helpers.service_info.dhcp import DhcpServiceInfo
from homeassistant.helpers.service_info.hassio import HassioServiceInfo
@@ -302,7 +304,15 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
)
):
return
if entry.source == SOURCE_IGNORE:
# Don't call _fetch_device_info() for ignored entries
raise AbortFlow("already_configured")
configured_host: str | None = entry.data.get(CONF_HOST)
configured_port: int | None = entry.data.get(CONF_PORT)
if configured_host == host and configured_port == port:
# Don't probe to verify the mac is correct since
# the host and port matches.
raise AbortFlow("already_configured")
configured_psk: str | None = entry.data.get(CONF_NOISE_PSK)
await self._fetch_device_info(host, port or configured_port, configured_psk)
updates: dict[str, Any] = {}

View File

@@ -17,7 +17,7 @@ DEFAULT_NEW_CONFIG_ALLOW_ALLOW_SERVICE_CALLS = False
DEFAULT_PORT: Final = 6053
STABLE_BLE_VERSION_STR = "2025.2.2"
STABLE_BLE_VERSION_STR = "2025.5.0"
STABLE_BLE_VERSION = AwesomeVersion(STABLE_BLE_VERSION_STR)
PROJECT_URLS = {
"esphome.bluetooth-proxy": "https://esphome.github.io/bluetooth-proxies/",

View File

@@ -223,7 +223,6 @@ class EsphomeEntity(EsphomeBaseEntity, Generic[_InfoT, _StateT]):
self._states = cast(dict[int, _StateT], entry_data.state[state_type])
assert entry_data.device_info is not None
device_info = entry_data.device_info
self._device_info = device_info
self._on_entry_data_changed()
self._key = entity_info.key
self._state_type = state_type
@@ -311,6 +310,11 @@ class EsphomeEntity(EsphomeBaseEntity, Generic[_InfoT, _StateT]):
@callback
def _on_entry_data_changed(self) -> None:
entry_data = self._entry_data
# Update the device info since it can change
# when the device is reconnected
if TYPE_CHECKING:
assert entry_data.device_info is not None
self._device_info = entry_data.device_info
self._api_version = entry_data.api_version
self._client = entry_data.client
if self._device_info.has_deep_sleep:

View File

@@ -19,7 +19,7 @@
"requirements": [
"aioesphomeapi==30.1.0",
"esphome-dashboard-api==1.3.0",
"bleak-esphome==2.14.0"
"bleak-esphome==2.15.1"
],
"zeroconf": ["_esphomelib._tcp.local."]
}

View File

@@ -29,7 +29,6 @@ from homeassistant.util.enum import try_parse_enum
from .const import DOMAIN
from .coordinator import ESPHomeDashboardCoordinator
from .dashboard import async_get_dashboard
from .domain_data import DomainData
from .entity import (
EsphomeEntity,
convert_api_error_ha_error,
@@ -62,7 +61,7 @@ async def async_setup_entry(
if (dashboard := async_get_dashboard(hass)) is None:
return
entry_data = DomainData.get(hass).get_entry_data(entry)
entry_data = entry.runtime_data
assert entry_data.device_info is not None
device_name = entry_data.device_info.name
unsubs: list[CALLBACK_TYPE] = []

View File

@@ -45,7 +45,7 @@ async def async_unload_entry(hass: HomeAssistant, entry: FeedReaderConfigEntry)
# if this is the last entry, remove the storage
if len(entries) == 1:
hass.data.pop(MY_KEY)
return await hass.config_entries.async_unload_platforms(entry, Platform.EVENT)
return await hass.config_entries.async_unload_platforms(entry, [Platform.EVENT])
async def _async_update_listener(

View File

@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "local_push",
"loggers": ["pyfibaro"],
"requirements": ["pyfibaro==0.8.2"]
"requirements": ["pyfibaro==0.8.3"]
}

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/forecast_solar",
"integration_type": "service",
"iot_class": "cloud_polling",
"requirements": ["forecast-solar==4.1.0"]
"requirements": ["forecast-solar==4.2.0"]
}

View File

@@ -144,6 +144,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
self.check_active_or_lock_mode()
if kwargs.get(ATTR_HVAC_MODE) is HVACMode.OFF:
await self.async_set_hkr_state("off")
elif (target_temp := kwargs.get(ATTR_TEMPERATURE)) is not None:
@@ -168,11 +169,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new operation mode."""
if self.data.holiday_active or self.data.summer_active:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="change_hvac_while_active_mode",
)
self.check_active_or_lock_mode()
if self.hvac_mode is hvac_mode:
LOGGER.debug(
"%s is already in requested hvac mode %s", self.name, hvac_mode
@@ -204,11 +201,7 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set preset mode."""
if self.data.holiday_active or self.data.summer_active:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="change_preset_while_active_mode",
)
self.check_active_or_lock_mode()
await self.async_set_hkr_state(PRESET_API_HKR_STATE_MAPPING[preset_mode])
@property
@@ -230,3 +223,17 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
attrs[ATTR_STATE_WINDOW_OPEN] = self.data.window_open
return attrs
def check_active_or_lock_mode(self) -> None:
"""Check if in summer/vacation mode or lock enabled."""
if self.data.holiday_active or self.data.summer_active:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="change_settings_while_active_mode",
)
if self.data.lock:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="change_settings_while_lock_enabled",
)

View File

@@ -92,7 +92,7 @@ class FritzboxDataUpdateCoordinator(DataUpdateCoordinator[FritzboxCoordinatorDat
available_main_ains = [
ain
for ain, dev in data.devices.items()
for ain, dev in data.devices.items() | data.templates.items()
if dev.device_and_unit_id[1] is None
]
device_reg = dr.async_get(self.hass)

View File

@@ -88,11 +88,11 @@
"manual_switching_disabled": {
"message": "Can't toggle switch while manual switching is disabled for the device."
},
"change_preset_while_active_mode": {
"message": "Can't change preset while holiday or summer mode is active on the device."
"change_settings_while_lock_enabled": {
"message": "Can't change settings while manual access for telephone, app, or user interface is disabled on the device"
},
"change_hvac_while_active_mode": {
"message": "Can't change HVAC mode while holiday or summer mode is active on the device."
"change_settings_while_active_mode": {
"message": "Can't change settings while holiday or summer mode is active on the device."
}
}
}

View File

@@ -45,7 +45,15 @@ type FroniusConfigEntry = ConfigEntry[FroniusSolarNet]
async def async_setup_entry(hass: HomeAssistant, entry: FroniusConfigEntry) -> bool:
"""Set up fronius from a config entry."""
host = entry.data[CONF_HOST]
fronius = Fronius(async_get_clientsession(hass), host)
fronius = Fronius(
async_get_clientsession(
hass,
# Fronius Gen24 firmware 1.35.4-1 redirects to HTTPS with self-signed
# certificate. See https://github.com/home-assistant/core/issues/138881
verify_ssl=False,
),
host,
)
solar_net = FroniusSolarNet(hass, entry, fronius)
await solar_net.init_devices()

View File

@@ -35,7 +35,7 @@ async def validate_host(
hass: HomeAssistant, host: str
) -> tuple[str, FroniusConfigEntryData]:
"""Validate the user input allows us to connect."""
fronius = Fronius(async_get_clientsession(hass), host)
fronius = Fronius(async_get_clientsession(hass, verify_ssl=False), host)
try:
datalogger_info: dict[str, Any]

View File

@@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20250430.2"]
"requirements": ["home-assistant-frontend==20250516.0"]
}

View File

@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/google",
"iot_class": "cloud_polling",
"loggers": ["googleapiclient"],
"requirements": ["gcal-sync==7.0.0", "oauth2client==4.1.3", "ical==9.1.0"]
"requirements": ["gcal-sync==7.0.1", "oauth2client==4.1.3", "ical==9.2.4"]
}

View File

@@ -2,11 +2,13 @@
from __future__ import annotations
import asyncio
import mimetypes
from pathlib import Path
from google.genai import Client
from google.genai.errors import APIError, ClientError
from google.genai.types import File, FileState
from requests.exceptions import Timeout
import voluptuous as vol
@@ -32,6 +34,8 @@ from .const import (
CONF_CHAT_MODEL,
CONF_PROMPT,
DOMAIN,
FILE_POLLING_INTERVAL_SECONDS,
LOGGER,
RECOMMENDED_CHAT_MODEL,
TIMEOUT_MILLIS,
)
@@ -91,8 +95,40 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
)
prompt_parts.append(uploaded_file)
async def wait_for_file_processing(uploaded_file: File) -> None:
"""Wait for file processing to complete."""
while True:
uploaded_file = await client.aio.files.get(
name=uploaded_file.name,
config={"http_options": {"timeout": TIMEOUT_MILLIS}},
)
if uploaded_file.state not in (
FileState.STATE_UNSPECIFIED,
FileState.PROCESSING,
):
break
LOGGER.debug(
"Waiting for file `%s` to be processed, current state: %s",
uploaded_file.name,
uploaded_file.state,
)
await asyncio.sleep(FILE_POLLING_INTERVAL_SECONDS)
if uploaded_file.state == FileState.FAILED:
raise HomeAssistantError(
f"File `{uploaded_file.name}` processing failed, reason: {uploaded_file.error.message}"
)
await hass.async_add_executor_job(append_files_to_prompt)
tasks = [
asyncio.create_task(wait_for_file_processing(part))
for part in prompt_parts
if isinstance(part, File) and part.state != FileState.ACTIVE
]
async with asyncio.timeout(TIMEOUT_MILLIS / 1000):
await asyncio.gather(*tasks)
try:
response = await client.aio.models.generate_content(
model=RECOMMENDED_CHAT_MODEL, contents=prompt_parts

View File

@@ -254,11 +254,11 @@ async def google_generative_ai_config_option_schema(
)
for api_model in sorted(api_models, key=lambda x: x.display_name or "")
if (
api_model.name != "models/gemini-1.0-pro" # duplicate of gemini-pro
and api_model.display_name
api_model.display_name
and api_model.name
and api_model.supported_actions
and "tts" not in api_model.name
and "vision" not in api_model.name
and api_model.supported_actions
and "generateContent" in api_model.supported_actions
)
]

View File

@@ -26,3 +26,4 @@ CONF_USE_GOOGLE_SEARCH_TOOL = "enable_google_search_tool"
RECOMMENDED_USE_GOOGLE_SEARCH_TOOL = False
TIMEOUT_MILLIS = 10000
FILE_POLLING_INTERVAL_SECONDS = 0.05

View File

@@ -319,11 +319,10 @@ class GoogleGenerativeAIConversationEntity(
tools.append(Tool(google_search=GoogleSearch()))
model_name = self.entry.options.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL)
# Gemini 1.0 doesn't support system_instruction while 1.5 does.
# Assume future versions will support it (if not, the request fails with a
# clear message at which point we can fix).
# Avoid INVALID_ARGUMENT Developer instruction is not enabled for <model>
supports_system_instruction = (
"gemini-1.0" not in model_name and "gemini-pro" not in model_name
"gemma" not in model_name
and "gemini-2.0-flash-preview-image-generation" not in model_name
)
prompt_content = cast(

View File

@@ -41,12 +41,12 @@
},
"data_description": {
"prompt": "Instruct how the LLM should respond. This can be a template.",
"enable_google_search_tool": "Only works with \"No control\" in the \"Control Home Assistant\" setting. See docs for a workaround using it with \"Assist\"."
"enable_google_search_tool": "Only works if there is nothing selected in the \"Control Home Assistant\" setting. See docs for a workaround using it with \"Assist\"."
}
}
},
"error": {
"invalid_google_search_option": "Google Search cannot be enabled alongside any Assist capability, this can only be used when Assist is set to \"No control\"."
"invalid_google_search_option": "Google Search can only be enabled if nothing is selected in the \"Control Home Assistant\" setting."
}
},
"services": {

View File

@@ -109,6 +109,7 @@ class HassIOIngress(HomeAssistantView):
delete = _handle
patch = _handle
options = _handle
head = _handle
async def _handle_websocket(
self, request: web.Request, token: str, path: str

View File

@@ -60,6 +60,9 @@ class HistoryStats:
self._start = start
self._end = end
self._pending_events: list[Event[EventStateChangedData]] = []
self._query_count = 0
async def async_update(
self, event: Event[EventStateChangedData] | None
) -> HistoryStatsState:
@@ -85,6 +88,14 @@ class HistoryStats:
utc_now = dt_util.utcnow()
now_timestamp = floored_timestamp(utc_now)
# If we end up querying data from the recorder when we get triggered by a new state
# change event, it is possible this function could be reentered a second time before
# the first recorder query returns. In that case a second recorder query will be done
# and we need to hold the new event so that we can append it after the second query.
# Otherwise the event will be dropped.
if event:
self._pending_events.append(event)
if current_period_start_timestamp > now_timestamp:
# History cannot tell the future
self._history_current_period = []
@@ -113,15 +124,14 @@ class HistoryStats:
start_changed = (
current_period_start_timestamp != previous_period_start_timestamp
)
end_changed = current_period_end_timestamp != previous_period_end_timestamp
if start_changed:
self._prune_history_cache(current_period_start_timestamp)
new_data = False
if event and (new_state := event.data["new_state"]) is not None:
if (
current_period_start_timestamp
<= floored_timestamp(new_state.last_changed)
<= current_period_end_timestamp
if current_period_start_timestamp <= floored_timestamp(
new_state.last_changed
):
self._history_current_period.append(
HistoryState(new_state.state, new_state.last_changed_timestamp)
@@ -131,26 +141,31 @@ class HistoryStats:
not new_data
and current_period_end_timestamp < now_timestamp
and not start_changed
and not end_changed
):
# If period has not changed and current time after the period end...
# Don't compute anything as the value cannot have changed
return self._state
else:
await self._async_history_from_db(
current_period_start_timestamp, current_period_end_timestamp
current_period_start_timestamp, now_timestamp
)
if event and (new_state := event.data["new_state"]) is not None:
if (
current_period_start_timestamp
<= floored_timestamp(new_state.last_changed)
<= current_period_end_timestamp
):
self._history_current_period.append(
HistoryState(new_state.state, new_state.last_changed_timestamp)
)
for pending_event in self._pending_events:
if (new_state := pending_event.data["new_state"]) is not None:
if current_period_start_timestamp <= floored_timestamp(
new_state.last_changed
):
self._history_current_period.append(
HistoryState(
new_state.state, new_state.last_changed_timestamp
)
)
self._has_recorder_data = True
if self._query_count == 0:
self._pending_events.clear()
seconds_matched, match_count = self._async_compute_seconds_and_changes(
now_timestamp,
current_period_start_timestamp,
@@ -165,12 +180,16 @@ class HistoryStats:
current_period_end_timestamp: float,
) -> None:
"""Update history data for the current period from the database."""
instance = get_instance(self.hass)
states = await instance.async_add_executor_job(
self._state_changes_during_period,
current_period_start_timestamp,
current_period_end_timestamp,
)
self._query_count += 1
try:
instance = get_instance(self.hass)
states = await instance.async_add_executor_job(
self._state_changes_during_period,
current_period_start_timestamp,
current_period_end_timestamp,
)
finally:
self._query_count -= 1
self._history_current_period = [
HistoryState(state.state, state.last_changed.timestamp())
for state in states
@@ -208,6 +227,9 @@ class HistoryStats:
current_state_matches = history_state.state in self._entity_states
state_change_timestamp = history_state.last_changed
if math.floor(state_change_timestamp) > end_timestamp:
break
if math.floor(state_change_timestamp) > now_timestamp:
# Shouldn't count states that are in the future
_LOGGER.debug(
@@ -215,7 +237,7 @@ class HistoryStats:
state_change_timestamp,
now_timestamp,
)
continue
break
if previous_state_matches:
elapsed += state_change_timestamp - last_state_change_timestamp

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/holiday",
"iot_class": "local_polling",
"requirements": ["holidays==0.70", "babel==2.15.0"]
"requirements": ["holidays==0.73", "babel==2.15.0"]
}

View File

@@ -234,7 +234,7 @@
"consumer_products_coffee_maker_program_coffee_world_black_eye": "Black eye",
"consumer_products_coffee_maker_program_coffee_world_dead_eye": "Dead eye",
"consumer_products_coffee_maker_program_beverage_hot_water": "Hot water",
"dishcare_dishwasher_program_pre_rinse": "Pre_rinse",
"dishcare_dishwasher_program_pre_rinse": "Pre-rinse",
"dishcare_dishwasher_program_auto_1": "Auto 1",
"dishcare_dishwasher_program_auto_2": "Auto 2",
"dishcare_dishwasher_program_auto_3": "Auto 3",
@@ -252,7 +252,7 @@
"dishcare_dishwasher_program_intensiv_power": "Intensive power",
"dishcare_dishwasher_program_magic_daily": "Magic daily",
"dishcare_dishwasher_program_super_60": "Super 60ºC",
"dishcare_dishwasher_program_kurz_60": "Kurz 60ºC",
"dishcare_dishwasher_program_kurz_60": "Speed 60ºC",
"dishcare_dishwasher_program_express_sparkle_65": "Express sparkle 65ºC",
"dishcare_dishwasher_program_machine_care": "Machine care",
"dishcare_dishwasher_program_steam_fresh": "Steam fresh",
@@ -1551,31 +1551,39 @@
}
},
"coffee_counter": {
"name": "Coffees"
"name": "Coffees",
"unit_of_measurement": "coffees"
},
"powder_coffee_counter": {
"name": "Powder coffees"
"name": "Powder coffees",
"unit_of_measurement": "[%key:component::home_connect::entity::sensor::coffee_counter::unit_of_measurement%]"
},
"hot_water_counter": {
"name": "Hot water"
},
"hot_water_cups_counter": {
"name": "Hot water cups"
"name": "Hot water cups",
"unit_of_measurement": "cups"
},
"hot_milk_counter": {
"name": "Hot milk cups"
"name": "Hot milk cups",
"unit_of_measurement": "[%key:component::home_connect::entity::sensor::hot_water_cups_counter::unit_of_measurement%]"
},
"frothy_milk_counter": {
"name": "Frothy milk cups"
"name": "Frothy milk cups",
"unit_of_measurement": "[%key:component::home_connect::entity::sensor::hot_water_cups_counter::unit_of_measurement%]"
},
"milk_counter": {
"name": "Milk cups"
"name": "Milk cups",
"unit_of_measurement": "[%key:component::home_connect::entity::sensor::hot_water_cups_counter::unit_of_measurement%]"
},
"coffee_and_milk_counter": {
"name": "Coffee and milk cups"
"name": "Coffee and milk cups",
"unit_of_measurement": "[%key:component::home_connect::entity::sensor::hot_water_cups_counter::unit_of_measurement%]"
},
"ristretto_espresso_counter": {
"name": "Ristretto espresso cups"
"name": "Ristretto espresso cups",
"unit_of_measurement": "[%key:component::home_connect::entity::sensor::hot_water_cups_counter::unit_of_measurement%]"
},
"battery_level": {
"name": "Battery level"

View File

@@ -90,16 +90,17 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
minor_version=2,
)
if config_entry.minor_version == 2:
# Add a `firmware_version` key
if config_entry.minor_version <= 3:
# Add a `firmware_version` key if it doesn't exist to handle entries created
# with minor version 1.3 where the firmware version was not set.
hass.config_entries.async_update_entry(
config_entry,
data={
**config_entry.data,
FIRMWARE_VERSION: None,
FIRMWARE_VERSION: config_entry.data.get(FIRMWARE_VERSION),
},
version=1,
minor_version=3,
minor_version=4,
)
_LOGGER.debug(

View File

@@ -62,7 +62,7 @@ class HomeAssistantYellowConfigFlow(BaseFirmwareConfigFlow, domain=DOMAIN):
"""Handle a config flow for Home Assistant Yellow."""
VERSION = 1
MINOR_VERSION = 3
MINOR_VERSION = 4
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Instantiate config flow."""
@@ -116,6 +116,11 @@ class HomeAssistantYellowConfigFlow(BaseFirmwareConfigFlow, domain=DOMAIN):
if self._probed_firmware_info is not None
else ApplicationType.EZSP
).value,
FIRMWARE_VERSION: (
self._probed_firmware_info.firmware_version
if self._probed_firmware_info is not None
else None
),
},
)

View File

@@ -8,7 +8,13 @@ from pyhap.const import CATEGORY_AIR_PURIFIER
from pyhap.service import Service
from pyhap.util import callback as pyhap_callback
from homeassistant.const import STATE_ON, STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.const import (
ATTR_UNIT_OF_MEASUREMENT,
STATE_ON,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
UnitOfTemperature,
)
from homeassistant.core import (
Event,
EventStateChangedData,
@@ -43,7 +49,12 @@ from .const import (
THRESHOLD_FILTER_CHANGE_NEEDED,
)
from .type_fans import ATTR_PRESET_MODE, CHAR_ROTATION_SPEED, Fan
from .util import cleanup_name_for_homekit, convert_to_float, density_to_air_quality
from .util import (
cleanup_name_for_homekit,
convert_to_float,
density_to_air_quality,
temperature_to_homekit,
)
_LOGGER = logging.getLogger(__name__)
@@ -345,8 +356,13 @@ class AirPurifier(Fan):
):
return
unit = new_state.attributes.get(
ATTR_UNIT_OF_MEASUREMENT, UnitOfTemperature.CELSIUS
)
current_temperature = temperature_to_homekit(current_temperature, unit)
_LOGGER.debug(
"%s: Linked temperature sensor %s changed to %d",
"%s: Linked temperature sensor %s changed to %d °C",
self.entity_id,
self.linked_temperature_sensor,
current_temperature,

View File

@@ -1,8 +1,11 @@
"""Support for HomematicIP Cloud events."""
from collections.abc import Callable
from dataclasses import dataclass
from typing import TYPE_CHECKING
from homematicip.base.channel_event import ChannelEvent
from homematicip.base.functionalChannels import FunctionalChannel
from homematicip.device import Device
from homeassistant.components.event import (
@@ -23,6 +26,9 @@ from .hap import HomematicipHAP
class HmipEventEntityDescription(EventEntityDescription):
"""Description of a HomematicIP Cloud event."""
channel_event_types: list[str] | None = None
channel_selector_fn: Callable[[FunctionalChannel], bool] | None = None
EVENT_DESCRIPTIONS = {
"doorbell": HmipEventEntityDescription(
@@ -30,6 +36,8 @@ EVENT_DESCRIPTIONS = {
translation_key="doorbell",
device_class=EventDeviceClass.DOORBELL,
event_types=["ring"],
channel_event_types=["DOOR_BELL_SENSOR_EVENT"],
channel_selector_fn=lambda channel: channel.channelRole == "DOOR_BELL_INPUT",
),
}
@@ -41,24 +49,29 @@ async def async_setup_entry(
) -> None:
"""Set up the HomematicIP cover from a config entry."""
hap = hass.data[DOMAIN][config_entry.unique_id]
entities: list[HomematicipGenericEntity] = []
async_add_entities(
entities.extend(
HomematicipDoorBellEvent(
hap,
device,
channel.index,
EVENT_DESCRIPTIONS["doorbell"],
description,
)
for description in EVENT_DESCRIPTIONS.values()
for device in hap.home.devices
for channel in device.functionalChannels
if channel.channelRole == "DOOR_BELL_INPUT"
if description.channel_selector_fn and description.channel_selector_fn(channel)
)
async_add_entities(entities)
class HomematicipDoorBellEvent(HomematicipGenericEntity, EventEntity):
"""Event class for HomematicIP doorbell events."""
_attr_device_class = EventDeviceClass.DOORBELL
entity_description: HmipEventEntityDescription
def __init__(
self,
@@ -86,9 +99,27 @@ class HomematicipDoorBellEvent(HomematicipGenericEntity, EventEntity):
@callback
def _async_handle_event(self, *args, **kwargs) -> None:
"""Handle the event fired by the functional channel."""
raised_channel_event = self._get_channel_event_from_args(*args)
if not self._should_raise(raised_channel_event):
return
event_types = self.entity_description.event_types
if TYPE_CHECKING:
assert event_types is not None
self._trigger_event(event_type=event_types[0])
self.async_write_ha_state()
def _should_raise(self, event_type: str) -> bool:
"""Check if the event should be raised."""
if self.entity_description.channel_event_types is None:
return False
return event_type in self.entity_description.channel_event_types
def _get_channel_event_from_args(self, *args) -> str:
"""Get the channel event."""
if isinstance(args[0], ChannelEvent):
return args[0].channelEventType
return ""

View File

@@ -9,10 +9,10 @@ from typing import Any
from homematicip.async_home import AsyncHome
from homematicip.auth import Auth
from homematicip.base.base_connection import HmipConnectionError
from homematicip.base.enums import EventType
from homematicip.connection.connection_context import ConnectionContextBuilder
from homematicip.connection.rest_connection import RestConnection
from homematicip.exceptions.connection_exceptions import HmipConnectionError
import homeassistant
from homeassistant.config_entries import ConfigEntry

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/homematicip_cloud",
"iot_class": "cloud_push",
"loggers": ["homematicip"],
"requirements": ["homematicip==2.0.1"]
"requirements": ["homematicip==2.0.1.1"]
}

View File

@@ -110,14 +110,14 @@ class AutomowerLawnMowerEntity(AutomowerAvailableEntity, LawnMowerEntity):
mower_attributes = self.mower_attributes
if mower_attributes.mower.state in PAUSED_STATES:
return LawnMowerActivity.PAUSED
if mower_attributes.mower.activity in MOWING_ACTIVITIES:
return LawnMowerActivity.MOWING
if mower_attributes.mower.activity == MowerActivities.GOING_HOME:
return LawnMowerActivity.RETURNING
if (mower_attributes.mower.state == "RESTRICTED") or (
mower_attributes.mower.activity in DOCKED_ACTIVITIES
):
return LawnMowerActivity.DOCKED
if mower_attributes.mower.state in MowerStates.IN_OPERATION:
if mower_attributes.mower.activity == MowerActivities.GOING_HOME:
return LawnMowerActivity.RETURNING
return LawnMowerActivity.MOWING
return LawnMowerActivity.ERROR
@property

View File

@@ -8,5 +8,5 @@
"iot_class": "cloud_push",
"loggers": ["aioautomower"],
"quality_scale": "silver",
"requirements": ["aioautomower==2025.4.4"]
"requirements": ["aioautomower==2025.5.1"]
}

View File

@@ -58,6 +58,7 @@ class INKBIRDActiveBluetoothProcessorCoordinator(
update_method=self._async_on_update,
needs_poll_method=self._async_needs_poll,
poll_method=self._async_poll_data,
connectable=False, # Polling only happens if active scanning is disabled
)
async def async_init(self) -> None:

View File

@@ -34,6 +34,10 @@
"local_name": "ITH-21-B",
"connectable": false
},
{
"local_name": "IBS-P02B",
"connectable": false
},
{
"local_name": "Ink@IAM-T1",
"connectable": true
@@ -49,5 +53,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/inkbird",
"iot_class": "local_push",
"requirements": ["inkbird-ble==0.15.0"]
"requirements": ["inkbird-ble==0.16.1"]
}

View File

@@ -10,6 +10,11 @@ from aiohttp import web
import voluptuous as vol
from homeassistant.components import http, sensor
from homeassistant.components.button import (
DOMAIN as BUTTON_DOMAIN,
SERVICE_PRESS as SERVICE_PRESS_BUTTON,
ButtonDeviceClass,
)
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
from homeassistant.components.cover import (
ATTR_POSITION,
@@ -20,6 +25,7 @@ from homeassistant.components.cover import (
CoverDeviceClass,
)
from homeassistant.components.http.data_validator import RequestDataValidator
from homeassistant.components.input_button import DOMAIN as INPUT_BUTTON_DOMAIN
from homeassistant.components.lock import (
DOMAIN as LOCK_DOMAIN,
SERVICE_LOCK,
@@ -80,6 +86,7 @@ __all__ = [
]
ONOFF_DEVICE_CLASSES = {
ButtonDeviceClass,
CoverDeviceClass,
ValveDeviceClass,
SwitchDeviceClass,
@@ -103,7 +110,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
intent.INTENT_TURN_ON,
HOMEASSISTANT_DOMAIN,
SERVICE_TURN_ON,
description="Turns on/opens a device or entity. For locks, this performs a 'lock' action. Use for requests like 'turn on', 'activate', 'enable', or 'lock'.",
description="Turns on/opens/presses a device or entity. For locks, this performs a 'lock' action. Use for requests like 'turn on', 'activate', 'enable', or 'lock'.",
device_classes=ONOFF_DEVICE_CLASSES,
),
)
@@ -168,6 +175,25 @@ class OnOffIntentHandler(intent.ServiceIntentHandler):
"""Call service on entity with handling for special cases."""
hass = intent_obj.hass
if state.domain in (BUTTON_DOMAIN, INPUT_BUTTON_DOMAIN):
if service != SERVICE_TURN_ON:
raise intent.IntentHandleError(
f"Entity {state.entity_id} cannot be turned off"
)
await self._run_then_background(
hass.async_create_task(
hass.services.async_call(
state.domain,
SERVICE_PRESS_BUTTON,
{ATTR_ENTITY_ID: state.entity_id},
context=intent_obj.context,
blocking=True,
)
)
)
return
if state.domain == COVER_DOMAIN:
# on = open
# off = close

View File

@@ -401,8 +401,7 @@ def _categorize_programs(isy_data: IsyData, programs: Programs) -> None:
for dtype, _, node_id in folder.children:
if dtype != TAG_FOLDER:
continue
entity_folder = folder[node_id]
entity_folder: Programs = folder[node_id]
actions = None
status = entity_folder.get_by_name(KEY_STATUS)
if not status or status.protocol != PROTO_PROGRAM:

View File

@@ -24,7 +24,7 @@
"integration_type": "hub",
"iot_class": "local_push",
"loggers": ["pyisy"],
"requirements": ["pyisy==3.4.0"],
"requirements": ["pyisy==3.4.1"],
"ssdp": [
{
"manufacturer": "Universal Devices Inc.",

View File

@@ -152,7 +152,10 @@ async def item_payload(item, get_thumbnail_url=None):
_LOGGER.debug("Unknown media type received: %s", media_content_type)
raise UnknownMediaType from err
thumbnail = item.get("thumbnail")
if "art" in item:
thumbnail = item["art"].get("poster", item.get("thumbnail"))
else:
thumbnail = item.get("thumbnail")
if thumbnail is not None and get_thumbnail_url is not None:
thumbnail = await get_thumbnail_url(
media_content_type, media_content_id, thumbnail_url=thumbnail
@@ -237,14 +240,16 @@ async def get_media_info(media_library, search_id, search_type):
title = None
media = None
properties = ["thumbnail"]
properties = ["thumbnail", "art"]
if search_type == MediaType.ALBUM:
if search_id:
album = await media_library.get_album_details(
album_id=int(search_id), properties=properties
)
thumbnail = media_library.thumbnail_url(
album["albumdetails"].get("thumbnail")
album["albumdetails"]["art"].get(
"poster", album["albumdetails"].get("thumbnail")
)
)
title = album["albumdetails"]["label"]
media = await media_library.get_songs(
@@ -256,6 +261,7 @@ async def get_media_info(media_library, search_id, search_type):
"album",
"thumbnail",
"track",
"art",
],
)
media = media.get("songs")
@@ -274,7 +280,9 @@ async def get_media_info(media_library, search_id, search_type):
artist_id=int(search_id), properties=properties
)
thumbnail = media_library.thumbnail_url(
artist["artistdetails"].get("thumbnail")
artist["artistdetails"]["art"].get(
"poster", artist["artistdetails"].get("thumbnail")
)
)
title = artist["artistdetails"]["label"]
else:
@@ -293,9 +301,10 @@ async def get_media_info(media_library, search_id, search_type):
movie_id=int(search_id), properties=properties
)
thumbnail = media_library.thumbnail_url(
movie["moviedetails"].get("thumbnail")
movie["moviedetails"]["art"].get(
"poster", movie["moviedetails"].get("thumbnail")
)
)
title = movie["moviedetails"]["label"]
else:
media = await media_library.get_movies(properties)
media = media.get("movies")
@@ -305,14 +314,16 @@ async def get_media_info(media_library, search_id, search_type):
if search_id:
media = await media_library.get_seasons(
tv_show_id=int(search_id),
properties=["thumbnail", "season", "tvshowid"],
properties=["thumbnail", "season", "tvshowid", "art"],
)
media = media.get("seasons")
tvshow = await media_library.get_tv_show_details(
tv_show_id=int(search_id), properties=properties
)
thumbnail = media_library.thumbnail_url(
tvshow["tvshowdetails"].get("thumbnail")
tvshow["tvshowdetails"]["art"].get(
"poster", tvshow["tvshowdetails"].get("thumbnail")
)
)
title = tvshow["tvshowdetails"]["label"]
else:
@@ -325,7 +336,7 @@ async def get_media_info(media_library, search_id, search_type):
media = await media_library.get_episodes(
tv_show_id=int(tv_show_id),
season_id=int(season_id),
properties=["thumbnail", "tvshowid", "seasonid"],
properties=["thumbnail", "tvshowid", "seasonid", "art"],
)
media = media.get("episodes")
if media:
@@ -333,7 +344,9 @@ async def get_media_info(media_library, search_id, search_type):
season_id=int(media[0]["seasonid"]), properties=properties
)
thumbnail = media_library.thumbnail_url(
season["seasondetails"].get("thumbnail")
season["seasondetails"]["art"].get(
"poster", season["seasondetails"].get("thumbnail")
)
)
title = season["seasondetails"]["label"]
@@ -343,6 +356,7 @@ async def get_media_info(media_library, search_id, search_type):
properties=["thumbnail", "channeltype", "channel", "broadcastnow"],
)
media = media.get("channels")
title = "Channels"
return thumbnail, title, media

View File

@@ -23,7 +23,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import issue_registry as ir
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import CONF_USE_BLUETOOTH, DOMAIN
from .coordinator import (
@@ -57,7 +57,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: LaMarzoccoConfigEntry) -
assert entry.unique_id
serial = entry.unique_id
client = async_create_clientsession(hass)
client = async_get_clientsession(hass)
cloud_client = LaMarzoccoCloudClient(
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],
@@ -70,7 +70,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: LaMarzoccoConfigEntry) -
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN, translation_key="authentication_failed"
) from ex
except RequestNotSuccessful as ex:
except (RequestNotSuccessful, TimeoutError) as ex:
_LOGGER.debug(ex, exc_info=True)
raise ConfigEntryNotReady(
translation_domain=DOMAIN, translation_key="api_error"

View File

@@ -5,7 +5,7 @@ from dataclasses import dataclass
from typing import cast
from pylamarzocco import LaMarzoccoMachine
from pylamarzocco.const import BackFlushStatus, MachineState, WidgetType
from pylamarzocco.const import BackFlushStatus, MachineState, ModelName, WidgetType
from pylamarzocco.models import BackFlush, MachineStatus
from homeassistant.components.binary_sensor import (
@@ -52,7 +52,7 @@ ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = (
).status
is MachineState.BREWING
),
available_fn=lambda device: device.websocket.connected,
available_fn=lambda coordinator: not coordinator.websocket_terminated,
entity_category=EntityCategory.DIAGNOSTIC,
),
LaMarzoccoBinarySensorEntityDescription(
@@ -66,6 +66,9 @@ ENTITIES: tuple[LaMarzoccoBinarySensorEntityDescription, ...] = (
is BackFlushStatus.REQUESTED
),
entity_category=EntityCategory.DIAGNOSTIC,
supported_fn=lambda coordinator: (
coordinator.device.dashboard.model_name != ModelName.GS3_MP
),
),
LaMarzoccoBinarySensorEntityDescription(
key="websocket_connected",

View File

@@ -33,7 +33,7 @@ from homeassistant.const import (
)
from homeassistant.core import callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.selector import (
SelectOptionDict,
SelectSelector,
@@ -83,7 +83,7 @@ class LmConfigFlow(ConfigFlow, domain=DOMAIN):
**user_input,
}
self._client = async_create_clientsession(self.hass)
self._client = async_get_clientsession(self.hass)
cloud_client = LaMarzoccoCloudClient(
username=data[CONF_USERNAME],
password=data[CONF_PASSWORD],

View File

@@ -44,6 +44,7 @@ class LaMarzoccoUpdateCoordinator(DataUpdateCoordinator[None]):
_default_update_interval = SCAN_INTERVAL
config_entry: LaMarzoccoConfigEntry
websocket_terminated = True
def __init__(
self,
@@ -92,25 +93,37 @@ class LaMarzoccoConfigUpdateCoordinator(LaMarzoccoUpdateCoordinator):
await self.device.get_dashboard()
_LOGGER.debug("Current status: %s", self.device.dashboard.to_dict())
_LOGGER.debug("Init WebSocket in background task")
self.config_entry.async_create_background_task(
hass=self.hass,
target=self.device.connect_dashboard_websocket(
update_callback=lambda _: self.async_set_updated_data(None)
),
target=self.connect_websocket(),
name="lm_websocket_task",
)
async def websocket_close(_: Any | None = None) -> None:
if self.device.websocket.connected:
await self.device.websocket.disconnect()
await self.device.websocket.disconnect()
self.config_entry.async_on_unload(
self.hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, websocket_close)
)
self.config_entry.async_on_unload(websocket_close)
async def connect_websocket(self) -> None:
"""Connect to the websocket."""
_LOGGER.debug("Init WebSocket in background task")
self.websocket_terminated = False
self.async_update_listeners()
await self.device.connect_dashboard_websocket(
update_callback=lambda _: self.async_set_updated_data(None),
connect_callback=self.async_update_listeners,
disconnect_callback=self.async_update_listeners,
)
self.websocket_terminated = True
self.async_update_listeners()
class LaMarzoccoSettingsUpdateCoordinator(LaMarzoccoUpdateCoordinator):
"""Coordinator for La Marzocco settings."""

View File

@@ -5,8 +5,10 @@ from __future__ import annotations
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.const import CONF_MAC, CONF_TOKEN
from homeassistant.core import HomeAssistant
from .const import CONF_USE_BLUETOOTH
from .coordinator import LaMarzoccoConfigEntry
TO_REDACT = {
@@ -21,4 +23,12 @@ async def async_get_config_entry_diagnostics(
"""Return diagnostics for a config entry."""
coordinator = entry.runtime_data.config_coordinator
device = coordinator.device
return async_redact_data(device.to_dict(), TO_REDACT)
data = {
"device": device.to_dict(),
"bluetooth_available": {
"options_enabled": entry.options.get(CONF_USE_BLUETOOTH, True),
CONF_MAC: CONF_MAC in entry.data,
CONF_TOKEN: CONF_TOKEN in entry.data,
},
}
return async_redact_data(data, TO_REDACT)

View File

@@ -3,7 +3,6 @@
from collections.abc import Callable
from dataclasses import dataclass
from pylamarzocco import LaMarzoccoMachine
from pylamarzocco.const import FirmwareType
from homeassistant.const import CONF_ADDRESS, CONF_MAC
@@ -23,7 +22,7 @@ from .coordinator import LaMarzoccoUpdateCoordinator
class LaMarzoccoEntityDescription(EntityDescription):
"""Description for all LM entities."""
available_fn: Callable[[LaMarzoccoMachine], bool] = lambda _: True
available_fn: Callable[[LaMarzoccoUpdateCoordinator], bool] = lambda _: True
supported_fn: Callable[[LaMarzoccoUpdateCoordinator], bool] = lambda _: True
@@ -74,7 +73,7 @@ class LaMarzoccoEntity(LaMarzoccoBaseEntity):
def available(self) -> bool:
"""Return True if entity is available."""
if super().available:
return self.entity_description.available_fn(self.coordinator.device)
return self.entity_description.available_fn(self.coordinator)
return False
def __init__(

View File

@@ -37,5 +37,5 @@
"iot_class": "cloud_push",
"loggers": ["pylamarzocco"],
"quality_scale": "platinum",
"requirements": ["pylamarzocco==2.0.0b6"]
"requirements": ["pylamarzocco==2.0.4"]
}

View File

@@ -100,8 +100,9 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = (
.seconds.seconds_out
),
available_fn=(
lambda machine: cast(
PreBrewing, machine.dashboard.config[WidgetType.CM_PRE_BREWING]
lambda coordinator: cast(
PreBrewing,
coordinator.device.dashboard.config[WidgetType.CM_PRE_BREWING],
).mode
is PreExtractionMode.PREINFUSION
),
@@ -140,8 +141,8 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = (
.times.pre_brewing[0]
.seconds.seconds_in
),
available_fn=lambda machine: cast(
PreBrewing, machine.dashboard.config[WidgetType.CM_PRE_BREWING]
available_fn=lambda coordinator: cast(
PreBrewing, coordinator.device.dashboard.config[WidgetType.CM_PRE_BREWING]
).mode
is PreExtractionMode.PREBREWING,
supported_fn=(
@@ -180,8 +181,9 @@ ENTITIES: tuple[LaMarzoccoNumberEntityDescription, ...] = (
.seconds.seconds_out
),
available_fn=(
lambda machine: cast(
PreBrewing, machine.dashboard.config[WidgetType.CM_PRE_BREWING]
lambda coordinator: cast(
PreBrewing,
coordinator.device.dashboard.config[WidgetType.CM_PRE_BREWING],
).mode
is PreExtractionMode.PREBREWING
),

View File

@@ -132,17 +132,18 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up sensor entities."""
coordinator = entry.runtime_data.config_coordinator
config_coordinator = entry.runtime_data.config_coordinator
statistic_coordinators = entry.runtime_data.statistics_coordinator
entities = [
LaMarzoccoSensorEntity(coordinator, description)
LaMarzoccoSensorEntity(config_coordinator, description)
for description in ENTITIES
if description.supported_fn(coordinator)
if description.supported_fn(config_coordinator)
]
entities.extend(
LaMarzoccoStatisticSensorEntity(coordinator, description)
LaMarzoccoStatisticSensorEntity(statistic_coordinators, description)
for description in STATISTIC_ENTITIES
if description.supported_fn(coordinator)
if description.supported_fn(statistic_coordinators)
)
async_add_entities(entities)

View File

@@ -4,7 +4,7 @@ import asyncio
from dataclasses import dataclass
from typing import Any
from pylamarzocco.const import FirmwareType, UpdateCommandStatus
from pylamarzocco.const import FirmwareType, UpdateStatus
from pylamarzocco.exceptions import RequestNotSuccessful
from homeassistant.components.update import (
@@ -125,7 +125,7 @@ class LaMarzoccoUpdateEntity(LaMarzoccoEntity, UpdateEntity):
await self.coordinator.device.update_firmware()
while (
update_progress := await self.coordinator.device.get_firmware()
).command_status is UpdateCommandStatus.IN_PROGRESS:
).command_status is UpdateStatus.IN_PROGRESS:
if counter >= MAX_UPDATE_WAIT:
_raise_timeout_error()
self._attr_update_percentage = update_progress.progress_percentage

View File

@@ -442,7 +442,10 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: # noqa:
brightness += params.pop(ATTR_BRIGHTNESS_STEP)
else:
brightness += round(params.pop(ATTR_BRIGHTNESS_STEP_PCT) / 100 * 255)
brightness_pct = round(brightness / 255 * 100)
brightness = round(
(brightness_pct + params.pop(ATTR_BRIGHTNESS_STEP_PCT)) / 100 * 255
)
params[ATTR_BRIGHTNESS] = max(0, min(255, brightness))

View File

@@ -7,6 +7,6 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["linkplay"],
"requirements": ["python-linkplay==0.2.4"],
"requirements": ["python-linkplay==0.2.5"],
"zeroconf": ["_linkplay._tcp.local."]
}

View File

@@ -89,20 +89,27 @@ class LocalCalendarEntity(CalendarEntity):
self, hass: HomeAssistant, start_date: datetime, end_date: datetime
) -> list[CalendarEvent]:
"""Get all events in a specific time frame."""
events = self._calendar.timeline_tz(start_date.tzinfo).overlapping(
start_date,
end_date,
)
return [_get_calendar_event(event) for event in events]
def events_in_range() -> list[CalendarEvent]:
events = self._calendar.timeline_tz(start_date.tzinfo).overlapping(
start_date,
end_date,
)
return [_get_calendar_event(event) for event in events]
return await self.hass.async_add_executor_job(events_in_range)
async def async_update(self) -> None:
"""Update entity state with the next upcoming event."""
now = dt_util.now()
events = self._calendar.timeline_tz(now.tzinfo).active_after(now)
if event := next(events, None):
self._event = _get_calendar_event(event)
else:
self._event = None
def next_event() -> CalendarEvent | None:
now = dt_util.now()
events = self._calendar.timeline_tz(now.tzinfo).active_after(now)
if event := next(events, None):
return _get_calendar_event(event)
return None
self._event = await self.hass.async_add_executor_job(next_event)
async def _async_store(self) -> None:
"""Persist the calendar to disk."""

View File

@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/local_calendar",
"iot_class": "local_polling",
"loggers": ["ical"],
"requirements": ["ical==9.1.0"]
"requirements": ["ical==9.2.4"]
}

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/local_todo",
"iot_class": "local_polling",
"requirements": ["ical==9.1.0"]
"requirements": ["ical==9.2.4"]
}

View File

@@ -9,7 +9,11 @@
"condition_type": {
"is_locked": "{entity_name} is locked",
"is_unlocked": "{entity_name} is unlocked",
"is_open": "{entity_name} is open"
"is_open": "{entity_name} is open",
"is_jammed": "{entity_name} is jammed",
"is_locking": "{entity_name} is locking",
"is_unlocking": "{entity_name} is unlocking",
"is_opening": "{entity_name} is opening"
},
"trigger_type": {
"locked": "{entity_name} locked",

View File

@@ -475,7 +475,7 @@ class MatrixBot:
file_stat = await aiofiles.os.stat(image_path)
_LOGGER.debug("Uploading file from path, %s", image_path)
async with aiofiles.open(image_path, "r+b") as image_file:
async with aiofiles.open(image_path, "rb") as image_file:
response, _ = await self._client.upload(
image_file,
content_type=mime_type,

View File

@@ -8,6 +8,6 @@
"iot_class": "calculated",
"loggers": ["yt_dlp"],
"quality_scale": "internal",
"requirements": ["yt-dlp[default]==2025.03.31"],
"requirements": ["yt-dlp[default]==2025.05.22"],
"single_config_entry": true
}

View File

@@ -93,7 +93,6 @@ async def async_setup_intents(hass: HomeAssistant) -> None:
DOMAIN,
SERVICE_VOLUME_SET,
required_domains={DOMAIN},
required_states={MediaPlayerState.PLAYING},
required_features=MediaPlayerEntityFeature.VOLUME_SET,
required_slots={
ATTR_MEDIA_VOLUME_LEVEL: intent.IntentSlotInfo(
@@ -159,7 +158,6 @@ class MediaUnpauseHandler(intent.ServiceIntentHandler):
DOMAIN,
SERVICE_MEDIA_PLAY,
required_domains={DOMAIN},
required_states={MediaPlayerState.PAUSED},
description="Resumes a media player",
platforms={DOMAIN},
device_classes={MediaPlayerDeviceClass},

View File

@@ -291,7 +291,7 @@
"description": "The term to search for."
},
"media_filter_classes": {
"name": "Media filter classes",
"name": "Media class filter",
"description": "List of media classes to filter the search results by."
}
}

View File

@@ -57,8 +57,8 @@ ATA_HVAC_MODE_REVERSE_LOOKUP = {v: k for k, v in ATA_HVAC_MODE_LOOKUP.items()}
ATW_ZONE_HVAC_MODE_LOOKUP = {
atw.ZONE_OPERATION_MODE_HEAT: HVACMode.HEAT,
atw.ZONE_OPERATION_MODE_COOL: HVACMode.COOL,
atw.ZONE_STATUS_HEAT: HVACMode.HEAT,
atw.ZONE_STATUS_COOL: HVACMode.COOL,
}
ATW_ZONE_HVAC_MODE_REVERSE_LOOKUP = {v: k for k, v in ATW_ZONE_HVAC_MODE_LOOKUP.items()}

View File

@@ -131,8 +131,7 @@ class MieleButton(MieleEntity, ButtonEntity):
return (
super().available
and self.entity_description.press_data
in self.coordinator.data.actions[self._device_id].process_actions
and self.entity_description.press_data in self.action.process_actions
)
async def async_press(self) -> None:

View File

@@ -174,6 +174,11 @@ class MieleClimate(MieleEntity, ClimateEntity):
t_key = ZONE1_DEVICES.get(
cast(MieleAppliance, self.device.device_type), "zone_1"
)
if self.device.device_type in (
MieleAppliance.FRIDGE,
MieleAppliance.FREEZER,
):
self._attr_name = None
if description.zone == 2:
if self.device.device_type in (
@@ -192,8 +197,7 @@ class MieleClimate(MieleEntity, ClimateEntity):
@property
def target_temperature(self) -> float | None:
"""Return the target temperature."""
if self.entity_description.target_fn(self.device) is None:
return None
return cast(float | None, self.entity_description.target_fn(self.device))
@property
@@ -201,9 +205,7 @@ class MieleClimate(MieleEntity, ClimateEntity):
"""Return the maximum target temperature."""
return cast(
float,
self.coordinator.data.actions[self._device_id]
.target_temperature[self.entity_description.zone - 1]
.max,
self.action.target_temperature[self.entity_description.zone - 1].max,
)
@property
@@ -211,9 +213,7 @@ class MieleClimate(MieleEntity, ClimateEntity):
"""Return the minimum target temperature."""
return cast(
float,
self.coordinator.data.actions[self._device_id]
.target_temperature[self.entity_description.zone - 1]
.min,
self.action.target_temperature[self.entity_description.zone - 1].min,
)
async def async_set_temperature(self, **kwargs: Any) -> None:

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