Compare commits

...

149 Commits

Author SHA1 Message Date
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
Franck Nijhof
d1512d46be
Bump version to 2025.4.0b7 2025-03-28 16:00:45 +00:00
Bram Kragten
0be7db6270
Update frontend to 20250328.0 (#141659) 2025-03-28 15:09:56 +00:00
Paulus Schoutsen
2af0282725
Enable the message box on default for satelitte announcement actions (#141654) 2025-03-28 15:09:51 +00:00
Franck Nijhof
ff458c8417
Bump version to 2025.4.0b6 2025-03-28 15:04:34 +00:00
Franck Nijhof
cc93152ff0
Fix ESPHome event entity staying unavailable (#141650) 2025-03-28 14:05:40 +00:00
Paulus Schoutsen
9965f01609
Ensure connection test sound has no preannouncement (#141647) 2025-03-28 14:05:37 +00:00
Jan Bouwhuis
e9c76ce694
Fix duplicate 'device' term in MQTT translation strings (#141646)
* Fix duplicate 'device' from MQTT translation strings

* Update homeassistant/components/mqtt/strings.json
2025-03-28 14:05:34 +00:00
Norbert Rittel
58ab7d350d
Fix sentence-casing in airvisual user strings (#141632) 2025-03-28 14:05:30 +00:00
Nick Pesce
e4d6e20ebd
Use correct default value for multi press buttons in the Matter integration (#141630)
* Respect the min 2 constraint for the switch MultiPressMax attribute

* Update test_event.py

* Update generic_switch_multi.json

* Fix issue and update tests
2025-03-28 14:05:27 +00:00
Tsvi Mostovicz
45e273897a
Jewish calendar match omer service variables requirement to documentation (#141620)
The documentation and the omer schema require a Nusach to be specified, but the YAML misses that requirement
2025-03-28 14:05:23 +00:00
Jan Bouwhuis
d9ec7142d7
Fix volatile_organic_compounds_parts translation string to be referenced for MQTT subentries device class selector (#141618)
* Fix ` volatile_organic_compounds_parts` translation string to be referenced for MQTT subentries device class selector

* Fix tests
2025-03-28 14:05:20 +00:00
Petro31
e162499267
Fix an issue with the switch preview in beta (#141617)
Fix an issue with the switch preview
2025-03-28 14:05:16 +00:00
Jan-Philipp Benecke
67f21429e3
Bump aiowebdav2 to 0.4.4 (#141615) 2025-03-28 14:05:12 +00:00
J. Nick Koston
a0563f06c9
Fix zeroconf logging level not being respected (#141601)
Removes an old logging workaround that is no longer needed

fixes #141558
2025-03-28 14:05:05 +00:00
Luke Lashley
e7c4fdc8bb
Bump Python-Snoo to 0.6.5 (#141599)
* Bump Python-Snoo to 0.6.5

* add to event_types
2025-03-28 14:05:00 +00:00
Norbert Rittel
c490e350bc
Make names of switch entities in gree consistent with docs (#141580) 2025-03-28 14:04:56 +00:00
Robert Resch
e11409ef99
Reverts #141363 "Deprecate SmartThings machine state sensors" (#141573)
Reverts #141363
2025-03-28 14:04:52 +00:00
Joost Lekkerkerker
5c8e415a76
Add default string and icon for light effect off (#141567) 2025-03-28 14:04:49 +00:00
alorente
e795fb9497
Fix missing response for queued mode scripts (#141460) 2025-03-28 14:04:45 +00:00
Norbert Rittel
d0afabb85c
Fix misleading friendly names of pvoutput sensors (#141312)
* Fix misleading friendly names of `pvoutput` sensors

* Update test_sensor.py

* Update test_sensor.py - prettier
2025-03-28 14:04:41 +00:00
Franck Nijhof
4f3e8e9b94
Bump version to 2025.4.0b5 2025-03-27 20:03:14 +00:00
Paul Bottein
46c1cbbc9c
Update frontend to 20250327.1 (#141596) 2025-03-27 20:03:01 +00:00
Simon Lamon
8d9a4ea278
Fix typing error in NMBS (#141589)
Fix typing error
2025-03-27 20:02:58 +00:00
Jan-Philipp Benecke
22c83e2393
Bump aiowebdav2 to 0.4.3 (#141586) 2025-03-27 20:02:55 +00:00
Joost Lekkerkerker
c83a75f6f9
Add brand for Bosch (#141561) 2025-03-27 20:02:51 +00:00
Franck Nijhof
841c727112
Bump version to 2025.4.0b4 2025-03-27 16:59:36 +00:00
Bram Kragten
d8c9655bfd
Update frontend to 20250327.0 (#141585) 2025-03-27 16:59:29 +00:00
Erik Montnemery
942ed89cc4
Revert "Promote after dependencies in bootstrap" (#141584)
Revert "Promote after dependencies in bootstrap (#140352)"

This reverts commit 376604096049ac2388a1c9d23c578402acbce0b5.
2025-03-27 16:59:25 +00:00
Franck Nijhof
a1fe6b9cf3
Bump version to 2025.4.0b3 2025-03-27 15:38:31 +00:00
Luke Lashley
2567181cc2
Better handle Roborock discovery (#141575) 2025-03-27 15:38:24 +00:00
Joost Lekkerkerker
028e4f6029
Also migrate completion time entities in SmartThings (#141572) 2025-03-27 15:38:21 +00:00
Martin Hjelmare
b82e1a9bef
Handle cloud subscription expired for backup upload (#141564)
Handle cloud backup subscription expired for upload
2025-03-27 15:38:18 +00:00
Joost Lekkerkerker
438f226c31
Add icons to hue effects (#141559) 2025-03-27 15:38:15 +00:00
Erwin Douna
2f139e3cb1
Tado fix HomeKit flow (#141525)
* Initial commit

* Fix

* Fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-03-27 15:38:07 +00:00
Franck Nijhof
5d75e96fbf
Bump version to 2025.4.0b2 2025-03-27 10:19:35 +00:00
Norbert Rittel
dcf2ec5c37
Fix sentence-casing in konnected strings, replace "override" with "custom" (#141553)
Fix sentence-casing in `konnected`strings, replace "Override" with "Custom"

Make string consistent with HA standards.

As "Override" can be misunderstood as the verb, replace it with "Custom".
2025-03-27 10:19:22 +00:00
Simon Lamon
2431e1ba98
Bump linkplay to v0.2.2 (#141542)
Bump linkplay
2025-03-27 10:19:18 +00:00
Thomas55555
4ead108c15
Handle webcal prefix in remote calendar (#141541)
Handel webcal prefix in remote calendar
2025-03-27 10:19:14 +00:00
Michael Hansen
ec8363fa49
Add default preannounce sound to Assist satellites (#141522)
* Add default preannounce sound

* Allow None to disable sound

* Register static path instead of HTTP view

* Fix path

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2025-03-27 10:19:09 +00:00
J. Diego Rodríguez Royo
e7ff0a3f8b
Improve some Home Connect deprecations (#141508) 2025-03-27 10:19:06 +00:00
Ivan Lopez Hernandez
f4c0eb4189
Initialize google.genai.Client in the executor (#141432)
* Intialize the client on an executor thread

* Fix MyPy error

* MyPy error

* Exception error

* Fix ruff

* Update __init__.py

---------

Co-authored-by: tronikos <tronikos@users.noreply.github.com>
2025-03-27 10:19:02 +00:00
Manu
b1ee5a76e1
Support for upcoming pyLoad-ng release in pyLoad integration (#141297)
Fix extra key `proxy` in pyLoad
2025-03-27 10:18:58 +00:00
Norbert Rittel
6b9e8c301b
Fix wrong friendly name for storage_power in solaredge (#141269)
* Fix wrong friendly name for `storage_power` in `solaredge`

"Stored power" is a contradiction in itself.
You can only store energy.

* Two additional spelling fixes

* Sentence-case "site"
2025-03-27 10:18:53 +00:00
Franck Nijhof
89c3266c7e
Bump version to 2025.4.0b1 2025-03-26 23:21:26 +00:00
Jan Bouwhuis
cff0a632e8
Fix QoS schema issue in MQTT subentries (#141531) 2025-03-26 23:21:17 +00:00
Jan Bouwhuis
e04d8557ae
Fix MQTT options flow QoS selector can not serialize (#141528) 2025-03-26 23:21:14 +00:00
Thomas55555
ca6286f241
Fix work area sensor for Husqvarna Automower (#141527)
* Fix work area sensor for Husqvarna Automower

* simplify
2025-03-26 23:21:10 +00:00
Robert Resch
35bcc9d5af
Show box for Smartthings rise number entity (#141526) 2025-03-26 23:21:07 +00:00
Joost Lekkerkerker
25b45ce867
Sort SmartThings devices to be created by parent device id (#141515) 2025-03-26 23:21:03 +00:00
Robert Resch
d568209bd5
Bump deebot-client to 12.4.0 (#141501) 2025-03-26 23:21:00 +00:00
Simone Chemelli
8a43e8af9e
Fix refresh state for Comelit alarm (#141370) 2025-03-26 23:20:56 +00:00
Franck Nijhof
785e5b2c16
Bump version to 2025.4.0b0 2025-03-26 17:41:03 +00:00
259 changed files with 8086 additions and 4078 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

@ -859,14 +859,8 @@ async def _async_set_up_integrations(
integrations, all_integrations = await _async_resolve_domains_and_preload(
hass, config
)
# Detect all cycles
integrations_after_dependencies = (
await loader.resolve_integrations_after_dependencies(
hass, all_integrations.values(), set(all_integrations)
)
)
all_domains = set(integrations_after_dependencies)
domains = set(integrations) & all_domains
all_domains = set(all_integrations)
domains = set(integrations)
_LOGGER.info(
"Domains to be set up: %s | %s",
@ -874,8 +868,6 @@ async def _async_set_up_integrations(
all_domains - domains,
)
async_set_domains_to_be_loaded(hass, all_domains)
# Initialize recorder
if "recorder" in all_domains:
recorder.async_initialize_recorder(hass)
@ -908,12 +900,24 @@ async def _async_set_up_integrations(
stage_dep_domains_unfiltered = {
dep
for domain in stage_domains
for dep in integrations_after_dependencies[domain]
for dep in all_integrations[domain].all_dependencies
if dep not in stage_domains
}
stage_dep_domains = stage_dep_domains_unfiltered - hass.config.components
stage_all_domains = stage_domains | stage_dep_domains
stage_all_integrations = {
domain: all_integrations[domain] for domain in stage_all_domains
}
# Detect all cycles
stage_integrations_after_dependencies = (
await loader.resolve_integrations_after_dependencies(
hass, stage_all_integrations.values(), stage_all_domains
)
)
stage_all_domains = set(stage_integrations_after_dependencies)
stage_domains &= stage_all_domains
stage_dep_domains &= stage_all_domains
_LOGGER.info(
"Setting up stage %s: %s | %s\nDependencies: %s | %s",
@ -924,6 +928,8 @@ async def _async_set_up_integrations(
stage_dep_domains_unfiltered - stage_dep_domains,
)
async_set_domains_to_be_loaded(hass, stage_all_domains)
if timeout is None:
await _async_setup_multi_components(hass, stage_all_domains, config)
continue

View File

@ -0,0 +1,5 @@
{
"domain": "bosch",
"name": "Bosch",
"integrations": ["bosch_alarm", "bosch_shc", "home_connect"]
}

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

@ -2,7 +2,7 @@
"config": {
"step": {
"geography_by_coords": {
"title": "Configure a Geography",
"title": "Configure a geography",
"description": "Use the AirVisual cloud API to monitor a latitude/longitude.",
"data": {
"api_key": "[%key:common::config_flow::data::api_key%]",
@ -56,12 +56,12 @@
"sensor": {
"pollutant_label": {
"state": {
"co": "Carbon Monoxide",
"n2": "Nitrogen Dioxide",
"co": "Carbon monoxide",
"n2": "Nitrogen dioxide",
"o3": "Ozone",
"p1": "PM10",
"p2": "PM2.5",
"s2": "Sulfur Dioxide"
"s2": "Sulfur dioxide"
}
},
"pollutant_level": {

View File

@ -1,9 +1,11 @@
"""Base class for assist satellite entities."""
import logging
from pathlib import Path
import voluptuous as vol
from homeassistant.components.http import StaticPathConfig
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers import config_validation as cv
@ -15,6 +17,8 @@ from .const import (
CONNECTION_TEST_DATA,
DATA_COMPONENT,
DOMAIN,
PREANNOUNCE_FILENAME,
PREANNOUNCE_URL,
AssistSatelliteEntityFeature,
)
from .entity import (
@ -56,6 +60,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
{
vol.Optional("message"): str,
vol.Optional("media_id"): str,
vol.Optional("preannounce"): bool,
vol.Optional("preannounce_media_id"): str,
}
),
@ -71,6 +76,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
{
vol.Optional("start_message"): str,
vol.Optional("start_media_id"): str,
vol.Optional("preannounce"): bool,
vol.Optional("preannounce_media_id"): str,
vol.Optional("extra_system_prompt"): str,
}
@ -84,6 +90,15 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
async_register_websocket_api(hass)
hass.http.register_view(ConnectionTestView())
# Default preannounce sound
await hass.http.async_register_static_paths(
[
StaticPathConfig(
PREANNOUNCE_URL, str(Path(__file__).parent / PREANNOUNCE_FILENAME)
)
]
)
return True

View File

@ -20,6 +20,9 @@ CONNECTION_TEST_DATA: HassKey[dict[str, asyncio.Event]] = HassKey(
f"{DOMAIN}_connection_tests"
)
PREANNOUNCE_FILENAME = "preannounce.mp3"
PREANNOUNCE_URL = f"/api/assist_satellite/static/{PREANNOUNCE_FILENAME}"
class AssistSatelliteEntityFeature(IntFlag):
"""Supported features of Assist satellite entity."""

View File

@ -28,7 +28,7 @@ from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import chat_session, entity
from homeassistant.helpers.entity import EntityDescription
from .const import AssistSatelliteEntityFeature
from .const import PREANNOUNCE_URL, AssistSatelliteEntityFeature
from .errors import AssistSatelliteError, SatelliteBusyError
_LOGGER = logging.getLogger(__name__)
@ -180,7 +180,8 @@ class AssistSatelliteEntity(entity.Entity):
self,
message: str | None = None,
media_id: str | None = None,
preannounce_media_id: str | None = None,
preannounce: bool = True,
preannounce_media_id: str = PREANNOUNCE_URL,
) -> None:
"""Play and show an announcement on the satellite.
@ -190,7 +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_media_id is provided, it is played before the announcement.
If preannounce is True, a sound is played before the announcement.
If preannounce_media_id is provided, it overrides the default sound.
Calls async_announce with message and media id.
"""
@ -200,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:
@ -228,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 = None,
preannounce: bool = True,
preannounce_media_id: str = PREANNOUNCE_URL,
) -> None:
"""Start a conversation from the satellite.
@ -238,7 +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 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.
"""
@ -255,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

@ -8,12 +8,18 @@ announce:
message:
required: false
example: "Time to wake up!"
default: ""
selector:
text:
media_id:
required: false
selector:
text:
preannounce:
required: false
default: true
selector:
boolean:
preannounce_media_id:
required: false
selector:
@ -28,6 +34,7 @@ start_conversation:
start_message:
required: false
example: "You left the lights on in the living room. Turn them off?"
default: ""
selector:
text:
start_media_id:
@ -38,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

@ -198,7 +198,8 @@ async def websocket_test_connection(
hass.async_create_background_task(
satellite.async_internal_announce(
media_id=f"{CONNECTION_TEST_URL_BASE}/{connection_id}"
media_id=f"{CONNECTION_TEST_URL_BASE}/{connection_id}",
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

@ -4,13 +4,14 @@ from __future__ import annotations
import asyncio
from collections.abc import AsyncIterator, Callable, Coroutine, Mapping
from http import HTTPStatus
import logging
import random
from typing import Any
from aiohttp import ClientError
from aiohttp import ClientError, ClientResponseError
from hass_nabucasa import Cloud, CloudError
from hass_nabucasa.api import CloudApiNonRetryableError
from hass_nabucasa.api import CloudApiError, CloudApiNonRetryableError
from hass_nabucasa.cloud_api import (
FilesHandlerListEntry,
async_files_delete_file,
@ -120,6 +121,8 @@ class CloudBackupAgent(BackupAgent):
"""
if not backup.protected:
raise BackupAgentError("Cloud backups must be protected")
if self._cloud.subscription_expired:
raise BackupAgentError("Cloud subscription has expired")
size = backup.size
try:
@ -152,6 +155,13 @@ class CloudBackupAgent(BackupAgent):
) from err
raise BackupAgentError(f"Failed to upload backup {err}") from err
except CloudError as err:
if (
isinstance(err, CloudApiError)
and isinstance(err.orig_exc, ClientResponseError)
and err.orig_exc.status == HTTPStatus.FORBIDDEN
and self._cloud.subscription_expired
):
raise BackupAgentError("Cloud subscription has expired") from err
if tries == _RETRY_LIMIT:
raise BackupAgentError(f"Failed to upload backup {err}") from err
tries += 1

View File

@ -41,6 +41,7 @@ ALARM_ACTIONS: dict[str, str] = {
ALARM_AREA_ARMED_STATUS: dict[str, int] = {
DISABLE: 0,
HOME_P1: 1,
HOME_P2: 2,
NIGHT: 3,
@ -128,20 +129,38 @@ class ComelitAlarmEntity(CoordinatorEntity[ComelitVedoSystem], AlarmControlPanel
AlarmAreaState.TRIGGERED: AlarmControlPanelState.TRIGGERED,
}.get(self._area.human_status)
async def _async_update_state(self, area_state: AlarmAreaState, armed: int) -> None:
"""Update state after action."""
self._area.human_status = area_state
self._area.armed = armed
await self.async_update_ha_state()
async def async_alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command."""
if code != str(self._api.device_pin):
return
await self._api.set_zone_status(self._area.index, ALARM_ACTIONS[DISABLE])
await self._async_update_state(
AlarmAreaState.DISARMED, ALARM_AREA_ARMED_STATUS[DISABLE]
)
async def async_alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command."""
await self._api.set_zone_status(self._area.index, ALARM_ACTIONS[AWAY])
await self._async_update_state(
AlarmAreaState.ARMED, ALARM_AREA_ARMED_STATUS[AWAY]
)
async def async_alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command."""
await self._api.set_zone_status(self._area.index, ALARM_ACTIONS[HOME])
await self._async_update_state(
AlarmAreaState.ARMED, ALARM_AREA_ARMED_STATUS[HOME_P1]
)
async def async_alarm_arm_night(self, code: str | None = None) -> None:
"""Send arm night command."""
await self._api.set_zone_status(self._area.index, ALARM_ACTIONS[NIGHT])
await self._async_update_state(
AlarmAreaState.ARMED, ALARM_AREA_ARMED_STATUS[NIGHT]
)

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

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

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

@ -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.3.1"]
"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

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

@ -33,6 +33,16 @@ class EsphomeEvent(EsphomeEntity[EventInfo, Event], EventEntity):
self._trigger_event(self._state.event_type)
self.async_write_ha_state()
@callback
def _on_device_update(self) -> None:
"""Call when device updates or entry data changes."""
super()._on_device_update()
if self._entry_data.available:
# Event entities should go available directly
# when the device comes online and not wait
# for the next data push.
self.async_write_ha_state()
async_setup_entry = partial(
platform_async_setup_entry,

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

@ -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==20250326.0"]
"requirements": ["home-assistant-frontend==20250401.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

@ -5,7 +5,7 @@ from __future__ import annotations
import mimetypes
from pathlib import Path
from google import genai # type: ignore[attr-defined]
from google.genai import Client
from google.genai.errors import APIError, ClientError
from requests.exceptions import Timeout
import voluptuous as vol
@ -43,7 +43,7 @@ CONF_FILENAMES = "filenames"
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
PLATFORMS = (Platform.CONVERSATION,)
type GoogleGenerativeAIConfigEntry = ConfigEntry[genai.Client]
type GoogleGenerativeAIConfigEntry = ConfigEntry[Client]
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
@ -139,7 +139,11 @@ async def async_setup_entry(
"""Set up Google Generative AI Conversation from a config entry."""
try:
client = genai.Client(api_key=entry.data[CONF_API_KEY])
def _init_client() -> Client:
return Client(api_key=entry.data[CONF_API_KEY])
client = await hass.async_add_executor_job(_init_client)
await client.aio.models.get(
model=entry.options.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL),
config={"http_options": {"timeout": TIMEOUT_MILLIS}},

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

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

@ -16,13 +16,13 @@
"name": "Panel light"
},
"quiet": {
"name": "Quiet"
"name": "Quiet mode"
},
"fresh_air": {
"name": "Fresh air"
},
"xfan": {
"name": "XFan"
"name": "Xtra fan"
},
"health_mode": {
"name": "Health mode"

View File

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

View File

@ -244,6 +244,7 @@ class HomeConnectDoorBinarySensor(HomeConnectBinarySensor):
BSH_DOOR_STATE_LOCKED: False,
BSH_DOOR_STATE_OPEN: True,
},
entity_registry_enabled_default=False,
),
)
self._attr_unique_id = f"{appliance.info.ha_id}-Door"
@ -283,7 +284,8 @@ class HomeConnectDoorBinarySensor(HomeConnectBinarySensor):
DOMAIN,
f"deprecated_binary_common_door_sensor_{self.entity_id}",
breaks_in_ha_version="2025.5.0",
is_fixable=False,
is_fixable=True,
is_persistent=True,
severity=IssueSeverity.WARNING,
translation_key="deprecated_binary_common_door_sensor",
translation_placeholders={

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

@ -134,15 +134,47 @@
},
"deprecated_binary_common_door_sensor": {
"title": "Deprecated binary door sensor detected in some automations or scripts",
"description": "The binary door sensor `{entity}`, which is deprecated, is used in the following automations or scripts:\n{items}\n\nA sensor entity with additional possible states is available and should be used going forward; Please use it on the above automations or scripts to fix this issue."
"fix_flow": {
"step": {
"confirm": {
"title": "[%key:component::home_connect::issues::deprecated_binary_common_door_sensor::title%]",
"description": "The binary door sensor `{entity}`, which is deprecated, is used in the following automations or scripts:\n{items}\n\nA sensor entity with additional possible states is available and should be used going forward; Please use it on the above automations or scripts to fix this issue."
}
}
}
},
"deprecated_command_actions": {
"title": "The command related actions are deprecated in favor of the new buttons",
"description": "The `pause_program` and `resume_program` actions have been deprecated in favor of new button entities, if the command is available for your appliance. Please update your automations, scripts and panels that use this action to use the button entities instead, and press on submit to fix the issue."
"fix_flow": {
"step": {
"confirm": {
"title": "[%key:component::home_connect::issues::deprecated_command_actions::title%]",
"description": "The `pause_program` and `resume_program` actions have been deprecated in favor of new button entities, if the command is available for your appliance. Please update your automations, scripts and panels that use this action to use the button entities instead, and press on submit to fix the issue."
}
}
}
},
"deprecated_program_switch_in_automations_scripts": {
"title": "Deprecated program switch detected in some automations or scripts",
"fix_flow": {
"step": {
"confirm": {
"title": "[%key:component::home_connect::issues::deprecated_program_switch_in_automations_scripts::title%]",
"description": "Program switches are deprecated and {entity_id} is used in the following automations or scripts:\n{items}\n\nYou can use the active program select entity to run the program without any additional options and get the current running program on the above automations or scripts to fix this issue."
}
}
}
},
"deprecated_program_switch": {
"title": "Deprecated program switch detected in some automations or scripts",
"description": "Program switches are deprecated and {entity_id} is used in the following automations or scripts:\n{items}\n\nYou can use the active program select entity to run the program without any additional options and get the current running program on the above automations or scripts to fix this issue."
"title": "Deprecated program switch entities",
"fix_flow": {
"step": {
"confirm": {
"title": "[%key:component::home_connect::issues::deprecated_program_switch::title%]",
"description": "The switch entity `{entity_id}` and all the other program switches are deprecated.\n\nPlease use the active program select entity instead."
}
}
}
},
"deprecated_set_program_and_option_actions": {
"title": "The executed action is deprecated",

View File

@ -266,7 +266,10 @@ class HomeConnectProgramSwitch(HomeConnectEntity, SwitchEntity):
super().__init__(
coordinator,
appliance,
SwitchEntityDescription(key=EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM),
SwitchEntityDescription(
key=EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM,
entity_registry_enabled_default=False,
),
)
self._attr_name = f"{appliance.info.name} {desc}"
self._attr_unique_id = f"{appliance.info.ha_id}-{desc}"
@ -304,11 +307,12 @@ class HomeConnectProgramSwitch(HomeConnectEntity, SwitchEntity):
async_create_issue(
self.hass,
DOMAIN,
f"deprecated_program_switch_{self.entity_id}",
f"deprecated_program_switch_in_automations_scripts_{self.entity_id}",
breaks_in_ha_version="2025.6.0",
is_fixable=False,
is_fixable=True,
is_persistent=True,
severity=IssueSeverity.WARNING,
translation_key="deprecated_program_switch",
translation_key="deprecated_program_switch_in_automations_scripts",
translation_placeholders={
"entity_id": self.entity_id,
"items": "\n".join(items_list),
@ -317,12 +321,34 @@ class HomeConnectProgramSwitch(HomeConnectEntity, SwitchEntity):
async def async_will_remove_from_hass(self) -> None:
"""Call when entity will be removed from hass."""
async_delete_issue(
self.hass,
DOMAIN,
f"deprecated_program_switch_in_automations_scripts_{self.entity_id}",
)
async_delete_issue(
self.hass, DOMAIN, f"deprecated_program_switch_{self.entity_id}"
)
def create_action_handler_issue(self) -> None:
"""Create deprecation issue."""
async_create_issue(
self.hass,
DOMAIN,
f"deprecated_program_switch_{self.entity_id}",
breaks_in_ha_version="2025.6.0",
is_fixable=True,
is_persistent=True,
severity=IssueSeverity.WARNING,
translation_key="deprecated_program_switch",
translation_placeholders={
"entity_id": self.entity_id,
},
)
async def async_turn_on(self, **kwargs: Any) -> None:
"""Start the program."""
self.create_action_handler_issue()
try:
await self.coordinator.client.start_program(
self.appliance.info.ha_id, program_key=self.program.key
@ -339,6 +365,7 @@ class HomeConnectProgramSwitch(HomeConnectEntity, SwitchEntity):
async def async_turn_off(self, **kwargs: Any) -> None:
"""Stop the program."""
self.create_action_handler_issue()
try:
await self.coordinator.client.stop_program(self.appliance.info.ha_id)
except HomeConnectError as err:

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

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

@ -1,4 +1,28 @@
{
"entity": {
"light": {
"hue_light": {
"state_attributes": {
"effect": {
"state": {
"candle": "mdi:candle",
"sparkle": "mdi:shimmer",
"glisten": "mdi:creation",
"sunrise": "mdi:weather-sunset-up",
"sunset": "mdi:weather-sunset",
"fire": "mdi:fire",
"prism": "mdi:triangle-outline",
"opal": "mdi:diamond-stone",
"underwater": "mdi:waves",
"cosmos": "mdi:star-shooting",
"sunbeam": "mdi:spotlight-beam",
"enchant": "mdi:magic-staff"
}
}
}
}
}
},
"services": {
"hue_activate_scene": {
"service": "mdi:palette"

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

@ -227,12 +227,16 @@ def _get_work_area_names(data: MowerAttributes) -> list[str]:
@callback
def _get_current_work_area_name(data: MowerAttributes) -> str:
"""Return the name of the current work area."""
if data.mower.work_area_id is None:
return STATE_NO_WORK_AREA_ACTIVE
if TYPE_CHECKING:
# Sensor does not get created if values are None
assert data.work_areas is not None
return data.work_areas[data.mower.work_area_id].name
if (
data.mower.work_area_id is not None
and data.mower.work_area_id in data.work_areas
):
return data.work_areas[data.mower.work_area_id].name
return STATE_NO_WORK_AREA_ACTIVE
@callback

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

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

@ -6,6 +6,7 @@ count_omer:
selector:
date:
nusach:
required: true
example: "sfarad"
default: "sfarad"
selector:

View File

@ -2,19 +2,19 @@
"config": {
"step": {
"import_confirm": {
"title": "Import Konnected Device",
"description": "A Konnected Alarm Panel with ID {id} has been discovered in configuration.yaml. This flow will allow you to import it into a config entry."
"title": "Import Konnected device",
"description": "A Konnected alarm panel with ID {id} has been discovered in configuration.yaml. This flow will allow you to import it into a config entry."
},
"user": {
"description": "Please enter the host information for your Konnected Panel.",
"description": "Please enter the host information for your Konnected panel.",
"data": {
"host": "[%key:common::config_flow::data::ip%]",
"port": "[%key:common::config_flow::data::port%]"
}
},
"confirm": {
"title": "Konnected Device Ready",
"description": "Model: {model}\nID: {id}\nHost: {host}\nPort: {port}\n\nYou can configure the IO and panel behavior in the Konnected Alarm Panel settings."
"title": "Konnected device ready",
"description": "Model: {model}\nID: {id}\nHost: {host}\nPort: {port}\n\nYou can configure the IO and panel behavior in the Konnected alarm panel settings."
}
},
"error": {
@ -45,8 +45,8 @@
}
},
"options_io_ext": {
"title": "Configure Extended I/O",
"description": "Select the configuration of the remaining I/O below. You'll be able to configure detailed options in the next steps.",
"title": "Configure extended I/O",
"description": "Select the configuration of the remaining I/O below. You'll be able to configure detailed options in the next steps.",
"data": {
"8": "Zone 8",
"9": "Zone 9",
@ -59,25 +59,25 @@
}
},
"options_binary": {
"title": "Configure Binary Sensor",
"title": "Configure binary sensor",
"description": "{zone} options",
"data": {
"type": "Binary Sensor Type",
"type": "Binary sensor type",
"name": "[%key:common::config_flow::data::name%]",
"inverse": "Invert the open/close state"
}
},
"options_digital": {
"title": "Configure Digital Sensor",
"title": "Configure digital sensor",
"description": "[%key:component::konnected::options::step::options_binary::description%]",
"data": {
"type": "Sensor Type",
"type": "Sensor type",
"name": "[%key:common::config_flow::data::name%]",
"poll_interval": "Poll Interval (minutes)"
"poll_interval": "Poll interval (minutes)"
}
},
"options_switch": {
"title": "Configure Switchable Output",
"title": "Configure switchable output",
"description": "{zone} options: state {state}",
"data": {
"name": "[%key:common::config_flow::data::name%]",
@ -89,18 +89,18 @@
}
},
"options_misc": {
"title": "Configure Misc",
"title": "Configure misc",
"description": "Please select the desired behavior for your panel",
"data": {
"discovery": "Respond to discovery requests on your network",
"blink": "Blink panel LED on when sending state change",
"override_api_host": "Override default Home Assistant API host panel URL",
"api_host": "Override API host URL"
"override_api_host": "Override default Home Assistant API host URL",
"api_host": "Custom API host URL"
}
}
},
"error": {
"bad_host": "Invalid Override API host URL"
"bad_host": "Invalid custom API host URL"
},
"abort": {
"not_konn_panel": "[%key:component::konnected::config::abort::not_konn_panel%]"

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

@ -1,7 +1,15 @@
{
"entity_component": {
"_": {
"default": "mdi:lightbulb"
"default": "mdi:lightbulb",
"state_attributes": {
"effect": {
"default": "mdi:circle-medium",
"state": {
"off": "mdi:star-off"
}
}
}
}
},
"services": {

View File

@ -93,7 +93,10 @@
"name": "Color temperature (Kelvin)"
},
"effect": {
"name": "Effect"
"name": "Effect",
"state": {
"off": "[%key:common::state::off%]"
}
},
"effect_list": {
"name": "Available effects"

View File

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

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

@ -69,7 +69,7 @@ class MatterEventEntity(MatterEntity, EventEntity):
max_presses_supported = self.get_matter_attribute_value(
clusters.Switch.Attributes.MultiPressMax
)
max_presses_supported = min(max_presses_supported or 1, 8)
max_presses_supported = min(max_presses_supported or 2, 8)
for i in range(max_presses_supported):
event_types.append(f"multi_press_{i + 1}") # noqa: PERF401
elif feature_map & SwitchFeature.kMomentarySwitch:

View File

@ -23,7 +23,11 @@ from homeassistant.helpers.network import (
from .const import CONTENT_AUTH_EXPIRY_TIME, MediaClass, MediaType
# Paths that we don't need to sign
PATHS_WITHOUT_AUTH = ("/api/tts_proxy/", "/api/esphome/ffmpeg_proxy/")
PATHS_WITHOUT_AUTH = (
"/api/tts_proxy/",
"/api/esphome/ffmpeg_proxy/",
"/api/assist_satellite/static/",
)
@callback

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

@ -153,7 +153,6 @@ from .util import (
learn_more_url,
valid_birth_will,
valid_publish_topic,
valid_qos_schema,
valid_subscribe_topic,
valid_subscribe_topic_template,
)
@ -182,7 +181,6 @@ PASSWORD_SELECTOR = TextSelector(TextSelectorConfig(type=TextSelectorType.PASSWO
QOS_SELECTOR = NumberSelector(
NumberSelectorConfig(mode=NumberSelectorMode.BOX, min=0, max=2)
)
QOS_DATA_SCHEMA = vol.All(QOS_SELECTOR, valid_qos_schema)
KEEPALIVE_SELECTOR = vol.All(
NumberSelector(
NumberSelectorConfig(
@ -1145,7 +1143,7 @@ class MQTTOptionsFlowHandler(OptionsFlow):
"birth_payload", description={"suggested_value": birth[CONF_PAYLOAD]}
)
] = TEXT_SELECTOR
fields[vol.Optional("birth_qos", default=birth[ATTR_QOS])] = QOS_DATA_SCHEMA
fields[vol.Optional("birth_qos", default=birth[ATTR_QOS])] = QOS_SELECTOR
fields[vol.Optional("birth_retain", default=birth[ATTR_RETAIN])] = (
BOOLEAN_SELECTOR
)
@ -1168,7 +1166,7 @@ class MQTTOptionsFlowHandler(OptionsFlow):
"will_payload", description={"suggested_value": will[CONF_PAYLOAD]}
)
] = TEXT_SELECTOR
fields[vol.Optional("will_qos", default=will[ATTR_QOS])] = QOS_DATA_SCHEMA
fields[vol.Optional("will_qos", default=will[ATTR_QOS])] = QOS_SELECTOR
fields[vol.Optional("will_retain", default=will[ATTR_RETAIN])] = (
BOOLEAN_SELECTOR
)
@ -1269,13 +1267,9 @@ class MQTTSubentryFlowHandler(ConfigSubentryFlow):
reconfig=True,
)
if user_input is not None:
merged_user_input, errors = validate_user_input(
user_input, MQTT_DEVICE_PLATFORM_FIELDS
)
_, errors = validate_user_input(user_input, MQTT_DEVICE_PLATFORM_FIELDS)
if not errors:
self._subentry_data[CONF_DEVICE] = cast(
MqttDeviceData, merged_user_input
)
self._subentry_data[CONF_DEVICE] = cast(MqttDeviceData, user_input)
if self.source == SOURCE_RECONFIGURE:
return await self.async_step_summary_menu()
return await self.async_step_entity()

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": {
@ -285,9 +285,9 @@
"invalid_uom": "The unit of measurement \"{unit_of_measurement}\" is not supported by the selected device class, please either remove the device class, select a device class which supports \"{unit_of_measurement}\", or pick a supported unit of measurement from the list",
"invalid_url": "Invalid URL",
"options_not_allowed_with_state_class_or_uom": "The 'Options' setting is not allowed when state class or unit of measurement are used",
"options_device_class_enum": "The 'Options' setting must be used with the Enumeration device class'. If you continue, the existing options will be reset",
"options_device_class_enum": "The 'Options' setting must be used with the Enumeration device class. If you continue, the existing options will be reset",
"options_with_enum_device_class": "Configure options for the enumeration sensor",
"uom_required_for_device_class": "The selected device device class requires a unit"
"uom_required_for_device_class": "The selected device class requires a unit"
}
}
},
@ -453,7 +453,7 @@
"temperature": "[%key:component::sensor::entity_component::temperature::name%]",
"timestamp": "[%key:component::sensor::entity_component::timestamp::name%]",
"volatile_organic_compounds": "[%key:component::sensor::entity_component::volatile_organic_compounds::name%]",
"volatile_organic_compounds_parts": "[%key:component::sensor::entity_component::volatile_organic_compounds::name%]",
"volatile_organic_compounds_parts": "[%key:component::sensor::entity_component::volatile_organic_compounds_parts::name%]",
"voltage": "[%key:component::sensor::entity_component::voltage::name%]",
"volume": "[%key:component::sensor::entity_component::volume::name%]",
"volume_flow_rate": "[%key:component::sensor::entity_component::volume_flow_rate::name%]",

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 = {
@ -697,8 +703,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

@ -360,7 +360,7 @@ class NMBSSensor(SensorEntity):
attrs[ATTR_LONGITUDE] = self.station_coordinates[1]
if self.is_via_connection and not self._excl_vias:
via = self._attrs.vias.via[0]
via = self._attrs.vias[0]
attrs["via"] = via.station
attrs["via_arrival_platform"] = via.arrival.platform

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.

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