Compare commits

...

123 Commits

Author SHA1 Message Date
Franck Nijhof
7af6a4f493 2025.4.1 (#142299)
* Fix blocking event loop - daikin (#141442)

* fix blocking event loop

* create ssl_context directly

* update manifest

* update manifest.json

* Made Google Search enable dependent on Assist availability (#141712)

* Made Google Search enable dependent on Assist availability

* Show error instead of rendering again

* Cleanup test code

* Fix humidifier platform for Comelit (#141854)

* Fix humidifier platform for Comelit

* apply review comment

* Bump evohome-async to 1.0.5 (#141871)

bump client to 1.0.5

* Replace "to log into" with "to log in to" in `incomfort` (#142060)

* Replace "to log into" with "to log in to" in `incomfort`

Also fix one missing sentence-casing of "gateway".

* Replace duplicate "data_description" strings with references

* Avoid unnecessary reload in apple_tv reauth flow (#142079)

* Add translation for hassio update entity name (#142090)

* Bump pyenphase to 1.25.5 (#142107)

* Hide broken ZBT-1 config entries on the hardware page (#142110)

* Hide bad ZBT-1 config entries on the hardware page

* Set up the bad config entry in the unit test

* Roll into a list comprehension

* Remove constant changes

* Fix condition in unit test

* Bump pysmhi to 1.0.1 (#142111)

* Avoid logging a warning when replacing an ignored config entry (#142114)

Replacing an ignored config entry with one from the user
flow should not generate a warning. We should only warn
if we are replacing a usable config entry.

Followup to adjust the warning added in #130567
cc @epenet

* Slow down polling in Tesla Fleet (#142130)

* Slow down polling

* Fix tests

* Bump tesla-fleet-api to v1.0.17 (#142131)

bump

* Tado bump to 0.18.11 (#142175)

* Bump to version 0.18.11

* Adding hassfest files

* Add preset mode to SmartThings climate (#142180)

* Add preset mode to SmartThings climate

* Add preset mode to SmartThings climate

* Do not create a HA mediaplayer for the builtin Music Assistant player (#142192)

Do not create a HA mediaplayer for the builtin Music player

* Do not fetch disconnected Home Connect appliances (#142200)

* Do not fetch disconnected Home Connect appliances

* Apply suggestions

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update docstring

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Fix fibaro setup (#142201)

* Fix circular mean by always storing and using the weighted one (#142208)

* Fix circular mean by always storing and using the weighted one

* fix

* Fix test

* Bump pySmartThings to 3.0.2 (#142257)

Co-authored-by: Robert Resch <robert@resch.dev>

* Update frontend to 20250404.0 (#142274)

* Bump forecast-solar lib to v4.1.0 (#142280)

Co-authored-by: Jan-Philipp Benecke <jan-philipp@bnck.me>

* Bump version to 2025.4.1

* Fix skyconnect tests (#142262)

fix tests

* Fix empty actions (#142292)

* Apply fix

* Add tests for alarm button cover lock

* update light

* add number tests

* test select

* add switch tests

* test vacuum

* update lock test

---------

Co-authored-by: Fredrik Erlandsson <fredrik.e@gmail.com>
Co-authored-by: Ivan Lopez Hernandez <ivan.lh.94@outlook.com>
Co-authored-by: Simone Chemelli <simone.chemelli@gmail.com>
Co-authored-by: David Bonnes <zxdavb@bonnes.me>
Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
Co-authored-by: Arie Catsman <120491684+catsmanac@users.noreply.github.com>
Co-authored-by: puddly <32534428+puddly@users.noreply.github.com>
Co-authored-by: G Johansson <goran.johansson@shiftit.se>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Brett Adams <Bre77@users.noreply.github.com>
Co-authored-by: Erwin Douna <e.douna@gmail.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Marcel van der Veldt <m.vanderveldt@outlook.com>
Co-authored-by: J. Diego Rodríguez Royo <jdrr1998@hotmail.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: rappenze <rappenze@yahoo.com>
Co-authored-by: Robert Resch <robert@resch.dev>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: Klaas Schoute <klaas_schoute@hotmail.com>
Co-authored-by: Jan-Philipp Benecke <jan-philipp@bnck.me>
Co-authored-by: Josef Zweck <josef@zweck.dev>
Co-authored-by: Petro31 <35082313+Petro31@users.noreply.github.com>
2025-04-04 22:59:10 +02:00
Petro31
c25f26a290 Fix empty actions (#142292)
* Apply fix

* Add tests for alarm button cover lock

* update light

* add number tests

* test select

* add switch tests

* test vacuum

* update lock test
2025-04-04 20:18:31 +00:00
Josef Zweck
8d62cb60a6 Fix skyconnect tests (#142262)
fix tests
2025-04-04 20:18:27 +00:00
Franck Nijhof
4f799069ea Bump version to 2025.4.1 2025-04-04 19:24:45 +00:00
Klaas Schoute
af708b78e0 Bump forecast-solar lib to v4.1.0 (#142280)
Co-authored-by: Jan-Philipp Benecke <jan-philipp@bnck.me>
2025-04-04 19:24:30 +00:00
Bram Kragten
f46e659740 Update frontend to 20250404.0 (#142274) 2025-04-04 19:24:27 +00:00
Joost Lekkerkerker
7bd517e6ff Bump pySmartThings to 3.0.2 (#142257)
Co-authored-by: Robert Resch <robert@resch.dev>
2025-04-04 19:24:23 +00:00
Robert Resch
e9abdab1f5 Fix circular mean by always storing and using the weighted one (#142208)
* Fix circular mean by always storing and using the weighted one

* fix

* Fix test
2025-04-04 19:24:20 +00:00
rappenze
86eee4f041 Fix fibaro setup (#142201) 2025-04-04 19:24:17 +00:00
J. Diego Rodríguez Royo
9db60c830c Do not fetch disconnected Home Connect appliances (#142200)
* Do not fetch disconnected Home Connect appliances

* Apply suggestions

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Update docstring

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-04-04 19:24:13 +00:00
Marcel van der Veldt
c43a4682b9 Do not create a HA mediaplayer for the builtin Music Assistant player (#142192)
Do not create a HA mediaplayer for the builtin Music player
2025-04-04 19:24:10 +00:00
Joost Lekkerkerker
2a4996055a Add preset mode to SmartThings climate (#142180)
* Add preset mode to SmartThings climate

* Add preset mode to SmartThings climate
2025-04-04 19:24:07 +00:00
Erwin Douna
4643fc2c14 Tado bump to 0.18.11 (#142175)
* Bump to version 0.18.11

* Adding hassfest files
2025-04-04 19:24:04 +00:00
Brett Adams
6410b90d82 Bump tesla-fleet-api to v1.0.17 (#142131)
bump
2025-04-04 19:24:00 +00:00
Brett Adams
e5c00eceae Slow down polling in Tesla Fleet (#142130)
* Slow down polling

* Fix tests
2025-04-04 19:23:55 +00:00
J. Nick Koston
fe65579df8 Avoid logging a warning when replacing an ignored config entry (#142114)
Replacing an ignored config entry with one from the user
flow should not generate a warning. We should only warn
if we are replacing a usable config entry.

Followup to adjust the warning added in #130567
cc @epenet
2025-04-04 19:23:52 +00:00
G Johansson
281beecb05 Bump pysmhi to 1.0.1 (#142111) 2025-04-04 19:23:48 +00:00
puddly
7546b5d269 Hide broken ZBT-1 config entries on the hardware page (#142110)
* Hide bad ZBT-1 config entries on the hardware page

* Set up the bad config entry in the unit test

* Roll into a list comprehension

* Remove constant changes

* Fix condition in unit test
2025-04-04 19:23:45 +00:00
Arie Catsman
490e3201b9 Bump pyenphase to 1.25.5 (#142107) 2025-04-04 19:23:42 +00:00
Paul Bottein
04be575139 Add translation for hassio update entity name (#142090) 2025-04-04 19:23:39 +00:00
Erik Montnemery
854cae7f12 Avoid unnecessary reload in apple_tv reauth flow (#142079) 2025-04-04 19:23:35 +00:00
Norbert Rittel
109d20978f Replace "to log into" with "to log in to" in incomfort (#142060)
* Replace "to log into" with "to log in to" in `incomfort`

Also fix one missing sentence-casing of "gateway".

* Replace duplicate "data_description" strings with references
2025-04-04 19:23:32 +00:00
David Bonnes
f8d284ec4b Bump evohome-async to 1.0.5 (#141871)
bump client to 1.0.5
2025-04-04 19:23:28 +00:00
Simone Chemelli
06ebe0810f Fix humidifier platform for Comelit (#141854)
* Fix humidifier platform for Comelit

* apply review comment
2025-04-04 19:23:25 +00:00
Ivan Lopez Hernandez
802ad2ff51 Made Google Search enable dependent on Assist availability (#141712)
* Made Google Search enable dependent on Assist availability

* Show error instead of rendering again

* Cleanup test code
2025-04-04 19:23:22 +00:00
Fredrik Erlandsson
9070a8d579 Fix blocking event loop - daikin (#141442)
* fix blocking event loop

* create ssl_context directly

* update manifest

* update manifest.json
2025-04-04 19:23:18 +00:00
Franck Nijhof
e8b2a3de8b 2025.4.0 (#141505) 2025-04-02 18:47:40 +02:00
Joost Lekkerkerker
39549d5dd4 Fix switch name Unknown in SmartThings (#142081)
Fix switch name Unknown
2025-04-02 15:16:50 +00:00
Franck Nijhof
0c19e47bd4 Bump version to 2025.4.0 2025-04-02 15:02:28 +00:00
Michael
05507d77e3 Fix state class for battery sensors in AVM Fritz!SmartHome (#142078)
* set proper state class for battery sensor

* fix tests
2025-04-02 15:02:04 +00:00
Franck Nijhof
94558e2d40 Bump version to 2025.4.0b15 2025-04-02 14:19:49 +00:00
puddly
4f22fe8f7f Translation key for ZBT-1 integration failing due to disconnection (#142077)
Translation key for device disconnected
2025-04-02 14:19:41 +00:00
Marcel van der Veldt
9e7dfbb857 Deprecate None effect instead of breaking it for Hue (#142073)
* Deprecate effect none instead of breaking it for Hue

* add guard for unknown effect value

* revert guard

* Fix

* Add test

* Add test

* Add test

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-04-02 14:19:38 +00:00
Joost Lekkerkerker
02d182239a Improve SmartThings switch deprecation (#142072) 2025-04-02 14:19:35 +00:00
Joost Lekkerkerker
4e0f581747 Improve SmartThings sensor deprecation (#142070)
* Improve SmartThings sensor deprecation

* Improve SmartThings sensor deprecation

* Improve SmartThings sensor deprecation
2025-04-02 14:19:32 +00:00
Joost Lekkerkerker
42d97d348c Add Eve brand (#142067) 2025-04-02 14:19:29 +00:00
Robert Resch
69380c85ca Bump deebot-client to 12.5.0 (#142046) 2025-04-02 14:19:25 +00:00
Abílio Costa
b38c647830 Allow excluding modules from noisy logs check (#142020)
* Allow excluding modules from noisy logs check

* Cache non-excluded modules; hardcode self module name; optimize call

* Address review comments
2025-04-02 14:19:22 +00:00
Petro31
2396fd1090 Fix weather templates using new style configuration (#136677) 2025-04-02 14:19:19 +00:00
Franck Nijhof
aa4eb89eee Bump version to 2025.4.0b14 2025-04-02 09:44:23 +00:00
J. Nick Koston
1b1bc6af95 Bump bluetooth-data-tools to 1.26.5 (#142045)
changelog: https://github.com/Bluetooth-Devices/bluetooth-data-tools/compare/v1.26.1...v1.26.5
2025-04-02 09:36:51 +00:00
J. Nick Koston
f17003a79c Bump aiohttp to 3.11.16 (#142034)
changelog: https://github.com/aio-libs/aiohttp/compare/v3.11.15...v3.11.16
2025-04-02 09:34:14 +00:00
TheJulianJES
ec70e8b0cd Bump ZHA to 0.0.55 (#142031) 2025-04-02 08:29:26 +00:00
puddly
d888c70ff0 Fix entity names for HA hardware firmware update entities (#142029)
* Fix entity names for HA hardware firmware update entities

* Fix unit tests
2025-04-02 08:29:23 +00:00
puddly
f29444002e Skip firmware config flow confirmation if the hardware is in use (#142017)
* Auto-confirm the discovery if we detect that the device is already in use

* Add a unit test
2025-04-02 08:29:20 +00:00
Tomek Wasilczyk
fc66997a36 Fix warning about unfinished oauth tasks on shutdown (#141969)
* Don't wait for OAuth token task on shutdown

To reproduce the warning:
1. Start authentication with integration using OAuth (e.g. SmartThings)
2. When redirected to external login site, just close the page
3. Settings -> Restart Home Assistant

* Clarify comment

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-04-02 08:29:16 +00:00
Erik Montnemery
35513ae072 Remove unused mypy ignore from google_generative_ai_conversation (#141549) 2025-04-02 08:29:13 +00:00
Franck Nijhof
cd363d48c3 Bump version to 2025.4.0b13 2025-04-01 19:12:16 +00:00
G Johansson
d47ef835d7 Fix train to for multiple stations in Trafikverket Train (#142016) 2025-04-01 19:11:51 +00:00
Bram Kragten
00177c699e Update frontend to 20250401.0 (#142010) 2025-04-01 19:11:48 +00:00
Joost Lekkerkerker
11b0086a01 Add LG ThinQ event bus listener to lifecycle hooks (#142006) 2025-04-01 19:11:44 +00:00
J. Nick Koston
ceb177f80e Bump aiohttp to 3.11.15 (#141967)
changelog: https://github.com/aio-libs/aiohttp/compare/v3.11.14...v3.11.15

fixes #141855
fixes #141146
2025-04-01 19:10:28 +00:00
Jan Bouwhuis
fa3832fbd7 Improve error handling and logging on MQTT update entity state updates when template rederings fails (#141960) 2025-04-01 19:07:10 +00:00
puddly
2b9c903429 Fix data in old SkyConnect integration config entries or delete them (#141959)
* Delete old SkyConnect integration config entries

* Try migrating, if possible

* Do not delete config entries, log a failure
2025-04-01 19:07:07 +00:00
puddly
a7c43f9b49 Reload the ZBT-1 integration on USB state changes (#141287)
* Reload the config entry when the ZBT-1 is unplugged

* Register the USB event handler globally to react better to re-plugs

* Fix existing unit tests

* Add an empty `CONFIG_SCHEMA`

* Add a unit test

* Fix unit tests

* Fix unit tests for Linux

* Address most review comments

* Address remaining review comments
2025-04-01 19:07:03 +00:00
Joost Lekkerkerker
b428196149 Improve SmartThings deprecation (#141939)
* Improve SmartThings deprecation

* Improve SmartThings deprecation
2025-04-01 19:01:43 +00:00
Erik Montnemery
e23da1a90f Fix import issues related to onboarding views (#141919)
* Fix import issues related to onboarding views

* Add ha-intents and numpy to pyproject.toml

* Add more requirements to pyproject.toml

* Add more requirements to pyproject.toml
2025-04-01 19:00:24 +00:00
Ben Jones
3951c2ea66 Handle empty or missing state values for MQTT light entities using 'template' schema (#141177)
* check for empty or missing values when processing state messages for MQTT light entities using 'template' schema

* normalise warning logs

* add tests (one is still failing and I can't work out why)

* fix test

* improve test coverage after PR review

* improve test coverage after PR review
2025-04-01 18:32:50 +00:00
Louis Christ
fee152654d Use saved volume when selecting preset in bluesound integration (#141079)
* Use load_preset to select preset as source

* Add tests

* Fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-04-01 18:32:47 +00:00
Mikko Koo
51073c948c Fix nordpool Not to return Unknown if price is exactly 0 (#140647)
* now the price will return even if it is exactly 0

* now the price will return even if it is exactly 0

* now the price will return even if it is exactly 0

* clean code

* clean code

* update testing code coverage

* change zero testing to SE4

* remove row duplicate

* fix date comments

* improve testing

* simplify if-return-0

* remove unnecessary tests

* order testing rows

* restore test_sensor_no_next_price

* remove_average_price_test

* fix test name
2025-04-01 18:32:44 +00:00
aaronburt
91438088a0 Correct unit conversion for OneDrive quota display (#140337)
* Correct unit conversion for OneDrive quota display

* Convert OneDrive quota values from bytes to GiB in coordinator and update strings
2025-04-01 18:32:39 +00:00
Franck Nijhof
427e1abdae Bump version to 2025.4.0b12 2025-03-31 20:12:58 +00:00
Steven Looman
6e7ac45ac0 Bump async-upnp-client to 0.44.0 (#141946) 2025-03-31 20:12:48 +00:00
Bram Kragten
4b3b9ebc29 Update frontend to 20250331.0 (#141943) 2025-03-31 20:12:43 +00:00
Franck Nijhof
649d8638ed Bump version to 2025.4.0b11 2025-03-31 18:34:34 +00:00
Jan-Philipp Benecke
12c4152dbe Bump aiowebdav2 to 0.4.5 (#141934) 2025-03-31 18:34:25 +00:00
Michael Hansen
8f9572bb05 Add preannounce boolean for announce/start conversation (#141930)
* Add preannounce boolean

* Fix disabling preannounce in wizard

* Fix casing

* Fix type of preannounce_media_id

* Adjust description of preannounce_media_id
2025-03-31 18:34:22 +00:00
Erik Montnemery
6d022ff4e0 Revert PR 136314 (Cleanup map references in lovelace) (#141928)
* Revert PR 136314 (Cleanup map references in lovelace)

* Update homeassistant/components/lovelace/__init__.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

* Fix dashboard creation

* Update homeassistant/components/lovelace/__init__.py

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-03-31 18:34:19 +00:00
Josef Zweck
c0c2edb90a Add None check to azure_storage (#141922) 2025-03-31 18:34:16 +00:00
Michael
b014219fdd Correct further sensor categorizations in AVM Fritz!Box tools (#141911)
mark margin and attenuation as diagnostic and disable them by default
2025-03-31 18:34:13 +00:00
Joost Lekkerkerker
216b8ef400 Don't create SmartThings entities for disabled components (#141909) 2025-03-31 18:34:10 +00:00
Joost Lekkerkerker
f2ccd46267 Fix SmartThings being able to understand incomplete DRLC (#141907) 2025-03-31 18:34:06 +00:00
Dan Raper
e16ba27ce8 Bump ohmepy to 1.5.1 (#141879)
* Bump ohmepy to 1.5.1

* Fix types for ohmepy version change
2025-03-31 18:34:03 +00:00
Thomas55555
506526a6a2 Handle 403 error in remote calendar (#141839)
* Handle 403 error in remote calendar

* tests
2025-03-31 18:34:00 +00:00
Franck Nijhof
a88678cf42 Fix SmartThings climate entity missing off HAVC mode (#141700)
* Fix smartthing climate entity missing off HAVC mode:

* Fix tests

* Fix test

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-03-31 18:33:57 +00:00
Retha Runolfsson
d0b61af7ec Add switchbot cover unit tests (#140265)
* add cover unit tests

* Add unit test for SwitchBot cover

* fix: use mock_restore_cache to mock the last state

* modify unit tests

* modify scripts as suggest

* improve readability

* adjust patch target per review comments

* adjust patch target per review comments

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
2025-03-31 18:33:53 +00:00
Franck Nijhof
04f5315ab2 Bump version to 2025.4.0b10 2025-03-31 08:09:39 +00:00
Paulus Schoutsen
7f9e4ba39e Ensure user always has first turn for Google Gen AI (#141893) 2025-03-31 08:09:10 +00:00
J. Nick Koston
06aaf188ea Fix duplicate call to async_write_ha_state when adding elkm1 entities (#141890)
When an entity is added state is always written in
add_to_platform_finish:

7336178e03/homeassistant/helpers/entity.py (L1384)

We should not do it in async_added_to_hass as well
2025-03-31 08:09:06 +00:00
J. Nick Koston
627f994872 Bump aioesphomeapi to 29.8.0 (#141888)
changelog: https://github.com/esphome/aioesphomeapi/compare/v29.7.0...v29.8.0
2025-03-31 08:09:03 +00:00
J. Nick Koston
9e81ec5aae Handle encryption being disabled on an ESPHome device (#141887)
fixes #121442
2025-03-31 08:09:00 +00:00
Franck Nijhof
69753fca1d Update pvo to v2.2.1 (#141847) 2025-03-31 08:08:57 +00:00
Michael
7773cc121e Fix the entity category for max throughput sensors in AVM Fritz!Box Tools (#141838)
correct the entity category for max throughput sensors
2025-03-31 08:08:54 +00:00
Michael
3aa56936ad Move setup messages from info to debug level (#141834)
move info to debug level
2025-03-31 08:08:51 +00:00
Franck Nijhof
e66416c23d Fix hardcoded UoM for total power sensor for Tuya zndb devices (#141822) 2025-03-31 08:08:48 +00:00
Jan Bouwhuis
a592feae3d Correct spelling for 'availability` in MQTT translation strings (#141818) 2025-03-31 08:08:45 +00:00
Aidan Timson
fc0d71e891 Fix System Bridge wait timeout wait condition (#141811)
* Fix System Bridge wait timeout wait condition

* Add DataMissingException as a timeout condition

* Add tests
2025-03-31 08:08:42 +00:00
Thomas55555
d4640f1d24 Bump ical to 9.0.3 (#141805) 2025-03-31 08:08:39 +00:00
Michael
6fe158836e Add boost preset to AVM Fritz!SmartHome climate entities (#141802)
* add boost preset to climate entities

* add set boost preset test
2025-03-31 08:08:36 +00:00
J. Nick Koston
629c0087f4 Bump PyISY to 3.1.15 (#141778)
changelog: https://github.com/automicus/PyISY/compare/v3.1.14...v3.1.15

fixes #141517
fixes #132279
2025-03-31 08:08:33 +00:00
J. Nick Koston
363bd75129 Fix blocking late import of httpcore from httpx (#141771)
There is a late import that blocks the event loop
in newer version
9e8ab40369/httpx/_transports/default.py (L75)
2025-03-31 08:08:30 +00:00
J. Nick Koston
7592d350a8 Bump aiohomekit to 3.2.13 (#141764)
changelog: https://github.com/Jc2k/aiohomekit/compare/3.2.8...3.2.13
2025-03-31 08:08:27 +00:00
puddly
8ac8401b4e Add helper methods to simplify USB integration testing (#141733)
* Add some helper methods to simplify USB integration testing

* Re-export `usb_device_from_port`
2025-03-31 08:08:24 +00:00
Joost Lekkerkerker
eed075dbfa Bump pySmartThings to 3.0.1 (#141722) 2025-03-31 08:08:21 +00:00
Florent Thoumie
23dbdedfb6 Bump iaqualink to 0.5.3 (#141709)
* Update to iaqualink 0.5.3 and silence warning

* Update to iaqualink 0.5.3 and silence warning

* Re-add via_device line
2025-03-31 08:08:18 +00:00
Franck Nijhof
85ad29e28e Ensure EcoNet operation modes are unique (#141689) 2025-03-31 08:08:15 +00:00
Michal Schwarz
35fc81b038 Fix order of palettes, presets and playlists in WLED integration (#132207)
* Fix order of palettes, presets and playlists in WLED integration

* fix tests: update palette items order

---------

Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-03-31 08:08:11 +00:00
Lucas Mindêllo de Andrade
5d45b84cd2 Remove sunweg integration (#124230)
* chore(sunweg): remove sunweg integration

* Update homeassistant/components/sunweg/strings.json

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>

* Update homeassistant/components/sunweg/manifest.json

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>

* feat: added async remove entry

* Clean setup_entry; add tests

---------

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
Co-authored-by: abmantis <amfcalt@gmail.com>
2025-03-31 08:06:54 +00:00
Franck Nijhof
7766649304 Bump version to 2025.4.0b9 2025-03-29 17:50:46 +00:00
Simone Chemelli
07e9020dfa Fix immediate state update for Comelit (#141735) 2025-03-29 17:50:36 +00:00
J. Diego Rodríguez Royo
f504a759e0 Set Home Connect program action field as not required (#141729)
* Set Home Connect program action field as not required

* Remove required field

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-03-29 17:50:32 +00:00
Joost Lekkerkerker
9927de4801 Only trigger events on button updates in SmartThings (#141720)
Only trigger events on button updates
2025-03-29 17:50:29 +00:00
Joost Lekkerkerker
1244fc4682 Only link the parent device if known in SmartThings (#141719)
Only link the parent device if we know the parent device
2025-03-29 17:50:26 +00:00
Norbert Rittel
e77a1b12f7 Sentence-case "Medium type" in mopeka (#141718) 2025-03-29 17:50:22 +00:00
J. Nick Koston
5459daaa10 Fix ESPHome entities not being removed when the ESPHome config removes an entire platform (#141708)
* Fix old ESPHome entities not being removed when configuration changes

fixes #140756

* make sure all callbacks fire

* make sure all callbacks fire

* make sure all callbacks fire

* make sure all callbacks fire

* revert

* cover
2025-03-29 17:50:18 +00:00
J. Nick Koston
400131df78 Fix ESPHome update entities being loaded before device_info is available (#141704)
* Fix ESPHome update entities being loaded before device_info is available

Since we load platforms when restoring config, the update
platform could be loaded before the connection to the
device was finished which meant device_info could still
be empty. Wait until device_info is available to
load the update platform.

fixes #135906

* Apply suggestions from code review

* move comment

* Update entry_data.py

Co-authored-by: TheJulianJES <TheJulianJES@users.noreply.github.com>

---------

Co-authored-by: TheJulianJES <TheJulianJES@users.noreply.github.com>
2025-03-29 17:50:15 +00:00
Franck Nijhof
28e1843ff9 Fix Tuya tdq category to pick up temp & humid (#141698) 2025-03-29 17:50:12 +00:00
Franck Nijhof
df777318d1 Handle invalid JSON errors in AirNow (#141695) 2025-03-29 17:50:08 +00:00
Jan Bouwhuis
6ad5e9e89c Improve MQTT translation strings (#141691)
* Improve MQTT options translation string

* more improvements
2025-03-29 17:50:05 +00:00
Norbert Rittel
a0bd8deee9 Replace "country" with common string in holiday (#141687) 2025-03-29 17:50:01 +00:00
Marcel van der Veldt
405cbd6a00 Always set pause feature on Music Assistant mediaplayers (#141686) 2025-03-29 17:49:58 +00:00
Marcel van der Veldt
3e0eb5ab2c Bump music assistant client to 1.2.0 (#141668)
* Bump music assistant client to 1.2.0

* Update test fixtures
2025-03-29 17:49:55 +00:00
Norbert Rittel
fad75a70b6 Add a common string for "country" (#141653) 2025-03-29 17:49:52 +00:00
Josef Zweck
d9720283df Add unkown to uncalibrated state for tedee (#141262) 2025-03-29 17:49:46 +00:00
Franck Nijhof
14eed1778b Bump version to 2025.4.0b8 2025-03-28 20:46:26 +00:00
Norbert Rittel
049aaa7e8b Fix grammar / sentence-casing in workday (#141682)
* Fix grammar / sentence-casing in `workday`

Also replace "country" with common string.

* Add two more references

* Fix second data description reference

* Add "given" to action description for better translations
2025-03-28 20:46:17 +00:00
J. Nick Koston
35717e8216 Increase websocket_api allowed peak time to 10s (#141680)
* Increase websocket_api allowed peak time to 10s

fixes #141624

During integration reload or startup, we can end up sending a message for
each entity being created for integrations that create them from an external
source (ie MQTT) because the messages come in one at a time. This can overload
the loop and/or client for more than 5s. While we have done significant work
to optimize for this path, we are at the limit at what we can expect clients
to be able to process in the time window, so increase the time window.

* adjust test
2025-03-28 20:46:13 +00:00
Franck Nijhof
2a081abc18 Fix camera proxy with sole image quality settings (#141676) 2025-03-28 20:46:10 +00:00
puddly
b7f29c7358 Handle all firmware types for ZBT-1 and Yellow update entities (#141674)
Handle other firmware types
2025-03-28 20:46:06 +00:00
Jason Hunter
3bb6373df5 Update Duke Energy package to fix integration (#141669)
* Update Duke Energy package to fix integration

* fix tests
2025-03-28 20:46:03 +00:00
Michael Hansen
e1b4edec50 Bump intents and always prefer more literal text (#141663) 2025-03-28 20:46:00 +00:00
puddly
147bee57e1 Include ZBT-1 and Yellow in device registry (#141623)
* Add the Yellow and ZBT-1 to the device registry

* Unload platforms

* Fix unit tests

* Rename the Yellow update entity to `Radio firmware`

* Rename `EmberZNet` to `EmberZNet Zigbee`

* Prefix the `sw_version` with the firmware type and clean up

* Fix unit tests

* Remove unnecessary `always_update=False` from data update coordinator
2025-03-28 20:45:56 +00:00
Erwin Douna
fcdaea64da Tado add proper off state (#135480)
* Add proper off state

* Remove current temp

* Add default frost temp
2025-03-28 20:45:53 +00:00
250 changed files with 8200 additions and 4076 deletions

2
CODEOWNERS generated
View File

@@ -1480,8 +1480,6 @@ build.json @home-assistant/supervisor
/tests/components/suez_water/ @ooii @jb101010-2
/homeassistant/components/sun/ @Swamp-Ig
/tests/components/sun/ @Swamp-Ig
/homeassistant/components/sunweg/ @rokam
/tests/components/sunweg/ @rokam
/homeassistant/components/supla/ @mwegrzynek
/homeassistant/components/surepetcare/ @benleb @danielhiversen
/tests/components/surepetcare/ @benleb @danielhiversen

View File

@@ -0,0 +1,5 @@
{
"domain": "eve",
"name": "Eve",
"iot_standards": ["matter"]
}

View File

@@ -8,7 +8,7 @@ from aiohttp import ClientSession
from aiohttp.client_exceptions import ClientConnectorError
from pyairnow import WebServiceAPI
from pyairnow.conv import aqi_to_concentration
from pyairnow.errors import AirNowError
from pyairnow.errors import AirNowError, InvalidJsonError
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
@@ -79,7 +79,7 @@ class AirNowDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
distance=self.distance,
)
except (AirNowError, ClientConnectorError) as error:
except (AirNowError, ClientConnectorError, InvalidJsonError) as error:
raise UpdateFailed(error) from error
if not obs:

View File

@@ -20,6 +20,7 @@ import voluptuous as vol
from homeassistant.components import zeroconf
from homeassistant.config_entries import (
SOURCE_IGNORE,
SOURCE_REAUTH,
SOURCE_ZEROCONF,
ConfigEntry,
ConfigFlow,
@@ -381,7 +382,9 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
CONF_IDENTIFIERS: list(combined_identifiers),
},
)
if entry.source != SOURCE_IGNORE:
# Don't reload ignored entries or in the middle of reauth,
# e.g. if the user is entering a new PIN
if entry.source != SOURCE_IGNORE and self.source != SOURCE_REAUTH:
self.hass.config_entries.async_schedule_reload(entry.entry_id)
if not allow_exist:
raise DeviceAlreadyConfigured

View File

@@ -60,7 +60,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
{
vol.Optional("message"): str,
vol.Optional("media_id"): str,
vol.Optional("preannounce_media_id"): vol.Any(str, None),
vol.Optional("preannounce"): bool,
vol.Optional("preannounce_media_id"): str,
}
),
cv.has_at_least_one_key("message", "media_id"),
@@ -75,7 +76,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
{
vol.Optional("start_message"): str,
vol.Optional("start_media_id"): str,
vol.Optional("preannounce_media_id"): vol.Any(str, None),
vol.Optional("preannounce"): bool,
vol.Optional("preannounce_media_id"): str,
vol.Optional("extra_system_prompt"): str,
}
),

View File

@@ -180,7 +180,8 @@ class AssistSatelliteEntity(entity.Entity):
self,
message: str | None = None,
media_id: str | None = None,
preannounce_media_id: str | None = PREANNOUNCE_URL,
preannounce: bool = True,
preannounce_media_id: str = PREANNOUNCE_URL,
) -> None:
"""Play and show an announcement on the satellite.
@@ -190,8 +191,8 @@ class AssistSatelliteEntity(entity.Entity):
If media_id is provided, it is played directly. It is possible
to omit the message and the satellite will not show any text.
If preannounce is True, a sound is played before the announcement.
If preannounce_media_id is provided, it overrides the default sound.
If preannounce_media_id is None, no sound is played.
Calls async_announce with message and media id.
"""
@@ -201,7 +202,9 @@ class AssistSatelliteEntity(entity.Entity):
message = ""
announcement = await self._resolve_announcement_media_id(
message, media_id, preannounce_media_id
message,
media_id,
preannounce_media_id=preannounce_media_id if preannounce else None,
)
if self._is_announcing:
@@ -229,7 +232,8 @@ class AssistSatelliteEntity(entity.Entity):
start_message: str | None = None,
start_media_id: str | None = None,
extra_system_prompt: str | None = None,
preannounce_media_id: str | None = PREANNOUNCE_URL,
preannounce: bool = True,
preannounce_media_id: str = PREANNOUNCE_URL,
) -> None:
"""Start a conversation from the satellite.
@@ -239,8 +243,8 @@ class AssistSatelliteEntity(entity.Entity):
If start_media_id is provided, it is played directly. It is possible
to omit the message and the satellite will not show any text.
If preannounce_media_id is provided, it is played before the announcement.
If preannounce_media_id is None, no sound is played.
If preannounce is True, a sound is played before the start message or media.
If preannounce_media_id is provided, it overrides the default sound.
Calls async_start_conversation.
"""
@@ -257,7 +261,9 @@ class AssistSatelliteEntity(entity.Entity):
start_message = ""
announcement = await self._resolve_announcement_media_id(
start_message, start_media_id, preannounce_media_id
start_message,
start_media_id,
preannounce_media_id=preannounce_media_id if preannounce else None,
)
if self._is_announcing:

View File

@@ -15,6 +15,11 @@ announce:
required: false
selector:
text:
preannounce:
required: false
default: true
selector:
boolean:
preannounce_media_id:
required: false
selector:
@@ -40,6 +45,11 @@ start_conversation:
required: false
selector:
text:
preannounce:
required: false
default: true
selector:
boolean:
preannounce_media_id:
required: false
selector:

View File

@@ -24,9 +24,13 @@
"name": "Media ID",
"description": "The media ID to announce instead of using text-to-speech."
},
"preannounce": {
"name": "Preannounce",
"description": "Play a sound before the announcement."
},
"preannounce_media_id": {
"name": "Preannounce Media ID",
"description": "The media ID to play before the announcement."
"name": "Preannounce media ID",
"description": "Custom media ID to play before the announcement."
}
}
},
@@ -46,9 +50,13 @@
"name": "Extra system prompt",
"description": "Provide background information to the AI about the request."
},
"preannounce": {
"name": "Preannounce",
"description": "Play a sound before the start message or media."
},
"preannounce_media_id": {
"name": "Preannounce Media ID",
"description": "The media ID to play before the start message or media."
"name": "Preannounce media ID",
"description": "Custom media ID to play before the start message or media."
}
}
}

View File

@@ -199,7 +199,7 @@ async def websocket_test_connection(
hass.async_create_background_task(
satellite.async_internal_announce(
media_id=f"{CONNECTION_TEST_URL_BASE}/{connection_id}",
preannounce_media_id=None,
preannounce=False,
),
f"assist_satellite_connection_test_{msg['entity_id']}",
)

View File

@@ -175,7 +175,8 @@ class AzureStorageBackupAgent(BackupAgent):
"""Find a blob by backup id."""
async for blob in self._client.list_blobs(include="metadata"):
if (
backup_id == blob.metadata.get("backup_id", "")
blob.metadata is not None
and backup_id == blob.metadata.get("backup_id", "")
and blob.metadata.get("metadata_version") == METADATA_VERSION
):
return blob

View File

@@ -501,18 +501,16 @@ class BluesoundPlayer(CoordinatorEntity[BluesoundCoordinator], MediaPlayerEntity
return
# presets and inputs might have the same name; presets have priority
url: str | None = None
for input_ in self._inputs:
if input_.text == source:
url = input_.url
await self._player.play_url(input_.url)
return
for preset in self._presets:
if preset.name == source:
url = preset.url
await self._player.load_preset(preset.id)
return
if url is None:
raise ServiceValidationError(f"Source {source} not found")
await self._player.play_url(url)
raise ServiceValidationError(f"Source {source} not found")
async def async_clear_playlist(self) -> None:
"""Clear players playlist."""

View File

@@ -19,7 +19,7 @@
"bleak-retry-connector==3.9.0",
"bluetooth-adapters==0.21.4",
"bluetooth-auto-recovery==1.4.5",
"bluetooth-data-tools==1.26.1",
"bluetooth-data-tools==1.26.5",
"dbus-fast==2.43.0",
"habluetooth==3.37.0"
]

View File

@@ -127,7 +127,11 @@ class CloudOAuth2Implementation(config_entry_oauth2_flow.AbstractOAuth2Implement
flow_id=flow_id, user_input=tokens
)
self.hass.async_create_task(await_tokens())
# It's a background task because it should be cancelled on shutdown and there's nothing else
# we can do in such case. There's also no need to wait for this during setup.
self.hass.async_create_background_task(
await_tokens(), name="Awaiting OAuth tokens"
)
return authorize_url

View File

@@ -8,7 +8,7 @@ from aiocomelit import ComelitSerialBridgeObject
from aiocomelit.const import COVER, STATE_COVER, STATE_OFF, STATE_ON
from homeassistant.components.cover import CoverDeviceClass, CoverEntity, CoverState
from homeassistant.core import HomeAssistant, callback
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.helpers.update_coordinator import CoordinatorEntity
@@ -98,13 +98,20 @@ class ComelitCoverEntity(
"""Return if the cover is opening."""
return self._current_action("opening")
async def _cover_set_state(self, action: int, state: int) -> None:
"""Set desired cover state."""
self._last_state = self.state
await self._api.set_device_status(COVER, self._device.index, action)
self.coordinator.data[COVER][self._device.index].status = state
self.async_write_ha_state()
async def async_close_cover(self, **kwargs: Any) -> None:
"""Close cover."""
await self._api.set_device_status(COVER, self._device.index, STATE_OFF)
await self._cover_set_state(STATE_OFF, 2)
async def async_open_cover(self, **kwargs: Any) -> None:
"""Open cover."""
await self._api.set_device_status(COVER, self._device.index, STATE_ON)
await self._cover_set_state(STATE_ON, 1)
async def async_stop_cover(self, **_kwargs: Any) -> None:
"""Stop the cover."""
@@ -112,13 +119,7 @@ class ComelitCoverEntity(
return
action = STATE_ON if self.is_closing else STATE_OFF
await self._api.set_device_status(COVER, self._device.index, action)
@callback
def _handle_coordinator_update(self) -> None:
"""Handle device update."""
self._last_state = self.state
self.async_write_ha_state()
await self._cover_set_state(action, 0)
async def async_added_to_hass(self) -> None:
"""Handle entity which will be added."""

View File

@@ -162,7 +162,7 @@ class ComelitHumidifierEntity(CoordinatorEntity[ComelitSerialBridge], Humidifier
async def async_set_humidity(self, humidity: int) -> None:
"""Set new target humidity."""
if self.mode == HumidifierComelitMode.OFF:
if not self._attr_is_on:
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="humidity_while_off",
@@ -190,9 +190,13 @@ class ComelitHumidifierEntity(CoordinatorEntity[ComelitSerialBridge], Humidifier
await self.coordinator.api.set_humidity_status(
self._device.index, self._set_command
)
self._attr_is_on = True
self.async_write_ha_state()
async def async_turn_off(self, **kwargs: Any) -> None:
"""Turn off."""
await self.coordinator.api.set_humidity_status(
self._device.index, HumidifierComelitCommand.OFF
)
self._attr_is_on = False
self.async_write_ha_state()

View File

@@ -59,7 +59,8 @@ class ComelitLightEntity(CoordinatorEntity[ComelitSerialBridge], LightEntity):
async def _light_set_state(self, state: int) -> None:
"""Set desired light state."""
await self.coordinator.api.set_device_status(LIGHT, self._device.index, state)
await self.coordinator.async_request_refresh()
self.coordinator.data[LIGHT][self._device.index].status = state
self.async_write_ha_state()
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the light on."""

View File

@@ -52,7 +52,9 @@
"rest": "Rest",
"sabotated": "Sabotated"
}
},
}
},
"humidifier": {
"humidifier": {
"name": "Humidifier"
},

View File

@@ -67,7 +67,8 @@ class ComelitSwitchEntity(CoordinatorEntity[ComelitSerialBridge], SwitchEntity):
await self.coordinator.api.set_device_status(
self._device.type, self._device.index, state
)
await self.coordinator.async_request_refresh()
self.coordinator.data[self._device.type][self._device.index].status = state
self.async_write_ha_state()
async def async_turn_on(self, **kwargs: Any) -> None:
"""Turn the switch on."""

View File

@@ -650,7 +650,14 @@ class DefaultAgent(ConversationEntity):
if (
(maybe_result is None) # first result
or (num_matched_entities > best_num_matched_entities)
or (
# More literal text matched
result.text_chunks_matched > maybe_result.text_chunks_matched
)
or (
# More entities matched
num_matched_entities > best_num_matched_entities
)
or (
# Fewer unmatched entities
(num_matched_entities == best_num_matched_entities)
@@ -662,16 +669,6 @@ class DefaultAgent(ConversationEntity):
and (num_unmatched_entities == best_num_unmatched_entities)
and (num_unmatched_ranges > best_num_unmatched_ranges)
)
or (
# More literal text matched
(num_matched_entities == best_num_matched_entities)
and (num_unmatched_entities == best_num_unmatched_entities)
and (num_unmatched_ranges == best_num_unmatched_ranges)
and (
result.text_chunks_matched
> maybe_result.text_chunks_matched
)
)
or (
# Prefer match failures with entities
(result.text_chunks_matched == maybe_result.text_chunks_matched)

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.3.24"]
"requirements": ["hassil==2.2.3", "home-assistant-intents==2025.3.28"]
}

View File

@@ -21,6 +21,7 @@ from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.util.ssl import client_context_no_verify
from .const import KEY_MAC, TIMEOUT
from .coordinator import DaikinConfigEntry, DaikinCoordinator
@@ -48,6 +49,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: DaikinConfigEntry) -> bo
key=entry.data.get(CONF_API_KEY),
uuid=entry.data.get(CONF_UUID),
password=entry.data.get(CONF_PASSWORD),
ssl_context=client_context_no_verify(),
)
_LOGGER.debug("Connection to %s successful", host)
except TimeoutError as err:

View File

@@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/daikin",
"iot_class": "local_polling",
"loggers": ["pydaikin"],
"requirements": ["pydaikin==2.14.1"],
"requirements": ["pydaikin==2.15.0"],
"zeroconf": ["_dkapi._tcp.local."]
}

View File

@@ -8,7 +8,7 @@
"documentation": "https://www.home-assistant.io/integrations/dlna_dmr",
"iot_class": "local_push",
"loggers": ["async_upnp_client"],
"requirements": ["async-upnp-client==0.43.0", "getmac==0.9.5"],
"requirements": ["async-upnp-client==0.44.0", "getmac==0.9.5"],
"ssdp": [
{
"deviceType": "urn:schemas-upnp-org:device:MediaRenderer:1",

View File

@@ -7,7 +7,7 @@
"dependencies": ["ssdp"],
"documentation": "https://www.home-assistant.io/integrations/dlna_dms",
"iot_class": "local_polling",
"requirements": ["async-upnp-client==0.43.0"],
"requirements": ["async-upnp-client==0.44.0"],
"ssdp": [
{
"deviceType": "urn:schemas-upnp-org:device:MediaServer:1",

View File

@@ -50,10 +50,10 @@ class DukeEnergyConfigFlow(ConfigFlow, domain=DOMAIN):
_LOGGER.exception("Unexpected exception")
errors["base"] = "unknown"
else:
username = auth["cdp_internal_user_id"].lower()
username = auth["internalUserID"].lower()
await self.async_set_unique_id(username)
self._abort_if_unique_id_configured()
email = auth["email"].lower()
email = auth["loginEmailAddress"].lower()
data = {
CONF_EMAIL: email,
CONF_USERNAME: username,

View File

@@ -6,5 +6,5 @@
"dependencies": ["recorder"],
"documentation": "https://www.home-assistant.io/integrations/duke_energy",
"iot_class": "cloud_polling",
"requirements": ["aiodukeenergy==0.2.2"]
"requirements": ["aiodukeenergy==0.3.0"]
}

View File

@@ -91,15 +91,15 @@ class EcoNetWaterHeater(EcoNetEntity[WaterHeater], WaterHeaterEntity):
def operation_list(self) -> list[str]:
"""List of available operation modes."""
econet_modes = self.water_heater.modes
op_list = []
operation_modes = set()
for mode in econet_modes:
if (
mode is not WaterHeaterOperationMode.UNKNOWN
and mode is not WaterHeaterOperationMode.VACATION
):
ha_mode = ECONET_STATE_TO_HA[mode]
op_list.append(ha_mode)
return op_list
operation_modes.add(ha_mode)
return list(operation_modes)
@property
def supported_features(self) -> WaterHeaterEntityFeature:

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==12.4.0"]
"requirements": ["py-sucks==0.9.10", "deebot-client==12.5.0"]
}

View File

@@ -100,7 +100,11 @@ class ElkEntity(Entity):
return {"index": self._element.index + 1}
def _element_changed(self, element: Element, changeset: dict[str, Any]) -> None:
pass
"""Handle changes to the element.
This method is called when the element changes. It should be
overridden by subclasses to handle the changes.
"""
@callback
def _element_callback(self, element: Element, changeset: dict[str, Any]) -> None:
@@ -111,7 +115,7 @@ class ElkEntity(Entity):
async def async_added_to_hass(self) -> None:
"""Register callback for ElkM1 changes and update entity state."""
self._element.add_callback(self._element_callback)
self._element_callback(self._element, {})
self._element_changed(self._element, {})
@property
def device_info(self) -> DeviceInfo:

View File

@@ -6,7 +6,7 @@
"documentation": "https://www.home-assistant.io/integrations/enphase_envoy",
"iot_class": "local_polling",
"loggers": ["pyenphase"],
"requirements": ["pyenphase==1.25.1"],
"requirements": ["pyenphase==1.25.5"],
"zeroconf": [
{
"type": "_enphase-envoy._tcp.local."

View File

@@ -128,8 +128,23 @@ class EsphomeFlowHandler(ConfigFlow, domain=DOMAIN):
self._password = ""
return await self._async_authenticate_or_add()
if error is None and entry_data.get(CONF_NOISE_PSK):
return await self.async_step_reauth_encryption_removed_confirm()
return await self.async_step_reauth_confirm()
async def async_step_reauth_encryption_removed_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reauthorization flow when encryption was removed."""
if user_input is not None:
self._noise_psk = None
return self._async_get_entry()
return self.async_show_form(
step_id="reauth_encryption_removed_confirm",
description_placeholders={"name": self._name},
)
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:

View File

@@ -282,15 +282,18 @@ class RuntimeEntryData:
) -> None:
"""Distribute an update of static infos to all platforms."""
# First, load all platforms
needed_platforms = set()
if async_get_dashboard(hass):
needed_platforms.add(Platform.UPDATE)
needed_platforms: set[Platform] = set()
if self.device_info and self.device_info.voice_assistant_feature_flags_compat(
self.api_version
):
needed_platforms.add(Platform.BINARY_SENSOR)
needed_platforms.add(Platform.SELECT)
if self.device_info:
if async_get_dashboard(hass):
# Only load the update platform if the device_info is set
# When we restore the entry, the device_info may not be set yet
# and we don't want to load the update platform since it needs
# a complete device_info.
needed_platforms.add(Platform.UPDATE)
if self.device_info.voice_assistant_feature_flags_compat(self.api_version):
needed_platforms.add(Platform.BINARY_SENSOR)
needed_platforms.add(Platform.SELECT)
ent_reg = er.async_get(hass)
registry_get_entity = ent_reg.async_get_entity_id
@@ -312,18 +315,19 @@ class RuntimeEntryData:
# Make a dict of the EntityInfo by type and send
# them to the listeners for each specific EntityInfo type
infos_by_type: dict[type[EntityInfo], list[EntityInfo]] = {}
infos_by_type: defaultdict[type[EntityInfo], list[EntityInfo]] = defaultdict(
list
)
for info in infos:
info_type = type(info)
if info_type not in infos_by_type:
infos_by_type[info_type] = []
infos_by_type[info_type].append(info)
infos_by_type[type(info)].append(info)
callbacks_by_type = self.entity_info_callbacks
for type_, entity_infos in infos_by_type.items():
if callbacks_ := callbacks_by_type.get(type_):
for callback_ in callbacks_:
callback_(entity_infos)
for type_, callbacks in self.entity_info_callbacks.items():
# If all entities for a type are removed, we
# still need to call the callbacks with an empty list
# to make sure the entities are removed.
entity_infos = infos_by_type.get(type_, [])
for callback_ in callbacks:
callback_(entity_infos)
# Finally update static info subscriptions
for callback_ in self.static_info_update_subscriptions:

View File

@@ -13,6 +13,7 @@ from aioesphomeapi import (
APIConnectionError,
APIVersion,
DeviceInfo as EsphomeDeviceInfo,
EncryptionHelloAPIError,
EntityInfo,
HomeassistantServiceCall,
InvalidAuthAPIError,
@@ -570,6 +571,7 @@ class ESPHomeManager:
if isinstance(
err,
(
EncryptionHelloAPIError,
RequiresEncryptionAPIError,
InvalidEncryptionKeyAPIError,
InvalidAuthAPIError,

View File

@@ -16,7 +16,7 @@
"loggers": ["aioesphomeapi", "noiseprotocol", "bleak_esphome"],
"mqtt": ["esphome/discover/#"],
"requirements": [
"aioesphomeapi==29.7.0",
"aioesphomeapi==29.8.0",
"esphome-dashboard-api==1.2.3",
"bleak-esphome==2.12.0"
],

View File

@@ -43,6 +43,9 @@
},
"description": "The ESPHome device {name} enabled transport encryption or changed the encryption key. Please enter the updated key. You can find it in the ESPHome Dashboard or in your device configuration."
},
"reauth_encryption_removed_confirm": {
"description": "The ESPHome device {name} disabled transport encryption. Please confirm that you want to remove the encryption key and allow unencrypted connections."
},
"discovery_confirm": {
"description": "Do you want to add the ESPHome node `{name}` to Home Assistant?",
"title": "Discovered ESPHome node"

View File

@@ -6,5 +6,5 @@
"iot_class": "cloud_polling",
"loggers": ["evohome", "evohomeasync", "evohomeasync2"],
"quality_scale": "legacy",
"requirements": ["evohome-async==1.0.4"]
"requirements": ["evohome-async==1.0.5"]
}

View File

@@ -301,6 +301,7 @@ class FibaroController:
device.ha_id = (
f"{slugify(room_name)}_{slugify(device.name)}_{device.fibaro_id}"
)
platform = None
if device.enabled and (not device.is_plugin or self._import_plugins):
platform = self._map_device_to_platform(device)
if platform is None:

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.0.0"]
"requirements": ["forecast-solar==4.1.0"]
}

View File

@@ -193,7 +193,6 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = (
translation_key="max_kb_s_sent",
native_unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND,
device_class=SensorDeviceClass.DATA_RATE,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=_retrieve_max_kb_s_sent_state,
),
FritzSensorEntityDescription(
@@ -201,7 +200,6 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = (
translation_key="max_kb_s_received",
native_unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND,
device_class=SensorDeviceClass.DATA_RATE,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=_retrieve_max_kb_s_received_state,
),
FritzSensorEntityDescription(
@@ -225,6 +223,7 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = (
translation_key="link_kb_s_sent",
native_unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND,
device_class=SensorDeviceClass.DATA_RATE,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=_retrieve_link_kb_s_sent_state,
),
FritzSensorEntityDescription(
@@ -232,12 +231,15 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = (
translation_key="link_kb_s_received",
native_unit_of_measurement=UnitOfDataRate.KILOBITS_PER_SECOND,
device_class=SensorDeviceClass.DATA_RATE,
entity_category=EntityCategory.DIAGNOSTIC,
value_fn=_retrieve_link_kb_s_received_state,
),
FritzSensorEntityDescription(
key="link_noise_margin_sent",
translation_key="link_noise_margin_sent",
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_fn=_retrieve_link_noise_margin_sent_state,
is_suitable=lambda info: info.wan_enabled and info.connection == DSL_CONNECTION,
),
@@ -245,6 +247,8 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = (
key="link_noise_margin_received",
translation_key="link_noise_margin_received",
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_fn=_retrieve_link_noise_margin_received_state,
is_suitable=lambda info: info.wan_enabled and info.connection == DSL_CONNECTION,
),
@@ -252,6 +256,8 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = (
key="link_attenuation_sent",
translation_key="link_attenuation_sent",
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_fn=_retrieve_link_attenuation_sent_state,
is_suitable=lambda info: info.wan_enabled and info.connection == DSL_CONNECTION,
),
@@ -259,6 +265,8 @@ SENSOR_TYPES: tuple[FritzSensorEntityDescription, ...] = (
key="link_attenuation_received",
translation_key="link_attenuation_received",
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS,
entity_category=EntityCategory.DIAGNOSTIC,
entity_registry_enabled_default=False,
value_fn=_retrieve_link_attenuation_received_state,
is_suitable=lambda info: info.wan_enabled and info.connection == DSL_CONNECTION,
),

View File

@@ -6,6 +6,7 @@ from typing import Any
from homeassistant.components.climate import (
ATTR_HVAC_MODE,
PRESET_BOOST,
PRESET_COMFORT,
PRESET_ECO,
ClimateEntity,
@@ -38,7 +39,7 @@ from .sensor import value_scheduled_preset
HVAC_MODES = [HVACMode.HEAT, HVACMode.OFF]
PRESET_HOLIDAY = "holiday"
PRESET_SUMMER = "summer"
PRESET_MODES = [PRESET_ECO, PRESET_COMFORT]
PRESET_MODES = [PRESET_ECO, PRESET_COMFORT, PRESET_BOOST]
SUPPORTED_FEATURES = (
ClimateEntityFeature.TARGET_TEMPERATURE
| ClimateEntityFeature.PRESET_MODE
@@ -194,6 +195,8 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
return PRESET_HOLIDAY
if self.data.summer_active:
return PRESET_SUMMER
if self.data.target_temperature == ON_API_TEMPERATURE:
return PRESET_BOOST
if self.data.target_temperature == self.data.comfort_temperature:
return PRESET_COMFORT
if self.data.target_temperature == self.data.eco_temperature:
@@ -211,6 +214,8 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
await self.async_set_temperature(temperature=self.data.comfort_temperature)
elif preset_mode == PRESET_ECO:
await self.async_set_temperature(temperature=self.data.eco_temperature)
elif preset_mode == PRESET_BOOST:
await self.async_set_temperature(temperature=ON_REPORT_SET_TEMPERATURE)
@property
def extra_state_attributes(self) -> ClimateExtraAttributes:

View File

@@ -137,6 +137,7 @@ SENSOR_TYPES: Final[tuple[FritzSensorEntityDescription, ...]] = (
key="battery",
native_unit_of_measurement=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
state_class=SensorStateClass.MEASUREMENT,
entity_category=EntityCategory.DIAGNOSTIC,
suitable=lambda device: device.battery_level is not None,
native_value=lambda device: device.battery_level,

View File

@@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system",
"quality_scale": "internal",
"requirements": ["home-assistant-frontend==20250328.0"]
"requirements": ["home-assistant-frontend==20250404.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.0.1"]
"requirements": ["gcal-sync==7.0.0", "oauth2client==4.1.3", "ical==9.0.3"]
}

View File

@@ -7,7 +7,7 @@ import logging
from types import MappingProxyType
from typing import Any
from google import genai # type: ignore[attr-defined]
from google import genai
from google.genai.errors import APIError, ClientError
from requests.exceptions import Timeout
import voluptuous as vol
@@ -179,28 +179,30 @@ class GoogleGenerativeAIOptionsFlow(OptionsFlow):
) -> ConfigFlowResult:
"""Manage the options."""
options: dict[str, Any] | MappingProxyType[str, Any] = self.config_entry.options
errors: dict[str, str] = {}
if user_input is not None:
if user_input[CONF_RECOMMENDED] == self.last_rendered_recommended:
if user_input[CONF_LLM_HASS_API] == "none":
user_input.pop(CONF_LLM_HASS_API)
return self.async_create_entry(title="", data=user_input)
if not (
user_input.get(CONF_LLM_HASS_API, "none") != "none"
and user_input.get(CONF_USE_GOOGLE_SEARCH_TOOL, False) is True
):
# Don't allow to save options that enable the Google Seearch tool with an Assist API
return self.async_create_entry(title="", data=user_input)
errors[CONF_USE_GOOGLE_SEARCH_TOOL] = "invalid_google_search_option"
# Re-render the options again, now with the recommended options shown/hidden
self.last_rendered_recommended = user_input[CONF_RECOMMENDED]
options = {
CONF_RECOMMENDED: user_input[CONF_RECOMMENDED],
CONF_PROMPT: user_input[CONF_PROMPT],
CONF_LLM_HASS_API: user_input[CONF_LLM_HASS_API],
}
options = user_input
schema = await google_generative_ai_config_option_schema(
self.hass, options, self._genai_client
)
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(schema),
step_id="init", data_schema=vol.Schema(schema), errors=errors
)

View File

@@ -356,6 +356,15 @@ class GoogleGenerativeAIConversationEntity(
messages.append(_convert_content(chat_content))
# The SDK requires the first message to be a user message
# This is not the case if user used `start_conversation`
# Workaround from https://github.com/googleapis/python-genai/issues/529#issuecomment-2740964537
if messages and messages[0].role != "user":
messages.insert(
0,
Content(role="user", parts=[Part.from_text(text=" ")]),
)
if tool_results:
messages.append(_create_google_tool_response_content(tool_results))
generateContentConfig = GenerateContentConfig(

View File

@@ -43,6 +43,9 @@
"prompt": "Instruct how the LLM should respond. This can be a template."
}
}
},
"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\"."
}
},
"services": {

View File

@@ -265,6 +265,11 @@
"version_latest": {
"name": "Newest version"
}
},
"update": {
"update": {
"name": "[%key:component::update::title%]"
}
}
},
"services": {

View File

@@ -39,7 +39,7 @@ from .entity import (
from .update_helper import update_addon, update_core
ENTITY_DESCRIPTION = UpdateEntityDescription(
name="Update",
translation_key="update",
key=ATTR_VERSION_LATEST,
)

View File

@@ -8,7 +8,7 @@
"step": {
"user": {
"data": {
"country": "Country"
"country": "[%key:common::config_flow::data::country%]"
}
},
"options": {

View File

@@ -73,6 +73,19 @@ class HomeConnectApplianceData:
self.settings.update(other.settings)
self.status.update(other.status)
@classmethod
def empty(cls, appliance: HomeAppliance) -> HomeConnectApplianceData:
"""Return empty data."""
return cls(
commands=set(),
events={},
info=appliance,
options={},
programs=[],
settings={},
status={},
)
class HomeConnectCoordinator(
DataUpdateCoordinator[dict[str, HomeConnectApplianceData]]
@@ -358,15 +371,7 @@ class HomeConnectCoordinator(
model=appliance.vib,
)
if appliance.ha_id not in self.data:
self.data[appliance.ha_id] = HomeConnectApplianceData(
commands=set(),
events={},
info=appliance,
options={},
programs=[],
settings={},
status={},
)
self.data[appliance.ha_id] = HomeConnectApplianceData.empty(appliance)
else:
self.data[appliance.ha_id].info.connected = appliance.connected
old_appliances.remove(appliance.ha_id)
@@ -402,6 +407,15 @@ class HomeConnectCoordinator(
name=appliance.name,
model=appliance.vib,
)
if not appliance.connected:
_LOGGER.debug(
"Appliance %s is not connected, skipping data fetch",
appliance.ha_id,
)
if appliance_data_to_update:
appliance_data_to_update.info.connected = False
return appliance_data_to_update
return HomeConnectApplianceData.empty(appliance)
try:
settings = {
setting.key: setting

View File

@@ -64,7 +64,6 @@ set_program_and_options:
- selected_program
program:
example: dishcare_dishwasher_program_auto2
required: true
selector:
select:
mode: dropdown

View File

@@ -31,7 +31,6 @@ class FirmwareUpdateCoordinator(DataUpdateCoordinator[FirmwareManifest]):
_LOGGER,
name="firmware update coordinator",
update_interval=FIRMWARE_REFRESH_INTERVAL,
always_update=False,
)
self.hass = hass
self.session = session

View File

@@ -33,6 +33,7 @@ from .util import (
OwningIntegration,
get_otbr_addon_manager,
get_zigbee_flasher_addon_manager,
guess_firmware_info,
guess_hardware_owners,
probe_silabs_firmware_info,
)
@@ -511,6 +512,16 @@ class BaseFirmwareConfigFlow(BaseFirmwareInstallFlow, ConfigFlow):
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm a discovery."""
assert self._device is not None
fw_info = await guess_firmware_info(self.hass, self._device)
# If our guess for the firmware type is actually running, we can save the user
# an unnecessary confirmation and silently confirm the flow
for owner in fw_info.owners:
if await owner.is_running(self.hass):
self._probed_firmware_info = fw_info
return self._async_flow_finished()
return await self.async_step_pick_firmware()

View File

@@ -95,8 +95,7 @@ class BaseFirmwareUpdateEntity(
_attr_supported_features = (
UpdateEntityFeature.INSTALL | UpdateEntityFeature.PROGRESS
)
# Until this entity can be associated with a device, we must manually name it
_attr_has_entity_name = False
_attr_has_entity_name = True
def __init__(
self,
@@ -195,11 +194,7 @@ class BaseFirmwareUpdateEntity(
def _update_attributes(self) -> None:
"""Recompute the attributes of the entity."""
# This entity is not currently associated with a device so we must manually
# give it a name
self._attr_name = f"{self._config_entry.title} Update"
self._attr_title = self.entity_description.firmware_name or "unknown"
self._attr_title = self.entity_description.firmware_name or "Unknown"
if (
self._current_firmware_info is None

View File

@@ -3,19 +3,79 @@
from __future__ import annotations
import logging
import os.path
from homeassistant.components.homeassistant_hardware.util import guess_firmware_info
from homeassistant.components.usb import (
USBDevice,
async_register_port_event_callback,
scan_serial_ports,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady, HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType
from .const import DESCRIPTION, DEVICE, FIRMWARE, FIRMWARE_VERSION, PRODUCT
from .const import (
DESCRIPTION,
DEVICE,
DOMAIN,
FIRMWARE,
FIRMWARE_VERSION,
MANUFACTURER,
PID,
PRODUCT,
SERIAL_NUMBER,
VID,
)
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the ZBT-1 integration."""
@callback
def async_port_event_callback(
added: set[USBDevice], removed: set[USBDevice]
) -> None:
"""Handle USB port events."""
current_entries_by_path = {
entry.data[DEVICE]: entry
for entry in hass.config_entries.async_entries(DOMAIN)
}
for device in added | removed:
path = device.device
entry = current_entries_by_path.get(path)
if entry is not None:
_LOGGER.debug(
"Device %r has changed state, reloading config entry %s",
path,
entry,
)
hass.config_entries.async_schedule_reload(entry.entry_id)
async_register_port_event_callback(hass, async_port_event_callback)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a Home Assistant SkyConnect config entry."""
# Postpone loading the config entry if the device is missing
device_path = entry.data[DEVICE]
if not await hass.async_add_executor_job(os.path.exists, device_path):
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="device_disconnected",
)
await hass.config_entries.async_forward_entry_setups(entry, ["update"])
return True
@@ -23,6 +83,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
await hass.config_entries.async_unload_platforms(entry, ["update"])
return True
@@ -30,7 +91,7 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
"""Migrate old entry."""
_LOGGER.debug(
"Migrating from version %s:%s", config_entry.version, config_entry.minor_version
"Migrating from version %s.%s", config_entry.version, config_entry.minor_version
)
if config_entry.version == 1:
@@ -65,6 +126,43 @@ async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
minor_version=3,
)
if config_entry.minor_version == 3:
# Old SkyConnect config entries were missing keys
if any(
key not in config_entry.data
for key in (VID, PID, MANUFACTURER, PRODUCT, SERIAL_NUMBER)
):
serial_ports = await hass.async_add_executor_job(scan_serial_ports)
serial_ports_info = {port.device: port for port in serial_ports}
device = config_entry.data[DEVICE]
if not (usb_info := serial_ports_info.get(device)):
raise HomeAssistantError(
f"USB device {device} is missing, cannot migrate"
)
hass.config_entries.async_update_entry(
config_entry,
data={
**config_entry.data,
VID: usb_info.vid,
PID: usb_info.pid,
MANUFACTURER: usb_info.manufacturer,
PRODUCT: usb_info.description,
DESCRIPTION: usb_info.description,
SERIAL_NUMBER: usb_info.serial_number,
},
version=1,
minor_version=4,
)
else:
# Existing entries are migrated by just incrementing the version
hass.config_entries.async_update_entry(
config_entry,
version=1,
minor_version=4,
)
_LOGGER.debug(
"Migration to version %s.%s successful",
config_entry.version,

View File

@@ -81,7 +81,7 @@ class HomeAssistantSkyConnectConfigFlow(
"""Handle a config flow for Home Assistant SkyConnect."""
VERSION = 1
MINOR_VERSION = 3
MINOR_VERSION = 4
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Initialize the config flow."""

View File

@@ -5,17 +5,21 @@ from __future__ import annotations
from homeassistant.components.hardware.models import HardwareInfo, USBInfo
from homeassistant.core import HomeAssistant, callback
from .config_flow import HomeAssistantSkyConnectConfigFlow
from .const import DOMAIN
from .util import get_hardware_variant
DOCUMENTATION_URL = "https://skyconnect.home-assistant.io/documentation/"
EXPECTED_ENTRY_VERSION = (
HomeAssistantSkyConnectConfigFlow.VERSION,
HomeAssistantSkyConnectConfigFlow.MINOR_VERSION,
)
@callback
def async_info(hass: HomeAssistant) -> list[HardwareInfo]:
"""Return board info."""
entries = hass.config_entries.async_entries(DOMAIN)
return [
HardwareInfo(
board=None,
@@ -31,4 +35,6 @@ def async_info(hass: HomeAssistant) -> list[HardwareInfo]:
url=DOCUMENTATION_URL,
)
for entry in entries
# Ignore unmigrated config entries in the hardware page
if (entry.version, entry.minor_version) == EXPECTED_ENTRY_VERSION
]

View File

@@ -195,5 +195,10 @@
"run_zigbee_flasher_addon": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::run_zigbee_flasher_addon%]",
"uninstall_zigbee_flasher_addon": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::uninstall_zigbee_flasher_addon%]"
}
},
"exceptions": {
"device_disconnected": {
"message": "The device is not plugged in"
}
}
}

View File

@@ -21,11 +21,20 @@ from homeassistant.components.update import UpdateDeviceClass
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import FIRMWARE, FIRMWARE_VERSION, NABU_CASA_FIRMWARE_RELEASES_URL
from .const import (
DOMAIN,
FIRMWARE,
FIRMWARE_VERSION,
NABU_CASA_FIRMWARE_RELEASES_URL,
PRODUCT,
SERIAL_NUMBER,
HardwareVariant,
)
_LOGGER = logging.getLogger(__name__)
@@ -42,7 +51,7 @@ FIRMWARE_ENTITY_DESCRIPTIONS: dict[
fw_type="skyconnect_zigbee_ncp",
version_key="ezsp_version",
expected_firmware_type=ApplicationType.EZSP,
firmware_name="EmberZNet",
firmware_name="EmberZNet Zigbee",
),
ApplicationType.SPINEL: FirmwareUpdateEntityDescription(
key="firmware",
@@ -55,6 +64,28 @@ FIRMWARE_ENTITY_DESCRIPTIONS: dict[
expected_firmware_type=ApplicationType.SPINEL,
firmware_name="OpenThread RCP",
),
ApplicationType.CPC: FirmwareUpdateEntityDescription(
key="firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
version_parser=lambda fw: fw,
fw_type="skyconnect_multipan",
version_key="cpc_version",
expected_firmware_type=ApplicationType.CPC,
firmware_name="Multiprotocol",
),
ApplicationType.GECKO_BOOTLOADER: FirmwareUpdateEntityDescription(
key="firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
version_parser=lambda fw: fw,
fw_type=None, # We don't want to update the bootloader
version_key="gecko_bootloader_version",
expected_firmware_type=ApplicationType.GECKO_BOOTLOADER,
firmware_name="Gecko Bootloader",
),
None: FirmwareUpdateEntityDescription(
key="firmware",
display_precision=0,
@@ -77,9 +108,16 @@ def _async_create_update_entity(
) -> FirmwareUpdateEntity:
"""Create an update entity that handles firmware type changes."""
firmware_type = config_entry.data[FIRMWARE]
entity_description = FIRMWARE_ENTITY_DESCRIPTIONS[
ApplicationType(firmware_type) if firmware_type is not None else None
]
try:
entity_description = FIRMWARE_ENTITY_DESCRIPTIONS[
ApplicationType(firmware_type)
]
except (KeyError, ValueError):
_LOGGER.debug(
"Unknown firmware type %r, using default entity description", firmware_type
)
entity_description = FIRMWARE_ENTITY_DESCRIPTIONS[None]
entity = FirmwareUpdateEntity(
device=config_entry.data["device"],
@@ -141,8 +179,18 @@ class FirmwareUpdateEntity(BaseFirmwareUpdateEntity):
"""Initialize the SkyConnect firmware update entity."""
super().__init__(device, config_entry, update_coordinator, entity_description)
self._attr_unique_id = (
f"{self._config_entry.data['serial_number']}_{self.entity_description.key}"
variant = HardwareVariant.from_usb_product_name(
self._config_entry.data[PRODUCT]
)
serial_number = self._config_entry.data[SERIAL_NUMBER]
self._attr_unique_id = f"{serial_number}_{self.entity_description.key}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, serial_number)},
name=f"{variant.full_name} ({serial_number[:8]})",
model=variant.full_name,
manufacturer="Nabu Casa",
serial_number=serial_number,
)
# Use the cached firmware info if it exists
@@ -155,6 +203,17 @@ class FirmwareUpdateEntity(BaseFirmwareUpdateEntity):
source="homeassistant_sky_connect",
)
def _update_attributes(self) -> None:
"""Recompute the attributes of the entity."""
super()._update_attributes()
assert self.device_entry is not None
device_registry = dr.async_get(self.hass)
device_registry.async_update_device(
device_id=self.device_entry.id,
sw_version=f"{self.entity_description.firmware_name} {self._attr_installed_version}",
)
@callback
def _firmware_info_callback(self, firmware_info: FirmwareInfo) -> None:
"""Handle updated firmware info being pushed by an integration."""

View File

@@ -62,6 +62,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
await hass.config_entries.async_unload_platforms(entry, ["update"])
return True

View File

@@ -2,8 +2,9 @@
DOMAIN = "homeassistant_yellow"
RADIO_MODEL = "Home Assistant Yellow"
RADIO_MANUFACTURER = "Nabu Casa"
MODEL = "Home Assistant Yellow"
MANUFACTURER = "Nabu Casa"
RADIO_DEVICE = "/dev/ttyAMA1"
ZHA_HW_DISCOVERY_DATA = {

View File

@@ -149,5 +149,12 @@
"run_zigbee_flasher_addon": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::run_zigbee_flasher_addon%]",
"uninstall_zigbee_flasher_addon": "[%key:component::homeassistant_hardware::firmware_picker::options::progress::uninstall_zigbee_flasher_addon%]"
}
},
"entity": {
"update": {
"radio_firmware": {
"name": "Radio firmware"
}
}
}
}

View File

@@ -21,13 +21,17 @@ from homeassistant.components.update import UpdateDeviceClass
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import entity_registry as er
from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .const import (
DOMAIN,
FIRMWARE,
FIRMWARE_VERSION,
MANUFACTURER,
MODEL,
NABU_CASA_FIRMWARE_RELEASES_URL,
RADIO_DEVICE,
)
@@ -39,7 +43,8 @@ FIRMWARE_ENTITY_DESCRIPTIONS: dict[
ApplicationType | None, FirmwareUpdateEntityDescription
] = {
ApplicationType.EZSP: FirmwareUpdateEntityDescription(
key="firmware",
key="radio_firmware",
translation_key="radio_firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
@@ -47,10 +52,11 @@ FIRMWARE_ENTITY_DESCRIPTIONS: dict[
fw_type="yellow_zigbee_ncp",
version_key="ezsp_version",
expected_firmware_type=ApplicationType.EZSP,
firmware_name="EmberZNet",
firmware_name="EmberZNet Zigbee",
),
ApplicationType.SPINEL: FirmwareUpdateEntityDescription(
key="firmware",
key="radio_firmware",
translation_key="radio_firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
@@ -60,8 +66,33 @@ FIRMWARE_ENTITY_DESCRIPTIONS: dict[
expected_firmware_type=ApplicationType.SPINEL,
firmware_name="OpenThread RCP",
),
ApplicationType.CPC: FirmwareUpdateEntityDescription(
key="radio_firmware",
translation_key="radio_firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
version_parser=lambda fw: fw,
fw_type="yellow_multipan",
version_key="cpc_version",
expected_firmware_type=ApplicationType.CPC,
firmware_name="Multiprotocol",
),
ApplicationType.GECKO_BOOTLOADER: FirmwareUpdateEntityDescription(
key="radio_firmware",
translation_key="radio_firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
version_parser=lambda fw: fw,
fw_type=None, # We don't want to update the bootloader
version_key="gecko_bootloader_version",
expected_firmware_type=ApplicationType.GECKO_BOOTLOADER,
firmware_name="Gecko Bootloader",
),
None: FirmwareUpdateEntityDescription(
key="firmware",
key="radio_firmware",
translation_key="radio_firmware",
display_precision=0,
device_class=UpdateDeviceClass.FIRMWARE,
entity_category=EntityCategory.CONFIG,
@@ -82,9 +113,16 @@ def _async_create_update_entity(
) -> FirmwareUpdateEntity:
"""Create an update entity that handles firmware type changes."""
firmware_type = config_entry.data[FIRMWARE]
entity_description = FIRMWARE_ENTITY_DESCRIPTIONS[
ApplicationType(firmware_type) if firmware_type is not None else None
]
try:
entity_description = FIRMWARE_ENTITY_DESCRIPTIONS[
ApplicationType(firmware_type)
]
except (KeyError, ValueError):
_LOGGER.debug(
"Unknown firmware type %r, using default entity description", firmware_type
)
entity_description = FIRMWARE_ENTITY_DESCRIPTIONS[None]
entity = FirmwareUpdateEntity(
device=RADIO_DEVICE,
@@ -145,8 +183,13 @@ class FirmwareUpdateEntity(BaseFirmwareUpdateEntity):
) -> None:
"""Initialize the Yellow firmware update entity."""
super().__init__(device, config_entry, update_coordinator, entity_description)
self._attr_unique_id = self.entity_description.key
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, "yellow")},
name=MODEL,
model=MODEL,
manufacturer=MANUFACTURER,
)
# Use the cached firmware info if it exists
if self._config_entry.data[FIRMWARE] is not None:
@@ -158,6 +201,17 @@ class FirmwareUpdateEntity(BaseFirmwareUpdateEntity):
source="homeassistant_yellow",
)
def _update_attributes(self) -> None:
"""Recompute the attributes of the entity."""
super()._update_attributes()
assert self.device_entry is not None
device_registry = dr.async_get(self.hass)
device_registry.async_update_device(
device_id=self.device_entry.id,
sw_version=f"{self.entity_description.firmware_name} {self._attr_installed_version}",
)
@callback
def _firmware_info_callback(self, firmware_info: FirmwareInfo) -> None:
"""Handle updated firmware info being pushed by an integration."""

View File

@@ -14,6 +14,6 @@
"documentation": "https://www.home-assistant.io/integrations/homekit_controller",
"iot_class": "local_push",
"loggers": ["aiohomekit", "commentjson"],
"requirements": ["aiohomekit==3.2.8"],
"requirements": ["aiohomekit==3.2.13"],
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
}

View File

@@ -197,5 +197,11 @@
}
}
}
},
"issues": {
"deprecated_effect_none": {
"title": "Light turned on with deprecated effect",
"description": "A light was turned on with the deprecated effect `None`. This has been replaced with `off`. Please update any automations, scenes, or scripts that use this effect."
}
}
}

View File

@@ -29,6 +29,7 @@ from homeassistant.components.light import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.util import color as color_util
from ..bridge import HueBridge
@@ -44,6 +45,9 @@ FALLBACK_MIN_KELVIN = 6500
FALLBACK_MAX_KELVIN = 2000
FALLBACK_KELVIN = 5800 # halfway
# HA 2025.4 replaced the deprecated effect "None" with HA default "off"
DEPRECATED_EFFECT_NONE = "None"
async def async_setup_entry(
hass: HomeAssistant,
@@ -233,6 +237,23 @@ class HueLight(HueBaseEntity, LightEntity):
self._color_temp_active = color_temp is not None
flash = kwargs.get(ATTR_FLASH)
effect = effect_str = kwargs.get(ATTR_EFFECT)
if effect_str == DEPRECATED_EFFECT_NONE:
# deprecated effect "None" is now "off"
effect_str = EFFECT_OFF
async_create_issue(
self.hass,
DOMAIN,
"deprecated_effect_none",
breaks_in_ha_version="2025.10.0",
is_fixable=False,
severity=IssueSeverity.WARNING,
translation_key="deprecated_effect_none",
)
self.logger.warning(
"Detected deprecated effect 'None' in %s, use 'off' instead. "
"This will stop working in HA 2025.10",
self.entity_id,
)
if effect_str == EFFECT_OFF:
# ignore effect if set to "off" and we have no effect active
# the special effect "off" is only used to stop an active effect

View File

@@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/iaqualink",
"iot_class": "cloud_polling",
"loggers": ["iaqualink"],
"requirements": ["iaqualink==0.5.0", "h2==4.1.0"],
"requirements": ["iaqualink==0.5.3", "h2==4.1.0"],
"single_config_entry": true
}

View File

@@ -10,8 +10,8 @@
},
"data_description": {
"host": "Hostname or IP-address of the Intergas gateway.",
"username": "The username to log into the gateway. This is `admin` in most cases.",
"password": "The password to log into the gateway, is printed at the bottom of the gateway or is `intergas` for some older devices."
"username": "The username to log in to the gateway. This is `admin` in most cases.",
"password": "The password to log in to the gateway, is printed at the bottom of the gateway or is `intergas` for some older devices."
}
},
"dhcp_auth": {
@@ -22,8 +22,8 @@
"password": "[%key:common::config_flow::data::password%]"
},
"data_description": {
"username": "The username to log into the gateway. This is `admin` in most cases.",
"password": "The password to log into the gateway, is printed at the bottom of the Gateway or is `intergas` for some older devices."
"username": "[%key:component::incomfort::config::step::user::data_description::username%]",
"password": "[%key:component::incomfort::config::step::user::data_description::password%]"
}
},
"dhcp_confirm": {

View File

@@ -138,7 +138,7 @@ async def async_setup_entry(
for vtype, _, vid in isy.variables.children:
numbers.append(isy.variables[vtype][vid])
if (
isy.conf[CONFIG_NETWORKING] or isy.conf[CONFIG_PORTAL]
isy.conf[CONFIG_NETWORKING] or isy.conf.get(CONFIG_PORTAL)
) and isy.networking.nobjs:
isy_data.devices[CONF_NETWORK] = _create_service_device_info(
isy, name=CONFIG_NETWORKING, unique_id=CONF_NETWORK

View File

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

View File

@@ -20,5 +20,5 @@
"documentation": "https://www.home-assistant.io/integrations/ld2410_ble",
"integration_type": "device",
"iot_class": "local_push",
"requirements": ["bluetooth-data-tools==1.26.1", "ld2410-ble==0.1.1"]
"requirements": ["bluetooth-data-tools==1.26.5", "ld2410-ble==0.1.1"]
}

View File

@@ -35,5 +35,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/led_ble",
"iot_class": "local_polling",
"requirements": ["bluetooth-data-tools==1.26.1", "led-ble==1.1.6"]
"requirements": ["bluetooth-data-tools==1.26.5", "led-ble==1.1.6"]
}

View File

@@ -63,10 +63,12 @@ class DeviceDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
# Add a callback to handle core config update.
self.unit_system: str | None = None
self.hass.bus.async_listen(
event_type=EVENT_CORE_CONFIG_UPDATE,
listener=self._handle_update_config,
event_filter=self.async_config_update_filter,
self.config_entry.async_on_unload(
self.hass.bus.async_listen(
event_type=EVENT_CORE_CONFIG_UPDATE,
listener=self._handle_update_config,
event_filter=self.async_config_update_filter,
)
)
async def _handle_update_config(self, _: Event) -> None:

View File

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

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.0.1"]
"requirements": ["ical==9.0.3"]
}

View File

@@ -6,7 +6,7 @@ from typing import Any
import voluptuous as vol
from homeassistant.components import frontend, websocket_api
from homeassistant.components import frontend, onboarding, websocket_api
from homeassistant.config import (
async_hass_config_yaml,
async_process_component_and_handle_errors,
@@ -17,6 +17,7 @@ from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import collection, config_validation as cv
from homeassistant.helpers.frame import report_usage
from homeassistant.helpers.service import async_register_admin_service
from homeassistant.helpers.translation import async_get_translations
from homeassistant.helpers.typing import ConfigType
from homeassistant.loader import async_get_integration
from homeassistant.util import slugify
@@ -282,6 +283,13 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
STORAGE_DASHBOARD_UPDATE_FIELDS,
).async_setup(hass)
def create_map_dashboard() -> None:
"""Create a map dashboard."""
hass.async_create_task(_create_map_dashboard(hass, dashboards_collection))
if not onboarding.async_is_onboarded(hass):
onboarding.async_add_listener(hass, create_map_dashboard)
return True
@@ -323,3 +331,25 @@ def _register_panel(
kwargs["sidebar_icon"] = config.get(CONF_ICON, DEFAULT_ICON)
frontend.async_register_built_in_panel(hass, DOMAIN, **kwargs)
async def _create_map_dashboard(
hass: HomeAssistant, dashboards_collection: dashboard.DashboardsCollection
) -> None:
"""Create a map dashboard."""
translations = await async_get_translations(
hass, hass.config.language, "dashboard", {onboarding.DOMAIN}
)
title = translations["component.onboarding.dashboard.map.title"]
await dashboards_collection.async_create_item(
{
CONF_ALLOW_SINGLE_WORD: True,
CONF_ICON: "mdi:map",
CONF_TITLE: title,
CONF_URL_PATH: "map",
}
)
map_store = hass.data[LOVELACE_DATA].dashboards["map"]
await map_store.async_save({"strategy": {"type": "map"}})

View File

@@ -6,7 +6,7 @@
"description": "[%key:component::bluetooth::config::step::user::description%]",
"data": {
"address": "[%key:common::config_flow::data::device%]",
"medium_type": "Medium Type"
"medium_type": "Medium type"
}
},
"bluetooth_confirm": {

View File

@@ -62,6 +62,7 @@ from ..entity import MqttEntity
from ..models import (
MqttCommandTemplate,
MqttValueTemplate,
PayloadSentinel,
PublishPayloadType,
ReceiveMessage,
)
@@ -126,7 +127,9 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity):
_command_templates: dict[
str, Callable[[PublishPayloadType, TemplateVarsType], PublishPayloadType]
]
_value_templates: dict[str, Callable[[ReceivePayloadType], ReceivePayloadType]]
_value_templates: dict[
str, Callable[[ReceivePayloadType, ReceivePayloadType], ReceivePayloadType]
]
_fixed_color_mode: ColorMode | str | None
_topics: dict[str, str | None]
@@ -203,73 +206,133 @@ class MqttLightTemplate(MqttEntity, LightEntity, RestoreEntity):
@callback
def _state_received(self, msg: ReceiveMessage) -> None:
"""Handle new MQTT messages."""
state = self._value_templates[CONF_STATE_TEMPLATE](msg.payload)
if state == STATE_ON:
state_value = self._value_templates[CONF_STATE_TEMPLATE](
msg.payload,
PayloadSentinel.NONE,
)
if not state_value:
_LOGGER.debug(
"Ignoring message from '%s' with empty state value", msg.topic
)
elif state_value == STATE_ON:
self._attr_is_on = True
elif state == STATE_OFF:
elif state_value == STATE_OFF:
self._attr_is_on = False
elif state == PAYLOAD_NONE:
elif state_value == PAYLOAD_NONE:
self._attr_is_on = None
else:
_LOGGER.warning("Invalid state value received")
_LOGGER.warning(
"Invalid state value '%s' received from %s",
state_value,
msg.topic,
)
if CONF_BRIGHTNESS_TEMPLATE in self._config:
try:
if brightness := int(
self._value_templates[CONF_BRIGHTNESS_TEMPLATE](msg.payload)
):
self._attr_brightness = brightness
else:
_LOGGER.debug(
"Ignoring zero brightness value for entity %s",
self.entity_id,
brightness_value = self._value_templates[CONF_BRIGHTNESS_TEMPLATE](
msg.payload,
PayloadSentinel.NONE,
)
if not brightness_value:
_LOGGER.debug(
"Ignoring message from '%s' with empty brightness value",
msg.topic,
)
else:
try:
if brightness := int(brightness_value):
self._attr_brightness = brightness
else:
_LOGGER.debug(
"Ignoring zero brightness value for entity %s",
self.entity_id,
)
except ValueError:
_LOGGER.warning(
"Invalid brightness value '%s' received from %s",
brightness_value,
msg.topic,
)
except ValueError:
_LOGGER.warning("Invalid brightness value received from %s", msg.topic)
if CONF_COLOR_TEMP_TEMPLATE in self._config:
try:
color_temp = self._value_templates[CONF_COLOR_TEMP_TEMPLATE](
msg.payload
color_temp_value = self._value_templates[CONF_COLOR_TEMP_TEMPLATE](
msg.payload,
PayloadSentinel.NONE,
)
if not color_temp_value:
_LOGGER.debug(
"Ignoring message from '%s' with empty color temperature value",
msg.topic,
)
self._attr_color_temp_kelvin = (
int(color_temp)
if self._color_temp_kelvin
else color_util.color_temperature_mired_to_kelvin(int(color_temp))
if color_temp != "None"
else None
)
except ValueError:
_LOGGER.warning("Invalid color temperature value received")
else:
try:
self._attr_color_temp_kelvin = (
int(color_temp_value)
if self._color_temp_kelvin
else color_util.color_temperature_mired_to_kelvin(
int(color_temp_value)
)
if color_temp_value != "None"
else None
)
except ValueError:
_LOGGER.warning(
"Invalid color temperature value '%s' received from %s",
color_temp_value,
msg.topic,
)
if (
CONF_RED_TEMPLATE in self._config
and CONF_GREEN_TEMPLATE in self._config
and CONF_BLUE_TEMPLATE in self._config
):
try:
red = self._value_templates[CONF_RED_TEMPLATE](msg.payload)
green = self._value_templates[CONF_GREEN_TEMPLATE](msg.payload)
blue = self._value_templates[CONF_BLUE_TEMPLATE](msg.payload)
if red == "None" and green == "None" and blue == "None":
self._attr_hs_color = None
else:
self._attr_hs_color = color_util.color_RGB_to_hs(
int(red), int(green), int(blue)
)
red_value = self._value_templates[CONF_RED_TEMPLATE](
msg.payload,
PayloadSentinel.NONE,
)
green_value = self._value_templates[CONF_GREEN_TEMPLATE](
msg.payload,
PayloadSentinel.NONE,
)
blue_value = self._value_templates[CONF_BLUE_TEMPLATE](
msg.payload,
PayloadSentinel.NONE,
)
if not red_value or not green_value or not blue_value:
_LOGGER.debug(
"Ignoring message from '%s' with empty color value", msg.topic
)
elif red_value == "None" and green_value == "None" and blue_value == "None":
self._attr_hs_color = None
self._update_color_mode()
except ValueError:
_LOGGER.warning("Invalid color value received")
else:
try:
self._attr_hs_color = color_util.color_RGB_to_hs(
int(red_value), int(green_value), int(blue_value)
)
self._update_color_mode()
except ValueError:
_LOGGER.warning("Invalid color value received from %s", msg.topic)
if CONF_EFFECT_TEMPLATE in self._config:
effect = str(self._value_templates[CONF_EFFECT_TEMPLATE](msg.payload))
if (
effect_list := self._config[CONF_EFFECT_LIST]
) and effect in effect_list:
self._attr_effect = effect
effect_value = self._value_templates[CONF_EFFECT_TEMPLATE](
msg.payload,
PayloadSentinel.NONE,
)
if not effect_value:
_LOGGER.debug(
"Ignoring message from '%s' with empty effect value", msg.topic
)
elif (effect_list := self._config[CONF_EFFECT_LIST]) and str(
effect_value
) in effect_list:
self._attr_effect = str(effect_value)
else:
_LOGGER.warning("Unsupported effect value received")
_LOGGER.warning(
"Unsupported effect value '%s' received from %s",
effect_value,
msg.topic,
)
@callback
def _prepare_subscribe_topics(self) -> None:

View File

@@ -126,7 +126,7 @@
"payload_not_available": "Payload not available"
},
"data_description": {
"availability_topic": "Topic to receive the availabillity payload on",
"availability_topic": "Topic to receive the availability payload on",
"availability_template": "A [template](https://www.home-assistant.io/docs/configuration/templating/#using-templates-with-the-mqtt-integration) to render the availability payload received on the availability topic",
"payload_available": "The payload that indicates the device is available (defaults to 'online')",
"payload_not_available": "The payload that indicates the device is not available (defaults to 'offline')"
@@ -219,10 +219,10 @@
"options": "Add option"
},
"data_description": {
"device_class": "The device class of the {platform} entity. [Learn more.]({url}#device_class)",
"state_class": "The [state_class](https://developers.home-assistant.io/docs/core/entity/sensor/#available-state-classes) of the sensor. [Learn more.]({url}#state_class)",
"device_class": "The Device class of the {platform} entity. [Learn more.]({url}#device_class)",
"state_class": "The [State class](https://developers.home-assistant.io/docs/core/entity/sensor/#available-state-classes) of the sensor. [Learn more.]({url}#state_class)",
"unit_of_measurement": "Defines the unit of measurement of the sensor, if any.",
"options": "Options for allowed sensor state values. The sensors device_class must be set to Enumeration. The options option cannot be used together with State Class or Unit of measurement."
"options": "Options for allowed sensor state values. The sensors Device class must be set to Enumeration. The 'Options' setting cannot be used together with State class or Unit of measurement."
},
"sections": {
"advanced_settings": {

View File

@@ -26,7 +26,7 @@ from . import subscription
from .config import DEFAULT_RETAIN, MQTT_RO_SCHEMA
from .const import CONF_COMMAND_TOPIC, CONF_RETAIN, CONF_STATE_TOPIC, PAYLOAD_EMPTY_JSON
from .entity import MqttEntity, async_setup_entity_entry_helper
from .models import MqttValueTemplate, ReceiveMessage
from .models import MqttValueTemplate, PayloadSentinel, ReceiveMessage
from .schemas import MQTT_ENTITY_COMMON_SCHEMA
from .util import valid_publish_topic, valid_subscribe_topic
@@ -136,7 +136,18 @@ class MqttUpdate(MqttEntity, UpdateEntity, RestoreEntity):
@callback
def _handle_state_message_received(self, msg: ReceiveMessage) -> None:
"""Handle receiving state message via MQTT."""
payload = self._templates[CONF_VALUE_TEMPLATE](msg.payload)
payload = self._templates[CONF_VALUE_TEMPLATE](
msg.payload, PayloadSentinel.DEFAULT
)
if payload is PayloadSentinel.DEFAULT:
_LOGGER.warning(
"Unable to process payload '%s' for topic %s, with value template '%s'",
msg.payload,
msg.topic,
self._config.get(CONF_VALUE_TEMPLATE),
)
return
if not payload or payload == PAYLOAD_EMPTY_JSON:
_LOGGER.debug(

View File

@@ -7,6 +7,6 @@
"documentation": "https://www.home-assistant.io/integrations/music_assistant",
"iot_class": "local_push",
"loggers": ["music_assistant"],
"requirements": ["music-assistant-client==1.1.1"],
"requirements": ["music-assistant-client==1.2.0"],
"zeroconf": ["_mass._tcp.local."]
}

View File

@@ -94,6 +94,12 @@ SUPPORTED_FEATURES_BASE = (
| MediaPlayerEntityFeature.MEDIA_ENQUEUE
| MediaPlayerEntityFeature.MEDIA_ANNOUNCE
| MediaPlayerEntityFeature.SEEK
# we always add pause support,
# regardless if the underlying player actually natively supports pause
# because the MA behavior is to internally handle pause with stop
# (and a resume position) and we'd like to keep the UX consistent
# background info: https://github.com/home-assistant/core/issues/140118
| MediaPlayerEntityFeature.PAUSE
)
QUEUE_OPTION_MAP = {
@@ -145,6 +151,8 @@ async def async_setup_entry(
assert event.object_id is not None
if event.object_id in added_ids:
return
if not player.expose_to_ha:
return
added_ids.add(event.object_id)
async_add_entities([MusicAssistantPlayer(mass, event.object_id)])
@@ -153,6 +161,8 @@ async def async_setup_entry(
mass_players = []
# add all current players
for player in mass.players:
if not player.expose_to_ha:
continue
added_ids.add(player.player_id)
mass_players.append(MusicAssistantPlayer(mass, player.player_id))
@@ -697,8 +707,6 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
supported_features = SUPPORTED_FEATURES_BASE
if PlayerFeature.SET_MEMBERS in self.player.supported_features:
supported_features |= MediaPlayerEntityFeature.GROUPING
if PlayerFeature.PAUSE in self.player.supported_features:
supported_features |= MediaPlayerEntityFeature.PAUSE
if self.player.mute_control != PLAYER_CONTROL_NONE:
supported_features |= MediaPlayerEntityFeature.VOLUME_MUTE
if self.player.volume_control != PLAYER_CONTROL_NONE:

View File

@@ -34,7 +34,7 @@ def validate_prices(
index: int,
) -> float | None:
"""Validate and return."""
if result := func(entity)[area][index]:
if (result := func(entity)[area][index]) is not None:
return result / 1000
return None

View File

@@ -2,8 +2,9 @@
from __future__ import annotations
from collections.abc import Awaitable, Callable
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from typing import Any
from ohme import ApiException, ChargerStatus, OhmeApiClient
@@ -23,7 +24,7 @@ PARALLEL_UPDATES = 1
class OhmeButtonDescription(OhmeEntityDescription, ButtonEntityDescription):
"""Class describing Ohme button entities."""
press_fn: Callable[[OhmeApiClient], Awaitable[None]]
press_fn: Callable[[OhmeApiClient], Coroutine[Any, Any, bool]]
BUTTON_DESCRIPTIONS = [

View File

@@ -7,5 +7,5 @@
"integration_type": "device",
"iot_class": "cloud_polling",
"quality_scale": "silver",
"requirements": ["ohme==1.4.1"]
"requirements": ["ohme==1.5.1"]
}

View File

@@ -1,7 +1,8 @@
"""Platform for number."""
from collections.abc import Awaitable, Callable
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from typing import Any
from ohme import ApiException, OhmeApiClient
@@ -22,7 +23,7 @@ PARALLEL_UPDATES = 1
class OhmeNumberDescription(OhmeEntityDescription, NumberEntityDescription):
"""Class describing Ohme number entities."""
set_fn: Callable[[OhmeApiClient, float], Awaitable[None]]
set_fn: Callable[[OhmeApiClient, float], Coroutine[Any, Any, bool]]
value_fn: Callable[[OhmeApiClient], float]
@@ -31,7 +32,7 @@ NUMBER_DESCRIPTION = [
key="target_percentage",
translation_key="target_percentage",
value_fn=lambda client: client.target_soc,
set_fn=lambda client, value: client.async_set_target(target_percent=value),
set_fn=lambda client, value: client.async_set_target(target_percent=int(value)),
native_min_value=0,
native_max_value=100,
native_step=1,
@@ -42,7 +43,7 @@ NUMBER_DESCRIPTION = [
translation_key="preconditioning_duration",
value_fn=lambda client: client.preconditioning,
set_fn=lambda client, value: client.async_set_target(
pre_condition_length=value
pre_condition_length=int(value)
),
native_min_value=0,
native_max_value=60,

View File

@@ -2,7 +2,7 @@
from __future__ import annotations
from collections.abc import Awaitable, Callable
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from typing import Any, Final
@@ -24,7 +24,7 @@ PARALLEL_UPDATES = 1
class OhmeSelectDescription(OhmeEntityDescription, SelectEntityDescription):
"""Class to describe an Ohme select entity."""
select_fn: Callable[[OhmeApiClient, Any], Awaitable[None]]
select_fn: Callable[[OhmeApiClient, Any], Coroutine[Any, Any, bool | None]]
options: list[str] | None = None
options_fn: Callable[[OhmeApiClient], list[str]] | None = None
current_option_fn: Callable[[OhmeApiClient], str | None]

View File

@@ -34,7 +34,7 @@ PARALLEL_UPDATES = 0
class OhmeSensorDescription(OhmeEntityDescription, SensorEntityDescription):
"""Class describing Ohme sensor entities."""
value_fn: Callable[[OhmeApiClient], str | int | float]
value_fn: Callable[[OhmeApiClient], str | int | float | None]
SENSOR_CHARGE_SESSION = [
@@ -129,6 +129,6 @@ class OhmeSensor(OhmeEntity, SensorEntity):
entity_description: OhmeSensorDescription
@property
def native_value(self) -> str | int | float:
def native_value(self) -> str | int | float | None:
"""Return the state of the sensor."""
return self.entity_description.value_fn(self.coordinator.client)

View File

@@ -78,7 +78,7 @@ def async_setup_services(hass: HomeAssistant) -> None:
"""List of charge slots."""
client = __get_client(service_call)
return {"slots": client.slots}
return {"slots": [slot.to_dict() for slot in client.slots]}
async def set_price_cap(
service_call: ServiceCall,

View File

@@ -1,8 +1,9 @@
"""Platform for time."""
from collections.abc import Awaitable, Callable
from collections.abc import Callable, Coroutine
from dataclasses import dataclass
from datetime import time
from typing import Any
from ohme import ApiException, OhmeApiClient
@@ -22,7 +23,7 @@ PARALLEL_UPDATES = 1
class OhmeTimeDescription(OhmeEntityDescription, TimeEntityDescription):
"""Class describing Ohme time entities."""
set_fn: Callable[[OhmeApiClient, time], Awaitable[None]]
set_fn: Callable[[OhmeApiClient, time], Coroutine[Any, Any, bool]]
value_fn: Callable[[OhmeApiClient], time]

View File

@@ -31,7 +31,7 @@ from homeassistant.helpers import area_registry as ar
from homeassistant.helpers.backup import async_get_manager as async_get_backup_manager
from homeassistant.helpers.system_info import async_get_system_info
from homeassistant.helpers.translation import async_get_translations
from homeassistant.setup import async_setup_component
from homeassistant.setup import SetupPhases, async_pause_setup, async_setup_component
if TYPE_CHECKING:
from . import OnboardingData, OnboardingStorage, OnboardingStoreData
@@ -60,7 +60,7 @@ async def async_setup(
hass.http.register_view(BackupInfoView(data))
hass.http.register_view(RestoreBackupView(data))
hass.http.register_view(UploadBackupView(data))
setup_cloud_views(hass, data)
await setup_cloud_views(hass, data)
class OnboardingView(HomeAssistantView):
@@ -430,9 +430,19 @@ class UploadBackupView(BackupOnboardingView, backup_http.UploadBackupView):
return await self._post(request)
def setup_cloud_views(hass: HomeAssistant, data: OnboardingStoreData) -> None:
async def setup_cloud_views(hass: HomeAssistant, data: OnboardingStoreData) -> None:
"""Set up the cloud views."""
with async_pause_setup(hass, SetupPhases.WAIT_IMPORT_PACKAGES):
# Import the cloud integration in an executor to avoid blocking the
# event loop.
def import_cloud() -> None:
"""Import the cloud integration."""
# pylint: disable-next=import-outside-toplevel
from homeassistant.components.cloud import http_api # noqa: F401
await hass.async_add_import_executor_job(import_cloud)
# The cloud integration is imported locally to avoid cloud being imported by
# bootstrap.py and to avoid circular imports.

View File

@@ -88,8 +88,8 @@ class OneDriveUpdateCoordinator(DataUpdateCoordinator[Drive]):
),
translation_key=key,
translation_placeholders={
"total": str(drive.quota.total),
"used": str(drive.quota.used),
"total": f"{drive.quota.total / (1024**3):.2f}",
"used": f"{drive.quota.used / (1024**3):.2f}",
},
)
return drive

View File

@@ -6,5 +6,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/private_ble_device",
"iot_class": "local_push",
"requirements": ["bluetooth-data-tools==1.26.1"]
"requirements": ["bluetooth-data-tools==1.26.5"]
}

View File

@@ -104,6 +104,15 @@ def _resize_image(image, opts):
new_width = opts.max_width
(old_width, old_height) = img.size
old_size = len(image)
# If no max_width specified, only apply quality changes if requested
if new_width is None:
if opts.quality is None:
return image
imgbuf = io.BytesIO()
img.save(imgbuf, "JPEG", optimize=True, quality=quality)
return imgbuf.getvalue()
if old_width <= new_width:
if opts.quality is None:
_LOGGER.debug("Image is smaller-than/equal-to requested width")

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/pvoutput",
"integration_type": "device",
"iot_class": "cloud_polling",
"requirements": ["pvo==2.2.0"]
"requirements": ["pvo==2.2.1"]
}

View File

@@ -139,14 +139,13 @@ def query_circular_mean(table: type[StatisticsBase]) -> tuple[Label, Label]:
# in Python.
# https://en.wikipedia.org/wiki/Circular_mean
radians = func.radians(table.mean)
weighted_sum_sin = func.sum(func.sin(radians) * table.mean_weight)
weighted_sum_cos = func.sum(func.cos(radians) * table.mean_weight)
weight = func.sqrt(
func.power(func.sum(func.sin(radians) * table.mean_weight), 2)
+ func.power(func.sum(func.cos(radians) * table.mean_weight), 2)
func.power(weighted_sum_sin, 2) + func.power(weighted_sum_cos, 2)
)
return (
func.degrees(
func.atan2(func.sum(func.sin(radians)), func.sum(func.cos(radians)))
).label("mean"),
func.degrees(func.atan2(weighted_sum_sin, weighted_sum_cos)).label("mean"),
weight.label("mean_weight"),
)
@@ -240,18 +239,20 @@ DEG_TO_RAD = math.pi / 180
RAD_TO_DEG = 180 / math.pi
def weighted_circular_mean(values: Iterable[tuple[float, float]]) -> float:
"""Return the weighted circular mean of the values."""
sin_sum = sum(math.sin(x * DEG_TO_RAD) * weight for x, weight in values)
cos_sum = sum(math.cos(x * DEG_TO_RAD) * weight for x, weight in values)
return (RAD_TO_DEG * math.atan2(sin_sum, cos_sum)) % 360
def weighted_circular_mean(
values: Iterable[tuple[float, float]],
) -> tuple[float, float]:
"""Return the weighted circular mean and the weight of the values."""
weighted_sin_sum, weighted_cos_sum = 0.0, 0.0
for x, weight in values:
rad_x = x * DEG_TO_RAD
weighted_sin_sum += math.sin(rad_x) * weight
weighted_cos_sum += math.cos(rad_x) * weight
def circular_mean(values: list[float]) -> float:
"""Return the circular mean of the values."""
sin_sum = sum(math.sin(x * DEG_TO_RAD) for x in values)
cos_sum = sum(math.cos(x * DEG_TO_RAD) for x in values)
return (RAD_TO_DEG * math.atan2(sin_sum, cos_sum)) % 360
return (
(RAD_TO_DEG * math.atan2(weighted_sin_sum, weighted_cos_sum)) % 360,
math.sqrt(weighted_sin_sum**2 + weighted_cos_sum**2),
)
_LOGGER = logging.getLogger(__name__)
@@ -300,6 +301,7 @@ class StatisticsRow(BaseStatisticsRow, total=False):
min: float | None
max: float | None
mean: float | None
mean_weight: float | None
change: float | None
@@ -1023,7 +1025,7 @@ def _reduce_statistics(
_want_sum = "sum" in types
for statistic_id, stat_list in stats.items():
max_values: list[float] = []
mean_values: list[float] = []
mean_values: list[tuple[float, float]] = []
min_values: list[float] = []
prev_stat: StatisticsRow = stat_list[0]
fake_entry: StatisticsRow = {"start": stat_list[-1]["start"] + period_seconds}
@@ -1039,12 +1041,15 @@ def _reduce_statistics(
}
if _want_mean:
row["mean"] = None
row["mean_weight"] = None
if mean_values:
match metadata[statistic_id][1]["mean_type"]:
case StatisticMeanType.ARITHMETIC:
row["mean"] = mean(mean_values)
row["mean"] = mean([x[0] for x in mean_values])
case StatisticMeanType.CIRCULAR:
row["mean"] = circular_mean(mean_values)
row["mean"], row["mean_weight"] = (
weighted_circular_mean(mean_values)
)
mean_values.clear()
if _want_min:
row["min"] = min(min_values) if min_values else None
@@ -1063,7 +1068,8 @@ def _reduce_statistics(
max_values.append(_max)
if _want_mean:
if (_mean := statistic.get("mean")) is not None:
mean_values.append(_mean)
_mean_weight = statistic.get("mean_weight") or 0.0
mean_values.append((_mean, _mean_weight))
if _want_min and (_min := statistic.get("min")) is not None:
min_values.append(_min)
prev_stat = statistic
@@ -1385,7 +1391,7 @@ def _get_max_mean_min_statistic(
match metadata[1]["mean_type"]:
case StatisticMeanType.CIRCULAR:
if circular_means := max_mean_min["circular_means"]:
mean_value = weighted_circular_mean(circular_means)
mean_value = weighted_circular_mean(circular_means)[0]
case StatisticMeanType.ARITHMETIC:
if (mean_value := max_mean_min.get("mean_acc")) is not None and (
duration := max_mean_min.get("duration")
@@ -1739,12 +1745,12 @@ def statistic_during_period(
_type_column_mapping = {
"last_reset": "last_reset_ts",
"max": "max",
"mean": "mean",
"min": "min",
"state": "state",
"sum": "sum",
"last_reset": ("last_reset_ts",),
"max": ("max",),
"mean": ("mean", "mean_weight"),
"min": ("min",),
"state": ("state",),
"sum": ("sum",),
}
@@ -1756,12 +1762,13 @@ def _generate_select_columns_for_types_stmt(
track_on: list[str | None] = [
table.__tablename__, # type: ignore[attr-defined]
]
for key, column in _type_column_mapping.items():
if key in types:
columns = columns.add_columns(getattr(table, column))
track_on.append(column)
else:
track_on.append(None)
for key, type_columns in _type_column_mapping.items():
for column in type_columns:
if key in types:
columns = columns.add_columns(getattr(table, column))
track_on.append(column)
else:
track_on.append(None)
return lambda_stmt(lambda: columns, track_on=track_on)
@@ -1944,6 +1951,12 @@ def _statistics_during_period_with_session(
hass, session, start_time, units, _types, table, metadata, result
)
# filter out mean_weight as it is only needed to reduce statistics
# and not needed in the result
for stats_rows in result.values():
for row in stats_rows:
row.pop("mean_weight", None)
# Return statistics combined with metadata
return result
@@ -2391,7 +2404,12 @@ def _sorted_statistics_to_dict(
field_map["last_reset"] = field_map.pop("last_reset_ts")
sum_idx = field_map["sum"] if "sum" in types else None
sum_only = len(types) == 1 and sum_idx is not None
row_mapping = tuple((key, field_map[key]) for key in types if key in field_map)
row_mapping = tuple(
(column, field_map[column])
for key in types
for column in ({key, *_type_column_mapping.get(key, ())})
if column in field_map
)
# Append all statistic entries, and optionally do unit conversion
table_duration_seconds = table.duration.total_seconds()
for meta_id, db_rows in stats_by_meta_id.items():

View File

@@ -1,5 +1,6 @@
"""Config flow for Remote Calendar integration."""
from http import HTTPStatus
import logging
from typing import Any
@@ -50,6 +51,13 @@ class RemoteCalendarConfigFlow(ConfigFlow, domain=DOMAIN):
client = get_async_client(self.hass)
try:
res = await client.get(user_input[CONF_URL], follow_redirects=True)
if res.status_code == HTTPStatus.FORBIDDEN:
errors["base"] = "forbidden"
return self.async_show_form(
step_id="user",
data_schema=STEP_USER_DATA_SCHEMA,
errors=errors,
)
res.raise_for_status()
except (HTTPError, InvalidURL) as err:
errors["base"] = "cannot_connect"

View File

@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["ical"],
"quality_scale": "silver",
"requirements": ["ical==9.0.1"]
"requirements": ["ical==9.0.3"]
}

View File

@@ -19,6 +19,7 @@
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"forbidden": "The server understood the request but refuses to authorize it.",
"invalid_ics_file": "[%key:component::local_calendar::config::error::invalid_ics_file%]"
}
},

View File

@@ -39,7 +39,7 @@
"samsungctl[websocket]==0.7.1",
"samsungtvws[async,encrypted]==2.7.2",
"wakeonlan==2.1.0",
"async-upnp-client==0.43.0"
"async-upnp-client==0.44.0"
],
"ssdp": [
{

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