Compare commits

..

231 Commits

Author SHA1 Message Date
Franck Nijhof
2f244b2b66 2025.3.4 (#141081) 2025-03-21 21:21:43 +01:00
Martin Hjelmare
1b7e53fd01 Improve Home Connect appliances test fixture (#139787)
Improve Home Connect appliances fixture
2025-03-21 19:45:18 +00:00
Franck Nijhof
bfabf972a8 Bump version to 2025.3.4 2025-03-21 19:35:24 +00:00
Luke Lashley
c0c997eed8 Bump python-snoo to 0.6.4 (#141030) 2025-03-21 19:35:03 +00:00
Luke Lashley
14b07087dc Bump Python-Snoo to 0.6.3 (#140628)
Bump python-Snoo to 0.6.3
2025-03-21 19:34:59 +00:00
puddly
f54a634563 Bump ZHA to 0.0.53 (#141025)
* Bump ZHA to 0.0.53

* Regenerate snapshot
2025-03-21 19:33:41 +00:00
J. Diego Rodríguez Royo
e98d518b0b Fix some Home Connect options keys (#141023)
Fix some options keys
2025-03-21 19:33:38 +00:00
starkillerOG
121ee27105 Reolink fix playback headers (#141015) 2025-03-21 19:33:35 +00:00
Ivan Lopez Hernandez
5681f4f2ea Ensure file is correctly uploaded by the GenAI SDK (#140969)
Opened the file outside of the SDK
2025-03-21 19:33:32 +00:00
Joost Lekkerkerker
8a63fa3bb7 Log SmartThings subscription error on exception (#140939) 2025-03-21 19:33:28 +00:00
Josef Zweck
983a2f513d Bump pylamarzocco to 1.4.9 (#140916) 2025-03-21 19:33:24 +00:00
Joost Lekkerkerker
aab349e787 Fix SmartThings ACs without supported AC modes (#140744) 2025-03-21 19:31:08 +00:00
Joost Lekkerkerker
21ced23c3c Bump pySmartThings to 2.7.4 (#140720)
* Bump pySmartThings to 2.7.3

* Bump pySmartThings to 2.7.3

* Fix

* Fix

* Fix
2025-03-21 19:25:33 +00:00
Josef Zweck
a453e9d4c2 Don't reload onedrive on options flow (#140712) 2025-03-21 19:21:22 +00:00
Adam Feldman
3f493dce06 Fix broken core integration Smart Meter Texas by switching it to use HA's SSL Context (#140694)
* Update __init__.py to use HA's SSLContext

* Update config_flow.py to use HA's SSLContext

* Use default context for config_flow.py

* Use default context instead in __init__.py

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Fix import in __init__.py

* Fix import in config_flow.py

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-03-21 19:19:06 +00:00
Joost Lekkerkerker
403fe36489 Check Celsius in SmartThings oven setpoint (#140687) 2025-03-21 19:19:03 +00:00
J. Nick Koston
66fd7d9e8a Bump PySwitchBot to 0.57.1 (#140681)
changelog: https://github.com/sblibs/pySwitchbot/compare/0.56.1...0.57.1

fixes #140405
2025-03-21 19:19:00 +00:00
Glenn Waters
c9ceade10d Fix Elk-M1 missing TLS 1.2 check (#140672)
* Fix for missing TLS 1.2 check

* Fix error message.

* combine startswith

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
2025-03-21 19:18:57 +00:00
Joost Lekkerkerker
85b6b3a360 Make Oven setpoint follow temperature UoM in SmartThings (#140666) 2025-03-21 19:18:08 +00:00
Maikel Punie
a2102f9b98 Fix optional password in Velbus config flow (#140615)
* Fix velbusconfigflow

* add tests

* Paramtize the tests

* Removed duplicate test in favor of another case

* more comments
2025-03-21 19:12:20 +00:00
J. Diego Rodríguez Royo
28cad1d085 Handle non documented options at Home Connect select entities (#140608)
* Allow non documented options at select entities

* Don't allow undocumented options
2025-03-21 19:12:17 +00:00
J. Diego Rodríguez Royo
9d8dbfbf3f Add 700 RPM option to washer spin speed options at Home Connect (#140607)
Add 700 RPM option to washer spin speed options
2025-03-21 19:12:14 +00:00
Hessel
1382a001e3 Change max ICP value to fixed value for Wallbox Integration (#140592)
change max ICP value to fixed value

Co-authored-by: Hessel van Es <hessel@datadragons.nl>
2025-03-21 19:12:10 +00:00
Pete Sage
88e3dcccda Album art not available for Sonos media library favorites (#140557)
* get album art uri for favorites

* add tests

* update typing

* update typing

* update typing

* simplify
2025-03-21 19:12:07 +00:00
J. Diego Rodríguez Royo
43e24cf833 Handle API rate limit error on Home Connect entities fetch (#139384)
* Handle API rate limit error on entities fetch

* Apply suggestions

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

* Add decorator (does not work)

* Fix decorator

* Apply suggestions

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

* Add test

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-03-21 19:12:03 +00:00
J. Diego Rodríguez Royo
65aef40a3f Fix initial fetch of Home Connect appliance data to handle API rate limit errors (#139379)
* Fix initial fetch of appliance data to handle API rate limit errors

* Apply comments

* Delete stale function

* Handle api rate limit error at options fetching

* Update appliances after stream non-breaking error

* Always initialize coordinator data

* Improve device update

* Update test description

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

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-03-21 19:11:58 +00:00
Franck Nijhof
4d1c89f0d1 2025.3.3 (#140583) 2025-03-14 12:55:51 +01:00
Franck Nijhof
831f2dc30e Bump version to 2025.3.3 2025-03-14 09:56:13 +00:00
ashionky
1566ab3b28 Fix missing UnitOfPower.MILLIWATT in sensor and number allowed units (#140567)
* MILLIWATT

* MILLIWATT
2025-03-14 09:53:08 +00:00
Joost Lekkerkerker
c852e1398c Set unit of measurement for SmartThings oven setpoint (#140560) 2025-03-14 09:53:05 +00:00
Joost Lekkerkerker
761be9342e Fix windowShadeLevel capability in SmartThings (#140552) 2025-03-14 09:52:58 +00:00
Maciej Bieniek
54ad44a574 Fix Shelly diagnostics for devices without WebSocket Outbound support (#140501)
* Don't assume that `ws` is always in config

* Fix device
2025-03-14 09:49:52 +00:00
Matthias Alphart
fed4015bab Update xknxproject to 3.8.2 (#140499) 2025-03-14 09:49:49 +00:00
Brett Adams
019a0ebf9b Bump Tesla Fleet API to 0.9.13 (#140485) 2025-03-14 09:49:45 +00:00
Joost Lekkerkerker
7607b7d494 Mark value in number.set_value action as required (#140445) 2025-03-14 09:49:40 +00:00
Maikel Punie
8b96a9606d Bump velbusaio to 2025.3.1 (#140443) 2025-03-14 09:49:37 +00:00
Jan-Philipp Benecke
6349821037 Only do WebDAV path migration when path differs (#140402) 2025-03-14 09:49:33 +00:00
Louis Christ
db26a42734 Use only IPv4 for zeroconf in bluesound integration (#140226)
* Use only ipv4 for zeroconf

* Fix tests

* Use only ip_address for ip version check

* Add test

* Reduce test
2025-03-14 09:49:30 +00:00
Glenn Waters
74fe35f44e Bump upb-lib to 0.6.1 (#140212) 2025-03-14 09:49:26 +00:00
jb101010-2
e648716ddf Bump pysuezV2 to 2.0.4 (#139824) 2025-03-14 09:49:23 +00:00
Luke Lashley
2e20245cdf Fix bug with all Roborock maps being set to the wrong map when empty (#138493)
* Fix bug with all maps being set to the same when empty

* fix parens

* fix other parens

* rework some of the logic

* few small updates

* Remove test that is no longer relevant

* remove updated time bump
2025-03-14 09:49:19 +00:00
Franck Nijhof
a12915fc14 2025.3.2 (#140392)
* Don't allow creating backups if Home Assistant is not running (#139499)

* Don't allow creating backups if hass is not running

* Revert "Don't allow creating backups if hass is not running"

This reverts commit 1bf545eb25f20fc27fe161691a94531cba7e005c.

* Set backup manager to idle only after Home Assistant has started

* Update according to discussion, add tests

* Add more test

* Bump govee_ble to 0.43.1 (#139862)

Bump govee_ble to 0.43.0

* Label emergency heat switch (#139872)

* Add label to emergency heat switch

* Use sentence case names

Co-authored-by: Franck Nijhof <frenck@frenck.nl>

---------

Co-authored-by: Franck Nijhof <frenck@frenck.nl>

* Bump sense-energy lib to 0.13.7 (#140068)

* Update jinja to 3.1.6 (#140069)

* Update evohome-async to 1.0.3 (#140083)

bump client to 1.0.3

* Fix HEOS discovery error when previously ignored (#140091)

Abort ignored discovery

* Map prewash job state in SmartThings (#140097)

* Check support for thermostat operating state in SmartThings (#140103)

* Handle None options in SmartThings (#140110)

* Handle None options in SmartThings

* Handle None options in SmartThings

* Fix MQTT JSON light not reporting color temp status if color is not supported (#140113)

* Fix HEOS user initiated setup when discovery is waiting confirmation (#140119)

* Support null supported Thermostat modes in SmartThings (#140101)

* Set device class for Oven Completion time in SmartThings (#140139)

* Revert "Check if the unit of measurement is valid before creating the entity" (#140155)

Revert "Check if the unit of measurement is valid before creating the entity …"

This reverts commit 99e1a7a676.

* Fix the order of the group members attribute of the Music Assistant integration (#140204)

* Fix events without user in Bring integration (#140213)

Fix events without publicUserUuid

* Log broad exception in Electricity Maps config flow (#140219)

* Bump evohome-async to 1.0.4 to fix  #140194 (#140230)

bump client, add test for fix  #140194

* Refresh Home Connect token during config entry setup (#140233)

* Refresh token during config entry setup

* Test 500 error

---------

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

* Add 900 RPM option to washer spin speed options at Home Connect (#140234)

Add 900 RPM option to washer spin speed options

* Fix todo tool broken with Gemini 2.0 models. (#140246)

* Change tool name for addlist item

* Change to HasListAddItem

* extract to function

* Fix version not always available in onewire (#140260)

* Fix `client_id` not generated when connecting to the MQTT broker (#140264)

Fix client_id not generated when connecting to the MQTT broker

* Bump velbusaio to 2025.3.0 (#140267)

* Fix dryer operating state in SmartThings (#140277)

* FGLair : Upgrade to ayla-iot-unofficial 1.4.7 (#140296)

Upgrade to ayla-iot-unofficial 1.4.7

* Bump pyheos to v1.0.3 (#140310)

Bump pyheos v1.0.3

* Bump ZHA to 0.0.52 (#140325)

* Bump pydrawise to 2025.3.0 (#140330)

* Bump teslemetry-stream (#140335)

Bump

* Fix no temperature unit in SmartThings (#140363)

* Fix double space quoting in WebDAV (#140364)

* Bump python-roborock to 2.12.2 (#140368)

bump python roboorck to 2.12.2

* Handle incomplete power consumption reports in SmartThings (#140370)

* Fix browsing Audible Favorites in Sonos (#140378)

* initial commit

* updates

* update test data

* Make sure SmartThings light can deal with unknown states (#140190)

* Fix

* add comment

* Make light unknown

* Make light unknown

* Delete subscription on shutdown of SmartThings (#140135)

* Cache subscription url in SmartThings

* Cache subscription url in SmartThings

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Bump pysmartthings to 2.7.1

* 2.7.2

---------

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

* Bump version to 2025.3.2

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: Evan Farrell <evan@evanfarrell.com>
Co-authored-by: John Hillery <34005807+jrhillery@users.noreply.github.com>
Co-authored-by: Keilin Bickar <TrumpetGod@gmail.com>
Co-authored-by: David Bonnes <zxdavb@bonnes.me>
Co-authored-by: Andrew Sayre <6730289+andrewsayre@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
Co-authored-by: msm595 <msm595@users.noreply.github.com>
Co-authored-by: Manu <4445816+tr4nt0r@users.noreply.github.com>
Co-authored-by: Jan-Philipp Benecke <jan-philipp@bnck.me>
Co-authored-by: J. Diego Rodríguez Royo <jdrr1998@hotmail.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Luke Lashley <conway220@gmail.com>
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
Co-authored-by: Maikel Punie <maikel.punie@gmail.com>
Co-authored-by: Antoine Reversat <a.reversat@gmail.com>
Co-authored-by: puddly <32534428+puddly@users.noreply.github.com>
Co-authored-by: David Knowles <dknowles2@gmail.com>
Co-authored-by: Brett Adams <Bre77@users.noreply.github.com>
Co-authored-by: Pete Sage <76050312+PeteRager@users.noreply.github.com>
2025-03-11 17:36:00 +01:00
Franck Nijhof
3d5e4b980f Bump version to 2025.3.2 2025-03-11 15:22:38 +00:00
Joost Lekkerkerker
f2f653efcf Delete subscription on shutdown of SmartThings (#140135)
* Cache subscription url in SmartThings

* Cache subscription url in SmartThings

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Bump pysmartthings to 2.7.1

* 2.7.2

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-03-11 15:21:27 +00:00
Joost Lekkerkerker
b5c7bdd98f Make sure SmartThings light can deal with unknown states (#140190)
* Fix

* add comment

* Make light unknown

* Make light unknown
2025-03-11 14:58:36 +00:00
Pete Sage
38e6133202 Fix browsing Audible Favorites in Sonos (#140378)
* initial commit

* updates

* update test data
2025-03-11 14:58:02 +00:00
Joost Lekkerkerker
8541dc5bde Handle incomplete power consumption reports in SmartThings (#140370) 2025-03-11 14:57:58 +00:00
Luke Lashley
5327996bad Bump python-roborock to 2.12.2 (#140368)
bump python roboorck to 2.12.2
2025-03-11 14:57:55 +00:00
Jan-Philipp Benecke
4ddc43a9d9 Fix double space quoting in WebDAV (#140364) 2025-03-11 14:57:51 +00:00
Joost Lekkerkerker
e6dea4179b Fix no temperature unit in SmartThings (#140363) 2025-03-11 14:57:47 +00:00
Brett Adams
0318b85517 Bump teslemetry-stream (#140335)
Bump
2025-03-11 14:57:43 +00:00
David Knowles
29987d443e Bump pydrawise to 2025.3.0 (#140330) 2025-03-11 14:57:27 +00:00
puddly
cbfd8707b9 Bump ZHA to 0.0.52 (#140325) 2025-03-11 14:57:18 +00:00
Andrew Sayre
5f158f5c87 Bump pyheos to v1.0.3 (#140310)
Bump pyheos v1.0.3
2025-03-11 14:57:14 +00:00
Antoine Reversat
d67ccd2fce FGLair : Upgrade to ayla-iot-unofficial 1.4.7 (#140296)
Upgrade to ayla-iot-unofficial 1.4.7
2025-03-11 14:57:10 +00:00
Joost Lekkerkerker
29c9d3804b Fix dryer operating state in SmartThings (#140277) 2025-03-11 14:57:07 +00:00
Maikel Punie
76d478c84f Bump velbusaio to 2025.3.0 (#140267) 2025-03-11 14:56:54 +00:00
Jan Bouwhuis
5d9d6f099c Fix client_id not generated when connecting to the MQTT broker (#140264)
Fix client_id not generated when connecting to the MQTT broker
2025-03-11 14:56:49 +00:00
epenet
e4b31640b3 Fix version not always available in onewire (#140260) 2025-03-11 14:56:43 +00:00
Luke Lashley
c43f6a67d0 Fix todo tool broken with Gemini 2.0 models. (#140246)
* Change tool name for addlist item

* Change to HasListAddItem

* extract to function
2025-03-11 14:55:51 +00:00
J. Diego Rodríguez Royo
0bbab63193 Add 900 RPM option to washer spin speed options at Home Connect (#140234)
Add 900 RPM option to washer spin speed options
2025-03-11 14:52:37 +00:00
J. Diego Rodríguez Royo
06188b8fbd Refresh Home Connect token during config entry setup (#140233)
* Refresh token during config entry setup

* Test 500 error

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-03-11 14:52:33 +00:00
David Bonnes
bbbb5cadd4 Bump evohome-async to 1.0.4 to fix #140194 (#140230)
bump client, add test for fix  #140194
2025-03-11 14:52:29 +00:00
Jan-Philipp Benecke
52fcdda429 Log broad exception in Electricity Maps config flow (#140219) 2025-03-11 14:52:25 +00:00
Manu
7d93ceb0f0 Fix events without user in Bring integration (#140213)
Fix events without publicUserUuid
2025-03-11 14:52:22 +00:00
msm595
873e4b77eb Fix the order of the group members attribute of the Music Assistant integration (#140204) 2025-03-11 14:52:17 +00:00
Jan Bouwhuis
61f0eabcbb Revert "Check if the unit of measurement is valid before creating the entity" (#140155)
Revert "Check if the unit of measurement is valid before creating the entity …"

This reverts commit 99e1a7a676.
2025-03-11 14:50:10 +00:00
Joost Lekkerkerker
134b5319e1 Set device class for Oven Completion time in SmartThings (#140139) 2025-03-11 14:50:05 +00:00
Joost Lekkerkerker
ee78e21950 Support null supported Thermostat modes in SmartThings (#140101) 2025-03-11 14:47:50 +00:00
Andrew Sayre
323bc54efc Fix HEOS user initiated setup when discovery is waiting confirmation (#140119) 2025-03-11 14:46:18 +00:00
Jan Bouwhuis
fd2dee3c11 Fix MQTT JSON light not reporting color temp status if color is not supported (#140113) 2025-03-11 14:46:14 +00:00
Joost Lekkerkerker
fc53322c07 Handle None options in SmartThings (#140110)
* Handle None options in SmartThings

* Handle None options in SmartThings
2025-03-11 14:46:08 +00:00
Joost Lekkerkerker
faf9977abb Check support for thermostat operating state in SmartThings (#140103) 2025-03-11 14:46:03 +00:00
Joost Lekkerkerker
7336c8fc07 Map prewash job state in SmartThings (#140097) 2025-03-11 14:36:17 +00:00
Andrew Sayre
5cfaeda95b Fix HEOS discovery error when previously ignored (#140091)
Abort ignored discovery
2025-03-11 14:36:13 +00:00
David Bonnes
a78e9039c6 Update evohome-async to 1.0.3 (#140083)
bump client to 1.0.3
2025-03-11 14:36:10 +00:00
Franck Nijhof
227f3cea25 Update jinja to 3.1.6 (#140069) 2025-03-11 14:36:06 +00:00
Keilin Bickar
cab4890246 Bump sense-energy lib to 0.13.7 (#140068) 2025-03-11 14:36:03 +00:00
John Hillery
95fd096bdd Label emergency heat switch (#139872)
* Add label to emergency heat switch

* Use sentence case names

Co-authored-by: Franck Nijhof <frenck@frenck.nl>

---------

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2025-03-11 14:35:59 +00:00
Evan Farrell
91cf8cb547 Bump govee_ble to 0.43.1 (#139862)
Bump govee_ble to 0.43.0
2025-03-11 14:35:56 +00:00
Erik Montnemery
3ce4f3f918 Don't allow creating backups if Home Assistant is not running (#139499)
* Don't allow creating backups if hass is not running

* Revert "Don't allow creating backups if hass is not running"

This reverts commit 1bf545eb25f20fc27fe161691a94531cba7e005c.

* Set backup manager to idle only after Home Assistant has started

* Update according to discussion, add tests

* Add more test
2025-03-11 14:35:46 +00:00
Franck Nijhof
4e89948b5c 2025.3.1 (#140061) 2025-03-07 18:54:39 +01:00
Franck Nijhof
9f95383201 Bump version to 2025.3.1 2025-03-07 17:03:29 +00:00
Joost Lekkerkerker
7e452521c8 Restore SmartThings button event (#140044)
* Restore SmartThings button event

* Fix
2025-03-07 17:03:16 +00:00
Michael
991de6f1d0 Bump py-synologydsm-api to 2.7.1 (#140052)
bump py-synologydsm-api to 2.7.1
2025-03-07 16:49:07 +00:00
Joost Lekkerkerker
be32e3fe8f Only keep valid powerConsumptionReports in SmartThings (#140049)
* power consumption report

* Only keep valid powerConsumptionReports in SmartThings
2025-03-07 16:49:03 +00:00
Joost Lekkerkerker
d6eb61e9ec Bump pysmartthings to 2.7.0 (#140047) 2025-03-07 16:49:00 +00:00
Joost Lekkerkerker
e74fe69d65 Fix SmartThings thermostat climate check (#140046)
* Fix SmartThings thermostat climate check

* Add tests
2025-03-07 16:48:55 +00:00
Joost Lekkerkerker
208406123e Fix SmartThings disabling working capabilities (#140039) 2025-03-07 16:03:40 +00:00
David Bonnes
8bcd135f3d Fix evohome to gracefully handle null schedules (#140036)
* extend tests to catch null schedules

* add fixture with null schedule

* remove null schedules for now

* fic the typing for _schedule attr (is list, not dict)

* add valid schedule to fixture

* update ssetpoints only if there is a schedule

* snapshot to match last change

* refactor: dont update switchpoints if no schedule

* add in warnings for null schedules

* add fixture for DHW without schedule
2025-03-07 16:03:36 +00:00
hahn-th
e7ea0e435e Add description for HomematicIP HCU1 in homematicip_cloud setup config flow (#140025)
add description for hcu1
2025-03-07 16:03:33 +00:00
Brett Adams
b15b680cfe Fix shift state default in Teslemetry and Tessie (#140018)
* Fix again

* Fix Tessie

* Update snap
2025-03-07 16:03:29 +00:00
Brett Adams
5e26d98bdf Fix powerwall 0% in Tessie and Tesla Fleet (#140017)
Fix powerwall zero
2025-03-07 16:03:26 +00:00
Martin Hjelmare
9f94ee280a Bump aiohomeconnect to 0.16.3 (#140014) 2025-03-07 16:03:23 +00:00
J. Diego Rodríguez Royo
efa98539fa Check operation state on Home Connect program sensor update (#140011)
Check operation state on program sensor update
2025-03-07 16:03:19 +00:00
David Bonnes
113cd4bfcc Fix regression to evohome debug logging (#140000)
* fix regression in debug logging

* lint
2025-03-07 16:03:15 +00:00
Ivan Lopez Hernandez
ccbaf76e44 Correctly retrieve only loaded Google Generative AI config_entries (#139999)
* Correctly retrieve only loaded config_entries

* Ruff
2025-03-07 16:03:08 +00:00
Jan-Philipp Benecke
5d9d93d3a1 Bump aiowebdav2 to 0.4.1 (#139988) 2025-03-07 16:03:04 +00:00
J. Nick Koston
c2c5274aac Bump nexia to 2.2.2 (#139986)
changelog: https://github.com/bdraco/nexia/compare/2.2.1...2.2.2
2025-03-07 16:03:01 +00:00
Joost Lekkerkerker
89756394c9 Fix SmartThings dust sensor UoM (#139977) 2025-03-07 16:02:57 +00:00
Bram Kragten
352aa88e79 Update frontend to 20250306.0 (#139965) 2025-03-07 16:02:54 +00:00
Joost Lekkerkerker
714962bd7a Fix SmartThings fan (#139962) 2025-03-07 16:02:50 +00:00
Luke Lashley
fb4c50b5dc Bump to python-snoo 0.6.1 (#139954) 2025-03-07 16:02:47 +00:00
Jan-Philipp Benecke
b4794b2029 Set content length when uploading files to WebDAV (#139950) 2025-03-07 16:02:43 +00:00
Joost Lekkerkerker
3a8c8accfe Add config entry level diagnostics to SmartThings (#139939)
* Add config entry level diagnostics to SmartThings

* Add config entry level diagnostics to SmartThings

* Add config entry level diagnostics to SmartThings
2025-03-07 16:02:40 +00:00
Jan-Philipp Benecke
844adfc590 Bump aiowebdav2 to 0.4.0 (#139938) 2025-03-07 16:02:36 +00:00
Joost Lekkerkerker
a279e23fb5 Bump pysmartthings to 2.6.1 (#139936)
* Bump pysmartthings to 2.6.1

* Bump pysmartthings to 2.6.1
2025-03-07 15:58:00 +00:00
Jan Bouwhuis
af9bbd0585 Check if the unit of measurement is valid before creating the entity (#139932) 2025-03-07 15:50:55 +00:00
Joost Lekkerkerker
1304194f09 Deduplicate climate modes in SmartThings (#139930)
* Deduplicate climate modes in SmartThings

* Deduplicate climate modes in SmartThings
2025-03-07 15:50:51 +00:00
J. Nick Koston
e909417a3f Bump thermobeacon-ble to 0.8.1 (#139919)
changelog: https://github.com/Bluetooth-Devices/thermobeacon-ble/compare/v0.8.0...v0.8.1

fixes #139917
2025-03-07 15:50:48 +00:00
Ivan Lopez Hernandez
02706c116d Trim the Schema allowed keys to match the Public Gemini API docs. (#139876)
* Trim the Schema allowed types to match the Public API docs, not the SDK types as those do not match

* Testing
2025-03-07 15:50:43 +00:00
peteS-UK
3af6b5cb4c Fix Unit of Measurement for Squeezebox duration sensor entity on LMS service (#139861)
UOM Fix
2025-03-07 15:42:02 +00:00
Ishima
35c1bb1ec5 Check support for demand load control in SmartThings AC (#139616)
* Check support for demand load control in SmartThings AC

* Fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-03-07 15:41:52 +00:00
Franck Nijhof
97cc3984c5 2025.3.0 (#139859)
* Add vesync debug mode in library (#134571)

* Debug mode pass through

* Correct code, shouldn't have been lambda

* listener for change

* ruff

* Update manifest.json

* Reflect correct logger title

* Ruff fix from merge

* Fix return value for DataUpdateCoordinator._async setup (#139181)

Fix return value for coodinator async setup

* Fix race in WS command recorder/info (#139177)

* Fix race in WS command recorder/info

* Add comment

* Remove unnecessary local import

* Bump aiohttp to 3.11.13 (#139197)

changelog: https://github.com/aio-libs/aiohttp/compare/v3.11.12...v3.11.13

* Update Linkplay constants for Arylic S10+ and Arylic Up2Stream Amp 2.1 (#138198)

* Add support for Apps and Radios to Squeezebox Media Browser (#135009)

* Add azure_storage as backup agent (#134085)

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

* Bump onedrive quality scale to platinum (#137451)

* Bump pyloadapi to v1.4.2 (#139140)

* Add missing translations to switchbot (#139212)

* Fix bug in check_translations fixture (#139206)

* Fix bug in check_translations fixture

* Fix check for ignored translation errors

* Fix websocket_api test

* Add missing exception translation to Home Connect (#139218)

Add missing exception translation

* Configure trusted publishing for PyPI file upload (#137607)

* Bump aiostreammagic to 2.11.0 (#139213)

* Add missing exception translation to Home Connect (#139223)

* Bump ohmepy to 1.3.2 (#139013)

* Fix kitchen_sink statistic issues (#139228)

* Bump aiowebdav2 to 0.3.0 (#139202)

* Bump pylamarzocco to 1.4.7 (#139231)

* Add backup helper (#139199)

* Add backup helper

* Add hassio to stage 1

* Apply same changes to newly merged `webdav` and `azure_storage` to fix inflight conflict

* Address comments, add tests

---------

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

* Reduce requests made by webdav (#139238)

* Reduce requests made by webdav

* Update homeassistant/components/webdav/backup.py

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

---------

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

* Add Homee valve platform (#139188)

* Fix units for LCN sensor (#138940)

* Add Ohme voltage and slot list sensor (#139203)

* Bump ohmepy to 1.3.1

* Bump ohmepy to 1.3.2

* Add voltage and slot list sensor

* CI fixes

* Change slot list sensor name

* Fix snapshot tests

* Initiate source list as instance variable in Volumio (#139243)

* `logbook.log` action: Make description of  `name` field UI-friendly (#139200)

* Treat "Twist Assist" & "Block to Block" as feature names and add descriptions in Z-Wave (#139239)

Treat "Twist Assist" & "Block to Block" as feature names and add descriptions

- name-case both "Twist Assist" and "Block to Block" so those feature names don't get translated
- for proper translation of both features add useful descriptions of what they actually do
- fix sentence-casing on "Operation type"

* Add climate's swing mode to LG ThinQ (#137619)

Co-authored-by: yunseon.park <yunseon.park@lge.com>

* Bump aiowithings to 3.1.6 (#139242)

* Add update reward action to Habitica integration (#139157)

* Add Re-Auth Flow to vesync (#137398)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Rework the velbus configflow to make it more user-friendly (#135609)

* Add missing ATTR_HVAC_MODE of async_set_temperature to LG ThinQ (#137621)

Co-authored-by: yunseon.park <yunseon.park@lge.com>

* Make Radarr units translatable (#139250)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Improve Minecraft Server config flow tests (#139251)

* Revert "Bump Stookwijzer to 1.5.7" (#139253)

* Add parallel updates to Home Connect (#139255)

* Bump fnv-hash-fast to 1.2.6 (#139246)

* Make default dim level configurable in Lutron (#137127)

* Set PARALLEL_UPDATES in all Minecraft Server platforms (#139259)

* Bump aiowebostv to 0.7.1 (#139244)

* Consistently capitalize "Velbus" brand name, camel-case "VelServ" (#139257)

* Bump cached-ipaddress to 0.9.2 (#139245)

* Make Sonarr component's units translatable (#139254)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Bump stookwijzer to 1.5.8 (#139258)

* Bump Velbus to bronze quality scale (#139256)

* Add Homee number platform (#138962)

Co-authored-by: Joostlek <joostlek@outlook.com>

* Fix yolink lock v2 state update (#138710)

* Set Minecraft Server quality scale to silver (#139265)

* Add OpenWeatherMap Minute forecast action (#128799)

* Fix Ezviz entity state for cameras that are offline (#136003)

* Use proper camel-case for "VeSync", fix sentence-casing in title (#139252)

Just a quick follow-up PR to fix these two spelling mistakes.

* Add request made by `rest_command` to debug log (#139266)

* Create repair for configured unavailable backup agents (#137382)

* Create repair for configured not loaded agents

* Rework to repair issue

* Extract logic to config function

* Update test

* Handle empty agend ids config update

* Address review comment

* Update tests

* Address comment

* Improve description of `openweathermap.get_minute_forecast` action (#139267)

* Use right import in ezviz (#139272)

* Change touchline dependency to pytouchline_extended (#136362)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Rename description field to notes in Habitica action (#139271)

* Add support for effects in Govee lights (#137846)

* Add Burbank Water and Power (BWP) virtual integration (#139027)

* Update adext to 0.4.4 (#139151)

* Add sound mode support to Onkyo (#133531)

* Use new python library for picnic component (#139111)

* Bump securetar to 2025.2.1 (#139273)

* Fix race in async_get_integrations with multiple calls when an integration is not found (#139270)

* Fix race in async_get_integrations with multiple calls when an integration is not found

* Fix race in async_get_integrations with multiple calls when an integration is not found

* Fix race in async_get_integrations with multiple calls when an integration is not found

* tweaks

* tweaks

* tweaks

* restore lost comment

* tweak test

* comment cache

* improve test

* improve comment

* Bump python-overseerr to 0.7.1 (#139263)

Co-authored-by: Shay Levy <levyshay1@gmail.com>

* Add coordinator to SMHI (#139052)

* Add coordinator to SMHI

* Remove not needed logging

* docstrings

* Make Radarr unit translation lowercase (#139261)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Add common state translation string for charging and discharging (#139074)

add common state translation string for charging and discharging

* Add test fixture ignore_translations_for_mock_domains (#139235)

* Add test fixture ignore_translations_for_mock_domains

* Fix fixture

* Avoid unnecessary attempt to get integration

* Really fix fixture

* Add forgotten parameter

* Address review comment

* Fix grammar in loader comments (#139276)

https://github.com/home-assistant/core/pull/139270#discussion_r1970315129

* Bump aiohomeconnect to 0.15.0 (#139277)

* Add current cavity temperature sensor to Home Connect (#139282)

* Bump anthropic to 0.47.2 (#139283)

* Adjust recorder validate_statistics handler (#139229)

* Fix re-connect logic in Apple TV integration (#139289)

* Revert "Bump stookwijzer==1.5.8" (#139287)

* Add option to ESPHome to subscribe to logs (#139073)

* Remove not used constants in smhi (#139298)

* Bump `aioshelly` to version `13.0.0` (#139294)

* Bump aioshelly to version 13.0.0

* MODEL_BLU_GATEWAY_GEN3 -> MODEL_BLU_GATEWAY_G3

* Remove timeout from vscode test launch configuration (#139288)

* Add missing Home Connect context at event listener registration for appliance options (#139292)

* Add missing context at event listener registration for appliance options

* Add tests

* Sort common translation strings (#139300)

sort common strings

* Add album artist media browser category to Squeezebox (#139210)

* Bump aioesphomeapi to 29.2.0 (#139309)

* Bump actions/download-artifact from 4.1.8 to 4.1.9 (#139317)

Bumps [actions/download-artifact](https://github.com/actions/download-artifact) from 4.1.8 to 4.1.9.
- [Release notes](https://github.com/actions/download-artifact/releases)
- [Commits](https://github.com/actions/download-artifact/compare/v4.1.8...v4.1.9)

---
updated-dependencies:
- dependency-name: actions/download-artifact
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Bump home-assistant/builder from 2024.08.2 to 2025.02.0 (#139316)

Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>

* Adjust remote ESPHome log subscription level on logging change (#139308)

* Fix homeassistant/expose_entity/list (#138872)

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>

* Bump `accuweather` to version `4.1.0` (#139320)

* Bump ZHA to 0.0.50 (#139318)

* Bump pytechnove to 2.0.0 (#139314)

* Update python-smarttub dependency to 0.0.39 (#139313)

* Fix anthropic blocking call (#139299)

* Bump pybotvac to 0.0.26 (#139330)

* Bump stookwijzer==1.6.0 (#139332)

* Improve error message when failing to create backups (#139262)

* Improve error message when failing to create backups

* Check for expected error message in tests

* Add translations and icon for Twinkly select entity (#139336)

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Bump recommended ESPHome Bluetooth proxy version to 2025.2.1 (#139196)

* Add default_db_url flag to WS command recorder/info (#139333)

* Improve action descriptions of LIFX integration (#139329)

Improve action description of lifx integration

- fix sentence-casing on two action names
- change "Kelvin" unit name to proper uppercase
- reference 'Theme' and 'Palette' fields by their friendly names for matching translations
- change paint_theme action description to match HA style

* Bump Music Assistant client to 1.1.1 (#139331)

* Refactor SmartThings (#137940)

* Add keys initiate_flow and entry_type to data entry translations (#138882)

* Add support for swing horizontal mode for mqtt climate (#139303)

* Add support for swing horizontal mode for mqtt climate

* Fix import

* Add entity translations to SmartThings (#139342)

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* Refactor SmartThings

* fix

* fix

* Add AC tests

* Add thermostat tests

* Add cover tests

* Add device tests

* Add light tests

* Add rest of the tests

* Add oauth

* Add oauth tests

* Add oauth tests

* Add oauth tests

* Add oauth tests

* Bump version

* Add rest of the tests

* Finalize

* Finalize

* Finalize

* Finalize

* Finalize

* Finalize

* Finalize

* Finalize

* Finalize

* Finalize

* Iterate over entities instead

* use set

* use const

* uncomment

* fix handler

* Fix device info

* Fix device info

* Fix lib

* Fix lib

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Fix

* Add fake fan

* Fix

* Add entity translations to SmartThings

* Fix

* Improve logging for selected options in Onkyo (#139279)

Different error for not selected option

* Change no fixtures comment in SmartThings (#139344)

* Set options for carbon monoxide detector sensor in SmartThings (#139346)

* Improve calculating supported features in template light (#139339)

* Update frontend to 20250226.0 (#139340)

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

* Use particulate matter device class in SmartThings (#139351)

Use particule matter device class in SmartThings

* Set options for dishwasher job state sensor in SmartThings (#139349)

* Set options for dishwasher machine state sensor in SmartThings (#139347)

* Set options for dishwasher machine state sensor in SmartThings

* Fix

* Set options for alarm sensor in SmartThings (#139345)

* Set options for alarm sensor in SmartThings

* Set options for alarm sensor in SmartThings

* Fix

* Fix variable scopes in scripts (#138883)

Co-authored-by: Erik <erik@montnemery.com>

* Add translatable states to SmartThings media source input (#139353)

Add translatable states to media source input

* Add translatable states to SmartThings media playback (#139354)

Add translatable states to media playback

* Add translatable states to oven mode in SmartThings (#139356)

* Add translatable states to oven job state in SmartThings (#139361)

* Add translatable states to oven machine state (#139358)

* Add translatable states to robot cleaner movement in SmartThings (#139363)

* Add translatable states to robot cleaner cleaning mode in SmartThings (#139362)

* Add translatable states to robot cleaner cleaning mode in SmartThings

* Update homeassistant/components/smartthings/strings.json

* Update homeassistant/components/smartthings/strings.json

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>

* Add translatable states to washer machine state in SmartThings (#139366)

* Add translatable states to smoke detector in SmartThings (#139365)

* Add translatable states to robot cleaner turbo mode in SmartThings (#139364)

* Add translatable states to washer job state in SmartThings (#139368)

* Add translatable states to washer job state in SmartThings

* fix

* Update homeassistant/components/smartthings/sensor.py

* Improve Home Connect oven cavity temperature sensor (#139355)

* Improve oven cavity temperature translation

* Fetch cavity temperature unit

* Handle generic Home Connect error

* Improve test clarity

* Add translatable states to dryer machine state in Smartthings (#139369)

* Add translatable states to dryer job state in SmartThings (#139370)

* Add translatable states to washer job state in SmartThings

* Add translatable states to dryer job state in Smartthings

* fix

* fix

* Don't create entities for disabled capabilities in SmartThings (#139343)

* Don't create entities for disabled capabilities in SmartThings

* Fix

* fix

* fix

* Fix typo in SmartThing string (#139373)

* Bump version to 2025.3.0b0

* Bump stookwijzer==1.6.1 (#139380)

* Bump ZHA to 0.0.51 (#139383)

* Bump ZHA to 0.0.51

* Fix unit tests not accounting for primary entities

* Bump intents to 2025.2.26 (#139387)

* Fix fetch options error for Home connect (#139392)

* Handle errors when obtaining options definitions

* Don't fetch program options if the program key is unknown

* Test to ensure that available program endpoint is not called on unknown program

* Bump onedrive to 0.0.12 (#139410)

* Bump onedrive to 0.0.12

* Add alternative name

* Bump pysmartthings to 2.0.0 (#139418)

* Bump pysmartthings to 2.0.0

* Fix

* Fix

* Fix

* Fix

* Bump habluetooth to 3.24.1 (#139420)

* Fix conversation agent fallback (#139421)

* Add diagnostics to SmartThings (#139423)

* Bump bleak-esphome to 2.8.0 (#139426)

* Bump reolink-aio to 0.12.1 (#139427)

* Fix Music Assistant media player entity features (#139428)

* Fix Music Assistant supported media player features

* Update supported features when player config changes

* Add tests

* Update frontend to 20250227.0 (#139437)

* Bump version to 2025.3.0b1

* Bump weatherflow4py to 1.3.1 (#135529)

* version bump of dep

* update requirements

* Add new mediatypes to Music Assistant integration (#139338)

* Bump Music Assistant client to 1.1.0

* Add some casts to help mypy

* Add handling of the new media types in Music Assistant

* mypy cleanup

* lint

* update snapshot

* Adjust tests

---------

Co-authored-by: Franck Nijhof <git@frenck.dev>

* Move climate intent to homeassistant integration (#139371)

* Move climate intent to homeassistant integration

* Move get temperature intent to intent integration

* Clean up old test

* Bump aiohomeconnect to 0.15.1 (#139445)

* Fix SmartThings diagnostics (#139447)

* Bump pysmartthings to 2.0.1 (#139454)

* Change webdav namespace to absolut URI (#139456)

* Change webdav namespace to absolut URI

* Add const file

* Improve onedrive migration (#139458)

* Bump pysmartthings to 2.1.0 (#139460)

* Only lowercase SmartThings media input source if we have it (#139468)

* Set SmartThings suggested display precision (#139470)

* Fix Gemini Schema validation for #139416 (#139478)

Fixed Schema validation for issue #139477

* Fail recorder.backup.async_pre_backup if Home Assistant is not running (#139491)

Fail recorder.backup.async_pre_backup if hass is not running

* Fix shift state in Teslemetry (#139505)

* Fix shift state

* Different fix

* Improve error handling in CoreBackupReaderWriter (#139508)

* Add diagnostics to onedrive (#139516)

* Add diagnostics to onedrive

* redact PII

* add raw data

* Make the Tuya backend library compatible with the newer paho mqtt client. (#139518)

* Make the Tuya backend library compatible with the newer paho mqtt client.

* Improve classnames and docstrings

* Suppress unsupported event 'EVT_USP_RpsPowerDeniedByPsuOverload' by bumping aiounifi to v83 (#139519)

Bump aiounifi to v83

* Don't split wheels builder anymore (#139522)

* Bump yt-dlp to 2025.02.19 (#139526)

* Update frontend to 20250228.0 (#139531)

* Bump version to 2025.3.0b2

* Add missing 'state_class' attribute for Growatt plant sensors (#132145)

* Add missing 'state_class' attribute for Growatt plant sensors

* Update total.py

* Update total.py 'TOTAL_INCREASING'

* Update total.py "maximum_output" -> 'TOTAL_INCREASING'

* Update homeassistant/components/growatt_server/sensor/total.py

---------

Co-authored-by: Franck Nijhof <frenck@frenck.nl>

* Bump env_canada to 0.8.0 (#138237)

* Bump env_canada to 0.8.0

* Fix requirements*.txt

* Grepped more

---------

Co-authored-by: Franck Nijhof <git@frenck.dev>

* Fix Nederlandse Spoorwegen to ignore trains in the past (#138331)

* Update NS integration to show first next train instead of just the first.

* Handle no first or next trip.

* Remove debug statement.

* Remove seconds and revert back to minutes.

* Make use of dt_util.now().

* Fix issue with next train if no first train.

* Use multiple indexed group-by queries to get start time states for MySQL (#138786)

* tweaks

* mysql

* mysql

* Update homeassistant/components/recorder/history/modern.py

* Update homeassistant/components/recorder/history/modern.py

* Update homeassistant/components/recorder/const.py

* Update homeassistant/components/recorder/statistics.py

* Apply suggestions from code review

* mysql

* mysql

* cover

* make sure db is fully init on old schema

* fixes

* fixes

* coverage

* coverage

* coverage

* s/slow_dependant_subquery/slow_dependent_subquery/g

* reword

* comment that callers are responsible for staying under the limit

* comment that callers are responsible for staying under the limit

* switch to kwargs

* reduce branching complexity

* split stats query

* preen

* split tests

* split tests

* Specify recorder as after dependency in sql integration (#139037)

* Specify recorder as after dependency in sql integration

* Remove hassfest exception

---------

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

* Handle IPv6 URLs in devolo Home Network (#139191)

* Handle IPv6 URLs in devolo Home Network

* Use yarl

* Fix bug in derivative sensor when source sensor's state is constant (#139230)

Previously, when the source sensor's state remains constant, the derivative
sensor repeats its latest value indefinitely.

This patch fixes this bug by consuming the state_reported event and updating
the sensor's output even when the source sensor doesn't change its state.

* Ensure Hue bridge is added first to the device registry (#139438)

* Fix update data for multiple Gree devices (#139469)

fix sync date for multiple devices

do not use handler for explicit update devices as internal communication lib do not provide which device is updated
use ha update loop

copy data object to prevent rewrite data from internal lib

allow more time to process response before log warning about long wait for response and make log message more clear

* Use last event as color mode in SmartThings (#139473)

* Use last event as color mode in SmartThings

* Use last event as color mode in SmartThings

* Fix

* Set SmartThings delta energy to Total (#139474)

* Fix alert not respecting can_acknowledge setting (#139483)

* fix(alert): check can_ack prior to acking

* fix(alert): add test for when can_acknowledge=False

* fix(alert): warn on can_ack blocking an ack

* Raise error when trying to acknowledge alert with can_acknowledge set to False

* Rewrite can_ack check as guard

Co-authored-by: Franck Nijhof <frenck@frenck.nl>

* Make can_ack service error msg human readable because it will show up in the UI

* format with ruff

* Make pytest aware of service error when acking an unackable alert

---------

Co-authored-by: Franck Nijhof <frenck@frenck.nl>

* Bump pysmartthings to 2.2.0 (#139539)

* Remove orphan devices on startup in SmartThings (#139541)

* Bump PySwitchBot to 0.56.1 (#139544)

changelog: https://github.com/sblibs/pySwitchbot/compare/0.56.0...0.56.1

* Bump pysmartthings to 2.3.0 (#139546)

* Improve SmartThings OCF device info (#139547)

* Add SmartThings Viper device info (#139548)

* Revert polling changes to HomeKit Controller (#139550)

This reverts #116200

We changed the polling logic to avoid polling if all chars are marked as watchable
to avoid crashing the firmware on a very limited set of devices as it was
more in line with what iOS does. In the end, the user ended up replacing
the device in #116143 because it turned out to be unreliable in other
ways. The vendor has since issued a firmware update that may resolve
the problem with all of these devices.

In practice it turns out many more devices
report that chars are evented and never send events. After a few months
of data and reports the trade-off does not seem worth it since
users are having to set up manual polling on a wide range of
devices. The amount of devices with evented chars that do not
actually send state vastly exceeds the number of devices that
might crash if they are polled too often so restore the previous
behavior

fixes #138561
fixes #100331
fixes #124529
fixes #123456
fixes #130763
fixes #124099
fixes #124916
fixes #135434
fixes #125273
fixes #124099
fixes #119617

* Bump pysmartthings to 2.4.0 (#139564)

* Bump Tesla Fleet API to v0.9.12 (#139565)

* bump

* Update manifest.json

* Fix versions

* remove tesla_bluetooth

* Remove mistake

* Bump aiowebdav2 to 0.3.1 (#139567)

* Validate scopes in SmartThings config flow (#139569)

* Only determine SmartThings swing modes if we support it (#139571)

Only determine swing modes if we support it

* Don't require not needed scopes in SmartThings (#139576)

* Don't require not needed scopes

* Don't require not needed scopes

* Homee: fix watchdog icon (#139577)

fix watchdog icon

* Bump aiohomekit to 3.2.8 (#139579)

changelog: https://github.com/Jc2k/aiohomekit/compare/3.2.7...3.2.8

* Fix duplicate unique id issue in Sensibo (#139582)

* Fix duplicate unique id issue in Sensibo

* Fixes

* Mods

* Improve field descriptions of `zha.permit` action (#139584)

Make the field descriptions of `source_ieee` and `install_code` UI-friendly by cross-referencing them using their friendly names to allow matching translations.

Better explain the alternative of using the `qr_code` field by adding that this contains both the IEEE address and the Install code of the joining device.

* Fix - Allow brightness only light MQTT json light to be set up using the `brightness` flag or via  `supported_color_modes` (#139585)

* Fix - Allow brightness only light MQTT json light to be set up using the `brightness` flag or via  `supported_color_modes`

* Improve comment

* Fix Manufacturer naming for Squeezelite model name for Squeezebox (#139586)

Squeezelite Manufacturer Fix

* Bump deebot-client to 12.3.1 (#139598)

* Fix handling of NaN float values for current humidity in ESPHome (#139600)

fixes #131837

* Bump aioshelly to 13.1.0 (#139601)

Co-authored-by: Franck Nijhof <git@frenck.dev>

* Bump inkbird-ble to 0.7.1 (#139603)

changelog: https://github.com/Bluetooth-Devices/inkbird-ble/compare/v0.7.0...v0.7.1

* Fix body text of imap message not available in custom event data template (#139609)

* Fix arm vacation mode showing as armed away in elkm1 (#139613)

Add native arm vacation mode support to elkm1

Vacation mode is currently implemented as a custom
service which will be deprecated in a future PR.

Note that the custom service was added long before
HA had a native vacation mode which was added
in #45980

* Still request scopes in SmartThings (#139626)

Still request scopes

* Bump pysmartthings to 2.4.1 (#139627)

* Bump version to 2025.3.0b3

* Fix unique identifiers where multiple IKEA Tradfri gateways are in use (#136060)

* Create unique identifiers where multiple gateways are in use

Resolving issue https://github.com/home-assistant/core/issues/134497

* Added migration function to __init__.py

Added migration function to execute upon initialisation, to:
a) remove the erroneously-added config)_entry added to the device (gateway B gets added as a config_entry to a device associated to gateway A), and
b) swap out the non-unique identifiers for genuinely unique identifiers.

* Added tests to simulate migration from bad data scenario (i.e. explicitly executing migrate_entity_unique_ids() from __init__.py)

* Ammendments suggested in first review

* Changes after second review

* Rewrite of test_migrate_config_entry_and_identifiers after feedback

* Converted migrate function into major version, updated tests

* Finalised variable naming convention per feedback, added test to validate config entry migrated to v2

* Hopefully final changes for cosmetic / comment stucture

* Further code-coverage in test_migrate_config_entry_and_identifiers()

* Minor test corrections

* Added test for non-tradfri identifiers

* Fix vicare exception for specific ventilation device type (#138343)

* fix for exception for specific ventilation device type + tests

* fix for exception for specific ventilation device type + tests

* New Testset just for fan

* update test_sensor.ambr

* Prevent zero interval in Calendar get_events service (#139378)

* Prevent zero interval in Calendar get_events service

* Fix holiday calendar tests

* Remove redundant entity_id

* Use translation for exception

* Replace check with voluptuous validator

* Revert strings.xml

* Fix Homee brightness sensors reporting in percent (#139409)

* fix brigtness sensor having percent as unit.

* add test for percent-brightness-sensor

* remove valve position and update tests

* Removed test, because covered by Snapshots

* fix review comments

* move device calss to init.

* fix test

* fix review comments

* add battery sensor back to test fixture

* fix

* Fix ability to remove orphan device in Music Assistant integration (#139431)

* Fix ability to remove orphan device in Music Assistant integration

* Add test

* Remove orphaned device entries at startup as well

* adjust mocked client

* Fix broken link in ESPHome BLE repair (#139639)

ESPHome always uses .0 in the URL for the changelog,
and we never had a patch version in the stable
BLE version field so we need to switch it to
.0 for the URL.

* Fix scope comparison in SmartThings (#139652)

* Avoid duplicate chat log content (#139679)

* Add additional roborock debug logging (#139680)

* Improve failure handling and logging for invalid map responses (#139681)

* Abort SmartThings flow if default_config is not enabled (#139700)

* Abort SmartThings flow if default_config is not enabled

* Abort SmartThings flow if default_config is not enabled

* Abort SmartThings flow if default_config is not enabled

* Bump ESPHome stable BLE version to 2025.2.2 (#139704)

ensure proxies have https://github.com/esphome/esphome/pull/8328
so they do not reboot themselves if disconnecting takes
too long

* Bump holidays to 0.68 (#139711)

* Bump aiowebostv to 0.7.2 (#139712)

* Bump sense-energy to 0.13.6 (#139714)

changes: https://github.com/scottbonline/sense/releases/tag/0.13.6

* Add nest translation string for `already_in_progress` (#139727)

* Bump google-nest-sdm to 7.1.4 (#139728)

* Delete refresh after a non-breaking error at event stream at Home Connect (#139740)

* Delete refresh after non-breaking error

And improve how many time does it take to retry to open stream

* Update tests

* Bump version to 2025.3.0b4

* Bump aiohomeconnect to 0.16.2 (#139750)

* Add Apollo Automation virtual integration (#139751)

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

* Fix incorrect weather state returned by HKO (#139757)

* Fix incorrect weather state

* Clean up unused import

---------

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
Co-authored-by: Franck Nijhof <git@frenck.dev>

* Bump pysmartthings to 2.5.0 (#139758)

* Bump pysmartthings to 2.5.0

* Bump pysmartthings to 2.5.0

* Fix home connect available (#139760)

* Fix home connect available

* Extend and clarify test

* Do not change connected state on stream interrupted

* Bump nexia to 2.1.1 (#139772)

changelog: https://github.com/bdraco/nexia/compare/2.0.9...2.1.1

fixes #133368

* Bump version to 2025.3.0b5

* Bump aiowebostv to 0.7.3 (#139788)

* Drop BETA postfix from Matter integration's title (#139816)

Drop BETA postfix from Matter title

Now that the whole Matter stack of Home Assistant is officially certified, we can drop the beta flag.

* Split the energy and data retrieval in WeHeat (#139211)

* Split the energy and data logs

* Make sure that pump_info name is set to device name, bump weheat

* Adding config entry

* Fixed circular import

* parallelisation of awaits

* Update homeassistant/components/weheat/binary_sensor.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Fix undefined weheatdata

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Bump version to 2025.3.0b6

* Update frontend to 20250305.0 (#139829)

* Bump version to 2025.3.0b7

* Get temperature data appropriate for hass.config.unit in LG ThinQ (#137626)

* Get temperature data appropriate for hass.config.unit

* Modify temperature_unit for init

* Modify unit's map

* Fix ruff error

---------

Co-authored-by: yunseon.park <yunseon.park@lge.com>

* Bump nexia to 2.2.1 (#139786)

* Bump nexia to 2.2.0

changelog: https://github.com/bdraco/nexia/compare/2.1.1...2.2.0

* Apply suggestions from code review

* Revert "Add scene support to roborock (#137203)" (#139840)

This reverts commit 379bf10675.

* Bump aioecowitt to 2025.3.1 (#139841)

* Bump aioecowitt to 2025.3.1

* Bump aioecowitt to 2025.3.1

* Bump onedrive-personal-sdk to 0.0.13 (#139846)

* Bump intents to 2025.3.5 (#139851)

Co-authored-by: Franck Nijhof <git@frenck.dev>

* Bump version to 2025.3.0b8

* Bump version to 2025.3.0

* Fix no disabled capabilities in SmartThings (#139860)

Fix no disabled capabilities

---------

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: cdnninja <jaydenaphillips@gmail.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
Co-authored-by: Tristan <tristan.steele@gmail.com>
Co-authored-by: peteS-UK <64092177+peteS-UK@users.noreply.github.com>
Co-authored-by: Josef Zweck <josef@zweck.dev>
Co-authored-by: Manu <4445816+tr4nt0r@users.noreply.github.com>
Co-authored-by: J. Diego Rodríguez Royo <jdrr1998@hotmail.com>
Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
Co-authored-by: Noah Husby <32528627+noahhusby@users.noreply.github.com>
Co-authored-by: Dan Raper <me@danr.uk>
Co-authored-by: Jan-Philipp Benecke <jan-philipp@bnck.me>
Co-authored-by: Markus Adrario <Mozilla@adrario.de>
Co-authored-by: Andre Lengwenus <alengwenus@gmail.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: LG-ThinQ-Integration <LG-ThinQ-Integration@lge.com>
Co-authored-by: yunseon.park <yunseon.park@lge.com>
Co-authored-by: Maikel Punie <maikel.punie@gmail.com>
Co-authored-by: Dan Bishop <d@nbishop.uk>
Co-authored-by: elmurato <1382097+elmurato@users.noreply.github.com>
Co-authored-by: Robert Resch <robert@resch.dev>
Co-authored-by: Cameron Ring <cameron@cs.stanford.edu>
Co-authored-by: Shay Levy <levyshay1@gmail.com>
Co-authored-by: fwestenberg <47930023+fwestenberg@users.noreply.github.com>
Co-authored-by: Matrix <justin@yosmart.com>
Co-authored-by: Andrew <34544450+10100011@users.noreply.github.com>
Co-authored-by: Renier Moorcroft <66512715+RenierM26@users.noreply.github.com>
Co-authored-by: Peter Brøndum <34370407+brondum@users.noreply.github.com>
Co-authored-by: Galorhallen <12990764+Galorhallen@users.noreply.github.com>
Co-authored-by: tronikos <tronikos@users.noreply.github.com>
Co-authored-by: Paul Traina <pleasantone@users.noreply.github.com>
Co-authored-by: Artur Pragacz <49985303+arturpragacz@users.noreply.github.com>
Co-authored-by: Noah Groß <me@codesalat.dev>
Co-authored-by: G Johansson <goran.johansson@shiftit.se>
Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>
Co-authored-by: Denis Shulyaka <Shulyaka@gmail.com>
Co-authored-by: Pierre Ståhl <pierre.staahl@gmail.com>
Co-authored-by: Maciej Bieniek <bieniu@users.noreply.github.com>
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: TheJulianJES <TheJulianJES@users.noreply.github.com>
Co-authored-by: Christophe Gagnier <Moustachauve@users.noreply.github.com>
Co-authored-by: Matt Zimmerman <mdz@users.noreply.github.com>
Co-authored-by: Ben Bridts <ben.bridts@gmail.com>
Co-authored-by: Paul Bottein <paul.bottein@gmail.com>
Co-authored-by: Marcel van der Veldt <m.vanderveldt@outlook.com>
Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
Co-authored-by: puddly <32534428+puddly@users.noreply.github.com>
Co-authored-by: Michael Hansen <mike@rhasspy.org>
Co-authored-by: starkillerOG <starkiller.og@gmail.com>
Co-authored-by: Jeef <jeeftor@users.noreply.github.com>
Co-authored-by: Ivan Lopez Hernandez <ivan.lh.94@outlook.com>
Co-authored-by: Brett Adams <Bre77@users.noreply.github.com>
Co-authored-by: Robert Svensson <Kane610@users.noreply.github.com>
Co-authored-by: LaithBudairi <69572447+LaithBudairi@users.noreply.github.com>
Co-authored-by: M-A <maruel@gmail.com>
Co-authored-by: Martreides <8385298+Martreides@users.noreply.github.com>
Co-authored-by: Guido Schmitz <Shutgun@users.noreply.github.com>
Co-authored-by: Juan Grande <juan.grande@gmail.com>
Co-authored-by: Filip Agh <filip11agh@gmail.com>
Co-authored-by: StaleLoafOfBread <45444205+StaleLoafOfBread@users.noreply.github.com>
Co-authored-by: cs12ag <70966712+cs12ag@users.noreply.github.com>
Co-authored-by: Niklas Neesen <n.neesen@me.com>
Co-authored-by: Allen Porter <allen@thebends.org>
Co-authored-by: Anthony Hou <anthony.tr.hou@gmail.com>
Co-authored-by: SteveDiks <126147459+SteveDiks@users.noreply.github.com>
2025-03-05 20:00:41 +01:00
Joost Lekkerkerker
98e317dd55 Fix no disabled capabilities in SmartThings (#139860)
Fix no disabled capabilities
2025-03-05 17:42:31 +00:00
Franck Nijhof
ed088aa72f Bump version to 2025.3.0 2025-03-05 17:39:36 +00:00
Franck Nijhof
51162320cb Bump version to 2025.3.0b8 2025-03-05 17:25:33 +00:00
Michael Hansen
b88eab8ba3 Bump intents to 2025.3.5 (#139851)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-03-05 17:23:04 +00:00
Josef Zweck
6c080ee650 Bump onedrive-personal-sdk to 0.0.13 (#139846) 2025-03-05 17:22:17 +00:00
Joost Lekkerkerker
8056b0df2b Bump aioecowitt to 2025.3.1 (#139841)
* Bump aioecowitt to 2025.3.1

* Bump aioecowitt to 2025.3.1
2025-03-05 17:22:14 +00:00
Allen Porter
3f94b7a61c Revert "Add scene support to roborock (#137203)" (#139840)
This reverts commit 379bf10675.
2025-03-05 17:22:11 +00:00
J. Nick Koston
1484e46317 Bump nexia to 2.2.1 (#139786)
* Bump nexia to 2.2.0

changelog: https://github.com/bdraco/nexia/compare/2.1.1...2.2.0

* Apply suggestions from code review
2025-03-05 17:22:08 +00:00
LG-ThinQ-Integration
2812c8a993 Get temperature data appropriate for hass.config.unit in LG ThinQ (#137626)
* Get temperature data appropriate for hass.config.unit

* Modify temperature_unit for init

* Modify unit's map

* Fix ruff error

---------

Co-authored-by: yunseon.park <yunseon.park@lge.com>
2025-03-05 17:22:04 +00:00
Franck Nijhof
5043e2ad10 Bump version to 2025.3.0b7 2025-03-05 11:01:06 +00:00
Bram Kragten
2c2fd76270 Update frontend to 20250305.0 (#139829) 2025-03-05 11:00:56 +00:00
Franck Nijhof
7001f8daaf Bump version to 2025.3.0b6 2025-03-05 09:39:26 +00:00
SteveDiks
b41fc932c5 Split the energy and data retrieval in WeHeat (#139211)
* Split the energy and data logs

* Make sure that pump_info name is set to device name, bump weheat

* Adding config entry

* Fixed circular import

* parallelisation of awaits

* Update homeassistant/components/weheat/binary_sensor.py

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>

* Fix undefined weheatdata

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-03-05 09:39:13 +00:00
Marcel van der Veldt
0872243297 Drop BETA postfix from Matter integration's title (#139816)
Drop BETA postfix from Matter title

Now that the whole Matter stack of Home Assistant is officially certified, we can drop the beta flag.
2025-03-05 08:44:44 +00:00
Shay Levy
bba889975a Bump aiowebostv to 0.7.3 (#139788) 2025-03-05 08:44:39 +00:00
Franck Nijhof
01e8ca6495 Bump version to 2025.3.0b5 2025-03-04 20:25:14 +00:00
J. Nick Koston
7d82375f81 Bump nexia to 2.1.1 (#139772)
changelog: https://github.com/bdraco/nexia/compare/2.0.9...2.1.1

fixes #133368
2025-03-04 20:24:56 +00:00
Martin Hjelmare
47033e587b Fix home connect available (#139760)
* Fix home connect available

* Extend and clarify test

* Do not change connected state on stream interrupted
2025-03-04 20:24:47 +00:00
Joost Lekkerkerker
e73b08b269 Bump pysmartthings to 2.5.0 (#139758)
* Bump pysmartthings to 2.5.0

* Bump pysmartthings to 2.5.0
2025-03-04 20:23:45 +00:00
Anthony Hou
a195a9107b Fix incorrect weather state returned by HKO (#139757)
* Fix incorrect weather state

* Clean up unused import

---------

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-03-04 20:12:25 +00:00
Joost Lekkerkerker
185949cc18 Add Apollo Automation virtual integration (#139751)
Co-authored-by: Robert Resch <robert@resch.dev>
2025-03-04 20:12:22 +00:00
J. Diego Rodríguez Royo
c129f27c95 Bump aiohomeconnect to 0.16.2 (#139750) 2025-03-04 20:12:16 +00:00
Franck Nijhof
6a5a66e2f9 Bump version to 2025.3.0b4 2025-03-04 10:46:11 +00:00
J. Diego Rodríguez Royo
db63d9fcbf Delete refresh after a non-breaking error at event stream at Home Connect (#139740)
* Delete refresh after non-breaking error

And improve how many time does it take to retry to open stream

* Update tests
2025-03-04 10:45:10 +00:00
Allen Porter
5b3d798eca Bump google-nest-sdm to 7.1.4 (#139728) 2025-03-04 10:45:06 +00:00
Allen Porter
a0dde2a7d6 Add nest translation string for already_in_progress (#139727) 2025-03-04 10:45:00 +00:00
J. Nick Koston
1bdc33d52d Bump sense-energy to 0.13.6 (#139714)
changes: https://github.com/scottbonline/sense/releases/tag/0.13.6
2025-03-04 10:44:57 +00:00
Shay Levy
f1d332da5a Bump aiowebostv to 0.7.2 (#139712) 2025-03-04 10:44:51 +00:00
G Johansson
304c13261a Bump holidays to 0.68 (#139711) 2025-03-04 10:44:48 +00:00
J. Nick Koston
c58cbfd6f4 Bump ESPHome stable BLE version to 2025.2.2 (#139704)
ensure proxies have https://github.com/esphome/esphome/pull/8328
so they do not reboot themselves if disconnecting takes
too long
2025-03-04 10:44:44 +00:00
Joost Lekkerkerker
b890d3e15a Abort SmartThings flow if default_config is not enabled (#139700)
* Abort SmartThings flow if default_config is not enabled

* Abort SmartThings flow if default_config is not enabled

* Abort SmartThings flow if default_config is not enabled
2025-03-04 10:44:41 +00:00
Allen Porter
2c9b8b6835 Improve failure handling and logging for invalid map responses (#139681) 2025-03-04 10:44:37 +00:00
Allen Porter
73cc1f51ca Add additional roborock debug logging (#139680) 2025-03-04 10:44:33 +00:00
Paulus Schoutsen
dca77e8232 Avoid duplicate chat log content (#139679) 2025-03-04 10:44:30 +00:00
Joost Lekkerkerker
03cb177e7c Fix scope comparison in SmartThings (#139652) 2025-03-04 10:44:26 +00:00
J. Nick Koston
ad04b53615 Fix broken link in ESPHome BLE repair (#139639)
ESPHome always uses .0 in the URL for the changelog,
and we never had a patch version in the stable
BLE version field so we need to switch it to
.0 for the URL.
2025-03-04 10:44:23 +00:00
Marcel van der Veldt
46bcb307f6 Fix ability to remove orphan device in Music Assistant integration (#139431)
* Fix ability to remove orphan device in Music Assistant integration

* Add test

* Remove orphaned device entries at startup as well

* adjust mocked client
2025-03-04 10:44:19 +00:00
Markus Adrario
b816625028 Fix Homee brightness sensors reporting in percent (#139409)
* fix brigtness sensor having percent as unit.

* add test for percent-brightness-sensor

* remove valve position and update tests

* Removed test, because covered by Snapshots

* fix review comments

* move device calss to init.

* fix test

* fix review comments

* add battery sensor back to test fixture

* fix
2025-03-04 10:44:15 +00:00
Abílio Costa
0940fc7806 Prevent zero interval in Calendar get_events service (#139378)
* Prevent zero interval in Calendar get_events service

* Fix holiday calendar tests

* Remove redundant entity_id

* Use translation for exception

* Replace check with voluptuous validator

* Revert strings.xml
2025-03-04 10:44:12 +00:00
Niklas Neesen
50aefc3653 Fix vicare exception for specific ventilation device type (#138343)
* fix for exception for specific ventilation device type + tests

* fix for exception for specific ventilation device type + tests

* New Testset just for fan

* update test_sensor.ambr
2025-03-04 10:44:09 +00:00
cs12ag
c0dc83cbc0 Fix unique identifiers where multiple IKEA Tradfri gateways are in use (#136060)
* Create unique identifiers where multiple gateways are in use

Resolving issue https://github.com/home-assistant/core/issues/134497

* Added migration function to __init__.py

Added migration function to execute upon initialisation, to:
a) remove the erroneously-added config)_entry added to the device (gateway B gets added as a config_entry to a device associated to gateway A), and
b) swap out the non-unique identifiers for genuinely unique identifiers.

* Added tests to simulate migration from bad data scenario (i.e. explicitly executing migrate_entity_unique_ids() from __init__.py)

* Ammendments suggested in first review

* Changes after second review

* Rewrite of test_migrate_config_entry_and_identifiers after feedback

* Converted migrate function into major version, updated tests

* Finalised variable naming convention per feedback, added test to validate config entry migrated to v2

* Hopefully final changes for cosmetic / comment stucture

* Further code-coverage in test_migrate_config_entry_and_identifiers()

* Minor test corrections

* Added test for non-tradfri identifiers
2025-03-04 10:44:01 +00:00
Bram Kragten
8382663be4 Bump version to 2025.3.0b3 2025-03-02 16:15:38 +01:00
Joost Lekkerkerker
7e1309d874 Bump pysmartthings to 2.4.1 (#139627) 2025-03-02 16:15:16 +01:00
Joost Lekkerkerker
1d0cba1a43 Still request scopes in SmartThings (#139626)
Still request scopes
2025-03-02 16:15:15 +01:00
J. Nick Koston
7d9a6ceb6b Fix arm vacation mode showing as armed away in elkm1 (#139613)
Add native arm vacation mode support to elkm1

Vacation mode is currently implemented as a custom
service which will be deprecated in a future PR.

Note that the custom service was added long before
HA had a native vacation mode which was added
in #45980
2025-03-02 16:15:14 +01:00
Jan Bouwhuis
6abdb28a03 Fix body text of imap message not available in custom event data template (#139609) 2025-03-02 16:15:13 +01:00
J. Nick Koston
3690e03951 Bump inkbird-ble to 0.7.1 (#139603)
changelog: https://github.com/Bluetooth-Devices/inkbird-ble/compare/v0.7.0...v0.7.1
2025-03-02 16:15:13 +01:00
Shay Levy
4fe4d14f16 Bump aioshelly to 13.1.0 (#139601)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-03-02 16:15:12 +01:00
J. Nick Koston
74e8ffa555 Fix handling of NaN float values for current humidity in ESPHome (#139600)
fixes #131837
2025-03-02 16:15:11 +01:00
Robert Resch
c257b228f1 Bump deebot-client to 12.3.1 (#139598) 2025-03-02 16:15:10 +01:00
peteS-UK
6ff0f67d03 Fix Manufacturer naming for Squeezelite model name for Squeezebox (#139586)
Squeezelite Manufacturer Fix
2025-03-02 16:15:10 +01:00
Jan Bouwhuis
8fdff9ca37 Fix - Allow brightness only light MQTT json light to be set up using the brightness flag or via supported_color_modes (#139585)
* Fix - Allow brightness only light MQTT json light to be set up using the `brightness` flag or via  `supported_color_modes`

* Improve comment
2025-03-02 16:15:09 +01:00
Norbert Rittel
9055dff9bd Improve field descriptions of zha.permit action (#139584)
Make the field descriptions of `source_ieee` and `install_code` UI-friendly by cross-referencing them using their friendly names to allow matching translations.

Better explain the alternative of using the `qr_code` field by adding that this contains both the IEEE address and the Install code of the joining device.
2025-03-02 16:15:08 +01:00
G Johansson
e766d681b5 Fix duplicate unique id issue in Sensibo (#139582)
* Fix duplicate unique id issue in Sensibo

* Fixes

* Mods
2025-03-02 16:15:07 +01:00
J. Nick Koston
511e57d0b3 Bump aiohomekit to 3.2.8 (#139579)
changelog: https://github.com/Jc2k/aiohomekit/compare/3.2.7...3.2.8
2025-03-02 16:15:04 +01:00
Markus Adrario
74be49d00d Homee: fix watchdog icon (#139577)
fix watchdog icon
2025-03-02 16:15:04 +01:00
Joost Lekkerkerker
684c3aac6b Don't require not needed scopes in SmartThings (#139576)
* Don't require not needed scopes

* Don't require not needed scopes
2025-03-02 16:15:03 +01:00
Joost Lekkerkerker
a718b6ebff Only determine SmartThings swing modes if we support it (#139571)
Only determine swing modes if we support it
2025-03-02 16:15:02 +01:00
Joost Lekkerkerker
f17274d417 Validate scopes in SmartThings config flow (#139569) 2025-03-02 16:15:01 +01:00
Jan-Philipp Benecke
1530139a61 Bump aiowebdav2 to 0.3.1 (#139567) 2025-03-02 16:15:01 +01:00
Brett Adams
f56d65b2ec Bump Tesla Fleet API to v0.9.12 (#139565)
* bump

* Update manifest.json

* Fix versions

* remove tesla_bluetooth

* Remove mistake
2025-03-02 16:15:00 +01:00
Joost Lekkerkerker
21277a81d3 Bump pysmartthings to 2.4.0 (#139564) 2025-03-02 16:14:59 +01:00
J. Nick Koston
e1ce5b8c69 Revert polling changes to HomeKit Controller (#139550)
This reverts #116200

We changed the polling logic to avoid polling if all chars are marked as watchable
to avoid crashing the firmware on a very limited set of devices as it was
more in line with what iOS does. In the end, the user ended up replacing
the device in #116143 because it turned out to be unreliable in other
ways. The vendor has since issued a firmware update that may resolve
the problem with all of these devices.

In practice it turns out many more devices
report that chars are evented and never send events. After a few months
of data and reports the trade-off does not seem worth it since
users are having to set up manual polling on a wide range of
devices. The amount of devices with evented chars that do not
actually send state vastly exceeds the number of devices that
might crash if they are polled too often so restore the previous
behavior

fixes #138561
fixes #100331
fixes #124529
fixes #123456
fixes #130763
fixes #124099
fixes #124916
fixes #135434
fixes #125273
fixes #124099
fixes #119617
2025-03-02 16:14:59 +01:00
Joost Lekkerkerker
0323a9c4e6 Add SmartThings Viper device info (#139548) 2025-03-02 16:14:58 +01:00
Joost Lekkerkerker
c7d89398a0 Improve SmartThings OCF device info (#139547) 2025-03-02 16:14:57 +01:00
Joost Lekkerkerker
8cc587d3a7 Bump pysmartthings to 2.3.0 (#139546) 2025-03-02 16:14:56 +01:00
J. Nick Koston
5ad156767a Bump PySwitchBot to 0.56.1 (#139544)
changelog: https://github.com/sblibs/pySwitchbot/compare/0.56.0...0.56.1
2025-03-02 16:14:56 +01:00
Joost Lekkerkerker
f54b3f4de2 Remove orphan devices on startup in SmartThings (#139541) 2025-03-02 16:14:55 +01:00
Joost Lekkerkerker
6f0c62dc9d Bump pysmartthings to 2.2.0 (#139539) 2025-03-02 16:14:54 +01:00
StaleLoafOfBread
dce8bca103 Fix alert not respecting can_acknowledge setting (#139483)
* fix(alert): check can_ack prior to acking

* fix(alert): add test for when can_acknowledge=False

* fix(alert): warn on can_ack blocking an ack

* Raise error when trying to acknowledge alert with can_acknowledge set to False

* Rewrite can_ack check as guard

Co-authored-by: Franck Nijhof <frenck@frenck.nl>

* Make can_ack service error msg human readable because it will show up in the UI

* format with ruff

* Make pytest aware of service error when acking an unackable alert

---------

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2025-03-02 16:14:53 +01:00
Joost Lekkerkerker
22af8af132 Set SmartThings delta energy to Total (#139474) 2025-03-02 16:14:52 +01:00
Joost Lekkerkerker
8a62b882bf Use last event as color mode in SmartThings (#139473)
* Use last event as color mode in SmartThings

* Use last event as color mode in SmartThings

* Fix
2025-03-02 16:14:52 +01:00
Filip Agh
708f22fe6f Fix update data for multiple Gree devices (#139469)
fix sync date for multiple devices

do not use handler for explicit update devices as internal communication lib do not provide which device is updated
use ha update loop

copy data object to prevent rewrite data from internal lib

allow more time to process response before log warning about long wait for response and make log message more clear
2025-03-02 16:14:51 +01:00
Marcel van der Veldt
a4e71e2055 Ensure Hue bridge is added first to the device registry (#139438) 2025-03-02 16:14:50 +01:00
Juan Grande
61a3cc37e0 Fix bug in derivative sensor when source sensor's state is constant (#139230)
Previously, when the source sensor's state remains constant, the derivative
sensor repeats its latest value indefinitely.

This patch fixes this bug by consuming the state_reported event and updating
the sensor's output even when the source sensor doesn't change its state.
2025-03-02 16:11:06 +01:00
Guido Schmitz
a0668e5a5b Handle IPv6 URLs in devolo Home Network (#139191)
* Handle IPv6 URLs in devolo Home Network

* Use yarl
2025-03-02 16:11:06 +01:00
G Johansson
b4b7142b55 Specify recorder as after dependency in sql integration (#139037)
* Specify recorder as after dependency in sql integration

* Remove hassfest exception

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
2025-03-02 16:11:05 +01:00
J. Nick Koston
108b71d33c Use multiple indexed group-by queries to get start time states for MySQL (#138786)
* tweaks

* mysql

* mysql

* Update homeassistant/components/recorder/history/modern.py

* Update homeassistant/components/recorder/history/modern.py

* Update homeassistant/components/recorder/const.py

* Update homeassistant/components/recorder/statistics.py

* Apply suggestions from code review

* mysql

* mysql

* cover

* make sure db is fully init on old schema

* fixes

* fixes

* coverage

* coverage

* coverage

* s/slow_dependant_subquery/slow_dependent_subquery/g

* reword

* comment that callers are responsible for staying under the limit

* comment that callers are responsible for staying under the limit

* switch to kwargs

* reduce branching complexity

* split stats query

* preen

* split tests

* split tests
2025-03-02 16:11:04 +01:00
Martreides
2636a47333 Fix Nederlandse Spoorwegen to ignore trains in the past (#138331)
* Update NS integration to show first next train instead of just the first.

* Handle no first or next trip.

* Remove debug statement.

* Remove seconds and revert back to minutes.

* Make use of dt_util.now().

* Fix issue with next train if no first train.
2025-03-02 16:11:03 +01:00
M-A
17116fcd6c Bump env_canada to 0.8.0 (#138237)
* Bump env_canada to 0.8.0

* Fix requirements*.txt

* Grepped more

---------

Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-03-02 16:11:03 +01:00
LaithBudairi
17c16144d1 Add missing 'state_class' attribute for Growatt plant sensors (#132145)
* Add missing 'state_class' attribute for Growatt plant sensors

* Update total.py

* Update total.py 'TOTAL_INCREASING'

* Update total.py "maximum_output" -> 'TOTAL_INCREASING'

* Update homeassistant/components/growatt_server/sensor/total.py

---------

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2025-03-02 16:11:02 +01:00
Bram Kragten
178d509d56 Bump version to 2025.3.0b2 2025-02-28 17:06:59 +01:00
Bram Kragten
09c129de40 Update frontend to 20250228.0 (#139531) 2025-02-28 17:06:51 +01:00
Joost Lekkerkerker
07128ba063 Bump yt-dlp to 2025.02.19 (#139526) 2025-02-28 17:06:50 +01:00
Robert Resch
a786ff53ff Don't split wheels builder anymore (#139522) 2025-02-28 17:06:50 +01:00
Robert Svensson
d2e19c829d Suppress unsupported event 'EVT_USP_RpsPowerDeniedByPsuOverload' by bumping aiounifi to v83 (#139519)
Bump aiounifi to v83
2025-02-28 17:06:49 +01:00
Jan Bouwhuis
94b342f26a Make the Tuya backend library compatible with the newer paho mqtt client. (#139518)
* Make the Tuya backend library compatible with the newer paho mqtt client.

* Improve classnames and docstrings
2025-02-28 17:06:48 +01:00
Josef Zweck
9e3e6b3f43 Add diagnostics to onedrive (#139516)
* Add diagnostics to onedrive

* redact PII

* add raw data
2025-02-28 17:06:47 +01:00
Erik Montnemery
4300900322 Improve error handling in CoreBackupReaderWriter (#139508) 2025-02-28 17:06:46 +01:00
Brett Adams
342e04974d Fix shift state in Teslemetry (#139505)
* Fix shift state

* Different fix
2025-02-28 17:06:46 +01:00
Erik Montnemery
fdb4c0a81f Fail recorder.backup.async_pre_backup if Home Assistant is not running (#139491)
Fail recorder.backup.async_pre_backup if hass is not running
2025-02-28 17:06:45 +01:00
Ivan Lopez Hernandez
6de878ffe4 Fix Gemini Schema validation for #139416 (#139478)
Fixed Schema validation for issue #139477
2025-02-28 17:06:44 +01:00
Joost Lekkerkerker
c63aaec09e Set SmartThings suggested display precision (#139470) 2025-02-28 17:06:43 +01:00
Joost Lekkerkerker
d8bf47c101 Only lowercase SmartThings media input source if we have it (#139468) 2025-02-28 17:06:42 +01:00
Joost Lekkerkerker
736ff8828d Bump pysmartthings to 2.1.0 (#139460) 2025-02-28 17:06:41 +01:00
Josef Zweck
b501999a4c Improve onedrive migration (#139458) 2025-02-28 17:06:40 +01:00
Jan-Philipp Benecke
3985f1c6c8 Change webdav namespace to absolut URI (#139456)
* Change webdav namespace to absolut URI

* Add const file
2025-02-28 17:06:39 +01:00
Joost Lekkerkerker
46ec3987a8 Bump pysmartthings to 2.0.1 (#139454) 2025-02-28 17:06:39 +01:00
Joost Lekkerkerker
df4e5a54e3 Fix SmartThings diagnostics (#139447) 2025-02-28 17:06:38 +01:00
J. Diego Rodríguez Royo
d8a259044f Bump aiohomeconnect to 0.15.1 (#139445) 2025-02-28 17:06:37 +01:00
Michael Hansen
0891669aee Move climate intent to homeassistant integration (#139371)
* Move climate intent to homeassistant integration

* Move get temperature intent to intent integration

* Clean up old test
2025-02-28 17:06:36 +01:00
Marcel van der Veldt
83c0351338 Add new mediatypes to Music Assistant integration (#139338)
* Bump Music Assistant client to 1.1.0

* Add some casts to help mypy

* Add handling of the new media types in Music Assistant

* mypy cleanup

* lint

* update snapshot

* Adjust tests

---------

Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-02-28 17:06:35 +01:00
Jeef
c5e5fe555d Bump weatherflow4py to 1.3.1 (#135529)
* version bump of dep

* update requirements
2025-02-28 17:06:34 +01:00
Bram Kragten
345ba73777 Bump version to 2025.3.0b1 2025-02-27 16:48:00 +01:00
Bram Kragten
e4200a79a2 Update frontend to 20250227.0 (#139437) 2025-02-27 16:47:52 +01:00
Marcel van der Veldt
381fa65ba0 Fix Music Assistant media player entity features (#139428)
* Fix Music Assistant supported media player features

* Update supported features when player config changes

* Add tests
2025-02-27 16:47:51 +01:00
starkillerOG
16314711b8 Bump reolink-aio to 0.12.1 (#139427) 2025-02-27 16:47:50 +01:00
J. Nick Koston
553abe4a4a Bump bleak-esphome to 2.8.0 (#139426) 2025-02-27 16:47:49 +01:00
Joost Lekkerkerker
6a1bbdb3a7 Add diagnostics to SmartThings (#139423) 2025-02-27 16:47:48 +01:00
Paulus Schoutsen
59d92c75bd Fix conversation agent fallback (#139421) 2025-02-27 16:47:47 +01:00
J. Nick Koston
7732e6878e Bump habluetooth to 3.24.1 (#139420) 2025-02-27 16:47:46 +01:00
Joost Lekkerkerker
2cde317d59 Bump pysmartthings to 2.0.0 (#139418)
* Bump pysmartthings to 2.0.0

* Fix

* Fix

* Fix

* Fix
2025-02-27 16:47:45 +01:00
Josef Zweck
0c08430507 Bump onedrive to 0.0.12 (#139410)
* Bump onedrive to 0.0.12

* Add alternative name
2025-02-27 16:47:45 +01:00
J. Diego Rodríguez Royo
fa6d7d5e3c Fix fetch options error for Home connect (#139392)
* Handle errors when obtaining options definitions

* Don't fetch program options if the program key is unknown

* Test to ensure that available program endpoint is not called on unknown program
2025-02-27 16:47:43 +01:00
Michael Hansen
585b950a46 Bump intents to 2025.2.26 (#139387) 2025-02-27 16:47:42 +01:00
puddly
3effc2e182 Bump ZHA to 0.0.51 (#139383)
* Bump ZHA to 0.0.51

* Fix unit tests not accounting for primary entities
2025-02-27 16:47:42 +01:00
fwestenberg
0e1602ff71 Bump stookwijzer==1.6.1 (#139380) 2025-02-27 16:47:41 +01:00
364 changed files with 24141 additions and 2370 deletions

View File

@@ -218,15 +218,7 @@ jobs:
sed -i "/uv/d" requirements.txt
sed -i "/uv/d" requirements_diff.txt
- name: Split requirements all
run: |
# We split requirements all into multiple files.
# This is to prevent the build from running out of memory when
# resolving packages on 32-bits systems (like armhf, armv7).
split -l $(expr $(expr $(cat requirements_all.txt | wc -l) + 1) / 3) requirements_all_wheels_${{ matrix.arch }}.txt requirements_all.txt
- name: Build wheels (part 1)
- name: Build wheels
uses: home-assistant/wheels@2024.11.0
with:
abi: ${{ matrix.abi }}
@@ -238,32 +230,4 @@ jobs:
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
requirements: "requirements_all.txtaa"
- name: Build wheels (part 2)
uses: home-assistant/wheels@2024.11.0
with:
abi: ${{ matrix.abi }}
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
env-file: true
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev;nasm;zlib-ng-dev"
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
requirements: "requirements_all.txtab"
- name: Build wheels (part 3)
uses: home-assistant/wheels@2024.11.0
with:
abi: ${{ matrix.abi }}
tag: musllinux_1_2
arch: ${{ matrix.arch }}
wheels-key: ${{ secrets.WHEELS_KEY }}
env-file: true
apk: "bluez-dev;libffi-dev;openssl-dev;glib-dev;eudev-dev;libxml2-dev;libxslt-dev;libpng-dev;libjpeg-turbo-dev;tiff-dev;cups-dev;gmp-dev;mpfr-dev;mpc1-dev;ffmpeg-dev;gammu-dev;yaml-dev;openblas-dev;fftw-dev;lapack-dev;gfortran;blas-dev;eigen-dev;freetype-dev;glew-dev;harfbuzz-dev;hdf5-dev;libdc1394-dev;libtbb-dev;mesa-dev;openexr-dev;openjpeg-dev;uchardet-dev;nasm;zlib-ng-dev"
skip-binary: aiohttp;charset-normalizer;grpcio;multidict;SQLAlchemy;propcache;protobuf;pymicro-vad;yarl
constraints: "homeassistant/package_constraints.txt"
requirements-diff: "requirements_diff.txt"
requirements: "requirements_all.txtac"
requirements: "requirements_all.txt"

View File

@@ -14,7 +14,7 @@ from homeassistant.components.notify import (
)
from homeassistant.const import STATE_IDLE, STATE_OFF, STATE_ON
from homeassistant.core import Event, EventStateChangedData, HassJob, HomeAssistant
from homeassistant.exceptions import ServiceNotFound
from homeassistant.exceptions import ServiceNotFound, ServiceValidationError
from homeassistant.helpers.entity import Entity
from homeassistant.helpers.event import (
async_track_point_in_time,
@@ -195,7 +195,8 @@ class AlertEntity(Entity):
async def async_turn_off(self, **kwargs: Any) -> None:
"""Async Acknowledge alert."""
LOGGER.debug("Acknowledged Alert: %s", self._attr_name)
if not self._can_ack:
raise ServiceValidationError("This alert cannot be acknowledged")
self._ack = True
self.async_write_ha_state()

View File

@@ -0,0 +1 @@
"""Virtual integration: Apollo Automation."""

View File

@@ -0,0 +1,6 @@
{
"domain": "apollo_automation",
"name": "Apollo Automation",
"integration_type": "virtual",
"supported_by": "esphome"
}

View File

@@ -1103,12 +1103,16 @@ class PipelineRun:
) & conversation.ConversationEntityFeature.CONTROL:
intent_filter = _async_local_fallback_intent_filter
# Try local intents first, if preferred.
elif self.pipeline.prefer_local_intents and (
intent_response := await conversation.async_handle_intents(
self.hass,
user_input,
intent_filter=intent_filter,
# Try local intents
if (
intent_response is None
and self.pipeline.prefer_local_intents
and (
intent_response := await conversation.async_handle_intents(
self.hass,
user_input,
intent_filter=intent_filter,
)
)
):
# Local intent matched

View File

@@ -14,6 +14,7 @@ from itertools import chain
import json
from pathlib import Path, PurePath
import shutil
import sys
import tarfile
import time
from typing import IO, TYPE_CHECKING, Any, Protocol, TypedDict, cast
@@ -117,6 +118,7 @@ class BackupManagerState(StrEnum):
IDLE = "idle"
CREATE_BACKUP = "create_backup"
BLOCKED = "blocked"
RECEIVE_BACKUP = "receive_backup"
RESTORE_BACKUP = "restore_backup"
@@ -225,6 +227,13 @@ class RestoreBackupEvent(ManagerStateEvent):
state: RestoreBackupState
@dataclass(frozen=True, kw_only=True, slots=True)
class BlockedEvent(ManagerStateEvent):
"""Backup manager blocked, Home Assistant is starting."""
manager_state: BackupManagerState = BackupManagerState.BLOCKED
class BackupPlatformProtocol(Protocol):
"""Define the format that backup platforms can have."""
@@ -308,6 +317,12 @@ class DecryptOnDowloadNotSupported(BackupManagerError):
_message = "On-the-fly decryption is not supported for this backup."
class BackupManagerExceptionGroup(BackupManagerError, ExceptionGroup):
"""Raised when multiple exceptions occur."""
error_code = "multiple_errors"
class BackupManager:
"""Define the format that backup managers can have."""
@@ -333,7 +348,7 @@ class BackupManager:
self.remove_next_delete_event: Callable[[], None] | None = None
# Latest backup event and backup event subscribers
self.last_event: ManagerStateEvent = IdleEvent()
self.last_event: ManagerStateEvent = BlockedEvent()
self.last_non_idle_event: ManagerStateEvent | None = None
self._backup_event_subscriptions = hass.data[
DATA_BACKUP
@@ -347,10 +362,19 @@ class BackupManager:
self.known_backups.load(stored["backups"])
await self._reader_writer.async_validate_config(config=self.config)
await self._reader_writer.async_resume_restore_progress_after_restart(
on_progress=self.async_on_backup_event
)
async def set_manager_idle_after_start(hass: HomeAssistant) -> None:
"""Set manager to idle after start."""
self.async_on_backup_event(IdleEvent())
if self.state == BackupManagerState.BLOCKED:
# If we're not finishing a restore job, set the manager to idle after start
start.async_at_started(self.hass, set_manager_idle_after_start)
await self.load_platforms()
@property
@@ -1286,7 +1310,7 @@ class BackupManager:
if (current_state := self.state) != (new_state := event.manager_state):
LOGGER.debug("Backup state: %s -> %s", current_state, new_state)
self.last_event = event
if not isinstance(event, IdleEvent):
if not isinstance(event, (BlockedEvent, IdleEvent)):
self.last_non_idle_event = event
for subscription in self._backup_event_subscriptions:
subscription(event)
@@ -1605,10 +1629,24 @@ class CoreBackupReaderWriter(BackupReaderWriter):
)
finally:
# Inform integrations the backup is done
# If there's an unhandled exception, we keep it so we can rethrow it in case
# the post backup actions also fail.
unhandled_exc = sys.exception()
try:
await manager.async_post_backup_actions()
except BackupManagerError as err:
raise BackupReaderWriterError(str(err)) from err
try:
await manager.async_post_backup_actions()
except BackupManagerError as err:
raise BackupReaderWriterError(str(err)) from err
except Exception as err:
if not unhandled_exc:
raise
# If there's an unhandled exception, we wrap both that and the exception
# from the post backup actions in an ExceptionGroup so the caller is
# aware of both exceptions.
raise BackupManagerExceptionGroup(
f"Multiple errors when creating backup: {unhandled_exc}, {err}",
[unhandled_exc, err],
) from None
def _mkdir_and_generate_backup_contents(
self,

View File

@@ -75,6 +75,9 @@ class BluesoundConfigFlow(ConfigFlow, domain=DOMAIN):
self, discovery_info: ZeroconfServiceInfo
) -> ConfigFlowResult:
"""Handle a flow initialized by zeroconf discovery."""
# the player can have an ipv6 address, but the api is only available on ipv4
if discovery_info.ip_address.version != 4:
return self.async_abort(reason="no_ipv4_address")
if discovery_info.port is not None:
self._port = discovery_info.port

View File

@@ -19,7 +19,8 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]"
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"no_ipv4_address": "No IPv4 address found."
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]"

View File

@@ -21,6 +21,6 @@
"bluetooth-auto-recovery==1.4.4",
"bluetooth-data-tools==1.23.4",
"dbus-fast==2.33.0",
"habluetooth==3.24.0"
"habluetooth==3.24.1"
]
}

View File

@@ -77,9 +77,12 @@ class BringEventEntity(BringBaseEntity, EventEntity):
attributes = asdict(activity.content)
attributes["last_activity_by"] = next(
x.name
for x in bring_list.users.users
if x.publicUuid == activity.content.publicUserUuid
(
x.name
for x in bring_list.users.users
if x.publicUuid == activity.content.publicUserUuid
),
None,
)
self._trigger_event(

View File

@@ -153,6 +153,27 @@ def _has_min_duration(
return validate
def _has_positive_interval(
start_key: str, end_key: str, duration_key: str
) -> Callable[[dict[str, Any]], dict[str, Any]]:
"""Verify that the time span between start and end is greater than zero."""
def validate(obj: dict[str, Any]) -> dict[str, Any]:
if (duration := obj.get(duration_key)) is not None:
if duration <= datetime.timedelta(seconds=0):
raise vol.Invalid(f"Expected positive duration ({duration})")
return obj
if (start := obj.get(start_key)) and (end := obj.get(end_key)):
if start >= end:
raise vol.Invalid(
f"Expected end time to be after start time ({start}, {end})"
)
return obj
return validate
def _has_same_type(*keys: Any) -> Callable[[dict[str, Any]], dict[str, Any]]:
"""Verify that all values are of the same type."""
@@ -281,6 +302,7 @@ SERVICE_GET_EVENTS_SCHEMA: Final = vol.All(
),
}
),
_has_positive_interval(EVENT_START_DATETIME, EVENT_END_DATETIME, EVENT_DURATION),
)
@@ -870,6 +892,7 @@ async def async_get_events_service(
end = start + service_call.data[EVENT_DURATION]
else:
end = service_call.data[EVENT_END_DATETIME]
calendar_event_list = await calendar.async_get_events(
calendar.hass, dt_util.as_local(start), dt_util.as_local(end)
)

View File

@@ -68,7 +68,6 @@ from .const import ( # noqa: F401
FAN_ON,
FAN_TOP,
HVAC_MODES,
INTENT_GET_TEMPERATURE,
INTENT_SET_TEMPERATURE,
PRESET_ACTIVITY,
PRESET_AWAY,

View File

@@ -126,7 +126,6 @@ DEFAULT_MAX_HUMIDITY = 99
DOMAIN = "climate"
INTENT_GET_TEMPERATURE = "HassClimateGetTemperature"
INTENT_SET_TEMPERATURE = "HassClimateSetTemperature"
SERVICE_SET_AUX_HEAT = "set_aux_heat"

View File

@@ -1,4 +1,4 @@
"""Intents for the client integration."""
"""Intents for the climate integration."""
from __future__ import annotations
@@ -11,7 +11,6 @@ from homeassistant.helpers import config_validation as cv, intent
from . import (
ATTR_TEMPERATURE,
DOMAIN,
INTENT_GET_TEMPERATURE,
INTENT_SET_TEMPERATURE,
SERVICE_SET_TEMPERATURE,
ClimateEntityFeature,
@@ -20,49 +19,9 @@ from . import (
async def async_setup_intents(hass: HomeAssistant) -> None:
"""Set up the climate intents."""
intent.async_register(hass, GetTemperatureIntent())
intent.async_register(hass, SetTemperatureIntent())
class GetTemperatureIntent(intent.IntentHandler):
"""Handle GetTemperature intents."""
intent_type = INTENT_GET_TEMPERATURE
description = "Gets the current temperature of a climate device or entity"
slot_schema = {
vol.Optional("area"): intent.non_empty_string,
vol.Optional("name"): intent.non_empty_string,
}
platforms = {DOMAIN}
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
"""Handle the intent."""
hass = intent_obj.hass
slots = self.async_validate_slots(intent_obj.slots)
name: str | None = None
if "name" in slots:
name = slots["name"]["value"]
area: str | None = None
if "area" in slots:
area = slots["area"]["value"]
match_constraints = intent.MatchTargetsConstraints(
name=name, area_name=area, domains=[DOMAIN], assistant=intent_obj.assistant
)
match_result = intent.async_match_targets(hass, match_constraints)
if not match_result.is_match:
raise intent.MatchFailedError(
result=match_result, constraints=match_constraints
)
response = intent_obj.create_response()
response.response_type = intent.IntentResponseType.QUERY_ANSWER
response.async_set_states(matched_states=match_result.states)
return response
class SetTemperatureIntent(intent.IntentHandler):
"""Handle SetTemperature intents."""

View File

@@ -3,11 +3,11 @@
from __future__ import annotations
from collections.abc import Mapping
import logging
from typing import Any
from aioelectricitymaps import (
ElectricityMaps,
ElectricityMapsError,
ElectricityMapsInvalidTokenError,
ElectricityMapsNoDataError,
)
@@ -36,6 +36,8 @@ TYPE_USE_HOME = "use_home_location"
TYPE_SPECIFY_COORDINATES = "specify_coordinates"
TYPE_SPECIFY_COUNTRY = "specify_country_code"
_LOGGER = logging.getLogger(__name__)
class ElectricityMapsConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Co2signal."""
@@ -158,7 +160,8 @@ class ElectricityMapsConfigFlow(ConfigFlow, domain=DOMAIN):
errors["base"] = "invalid_auth"
except ElectricityMapsNoDataError:
errors["base"] = "no_data"
except ElectricityMapsError:
except Exception:
_LOGGER.exception("Unexpected error occurred while checking API key")
errors["base"] = "unknown"
else:
if self.source == SOURCE_REAUTH:

View File

@@ -49,7 +49,11 @@ def async_get_chat_log(
raise RuntimeError(
"Cannot attach chat log delta listener unless initial caller"
)
if user_input is not None:
if user_input is not None and (
(content := chat_log.content[-1]).role != "user"
# MyPy doesn't understand that content is a UserContent here
or content.content != user_input.text # type: ignore[union-attr]
):
chat_log.async_add_user_content(UserContent(content=user_input.text))
yield chat_log

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

View File

@@ -24,7 +24,14 @@ from homeassistant.const import (
STATE_UNKNOWN,
UnitOfTime,
)
from homeassistant.core import Event, EventStateChangedData, HomeAssistant, callback
from homeassistant.core import (
Event,
EventStateChangedData,
EventStateReportedData,
HomeAssistant,
State,
callback,
)
from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers.device import async_device_info_to_link_from_entity
from homeassistant.helpers.device_registry import DeviceInfo
@@ -32,7 +39,10 @@ from homeassistant.helpers.entity_platform import (
AddConfigEntryEntitiesCallback,
AddEntitiesCallback,
)
from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.helpers.event import (
async_track_state_change_event,
async_track_state_report_event,
)
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from .const import (
@@ -200,13 +210,33 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
_LOGGER.warning("Could not restore last state: %s", err)
@callback
def calc_derivative(event: Event[EventStateChangedData]) -> None:
def on_state_reported(event: Event[EventStateReportedData]) -> None:
"""Handle constant sensor state."""
if self._attr_native_value == Decimal(0):
# If the derivative is zero, and the source sensor hasn't
# changed state, then we know it will still be zero.
return
new_state = event.data["new_state"]
if new_state is not None:
calc_derivative(
new_state, new_state.state, event.data["old_last_reported"]
)
@callback
def on_state_changed(event: Event[EventStateChangedData]) -> None:
"""Handle changed sensor state."""
new_state = event.data["new_state"]
old_state = event.data["old_state"]
if new_state is not None and old_state is not None:
calc_derivative(new_state, old_state.state, old_state.last_reported)
def calc_derivative(
new_state: State, old_value: str, old_last_reported: datetime
) -> None:
"""Handle the sensor state changes."""
if (
(old_state := event.data["old_state"]) is None
or old_state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE)
or (new_state := event.data["new_state"]) is None
or new_state.state in (STATE_UNKNOWN, STATE_UNAVAILABLE)
if old_value in (STATE_UNKNOWN, STATE_UNAVAILABLE) or new_state.state in (
STATE_UNKNOWN,
STATE_UNAVAILABLE,
):
return
@@ -220,15 +250,15 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
self._state_list = [
(time_start, time_end, state)
for time_start, time_end, state in self._state_list
if (new_state.last_updated - time_end).total_seconds()
if (new_state.last_reported - time_end).total_seconds()
< self._time_window
]
try:
elapsed_time = (
new_state.last_updated - old_state.last_updated
new_state.last_reported - old_last_reported
).total_seconds()
delta_value = Decimal(new_state.state) - Decimal(old_state.state)
delta_value = Decimal(new_state.state) - Decimal(old_value)
new_derivative = (
delta_value
/ Decimal(elapsed_time)
@@ -240,7 +270,7 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
_LOGGER.warning("While calculating derivative: %s", err)
except DecimalException as err:
_LOGGER.warning(
"Invalid state (%s > %s): %s", old_state.state, new_state.state, err
"Invalid state (%s > %s): %s", old_value, new_state.state, err
)
except AssertionError as err:
_LOGGER.error("Could not calculate derivative: %s", err)
@@ -257,7 +287,7 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
# add latest derivative to the window list
self._state_list.append(
(old_state.last_updated, new_state.last_updated, new_derivative)
(old_last_reported, new_state.last_reported, new_derivative)
)
def calculate_weight(
@@ -277,13 +307,19 @@ class DerivativeSensor(RestoreSensor, SensorEntity):
else:
derivative = Decimal("0.00")
for start, end, value in self._state_list:
weight = calculate_weight(start, end, new_state.last_updated)
weight = calculate_weight(start, end, new_state.last_reported)
derivative = derivative + (value * Decimal(weight))
self._attr_native_value = round(derivative, self._round_digits)
self.async_write_ha_state()
self.async_on_remove(
async_track_state_change_event(
self.hass, self._sensor_source_id, calc_derivative
self.hass, self._sensor_source_id, on_state_changed
)
)
self.async_on_remove(
async_track_state_report_event(
self.hass, self._sensor_source_id, on_state_reported
)
)

View File

@@ -8,6 +8,7 @@ from devolo_plc_api.device_api import (
WifiGuestAccessGet,
)
from devolo_plc_api.plcnet_api import DataRate, LogicalNetwork
from yarl import URL
from homeassistant.const import ATTR_CONNECTIONS
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
@@ -43,7 +44,7 @@ class DevoloEntity(Entity):
self.entry = entry
self._attr_device_info = DeviceInfo(
configuration_url=f"http://{self.device.ip}",
configuration_url=URL.build(scheme="http", host=self.device.ip),
identifiers={(DOMAIN, str(self.device.serial_number))},
manufacturer="devolo",
model=self.device.product,

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

View File

@@ -6,5 +6,5 @@
"dependencies": ["webhook"],
"documentation": "https://www.home-assistant.io/integrations/ecowitt",
"iot_class": "local_push",
"requirements": ["aioecowitt==2024.2.1"]
"requirements": ["aioecowitt==2025.3.1"]
}

View File

@@ -101,9 +101,11 @@ def hostname_from_url(url: str) -> str:
def _host_validator(config: dict[str, str]) -> dict[str, str]:
"""Validate that a host is properly configured."""
if config[CONF_HOST].startswith("elks://"):
if config[CONF_HOST].startswith(("elks://", "elksv1_2://")):
if CONF_USERNAME not in config or CONF_PASSWORD not in config:
raise vol.Invalid("Specify username and password for elks://")
raise vol.Invalid(
"Specify username and password for elks:// or elksv1_2://"
)
elif not config[CONF_HOST].startswith("elk://") and not config[
CONF_HOST
].startswith("serial://"):

View File

@@ -105,6 +105,7 @@ class ElkArea(ElkAttachedEntity, AlarmControlPanelEntity, RestoreEntity):
AlarmControlPanelEntityFeature.ARM_HOME
| AlarmControlPanelEntityFeature.ARM_AWAY
| AlarmControlPanelEntityFeature.ARM_NIGHT
| AlarmControlPanelEntityFeature.ARM_VACATION
)
_element: Area
@@ -204,7 +205,7 @@ class ElkArea(ElkAttachedEntity, AlarmControlPanelEntity, RestoreEntity):
ArmedStatus.ARMED_STAY_INSTANT: AlarmControlPanelState.ARMED_HOME,
ArmedStatus.ARMED_TO_NIGHT: AlarmControlPanelState.ARMED_NIGHT,
ArmedStatus.ARMED_TO_NIGHT_INSTANT: AlarmControlPanelState.ARMED_NIGHT,
ArmedStatus.ARMED_TO_VACATION: AlarmControlPanelState.ARMED_AWAY,
ArmedStatus.ARMED_TO_VACATION: AlarmControlPanelState.ARMED_VACATION,
}
if self._element.alarm_state is None:

View File

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

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/environment_canada",
"iot_class": "cloud_polling",
"loggers": ["env_canada"],
"requirements": ["env-canada==0.7.2"]
"requirements": ["env-canada==0.8.0"]
}

View File

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

View File

@@ -3,6 +3,7 @@
from __future__ import annotations
from functools import partial
from math import isfinite
from typing import Any, cast
from aioesphomeapi import (
@@ -238,9 +239,13 @@ class EsphomeClimateEntity(EsphomeEntity[ClimateInfo, ClimateState], ClimateEnti
@esphome_state_property
def current_humidity(self) -> int | None:
"""Return the current humidity."""
if not self._static_info.supports_current_humidity:
if (
not self._static_info.supports_current_humidity
or (val := self._state.current_humidity) is None
or not isfinite(val)
):
return None
return round(self._state.current_humidity)
return round(val)
@property
@esphome_float_state_property

View File

@@ -13,11 +13,13 @@ DEFAULT_ALLOW_SERVICE_CALLS = True
DEFAULT_NEW_CONFIG_ALLOW_ALLOW_SERVICE_CALLS = False
STABLE_BLE_VERSION_STR = "2025.2.1"
STABLE_BLE_VERSION_STR = "2025.2.2"
STABLE_BLE_VERSION = AwesomeVersion(STABLE_BLE_VERSION_STR)
PROJECT_URLS = {
"esphome.bluetooth-proxy": "https://esphome.github.io/bluetooth-proxies/",
}
DEFAULT_URL = f"https://esphome.io/changelog/{STABLE_BLE_VERSION_STR}.html"
# ESPHome always uses .0 for the changelog URL
STABLE_BLE_URL_VERSION = f"{STABLE_BLE_VERSION.major}.{STABLE_BLE_VERSION.minor}.0"
DEFAULT_URL = f"https://esphome.io/changelog/{STABLE_BLE_URL_VERSION}.html"
DATA_FFMPEG_PROXY = f"{DOMAIN}.ffmpeg_proxy"

View File

@@ -18,7 +18,7 @@
"requirements": [
"aioesphomeapi==29.2.0",
"esphome-dashboard-api==1.2.3",
"bleak-esphome==2.7.1"
"bleak-esphome==2.8.0"
],
"zeroconf": ["_esphomelib._tcp.local."]
}

View File

@@ -11,6 +11,7 @@ from typing import Any
import evohomeasync as ec1
import evohomeasync2 as ec2
from evohomeasync2.const import (
SZ_DHW,
SZ_GATEWAY_ID,
SZ_GATEWAY_INFO,
SZ_GATEWAYS,
@@ -19,8 +20,9 @@ from evohomeasync2.const import (
SZ_TEMPERATURE_CONTROL_SYSTEMS,
SZ_TIME_ZONE,
SZ_USE_DAYLIGHT_SAVE_SWITCHING,
SZ_ZONES,
)
from evohomeasync2.schemas.typedefs import EvoLocStatusResponseT
from evohomeasync2.schemas.typedefs import EvoLocStatusResponseT, EvoTcsConfigResponseT
from homeassistant.const import CONF_SCAN_INTERVAL
from homeassistant.core import HomeAssistant
@@ -113,17 +115,19 @@ class EvoDataUpdateCoordinator(DataUpdateCoordinator):
SZ_USE_DAYLIGHT_SAVE_SWITCHING
],
}
tcs_info: EvoTcsConfigResponseT = self.tcs.config # type: ignore[assignment]
tcs_info[SZ_ZONES] = [zone.config for zone in self.tcs.zones]
if self.tcs.hotwater:
tcs_info[SZ_DHW] = self.tcs.hotwater.config
gwy_info = {
SZ_GATEWAY_ID: self.loc.gateways[0].id,
SZ_TEMPERATURE_CONTROL_SYSTEMS: [
self.loc.gateways[0].systems[0].config
],
SZ_TEMPERATURE_CONTROL_SYSTEMS: [tcs_info],
}
config = {
SZ_LOCATION_INFO: loc_info,
SZ_GATEWAYS: [{SZ_GATEWAY_INFO: gwy_info}],
}
self.logger.debug("Config = %s", config)
self.logger.debug("Config = %s", [config])
async def call_client_api(
self,
@@ -203,10 +207,18 @@ class EvoDataUpdateCoordinator(DataUpdateCoordinator):
async def _update_v2_schedules(self) -> None:
for zone in self.tcs.zones:
await zone.get_schedule()
try:
await zone.get_schedule()
except ec2.InvalidScheduleError as err:
self.logger.warning(
"Zone '%s' has an invalid/missing schedule: %r", zone.name, err
)
if dhw := self.tcs.hotwater:
await dhw.get_schedule()
try:
await dhw.get_schedule()
except ec2.InvalidScheduleError as err:
self.logger.warning("DHW has an invalid/missing schedule: %r", err)
async def _async_update_data(self) -> EvoLocStatusResponseT: # type: ignore[override]
"""Fetch the latest state of an entire TCC Location.

View File

@@ -6,6 +6,7 @@ import logging
from typing import Any
import evohomeasync2 as evo
from evohomeasync2.schemas.typedefs import DayOfWeekDhwT
from homeassistant.core import callback
from homeassistant.helpers.dispatcher import async_dispatcher_connect
@@ -102,7 +103,7 @@ class EvoChild(EvoEntity):
self._evo_tcs = evo_device.tcs
self._schedule: dict[str, Any] | None = None
self._schedule: list[DayOfWeekDhwT] | None = None
self._setpoints: dict[str, Any] = {}
@property
@@ -123,6 +124,9 @@ class EvoChild(EvoEntity):
Only Zones & DHW controllers (but not the TCS) can have schedules.
"""
if not self._schedule:
return self._setpoints
this_sp_dtm, this_sp_val = self._evo_device.this_switchpoint
next_sp_dtm, next_sp_val = self._evo_device.next_switchpoint
@@ -152,10 +156,10 @@ class EvoChild(EvoEntity):
self._evo_device,
err,
)
self._schedule = {}
self._schedule = []
return
else:
self._schedule = schedule or {} # mypy hint
self._schedule = schedule # type: ignore[assignment]
_LOGGER.debug("Schedule['%s'] = %s", self.name, schedule)

View File

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

View File

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

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/fujitsu_fglair",
"iot_class": "cloud_polling",
"requirements": ["ayla-iot-unofficial==1.4.5"]
"requirements": ["ayla-iot-unofficial==1.4.7"]
}

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
import mimetypes
from pathlib import Path
from google import genai # type: ignore[attr-defined]
@@ -65,9 +66,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
prompt_parts = [call.data[CONF_PROMPT]]
config_entry: GoogleGenerativeAIConfigEntry = hass.config_entries.async_entries(
DOMAIN
)[0]
config_entry: GoogleGenerativeAIConfigEntry = (
hass.config_entries.async_loaded_entries(DOMAIN)[0]
)
client = config_entry.runtime_data
@@ -83,7 +84,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
)
if not Path(filename).exists():
raise HomeAssistantError(f"`{filename}` does not exist")
prompt_parts.append(client.files.upload(file=filename))
mimetype = mimetypes.guess_type(filename)[0]
with open(filename, "rb") as file:
uploaded_file = client.files.upload(
file=file, config={"mime_type": mimetype}
)
prompt_parts.append(uploaded_file)
await hass.async_add_executor_job(append_files_to_prompt)

View File

@@ -64,28 +64,18 @@ async def async_setup_entry(
SUPPORTED_SCHEMA_KEYS = {
"min_items",
"example",
"property_ordering",
"pattern",
"minimum",
"default",
"any_of",
"max_length",
"title",
"min_properties",
"min_length",
"max_items",
"maximum",
"nullable",
"max_properties",
# Gemini API does not support all of the OpenAPI schema
# SoT: https://ai.google.dev/api/caching#Schema
"type",
"description",
"enum",
"format",
"items",
"description",
"nullable",
"enum",
"max_items",
"min_items",
"properties",
"required",
"items",
}
@@ -109,11 +99,20 @@ def _format_schema(schema: dict[str, Any]) -> Schema:
key = _camel_to_snake(key)
if key not in SUPPORTED_SCHEMA_KEYS:
continue
if key == "any_of":
val = [_format_schema(subschema) for subschema in val]
if key == "type":
val = val.upper()
if key == "items":
elif key == "format":
# Gemini API does not support all formats, see: https://ai.google.dev/api/caching#Schema
# formats that are not supported are ignored
if schema.get("type") == "string" and val not in ("enum", "date-time"):
continue
if schema.get("type") == "number" and val not in ("float", "double"):
continue
if schema.get("type") == "integer" and val not in ("int32", "int64"):
continue
if schema.get("type") not in ("string", "number", "integer"):
continue
elif key == "items":
val = _format_schema(val)
elif key == "properties":
val = {k: _format_schema(v) for k, v in val.items()}
@@ -277,6 +276,13 @@ class GoogleGenerativeAIConversationEntity(
):
return await self._async_handle_message(user_input, chat_log)
def _fix_tool_name(self, tool_name: str) -> str:
"""Fix tool name if needed."""
# The Gemini 2.0+ tokenizer seemingly has a issue with the HassListAddItem tool
# name. This makes sure when it incorrectly changes the name, that we change it
# back for HA to call.
return tool_name if tool_name != "HasListAddItem" else "HassListAddItem"
async def _async_handle_message(
self,
user_input: conversation.ConversationInput,
@@ -436,7 +442,10 @@ class GoogleGenerativeAIConversationEntity(
tool_name = tool_call.name
tool_args = _escape_decode(tool_call.args)
tool_calls.append(
llm.ToolInput(tool_name=tool_name, tool_args=tool_args)
llm.ToolInput(
tool_name=self._fix_tool_name(tool_name),
tool_args=tool_args,
)
)
chat_request = _create_google_tool_response_content(

View File

@@ -135,5 +135,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/govee_ble",
"iot_class": "local_push",
"requirements": ["govee-ble==0.43.0"]
"requirements": ["govee-ble==0.43.1"]
}

View File

@@ -20,3 +20,4 @@ MAX_ERRORS = 2
TARGET_TEMPERATURE_STEP = 1
UPDATE_INTERVAL = 60
MAX_EXPECTED_RESPONSE_TIME_INTERVAL = UPDATE_INTERVAL * 2

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
import copy
from datetime import datetime, timedelta
import logging
from typing import Any
@@ -24,6 +25,7 @@ from .const import (
DISPATCH_DEVICE_DISCOVERED,
DOMAIN,
MAX_ERRORS,
MAX_EXPECTED_RESPONSE_TIME_INTERVAL,
UPDATE_INTERVAL,
)
@@ -48,7 +50,6 @@ class DeviceDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
always_update=False,
)
self.device = device
self.device.add_handler(Response.DATA, self.device_state_updated)
self.device.add_handler(Response.RESULT, self.device_state_updated)
self._error_count: int = 0
@@ -88,7 +89,9 @@ class DeviceDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
# raise update failed if time for more than MAX_ERRORS has passed since last update
now = utcnow()
elapsed_success = now - self._last_response_time
if self.update_interval and elapsed_success >= self.update_interval:
if self.update_interval and elapsed_success >= timedelta(
seconds=MAX_EXPECTED_RESPONSE_TIME_INTERVAL
):
if not self._last_error_time or (
(now - self.update_interval) >= self._last_error_time
):
@@ -96,16 +99,19 @@ class DeviceDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
self._error_count += 1
_LOGGER.warning(
"Device %s is unresponsive for %s seconds",
"Device %s took an unusually long time to respond, %s seconds",
self.name,
elapsed_success,
)
else:
self._error_count = 0
if self.last_update_success and self._error_count >= MAX_ERRORS:
raise UpdateFailed(
f"Device {self.name} is unresponsive for too long and now unavailable"
)
return self.device.raw_properties
self._last_response_time = utcnow()
return copy.deepcopy(self.device.raw_properties)
async def push_state_update(self):
"""Send state updates to the physical device."""

View File

@@ -26,6 +26,7 @@ TOTAL_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="todayEnergy",
native_unit_of_measurement=UnitOfEnergy.KILO_WATT_HOUR,
device_class=SensorDeviceClass.ENERGY,
state_class=SensorStateClass.TOTAL_INCREASING,
),
GrowattSensorEntityDescription(
key="total_output_power",
@@ -33,6 +34,7 @@ TOTAL_SENSOR_TYPES: tuple[GrowattSensorEntityDescription, ...] = (
api_key="invTodayPpv",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
state_class=SensorStateClass.MEASUREMENT,
),
GrowattSensorEntityDescription(
key="total_energy_output",

View File

@@ -14,7 +14,12 @@ from pyheos import (
)
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult, OptionsFlow
from homeassistant.config_entries import (
SOURCE_IGNORE,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import callback
from homeassistant.helpers import selector
@@ -141,8 +146,10 @@ class HeosFlowHandler(ConfigFlow, domain=DOMAIN):
hostname = urlparse(discovery_info.ssdp_location).hostname
assert hostname is not None
# Abort early when discovered host is part of the current system
if entry and hostname in _get_current_hosts(entry):
# Abort early when discovery is ignored or host is part of the current system
if entry and (
entry.source == SOURCE_IGNORE or hostname in _get_current_hosts(entry)
):
return self.async_abort(reason="single_instance_allowed")
# Connect to discovered host and get system information
@@ -198,7 +205,7 @@ class HeosFlowHandler(ConfigFlow, domain=DOMAIN):
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Obtain host and validate connection."""
await self.async_set_unique_id(DOMAIN)
await self.async_set_unique_id(DOMAIN, raise_on_progress=False)
self._abort_if_unique_id_configured(error="single_instance_allowed")
# Try connecting to host if provided
errors: dict[str, str] = {}

View File

@@ -159,13 +159,12 @@ class HeosCoordinator(DataUpdateCoordinator[None]):
async def _async_on_reconnected(self) -> None:
"""Handle when reconnected so resources are updated and entities marked available."""
await self._async_update_players()
await self._async_update_sources()
_LOGGER.warning("Successfully reconnected to HEOS host %s", self.host)
self.async_update_listeners()
async def _async_on_controller_event(
self, event: str, data: PlayerUpdateResult | None
self, event: str, data: PlayerUpdateResult | None = None
) -> None:
"""Handle a controller event, such as players or groups changed."""
if event == const.EVENT_PLAYERS_CHANGED:

View File

@@ -8,7 +8,7 @@
"iot_class": "local_push",
"loggers": ["pyheos"],
"quality_scale": "platinum",
"requirements": ["pyheos==1.0.2"],
"requirements": ["pyheos==1.0.3"],
"ssdp": [
{
"st": "urn:schemas-denon-com:device:ACT-Denon:1"

View File

@@ -11,7 +11,6 @@ from hko import HKO, HKOError
from homeassistant.components.weather import (
ATTR_CONDITION_CLOUDY,
ATTR_CONDITION_FOG,
ATTR_CONDITION_HAIL,
ATTR_CONDITION_LIGHTNING_RAINY,
ATTR_CONDITION_PARTLYCLOUDY,
ATTR_CONDITION_POURING,
@@ -145,7 +144,7 @@ class HKOUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Return the condition corresponding to the weather info."""
info = info.lower()
if WEATHER_INFO_RAIN in info:
return ATTR_CONDITION_HAIL
return ATTR_CONDITION_RAINY
if WEATHER_INFO_SNOW in info and WEATHER_INFO_RAIN in info:
return ATTR_CONDITION_SNOWY_RAINY
if WEATHER_INFO_SNOW in info:

View File

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

View File

@@ -16,11 +16,17 @@ from aiohomeconnect.model import (
SettingKey,
)
from aiohomeconnect.model.error import HomeConnectError
import aiohttp
import voluptuous as vol
from homeassistant.const import ATTR_DEVICE_ID, Platform
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import HomeAssistantError, ServiceValidationError
from homeassistant.exceptions import (
ConfigEntryAuthFailed,
ConfigEntryNotReady,
HomeAssistantError,
ServiceValidationError,
)
from homeassistant.helpers import (
config_entry_oauth2_flow,
config_validation as cv,
@@ -611,18 +617,31 @@ async def async_setup_entry(hass: HomeAssistant, entry: HomeConnectConfigEntry)
session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation)
config_entry_auth = AsyncConfigEntryAuth(hass, session)
try:
await config_entry_auth.async_get_access_token()
except aiohttp.ClientResponseError as err:
if 400 <= err.status < 500:
raise ConfigEntryAuthFailed from err
raise ConfigEntryNotReady from err
except aiohttp.ClientError as err:
raise ConfigEntryNotReady from err
home_connect_client = HomeConnectClient(config_entry_auth)
coordinator = HomeConnectCoordinator(hass, entry, home_connect_client)
await coordinator.async_config_entry_first_refresh()
await coordinator.async_setup()
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.runtime_data.start_event_listener()
entry.async_create_background_task(
hass,
coordinator.async_refresh(),
f"home_connect-initial-full-refresh-{entry.entry_id}",
)
return True

View File

@@ -137,41 +137,6 @@ def setup_home_connect_entry(
defaultdict(list)
)
entities: list[HomeConnectEntity] = []
for appliance in entry.runtime_data.data.values():
entities_to_add = get_entities_for_appliance(entry, appliance)
if get_option_entities_for_appliance:
entities_to_add.extend(get_option_entities_for_appliance(entry, appliance))
for event_key in (
EventKey.BSH_COMMON_ROOT_ACTIVE_PROGRAM,
EventKey.BSH_COMMON_ROOT_SELECTED_PROGRAM,
):
changed_options_listener_remove_callback = (
entry.runtime_data.async_add_listener(
partial(
_create_option_entities,
entry,
appliance,
known_entity_unique_ids,
get_option_entities_for_appliance,
async_add_entities,
),
(appliance.info.ha_id, event_key),
)
)
entry.async_on_unload(changed_options_listener_remove_callback)
changed_options_listener_remove_callbacks[appliance.info.ha_id].append(
changed_options_listener_remove_callback
)
known_entity_unique_ids.update(
{
cast(str, entity.unique_id): appliance.info.ha_id
for entity in entities_to_add
}
)
entities.extend(entities_to_add)
async_add_entities(entities)
entry.async_on_unload(
entry.runtime_data.async_add_special_listener(
partial(

View File

@@ -10,6 +10,7 @@ from .utils import bsh_key_to_translation_key
DOMAIN = "home_connect"
API_DEFAULT_RETRY_AFTER = 60
APPLIANCES_WITH_PROGRAMS = (
"CleaningRobot",
@@ -284,7 +285,9 @@ SPIN_SPEED_OPTIONS = {
"LaundryCare.Washer.EnumType.SpinSpeed.Off",
"LaundryCare.Washer.EnumType.SpinSpeed.RPM400",
"LaundryCare.Washer.EnumType.SpinSpeed.RPM600",
"LaundryCare.Washer.EnumType.SpinSpeed.RPM700",
"LaundryCare.Washer.EnumType.SpinSpeed.RPM800",
"LaundryCare.Washer.EnumType.SpinSpeed.RPM900",
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1000",
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1200",
"LaundryCare.Washer.EnumType.SpinSpeed.RPM1400",

View File

@@ -2,7 +2,7 @@
from __future__ import annotations
import asyncio
from asyncio import sleep as asyncio_sleep
from collections import defaultdict
from collections.abc import Callable
from dataclasses import dataclass
@@ -29,6 +29,7 @@ from aiohomeconnect.model.error import (
HomeConnectApiError,
HomeConnectError,
HomeConnectRequestError,
TooManyRequestsError,
UnauthorizedError,
)
from aiohomeconnect.model.program import EnumerateProgram, ProgramDefinitionOption
@@ -36,19 +37,17 @@ from propcache.api import cached_property
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import APPLIANCES_WITH_PROGRAMS, DOMAIN
from .const import API_DEFAULT_RETRY_AFTER, APPLIANCES_WITH_PROGRAMS, DOMAIN
from .utils import get_dict_from_home_connect_error
_LOGGER = logging.getLogger(__name__)
type HomeConnectConfigEntry = ConfigEntry[HomeConnectCoordinator]
EVENT_STREAM_RECONNECT_DELAY = 30
@dataclass(frozen=True, kw_only=True)
class HomeConnectApplianceData:
@@ -100,6 +99,7 @@ class HomeConnectCoordinator(
CALLBACK_TYPE, tuple[CALLBACK_TYPE, tuple[EventKey, ...]]
] = {}
self.device_registry = dr.async_get(self.hass)
self.data = {}
@cached_property
def context_listeners(self) -> dict[tuple[str, EventKey], list[CALLBACK_TYPE]]:
@@ -155,12 +155,22 @@ class HomeConnectCoordinator(
f"home_connect-events_listener_task-{self.config_entry.entry_id}",
)
async def _event_listener(self) -> None:
async def _event_listener(self) -> None: # noqa: C901
"""Match event with listener for event type."""
retry_time = 10
while True:
try:
async for event_message in self.client.stream_all_events():
retry_time = 10
event_message_ha_id = event_message.ha_id
if (
event_message_ha_id in self.data
and not self.data[event_message_ha_id].info.connected
):
self.data[event_message_ha_id].info.connected = True
self._call_all_event_listeners_for_appliance(
event_message_ha_id
)
match event_message.type:
case EventType.STATUS:
statuses = self.data[event_message_ha_id].status
@@ -256,20 +266,25 @@ class HomeConnectCoordinator(
except (EventStreamInterruptedError, HomeConnectRequestError) as error:
_LOGGER.debug(
"Non-breaking error (%s) while listening for events,"
" continuing in 30 seconds",
" continuing in %s seconds",
type(error).__name__,
retry_time,
)
await asyncio.sleep(EVENT_STREAM_RECONNECT_DELAY)
await asyncio_sleep(retry_time)
retry_time = min(retry_time * 2, 3600)
except HomeConnectApiError as error:
_LOGGER.error("Error while listening for events: %s", error)
self.hass.config_entries.async_schedule_reload(
self.config_entry.entry_id
)
break
# if there was a non-breaking error, we continue listening
# but we need to refresh the data to get the possible changes
# that happened while the event stream was interrupted
await self.async_refresh()
# Trigger to delete the possible depaired device entities
# from known_entities variable at common.py
for listener, context in self._special_listeners.values():
assert isinstance(context, tuple)
if EventKey.BSH_COMMON_APPLIANCE_DEPAIRED in context:
listener()
@callback
def _call_event_listener(self, event_message: EventMessage) -> None:
@@ -288,6 +303,42 @@ class HomeConnectCoordinator(
async def _async_update_data(self) -> dict[str, HomeConnectApplianceData]:
"""Fetch data from Home Connect."""
await self._async_setup()
for appliance_data in self.data.values():
appliance = appliance_data.info
ha_id = appliance.ha_id
while True:
try:
self.data[ha_id] = await self._get_appliance_data(
appliance, self.data.get(ha_id)
)
except TooManyRequestsError as err:
_LOGGER.debug(
"Rate limit exceeded on initial fetch: %s",
err,
)
await asyncio_sleep(err.retry_after or API_DEFAULT_RETRY_AFTER)
else:
break
for listener, context in self._special_listeners.values():
assert isinstance(context, tuple)
if EventKey.BSH_COMMON_APPLIANCE_PAIRED in context:
listener()
return self.data
async def async_setup(self) -> None:
"""Set up the devices."""
try:
await self._async_setup()
except UpdateFailed as err:
raise ConfigEntryNotReady from err
async def _async_setup(self) -> None:
"""Set up the devices."""
old_appliances = set(self.data.keys())
try:
appliances = await self.client.get_home_appliances()
except UnauthorizedError as error:
@@ -297,18 +348,46 @@ class HomeConnectCoordinator(
translation_placeholders=get_dict_from_home_connect_error(error),
) from error
except HomeConnectError as error:
for appliance_data in self.data.values():
appliance_data.info.connected = False
raise UpdateFailed(
translation_domain=DOMAIN,
translation_key="fetch_api_error",
translation_placeholders=get_dict_from_home_connect_error(error),
) from error
return {
appliance.ha_id: await self._get_appliance_data(
appliance, self.data.get(appliance.ha_id) if self.data else None
for appliance in appliances.homeappliances:
self.device_registry.async_get_or_create(
config_entry_id=self.config_entry.entry_id,
identifiers={(DOMAIN, appliance.ha_id)},
manufacturer=appliance.brand,
name=appliance.name,
model=appliance.vib,
)
for appliance in appliances.homeappliances
}
if appliance.ha_id not in self.data:
self.data[appliance.ha_id] = HomeConnectApplianceData(
commands=set(),
events={},
info=appliance,
options={},
programs=[],
settings={},
status={},
)
else:
self.data[appliance.ha_id].info.connected = appliance.connected
old_appliances.remove(appliance.ha_id)
for ha_id in old_appliances:
self.data.pop(ha_id, None)
device = self.device_registry.async_get_device(
identifiers={(DOMAIN, ha_id)}
)
if device:
self.device_registry.async_update_device(
device_id=device.id,
remove_config_entry_id=self.config_entry.entry_id,
)
async def _get_appliance_data(
self,
@@ -330,6 +409,8 @@ class HomeConnectCoordinator(
await self.client.get_settings(appliance.ha_id)
).settings
}
except TooManyRequestsError:
raise
except HomeConnectError as error:
_LOGGER.debug(
"Error fetching settings for %s: %s",
@@ -344,6 +425,8 @@ class HomeConnectCoordinator(
status.key: status
for status in (await self.client.get_status(appliance.ha_id)).status
}
except TooManyRequestsError:
raise
except HomeConnectError as error:
_LOGGER.debug(
"Error fetching status for %s: %s",
@@ -360,6 +443,8 @@ class HomeConnectCoordinator(
if appliance.type in APPLIANCES_WITH_PROGRAMS:
try:
all_programs = await self.client.get_all_programs(appliance.ha_id)
except TooManyRequestsError:
raise
except HomeConnectError as error:
_LOGGER.debug(
"Error fetching programs for %s: %s",
@@ -418,6 +503,8 @@ class HomeConnectCoordinator(
await self.client.get_available_commands(appliance.ha_id)
).commands
}
except TooManyRequestsError:
raise
except HomeConnectError:
commands = set()
@@ -440,13 +527,29 @@ class HomeConnectCoordinator(
self, ha_id: str, program_key: ProgramKey
) -> dict[OptionKey, ProgramDefinitionOption]:
"""Get options with constraints for appliance."""
return {
option.key: option
for option in (
await self.client.get_available_program(ha_id, program_key=program_key)
).options
or []
}
if program_key is ProgramKey.UNKNOWN:
return {}
try:
return {
option.key: option
for option in (
await self.client.get_available_program(
ha_id, program_key=program_key
)
).options
or []
}
except TooManyRequestsError:
raise
except HomeConnectError as error:
_LOGGER.debug(
"Error fetching options for %s: %s",
ha_id,
error
if isinstance(error, HomeConnectApiError)
else type(error).__name__,
)
return {}
async def update_options(
self, ha_id: str, event_key: EventKey, program_key: ProgramKey
@@ -456,8 +559,7 @@ class HomeConnectCoordinator(
events = self.data[ha_id].events
options_to_notify = options.copy()
options.clear()
if program_key is not ProgramKey.UNKNOWN:
options.update(await self.get_options_definitions(ha_id, program_key))
options.update(await self.get_options_definitions(ha_id, program_key))
for option in options.values():
option_value = option.constraints.default if option.constraints else None

View File

@@ -1,20 +1,28 @@
"""Home Connect entity base class."""
from abc import abstractmethod
from collections.abc import Callable, Coroutine
import contextlib
from datetime import datetime
import logging
from typing import cast
from typing import Any, Concatenate, cast
from aiohomeconnect.model import EventKey, OptionKey
from aiohomeconnect.model.error import ActiveProgramNotSetError, HomeConnectError
from aiohomeconnect.model.error import (
ActiveProgramNotSetError,
HomeConnectError,
TooManyRequestsError,
)
from homeassistant.const import STATE_UNAVAILABLE
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .const import API_DEFAULT_RETRY_AFTER, DOMAIN
from .coordinator import HomeConnectApplianceData, HomeConnectCoordinator
from .utils import get_dict_from_home_connect_error
@@ -51,8 +59,10 @@ class HomeConnectEntity(CoordinatorEntity[HomeConnectCoordinator]):
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
self.update_native_value()
available = self._attr_available = self.appliance.info.connected
self.async_write_ha_state()
_LOGGER.debug("Updated %s, new state: %s", self.entity_id, self.state)
state = STATE_UNAVAILABLE if not available else self.state
_LOGGER.debug("Updated %s, new state: %s", self.entity_id, state)
@property
def bsh_key(self) -> str:
@@ -61,10 +71,13 @@ class HomeConnectEntity(CoordinatorEntity[HomeConnectCoordinator]):
@property
def available(self) -> bool:
"""Return True if entity is available."""
return (
self.appliance.info.connected and self._attr_available and super().available
)
"""Return True if entity is available.
Do not use self.last_update_success for available state
as event updates should take precedence over the coordinator
refresh.
"""
return self._attr_available
class HomeConnectOptionEntity(HomeConnectEntity):
@@ -121,3 +134,34 @@ class HomeConnectOptionEntity(HomeConnectEntity):
def bsh_key(self) -> OptionKey:
"""Return the BSH key."""
return cast(OptionKey, self.entity_description.key)
def constraint_fetcher[_EntityT: HomeConnectEntity, **_P](
func: Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, Any]],
) -> Callable[Concatenate[_EntityT, _P], Coroutine[Any, Any, None]]:
"""Decorate the function to catch Home Connect too many requests error and retry later.
If it needs to be called later, it will call async_write_ha_state function
"""
async def handler_to_return(
self: _EntityT, *args: _P.args, **kwargs: _P.kwargs
) -> None:
async def handler(_datetime: datetime | None = None) -> None:
try:
await func(self, *args, **kwargs)
except TooManyRequestsError as err:
if (retry_after := err.retry_after) is None:
retry_after = API_DEFAULT_RETRY_AFTER
async_call_later(self.hass, retry_after, handler)
except HomeConnectError as err:
_LOGGER.error(
"Error fetching constraints for %s: %s", self.entity_id, err
)
else:
if _datetime is not None:
self.async_write_ha_state()
await handler()
return handler_to_return

View File

@@ -7,6 +7,6 @@
"documentation": "https://www.home-assistant.io/integrations/home_connect",
"iot_class": "cloud_push",
"loggers": ["aiohomeconnect"],
"requirements": ["aiohomeconnect==0.15.0"],
"requirements": ["aiohomeconnect==0.16.3"],
"single_config_entry": true
}

View File

@@ -25,7 +25,7 @@ from .const import (
UNIT_MAP,
)
from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
from .entity import HomeConnectEntity, HomeConnectOptionEntity
from .entity import HomeConnectEntity, HomeConnectOptionEntity, constraint_fetcher
from .utils import get_dict_from_home_connect_error
_LOGGER = logging.getLogger(__name__)
@@ -189,19 +189,25 @@ class HomeConnectNumberEntity(HomeConnectEntity, NumberEntity):
},
) from err
@constraint_fetcher
async def async_fetch_constraints(self) -> None:
"""Fetch the max and min values and step for the number entity."""
try:
setting_key = cast(SettingKey, self.bsh_key)
data = self.appliance.settings.get(setting_key)
if not data or not data.unit or not data.constraints:
data = await self.coordinator.client.get_setting(
self.appliance.info.ha_id, setting_key=SettingKey(self.bsh_key)
self.appliance.info.ha_id, setting_key=setting_key
)
except HomeConnectError as err:
_LOGGER.error("An error occurred: %s", err)
else:
if data.unit:
self._attr_native_unit_of_measurement = data.unit
self.set_constraints(data)
def set_constraints(self, setting: GetSetting) -> None:
"""Set constraints for the number entity."""
if setting.unit:
self._attr_native_unit_of_measurement = UNIT_MAP.get(
setting.unit, setting.unit
)
if not (constraints := setting.constraints):
return
if constraints.max:
@@ -222,10 +228,10 @@ class HomeConnectNumberEntity(HomeConnectEntity, NumberEntity):
"""When entity is added to hass."""
await super().async_added_to_hass()
data = self.appliance.settings[cast(SettingKey, self.bsh_key)]
self._attr_native_unit_of_measurement = data.unit
self.set_constraints(data)
if (
not hasattr(self, "_attr_native_min_value")
not hasattr(self, "_attr_native_unit_of_measurement")
or not hasattr(self, "_attr_native_min_value")
or not hasattr(self, "_attr_native_max_value")
or not hasattr(self, "_attr_native_step")
):
@@ -253,7 +259,6 @@ class HomeConnectOptionNumberEntity(HomeConnectOptionEntity, NumberEntity):
or candidate_unit != self._attr_native_unit_of_measurement
):
self._attr_native_unit_of_measurement = candidate_unit
self.__dict__.pop("unit_of_measurement", None)
option_constraints = option_definition.constraints
if option_constraints:
if (

View File

@@ -1,8 +1,8 @@
"""Provides a select platform for Home Connect."""
from collections.abc import Callable, Coroutine
import contextlib
from dataclasses import dataclass
import logging
from typing import Any, cast
from aiohomeconnect.client import Client as HomeConnectClient
@@ -47,9 +47,11 @@ from .coordinator import (
HomeConnectConfigEntry,
HomeConnectCoordinator,
)
from .entity import HomeConnectEntity, HomeConnectOptionEntity
from .entity import HomeConnectEntity, HomeConnectOptionEntity, constraint_fetcher
from .utils import bsh_key_to_translation_key, get_dict_from_home_connect_error
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 1
FUNCTIONAL_LIGHT_COLOR_TEMPERATURE_ENUM = {
@@ -413,6 +415,7 @@ class HomeConnectSelectEntity(HomeConnectEntity, SelectEntity):
"""Select setting class for Home Connect."""
entity_description: HomeConnectSelectEntityDescription
_original_option_keys: set[str | None]
def __init__(
self,
@@ -421,6 +424,7 @@ class HomeConnectSelectEntity(HomeConnectEntity, SelectEntity):
desc: HomeConnectSelectEntityDescription,
) -> None:
"""Initialize the entity."""
self._original_option_keys = set(desc.values_translation_key)
super().__init__(
coordinator,
appliance,
@@ -458,23 +462,29 @@ class HomeConnectSelectEntity(HomeConnectEntity, SelectEntity):
async def async_added_to_hass(self) -> None:
"""When entity is added to hass."""
await super().async_added_to_hass()
await self.async_fetch_options()
@constraint_fetcher
async def async_fetch_options(self) -> None:
"""Fetch options from the API."""
setting = self.appliance.settings.get(cast(SettingKey, self.bsh_key))
if (
not setting
or not setting.constraints
or not setting.constraints.allowed_values
):
with contextlib.suppress(HomeConnectError):
setting = await self.coordinator.client.get_setting(
self.appliance.info.ha_id,
setting_key=cast(SettingKey, self.bsh_key),
)
setting = await self.coordinator.client.get_setting(
self.appliance.info.ha_id,
setting_key=cast(SettingKey, self.bsh_key),
)
if setting and setting.constraints and setting.constraints.allowed_values:
self._original_option_keys = set(setting.constraints.allowed_values)
self._attr_options = [
self.entity_description.values_translation_key[option]
for option in setting.constraints.allowed_values
if option in self.entity_description.values_translation_key
for option in self._original_option_keys
if option is not None
and option in self.entity_description.values_translation_key
]
@@ -491,7 +501,7 @@ class HomeConnectSelectOptionEntity(HomeConnectOptionEntity, SelectEntity):
desc: HomeConnectSelectEntityDescription,
) -> None:
"""Initialize the entity."""
self._original_option_keys = set(desc.values_translation_key.keys())
self._original_option_keys = set(desc.values_translation_key)
super().__init__(
coordinator,
appliance,
@@ -524,5 +534,5 @@ class HomeConnectSelectOptionEntity(HomeConnectOptionEntity, SelectEntity):
self.entity_description.values_translation_key[option]
for option in self._original_option_keys
if option is not None
and option in self.entity_description.values_translation_key
]
self.__dict__.pop("options", None)

View File

@@ -1,12 +1,11 @@
"""Provides a sensor for Home Connect."""
import contextlib
from dataclasses import dataclass
from datetime import timedelta
import logging
from typing import cast
from aiohomeconnect.model import EventKey, StatusKey
from aiohomeconnect.model.error import HomeConnectError
from homeassistant.components.sensor import (
SensorDeviceClass,
@@ -28,7 +27,9 @@ from .const import (
UNIT_MAP,
)
from .coordinator import HomeConnectApplianceData, HomeConnectConfigEntry
from .entity import HomeConnectEntity
from .entity import HomeConnectEntity, constraint_fetcher
_LOGGER = logging.getLogger(__name__)
PARALLEL_UPDATES = 0
@@ -335,16 +336,14 @@ class HomeConnectSensor(HomeConnectEntity, SensorEntity):
else:
await self.fetch_unit()
@constraint_fetcher
async def fetch_unit(self) -> None:
"""Fetch the unit of measurement."""
with contextlib.suppress(HomeConnectError):
data = await self.coordinator.client.get_status_value(
self.appliance.info.ha_id, status_key=cast(StatusKey, self.bsh_key)
)
if data.unit:
self._attr_native_unit_of_measurement = UNIT_MAP.get(
data.unit, data.unit
)
data = await self.coordinator.client.get_status_value(
self.appliance.info.ha_id, status_key=cast(StatusKey, self.bsh_key)
)
if data.unit:
self._attr_native_unit_of_measurement = UNIT_MAP.get(data.unit, data.unit)
class HomeConnectProgramSensor(HomeConnectSensor):
@@ -386,6 +385,13 @@ class HomeConnectProgramSensor(HomeConnectSensor):
def update_native_value(self) -> None:
"""Update the program sensor's status."""
self.program_running = (
status := self.appliance.status.get(StatusKey.BSH_COMMON_OPERATION_STATE)
) is not None and status.value in [
BSH_OPERATION_STATE_RUN,
BSH_OPERATION_STATE_PAUSE,
BSH_OPERATION_STATE_FINISHED,
]
event = self.appliance.events.get(cast(EventKey, self.bsh_key))
if event:
self._update_native_value(event.value)

View File

@@ -468,11 +468,11 @@ set_program_and_options:
translation_key: venting_level
options:
- cooking_hood_enum_type_stage_fan_off
- cooking_hood_enum_type_stage_fan_stage01
- cooking_hood_enum_type_stage_fan_stage02
- cooking_hood_enum_type_stage_fan_stage03
- cooking_hood_enum_type_stage_fan_stage04
- cooking_hood_enum_type_stage_fan_stage05
- cooking_hood_enum_type_stage_fan_stage_01
- cooking_hood_enum_type_stage_fan_stage_02
- cooking_hood_enum_type_stage_fan_stage_03
- cooking_hood_enum_type_stage_fan_stage_04
- cooking_hood_enum_type_stage_fan_stage_05
cooking_hood_option_intensive_level:
example: cooking_hood_enum_type_intensive_stage_intensive_stage1
required: false
@@ -528,7 +528,7 @@ set_program_and_options:
collapsed: true
fields:
laundry_care_washer_option_temperature:
example: laundry_care_washer_enum_type_temperature_g_c40
example: laundry_care_washer_enum_type_temperature_g_c_40
required: false
selector:
select:
@@ -536,14 +536,14 @@ set_program_and_options:
translation_key: washer_temperature
options:
- laundry_care_washer_enum_type_temperature_cold
- laundry_care_washer_enum_type_temperature_g_c20
- laundry_care_washer_enum_type_temperature_g_c30
- laundry_care_washer_enum_type_temperature_g_c40
- laundry_care_washer_enum_type_temperature_g_c50
- laundry_care_washer_enum_type_temperature_g_c60
- laundry_care_washer_enum_type_temperature_g_c70
- laundry_care_washer_enum_type_temperature_g_c80
- laundry_care_washer_enum_type_temperature_g_c90
- laundry_care_washer_enum_type_temperature_g_c_20
- laundry_care_washer_enum_type_temperature_g_c_30
- laundry_care_washer_enum_type_temperature_g_c_40
- laundry_care_washer_enum_type_temperature_g_c_50
- laundry_care_washer_enum_type_temperature_g_c_60
- laundry_care_washer_enum_type_temperature_g_c_70
- laundry_care_washer_enum_type_temperature_g_c_80
- laundry_care_washer_enum_type_temperature_g_c_90
- laundry_care_washer_enum_type_temperature_ul_cold
- laundry_care_washer_enum_type_temperature_ul_warm
- laundry_care_washer_enum_type_temperature_ul_hot
@@ -557,13 +557,15 @@ set_program_and_options:
translation_key: spin_speed
options:
- laundry_care_washer_enum_type_spin_speed_off
- laundry_care_washer_enum_type_spin_speed_r_p_m400
- laundry_care_washer_enum_type_spin_speed_r_p_m600
- laundry_care_washer_enum_type_spin_speed_r_p_m800
- laundry_care_washer_enum_type_spin_speed_r_p_m1000
- laundry_care_washer_enum_type_spin_speed_r_p_m1200
- laundry_care_washer_enum_type_spin_speed_r_p_m1400
- laundry_care_washer_enum_type_spin_speed_r_p_m1600
- laundry_care_washer_enum_type_spin_speed_r_p_m_400
- laundry_care_washer_enum_type_spin_speed_r_p_m_600
- laundry_care_washer_enum_type_spin_speed_r_p_m_700
- laundry_care_washer_enum_type_spin_speed_r_p_m_800
- laundry_care_washer_enum_type_spin_speed_r_p_m_900
- laundry_care_washer_enum_type_spin_speed_r_p_m_1000
- laundry_care_washer_enum_type_spin_speed_r_p_m_1200
- laundry_care_washer_enum_type_spin_speed_r_p_m_1400
- laundry_care_washer_enum_type_spin_speed_r_p_m_1600
- laundry_care_washer_enum_type_spin_speed_ul_off
- laundry_care_washer_enum_type_spin_speed_ul_low
- laundry_care_washer_enum_type_spin_speed_ul_medium

View File

@@ -417,11 +417,11 @@
"venting_level": {
"options": {
"cooking_hood_enum_type_stage_fan_off": "Fan off",
"cooking_hood_enum_type_stage_fan_stage01": "Fan stage 1",
"cooking_hood_enum_type_stage_fan_stage02": "Fan stage 2",
"cooking_hood_enum_type_stage_fan_stage03": "Fan stage 3",
"cooking_hood_enum_type_stage_fan_stage04": "Fan stage 4",
"cooking_hood_enum_type_stage_fan_stage05": "Fan stage 5"
"cooking_hood_enum_type_stage_fan_stage_01": "Fan stage 1",
"cooking_hood_enum_type_stage_fan_stage_02": "Fan stage 2",
"cooking_hood_enum_type_stage_fan_stage_03": "Fan stage 3",
"cooking_hood_enum_type_stage_fan_stage_04": "Fan stage 4",
"cooking_hood_enum_type_stage_fan_stage_05": "Fan stage 5"
}
},
"intensive_level": {
@@ -441,14 +441,14 @@
"washer_temperature": {
"options": {
"laundry_care_washer_enum_type_temperature_cold": "Cold",
"laundry_care_washer_enum_type_temperature_g_c20": "20ºC clothes",
"laundry_care_washer_enum_type_temperature_g_c30": "30ºC clothes",
"laundry_care_washer_enum_type_temperature_g_c40": "40ºC clothes",
"laundry_care_washer_enum_type_temperature_g_c50": "50ºC clothes",
"laundry_care_washer_enum_type_temperature_g_c60": "60ºC clothes",
"laundry_care_washer_enum_type_temperature_g_c70": "70ºC clothes",
"laundry_care_washer_enum_type_temperature_g_c80": "80ºC clothes",
"laundry_care_washer_enum_type_temperature_g_c90": "90ºC clothes",
"laundry_care_washer_enum_type_temperature_g_c_20": "20ºC clothes",
"laundry_care_washer_enum_type_temperature_g_c_30": "30ºC clothes",
"laundry_care_washer_enum_type_temperature_g_c_40": "40ºC clothes",
"laundry_care_washer_enum_type_temperature_g_c_50": "50ºC clothes",
"laundry_care_washer_enum_type_temperature_g_c_60": "60ºC clothes",
"laundry_care_washer_enum_type_temperature_g_c_70": "70ºC clothes",
"laundry_care_washer_enum_type_temperature_g_c_80": "80ºC clothes",
"laundry_care_washer_enum_type_temperature_g_c_90": "90ºC clothes",
"laundry_care_washer_enum_type_temperature_ul_cold": "Cold",
"laundry_care_washer_enum_type_temperature_ul_warm": "Warm",
"laundry_care_washer_enum_type_temperature_ul_hot": "Hot",
@@ -458,13 +458,15 @@
"spin_speed": {
"options": {
"laundry_care_washer_enum_type_spin_speed_off": "Off",
"laundry_care_washer_enum_type_spin_speed_r_p_m400": "400 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m600": "600 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m800": "800 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m1000": "1000 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m1200": "1200 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m1400": "1400 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m1600": "1600 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m_400": "400 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m_600": "600 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m_700": "700 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m_800": "800 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m_900": "900 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m_1000": "1000 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m_1200": "1200 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m_1400": "1400 rpm",
"laundry_care_washer_enum_type_spin_speed_r_p_m_1600": "1600 rpm",
"laundry_care_washer_enum_type_spin_speed_ul_off": "Off",
"laundry_care_washer_enum_type_spin_speed_ul_low": "Low",
"laundry_care_washer_enum_type_spin_speed_ul_medium": "Medium",
@@ -1382,11 +1384,11 @@
"name": "[%key:component::home_connect::services::set_program_and_options::fields::cooking_hood_option_venting_level::name%]",
"state": {
"cooking_hood_enum_type_stage_fan_off": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_off%]",
"cooking_hood_enum_type_stage_fan_stage01": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage01%]",
"cooking_hood_enum_type_stage_fan_stage02": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage02%]",
"cooking_hood_enum_type_stage_fan_stage03": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage03%]",
"cooking_hood_enum_type_stage_fan_stage04": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage04%]",
"cooking_hood_enum_type_stage_fan_stage05": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage05%]"
"cooking_hood_enum_type_stage_fan_stage_01": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage_01%]",
"cooking_hood_enum_type_stage_fan_stage_02": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage_02%]",
"cooking_hood_enum_type_stage_fan_stage_03": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage_03%]",
"cooking_hood_enum_type_stage_fan_stage_04": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage_04%]",
"cooking_hood_enum_type_stage_fan_stage_05": "[%key:component::home_connect::selector::venting_level::options::cooking_hood_enum_type_stage_fan_stage_05%]"
}
},
"intensive_level": {
@@ -1409,14 +1411,14 @@
"name": "[%key:component::home_connect::services::set_program_and_options::fields::laundry_care_washer_option_temperature::name%]",
"state": {
"laundry_care_washer_enum_type_temperature_cold": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_cold%]",
"laundry_care_washer_enum_type_temperature_g_c20": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c20%]",
"laundry_care_washer_enum_type_temperature_g_c30": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c30%]",
"laundry_care_washer_enum_type_temperature_g_c40": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c40%]",
"laundry_care_washer_enum_type_temperature_g_c50": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c50%]",
"laundry_care_washer_enum_type_temperature_g_c60": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c60%]",
"laundry_care_washer_enum_type_temperature_g_c70": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c70%]",
"laundry_care_washer_enum_type_temperature_g_c80": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c80%]",
"laundry_care_washer_enum_type_temperature_g_c90": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c90%]",
"laundry_care_washer_enum_type_temperature_g_c_20": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c_20%]",
"laundry_care_washer_enum_type_temperature_g_c_30": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c_30%]",
"laundry_care_washer_enum_type_temperature_g_c_40": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c_40%]",
"laundry_care_washer_enum_type_temperature_g_c_50": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c_50%]",
"laundry_care_washer_enum_type_temperature_g_c_60": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c_60%]",
"laundry_care_washer_enum_type_temperature_g_c_70": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c_70%]",
"laundry_care_washer_enum_type_temperature_g_c_80": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c_80%]",
"laundry_care_washer_enum_type_temperature_g_c_90": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_g_c_90%]",
"laundry_care_washer_enum_type_temperature_ul_cold": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_ul_cold%]",
"laundry_care_washer_enum_type_temperature_ul_warm": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_ul_warm%]",
"laundry_care_washer_enum_type_temperature_ul_hot": "[%key:component::home_connect::selector::washer_temperature::options::laundry_care_washer_enum_type_temperature_ul_hot%]",
@@ -1427,13 +1429,15 @@
"name": "[%key:component::home_connect::services::set_program_and_options::fields::laundry_care_washer_option_spin_speed::name%]",
"state": {
"laundry_care_washer_enum_type_spin_speed_off": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_off%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m400": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m400%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m600": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m600%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m800": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m800%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m1000": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m1000%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m1200": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m1200%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m1400": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m1400%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m1600": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m1600%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m_400": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_400%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m_600": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_600%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m_700": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_700%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m_800": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_800%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m_900": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_900%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m_1000": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_1000%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m_1200": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_1200%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m_1400": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_1400%]",
"laundry_care_washer_enum_type_spin_speed_r_p_m_1600": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_r_p_m_1600%]",
"laundry_care_washer_enum_type_spin_speed_ul_off": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_off%]",
"laundry_care_washer_enum_type_spin_speed_ul_low": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_low%]",
"laundry_care_washer_enum_type_spin_speed_ul_medium": "[%key:component::home_connect::selector::spin_speed::options::laundry_care_washer_enum_type_spin_speed_ul_medium%]",

View File

@@ -1,6 +1,12 @@
{
"entity": {
"sensor": {
"brightness": {
"default": "mdi:brightness-5"
},
"brightness_instance": {
"default": "mdi:brightness-5"
},
"link_quality": {
"default": "mdi:signal"
},
@@ -9,7 +15,7 @@
}
},
"switch": {
"watchdog_on_off": {
"watchdog": {
"default": "mdi:dog"
},
"manual_operation": {

View File

@@ -40,10 +40,22 @@ def get_window_value(attribute: HomeeAttribute) -> str | None:
return vals.get(attribute.current_value)
def get_brightness_device_class(
attribute: HomeeAttribute, device_class: SensorDeviceClass | None
) -> SensorDeviceClass | None:
"""Return the device class for a brightness sensor."""
if attribute.unit == "%":
return None
return device_class
@dataclass(frozen=True, kw_only=True)
class HomeeSensorEntityDescription(SensorEntityDescription):
"""A class that describes Homee sensor entities."""
device_class_fn: Callable[
[HomeeAttribute, SensorDeviceClass | None], SensorDeviceClass | None
] = lambda attribute, device_class: device_class
value_fn: Callable[[HomeeAttribute], str | float | None] = (
lambda value: value.current_value
)
@@ -67,6 +79,7 @@ SENSOR_DESCRIPTIONS: dict[AttributeType, HomeeSensorEntityDescription] = {
AttributeType.BRIGHTNESS: HomeeSensorEntityDescription(
key="brightness",
device_class=SensorDeviceClass.ILLUMINANCE,
device_class_fn=get_brightness_device_class,
state_class=SensorStateClass.MEASUREMENT,
value_fn=(
lambda attribute: attribute.current_value * 1000
@@ -303,6 +316,9 @@ class HomeeSensor(HomeeEntity, SensorEntity):
if attribute.instance > 0:
self._attr_translation_key = f"{self._attr_translation_key}_instance"
self._attr_translation_placeholders = {"instance": str(attribute.instance)}
self._attr_device_class = description.device_class_fn(
attribute, description.device_class
)
@property
def native_value(self) -> float | str | None:

View File

@@ -111,6 +111,9 @@
}
},
"sensor": {
"brightness": {
"name": "Illuminance"
},
"brightness_instance": {
"name": "Illuminance {instance}"
},

View File

@@ -154,7 +154,6 @@ class HKDevice:
self._pending_subscribes: set[tuple[int, int]] = set()
self._subscribe_timer: CALLBACK_TYPE | None = None
self._load_platforms_lock = asyncio.Lock()
self._full_update_requested: bool = False
@property
def entity_map(self) -> Accessories:
@@ -841,48 +840,11 @@ class HKDevice:
async def async_request_update(self, now: datetime | None = None) -> None:
"""Request an debounced update from the accessory."""
self._full_update_requested = True
await self._debounced_update.async_call()
async def async_update(self, now: datetime | None = None) -> None:
"""Poll state of all entities attached to this bridge/accessory."""
to_poll = self.pollable_characteristics
accessories = self.entity_map.accessories
if (
not self._full_update_requested
and len(accessories) == 1
and self.available
and not (to_poll - self.watchable_characteristics)
and self.pairing.is_available
and await self.pairing.controller.async_reachable(
self.unique_id, timeout=5.0
)
):
# If its a single accessory and all chars are watchable,
# only poll the firmware version to keep the connection alive
# https://github.com/home-assistant/core/issues/123412
#
# Firmware revision is used here since iOS does this to keep camera
# connections alive, and the goal is to not regress
# https://github.com/home-assistant/core/issues/116143
# by polling characteristics that are not normally polled frequently
# and may not be tested by the device vendor.
#
_LOGGER.debug(
"Accessory is reachable, limiting poll to firmware version: %s",
self.unique_id,
)
first_accessory = accessories[0]
accessory_info = first_accessory.services.first(
service_type=ServicesTypes.ACCESSORY_INFORMATION
)
assert accessory_info is not None
firmware_iid = accessory_info[CharacteristicsTypes.FIRMWARE_REVISION].iid
to_poll = {(first_accessory.aid, firmware_iid)}
self._full_update_requested = False
if not to_poll:
self.async_update_available_state()
_LOGGER.debug(

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.7"],
"requirements": ["aiohomekit==3.2.8"],
"zeroconf": ["_hap._tcp.local.", "_hap._udp.local."]
}

View File

@@ -3,6 +3,7 @@
"step": {
"init": {
"title": "Pick Homematic IP access point",
"description": "If you are about to register a **Homematic IP HCU1**, please press the button on top of the device before you continue.\n\nThe registration process must be completed within 5 minutes.",
"data": {
"hapid": "Access point ID (SGTIN)",
"pin": "[%key:common::config_flow::data::pin%]",

View File

@@ -94,7 +94,12 @@ async def async_setup_devices(bridge: HueBridge):
add_device(hue_resource)
# create/update all current devices found in controllers
known_devices = [add_device(hue_device) for hue_device in dev_controller]
# sort the devices to ensure bridges are added first
hue_devices = list(dev_controller)
hue_devices.sort(
key=lambda dev: dev.metadata.archetype != DeviceArchetypes.BRIDGE_V2
)
known_devices = [add_device(hue_device) for hue_device in hue_devices]
known_devices += [add_device(hue_room) for hue_room in api.groups.room]
known_devices += [add_device(hue_zone) for hue_zone in api.groups.zone]

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/hydrawise",
"iot_class": "cloud_polling",
"loggers": ["pydrawise"],
"requirements": ["pydrawise==2025.2.0"]
"requirements": ["pydrawise==2025.3.0"]
}

View File

@@ -280,7 +280,7 @@ class ImapDataUpdateCoordinator(DataUpdateCoordinator[int | None]):
if self.custom_event_template is not None:
try:
data["custom"] = self.custom_event_template.async_render(
data, parse_result=True
data | {"text": message.text}, parse_result=True
)
_LOGGER.debug(
"IMAP custom template (%s) for msguid %s (%s) rendered to: %s, initial: %s",

View File

@@ -28,5 +28,5 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/inkbird",
"iot_class": "local_push",
"requirements": ["inkbird-ble==0.7.0"]
"requirements": ["inkbird-ble==0.7.1"]
}

View File

@@ -9,6 +9,7 @@ from aiohttp import web
import voluptuous as vol
from homeassistant.components import http
from homeassistant.components.climate import DOMAIN as CLIMATE_DOMAIN
from homeassistant.components.cover import (
ATTR_POSITION,
DOMAIN as COVER_DOMAIN,
@@ -140,6 +141,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
intent.async_register(hass, GetCurrentDateIntentHandler())
intent.async_register(hass, GetCurrentTimeIntentHandler())
intent.async_register(hass, RespondIntentHandler())
intent.async_register(hass, GetTemperatureIntent())
return True
@@ -444,6 +446,48 @@ class RespondIntentHandler(intent.IntentHandler):
return response
class GetTemperatureIntent(intent.IntentHandler):
"""Handle GetTemperature intents."""
intent_type = intent.INTENT_GET_TEMPERATURE
description = "Gets the current temperature of a climate device or entity"
slot_schema = {
vol.Optional("area"): intent.non_empty_string,
vol.Optional("name"): intent.non_empty_string,
}
platforms = {CLIMATE_DOMAIN}
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
"""Handle the intent."""
hass = intent_obj.hass
slots = self.async_validate_slots(intent_obj.slots)
name: str | None = None
if "name" in slots:
name = slots["name"]["value"]
area: str | None = None
if "area" in slots:
area = slots["area"]["value"]
match_constraints = intent.MatchTargetsConstraints(
name=name,
area_name=area,
domains=[CLIMATE_DOMAIN],
assistant=intent_obj.assistant,
)
match_result = intent.async_match_targets(hass, match_constraints)
if not match_result.is_match:
raise intent.MatchFailedError(
result=match_result, constraints=match_constraints
)
response = intent_obj.create_response()
response.response_type = intent.IntentResponseType.QUERY_ANSWER
response.async_set_states(matched_states=match_result.states)
return response
async def _async_process_intent(
hass: HomeAssistant, domain: str, platform: IntentPlatformProtocol
) -> None:

View File

@@ -11,7 +11,7 @@
"loggers": ["xknx", "xknxproject"],
"requirements": [
"xknx==3.6.0",
"xknxproject==3.8.1",
"xknxproject==3.8.2",
"knx-frontend==2025.1.30.194235"
],
"single_config_entry": true

View File

@@ -61,6 +61,42 @@ async def async_setup_entry(hass: HomeAssistant, entry: LaMarzoccoConfigEntry) -
client=client,
)
# initialize the firmware update coordinator early to check the firmware version
firmware_device = LaMarzoccoMachine(
model=entry.data[CONF_MODEL],
serial_number=entry.unique_id,
name=entry.data[CONF_NAME],
cloud_client=cloud_client,
)
firmware_coordinator = LaMarzoccoFirmwareUpdateCoordinator(
hass, entry, firmware_device
)
await firmware_coordinator.async_config_entry_first_refresh()
gateway_version = version.parse(
firmware_device.firmware[FirmwareType.GATEWAY].current_version
)
if gateway_version >= version.parse("v5.0.9"):
# remove host from config entry, it is not supported anymore
data = {k: v for k, v in entry.data.items() if k != CONF_HOST}
hass.config_entries.async_update_entry(
entry,
data=data,
)
elif gateway_version < version.parse("v3.4-rc5"):
# incompatible gateway firmware, create an issue
ir.async_create_issue(
hass,
DOMAIN,
"unsupported_gateway_firmware",
is_fixable=False,
severity=ir.IssueSeverity.ERROR,
translation_key="unsupported_gateway_firmware",
translation_placeholders={"gateway_version": str(gateway_version)},
)
# initialize local API
local_client: LaMarzoccoLocalClient | None = None
if (host := entry.data.get(CONF_HOST)) is not None:
@@ -117,30 +153,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: LaMarzoccoConfigEntry) -
coordinators = LaMarzoccoRuntimeData(
LaMarzoccoConfigUpdateCoordinator(hass, entry, device, local_client),
LaMarzoccoFirmwareUpdateCoordinator(hass, entry, device),
firmware_coordinator,
LaMarzoccoStatisticsUpdateCoordinator(hass, entry, device),
)
# API does not like concurrent requests, so no asyncio.gather here
await coordinators.config_coordinator.async_config_entry_first_refresh()
await coordinators.firmware_coordinator.async_config_entry_first_refresh()
await coordinators.statistics_coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinators
gateway_version = device.firmware[FirmwareType.GATEWAY].current_version
if version.parse(gateway_version) < version.parse("v3.4-rc5"):
# incompatible gateway firmware, create an issue
ir.async_create_issue(
hass,
DOMAIN,
"unsupported_gateway_firmware",
is_fixable=False,
severity=ir.IssueSeverity.ERROR,
translation_key="unsupported_gateway_firmware",
translation_placeholders={"gateway_version": gateway_version},
)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
async def update_listener(

View File

@@ -37,5 +37,5 @@
"iot_class": "cloud_polling",
"loggers": ["pylamarzocco"],
"quality_scale": "platinum",
"requirements": ["pylamarzocco==1.4.7"]
"requirements": ["pylamarzocco==1.4.9"]
}

View File

@@ -144,9 +144,12 @@ KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
set_value_fn=lambda machine, value, key: machine.set_prebrew_time(
prebrew_off_time=value, key=key
),
native_value_fn=lambda config, key: config.prebrew_configuration[key].off_time,
native_value_fn=lambda config, key: config.prebrew_configuration[key][
0
].off_time,
available_fn=lambda device: len(device.config.prebrew_configuration) > 0
and device.config.prebrew_mode == PrebrewMode.PREBREW,
and device.config.prebrew_mode
in (PrebrewMode.PREBREW, PrebrewMode.PREBREW_ENABLED),
supported_fn=lambda coordinator: coordinator.device.model
!= MachineModel.GS3_MP,
),
@@ -162,9 +165,12 @@ KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
set_value_fn=lambda machine, value, key: machine.set_prebrew_time(
prebrew_on_time=value, key=key
),
native_value_fn=lambda config, key: config.prebrew_configuration[key].off_time,
native_value_fn=lambda config, key: config.prebrew_configuration[key][
0
].off_time,
available_fn=lambda device: len(device.config.prebrew_configuration) > 0
and device.config.prebrew_mode == PrebrewMode.PREBREW,
and device.config.prebrew_mode
in (PrebrewMode.PREBREW, PrebrewMode.PREBREW_ENABLED),
supported_fn=lambda coordinator: coordinator.device.model
!= MachineModel.GS3_MP,
),
@@ -180,8 +186,8 @@ KEY_ENTITIES: tuple[LaMarzoccoKeyNumberEntityDescription, ...] = (
set_value_fn=lambda machine, value, key: machine.set_preinfusion_time(
preinfusion_time=value, key=key
),
native_value_fn=lambda config, key: config.prebrew_configuration[
key
native_value_fn=lambda config, key: config.prebrew_configuration[key][
1
].preinfusion_time,
available_fn=lambda device: len(device.config.prebrew_configuration) > 0
and device.config.prebrew_mode == PrebrewMode.PREINFUSION,

View File

@@ -38,6 +38,7 @@ STEAM_LEVEL_LM_TO_HA = {value: key for key, value in STEAM_LEVEL_HA_TO_LM.items(
PREBREW_MODE_HA_TO_LM = {
"disabled": PrebrewMode.DISABLED,
"prebrew": PrebrewMode.PREBREW,
"prebrew_enabled": PrebrewMode.PREBREW_ENABLED,
"preinfusion": PrebrewMode.PREINFUSION,
}

View File

@@ -148,6 +148,7 @@
"state": {
"disabled": "Disabled",
"prebrew": "Prebrew",
"prebrew_enabled": "Prebrew",
"preinfusion": "Preinfusion"
}
},

View File

@@ -110,7 +110,9 @@ class ThinQClimateEntity(ThinQEntity, ClimateEntity):
self._attr_hvac_modes = [HVACMode.OFF]
self._attr_hvac_mode = HVACMode.OFF
self._attr_preset_modes = []
self._attr_temperature_unit = UnitOfTemperature.CELSIUS
self._attr_temperature_unit = (
self._get_unit_of_measurement(self.data.unit) or UnitOfTemperature.CELSIUS
)
self._requested_hvac_mode: str | None = None
# Set up HVAC modes.
@@ -182,6 +184,11 @@ class ThinQClimateEntity(ThinQEntity, ClimateEntity):
self._attr_target_temperature_high = self.data.target_temp_high
self._attr_target_temperature_low = self.data.target_temp_low
# Update unit.
self._attr_temperature_unit = (
self._get_unit_of_measurement(self.data.unit) or UnitOfTemperature.CELSIUS
)
_LOGGER.debug(
"[%s:%s] update status: c:%s, t:%s, l:%s, h:%s, hvac:%s, unit:%s, step:%s",
self.coordinator.device_name,

View File

@@ -3,6 +3,8 @@
from datetime import timedelta
from typing import Final
from homeassistant.const import UnitOfTemperature
# Config flow
DOMAIN = "lg_thinq"
COMPANY = "LGE"
@@ -18,3 +20,10 @@ MQTT_SUBSCRIPTION_INTERVAL: Final = timedelta(days=1)
# MQTT: Message types
DEVICE_PUSH_MESSAGE: Final = "DEVICE_PUSH"
DEVICE_STATUS_MESSAGE: Final = "DEVICE_STATUS"
# Unit conversion map
DEVICE_UNIT_TO_HA: dict[str, str] = {
"F": UnitOfTemperature.FAHRENHEIT,
"C": UnitOfTemperature.CELSIUS,
}
REVERSE_DEVICE_UNIT_TO_HA = {v: k for k, v in DEVICE_UNIT_TO_HA.items()}

View File

@@ -2,19 +2,21 @@
from __future__ import annotations
from collections.abc import Mapping
import logging
from typing import TYPE_CHECKING, Any
from thinqconnect import ThinQAPIException
from thinqconnect.integration import HABridge
from homeassistant.core import HomeAssistant
from homeassistant.const import EVENT_CORE_CONFIG_UPDATE
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
if TYPE_CHECKING:
from . import ThinqConfigEntry
from .const import DOMAIN
from .const import DOMAIN, REVERSE_DEVICE_UNIT_TO_HA
_LOGGER = logging.getLogger(__name__)
@@ -54,6 +56,40 @@ class DeviceDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
f"{self.device_id}_{self.sub_id}" if self.sub_id else self.device_id
)
# Set your preferred temperature unit. This will allow us to retrieve
# temperature values from the API in a converted value corresponding to
# preferred unit.
self._update_preferred_temperature_unit()
# 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,
)
async def _handle_update_config(self, _: Event) -> None:
"""Handle update core config."""
self._update_preferred_temperature_unit()
await self.async_refresh()
@callback
def async_config_update_filter(self, event_data: Mapping[str, Any]) -> bool:
"""Filter out unwanted events."""
if (unit_system := event_data.get("unit_system")) != self.unit_system:
self.unit_system = unit_system
return True
return False
def _update_preferred_temperature_unit(self) -> None:
"""Update preferred temperature unit."""
self.api.set_preferred_temperature_unit(
REVERSE_DEVICE_UNIT_TO_HA.get(self.hass.config.units.temperature_unit)
)
async def _async_update_data(self) -> dict[str, Any]:
"""Request to the server to update the status from full response data."""
try:

View File

@@ -10,25 +10,19 @@ from thinqconnect import ThinQAPIException
from thinqconnect.devices.const import Location
from thinqconnect.integration import PropertyState
from homeassistant.const import UnitOfTemperature
from homeassistant.core import callback
from homeassistant.exceptions import ServiceValidationError
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.entity import EntityDescription
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import COMPANY, DOMAIN
from .const import COMPANY, DEVICE_UNIT_TO_HA, DOMAIN
from .coordinator import DeviceDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
EMPTY_STATE = PropertyState()
UNIT_CONVERSION_MAP: dict[str, str] = {
"F": UnitOfTemperature.FAHRENHEIT,
"C": UnitOfTemperature.CELSIUS,
}
class ThinQEntity(CoordinatorEntity[DeviceDataUpdateCoordinator]):
"""The base implementation of all lg thinq entities."""
@@ -75,7 +69,7 @@ class ThinQEntity(CoordinatorEntity[DeviceDataUpdateCoordinator]):
if unit is None:
return None
return UNIT_CONVERSION_MAP.get(unit)
return DEVICE_UNIT_TO_HA.get(unit)
def _update_status(self) -> None:
"""Update status itself.

View File

@@ -1,6 +1,6 @@
{
"domain": "matter",
"name": "Matter (BETA)",
"name": "Matter",
"after_dependencies": ["hassio"],
"codeowners": ["@home-assistant/matter"],
"config_flow": true,

View File

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

View File

@@ -15,6 +15,7 @@ import socket
import ssl
import time
from typing import TYPE_CHECKING, Any
from uuid import uuid4
import certifi
@@ -292,7 +293,7 @@ class MqttClientSetup:
"""
# We don't import on the top because some integrations
# should be able to optionally rely on MQTT.
import paho.mqtt.client as mqtt # pylint: disable=import-outside-toplevel
from paho.mqtt import client as mqtt # pylint: disable=import-outside-toplevel
# pylint: disable-next=import-outside-toplevel
from .async_client import AsyncMQTTClient
@@ -309,9 +310,10 @@ class MqttClientSetup:
clean_session = True
if (client_id := config.get(CONF_CLIENT_ID)) is None:
# PAHO MQTT relies on the MQTT server to generate random client IDs.
# However, that feature is not mandatory so we generate our own.
client_id = None
# PAHO MQTT relies on the MQTT server to generate random client ID
# for protocol version 3.1, however, that feature is not mandatory
# so we generate our own.
client_id = mqtt._base62(uuid4().int, padding=22) # noqa: SLF001
transport: str = config.get(CONF_TRANSPORT, DEFAULT_TRANSPORT)
self._client = AsyncMQTTClient(
callback_api_version=mqtt.CallbackAPIVersion.VERSION2,

View File

@@ -31,7 +31,6 @@ from homeassistant.components.light import (
LightEntity,
LightEntityFeature,
brightness_supported,
color_supported,
valid_supported_color_modes,
)
from homeassistant.const import (
@@ -217,6 +216,10 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
self._attr_color_mode = next(iter(self.supported_color_modes))
else:
self._attr_color_mode = ColorMode.UNKNOWN
elif config.get(CONF_BRIGHTNESS):
# Brightness is supported and no supported_color_modes are set,
# so set brightness as the supported color mode.
self._attr_supported_color_modes = {ColorMode.BRIGHTNESS}
def _update_color(self, values: dict[str, Any]) -> None:
color_mode: str = values["color_mode"]
@@ -289,7 +292,7 @@ class MqttLightJson(MqttEntity, LightEntity, RestoreEntity):
elif values["state"] is None:
self._attr_is_on = None
if color_supported(self.supported_color_modes) and "color_mode" in values:
if "color_mode" in values:
self._update_color(values)
if brightness_supported(self.supported_color_modes):

View File

@@ -9,7 +9,7 @@ from typing import TYPE_CHECKING
from music_assistant_client import MusicAssistantClient
from music_assistant_client.exceptions import CannotConnect, InvalidServerVersion
from music_assistant_models.enums import EventType
from music_assistant_models.errors import MusicAssistantError
from music_assistant_models.errors import ActionUnavailable, MusicAssistantError
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.const import CONF_URL, EVENT_HOMEASSISTANT_STOP, Platform
@@ -23,7 +23,7 @@ from homeassistant.helpers.issue_registry import (
async_delete_issue,
)
from .actions import register_actions
from .actions import get_music_assistant_client, register_actions
from .const import DOMAIN, LOGGER
if TYPE_CHECKING:
@@ -137,6 +137,18 @@ async def async_setup_entry(
mass.subscribe(handle_player_removed, EventType.PLAYER_REMOVED)
)
# check if any playerconfigs have been removed while we were disconnected
all_player_configs = await mass.config.get_player_configs()
player_ids = {player.player_id for player in all_player_configs}
dev_reg = dr.async_get(hass)
dev_entries = dr.async_entries_for_config_entry(dev_reg, entry.entry_id)
for device in dev_entries:
for identifier in device.identifiers:
if identifier[0] == DOMAIN and identifier[1] not in player_ids:
dev_reg.async_update_device(
device.id, remove_config_entry_id=entry.entry_id
)
return True
@@ -174,3 +186,31 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
await mass_entry_data.mass.disconnect()
return unload_ok
async def async_remove_config_entry_device(
hass: HomeAssistant, config_entry: ConfigEntry, device_entry: dr.DeviceEntry
) -> bool:
"""Remove a config entry from a device."""
player_id = next(
(
identifier[1]
for identifier in device_entry.identifiers
if identifier[0] == DOMAIN
),
None,
)
if player_id is None:
# this should not be possible at all, but guard it anyways
return False
mass = get_music_assistant_client(hass, config_entry.entry_id)
if mass.players.get(player_id) is None:
# player is already removed on the server, this is an orphaned device
return True
# try to remove the player from the server
try:
await mass.config.remove_player_config(player_id)
except ActionUnavailable:
return False
else:
return True

View File

@@ -23,6 +23,7 @@ from .const import (
ATTR_ALBUM_TYPE,
ATTR_ALBUMS,
ATTR_ARTISTS,
ATTR_AUDIOBOOKS,
ATTR_CONFIG_ENTRY_ID,
ATTR_FAVORITE,
ATTR_ITEMS,
@@ -32,6 +33,7 @@ from .const import (
ATTR_OFFSET,
ATTR_ORDER_BY,
ATTR_PLAYLISTS,
ATTR_PODCASTS,
ATTR_RADIO,
ATTR_SEARCH,
ATTR_SEARCH_ALBUM,
@@ -48,7 +50,15 @@ from .schemas import (
if TYPE_CHECKING:
from music_assistant_client import MusicAssistantClient
from music_assistant_models.media_items import Album, Artist, Playlist, Radio, Track
from music_assistant_models.media_items import (
Album,
Artist,
Audiobook,
Playlist,
Podcast,
Radio,
Track,
)
from . import MusicAssistantConfigEntry
@@ -155,6 +165,14 @@ async def handle_search(call: ServiceCall) -> ServiceResponse:
media_item_dict_from_mass_item(mass, item)
for item in search_results.radio
],
ATTR_AUDIOBOOKS: [
media_item_dict_from_mass_item(mass, item)
for item in search_results.audiobooks
],
ATTR_PODCASTS: [
media_item_dict_from_mass_item(mass, item)
for item in search_results.podcasts
],
}
)
return response
@@ -175,7 +193,13 @@ async def handle_get_library(call: ServiceCall) -> ServiceResponse:
"order_by": order_by,
}
library_result: (
list[Album] | list[Artist] | list[Track] | list[Radio] | list[Playlist]
list[Album]
| list[Artist]
| list[Track]
| list[Radio]
| list[Playlist]
| list[Audiobook]
| list[Podcast]
)
if media_type == MediaType.ALBUM:
library_result = await mass.music.get_library_albums(
@@ -199,6 +223,14 @@ async def handle_get_library(call: ServiceCall) -> ServiceResponse:
library_result = await mass.music.get_library_playlists(
**base_params,
)
elif media_type == MediaType.AUDIOBOOK:
library_result = await mass.music.get_library_audiobooks(
**base_params,
)
elif media_type == MediaType.PODCAST:
library_result = await mass.music.get_library_podcasts(
**base_params,
)
else:
raise ServiceValidationError(f"Unsupported media type {media_type}")

View File

@@ -34,6 +34,8 @@ ATTR_ARTISTS = "artists"
ATTR_ALBUMS = "albums"
ATTR_TRACKS = "tracks"
ATTR_PLAYLISTS = "playlists"
ATTR_AUDIOBOOKS = "audiobooks"
ATTR_PODCASTS = "podcasts"
ATTR_RADIO = "radio"
ATTR_ITEMS = "items"
ATTR_RADIO_MODE = "radio_mode"

View File

@@ -9,6 +9,7 @@ import functools
import os
from typing import TYPE_CHECKING, Any, Concatenate
from music_assistant_models.constants import PLAYER_CONTROL_NONE
from music_assistant_models.enums import (
EventType,
MediaType,
@@ -80,19 +81,14 @@ if TYPE_CHECKING:
from music_assistant_client import MusicAssistantClient
from music_assistant_models.player import Player
SUPPORTED_FEATURES = (
MediaPlayerEntityFeature.PAUSE
| MediaPlayerEntityFeature.VOLUME_SET
| MediaPlayerEntityFeature.STOP
SUPPORTED_FEATURES_BASE = (
MediaPlayerEntityFeature.STOP
| MediaPlayerEntityFeature.PREVIOUS_TRACK
| MediaPlayerEntityFeature.NEXT_TRACK
| MediaPlayerEntityFeature.SHUFFLE_SET
| MediaPlayerEntityFeature.REPEAT_SET
| MediaPlayerEntityFeature.TURN_ON
| MediaPlayerEntityFeature.TURN_OFF
| MediaPlayerEntityFeature.PLAY
| MediaPlayerEntityFeature.PLAY_MEDIA
| MediaPlayerEntityFeature.VOLUME_STEP
| MediaPlayerEntityFeature.CLEAR_PLAYLIST
| MediaPlayerEntityFeature.BROWSE_MEDIA
| MediaPlayerEntityFeature.MEDIA_ENQUEUE
@@ -212,11 +208,7 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
"""Initialize MediaPlayer entity."""
super().__init__(mass, player_id)
self._attr_icon = self.player.icon.replace("mdi-", "mdi:")
self._attr_supported_features = SUPPORTED_FEATURES
if PlayerFeature.SET_MEMBERS in self.player.supported_features:
self._attr_supported_features |= MediaPlayerEntityFeature.GROUPING
if PlayerFeature.VOLUME_MUTE in self.player.supported_features:
self._attr_supported_features |= MediaPlayerEntityFeature.VOLUME_MUTE
self._set_supported_features()
self._attr_device_class = MediaPlayerDeviceClass.SPEAKER
self._prev_time: float = 0
@@ -241,6 +233,19 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
)
)
# we subscribe to the player config changed event to update
# the supported features of the player
async def player_config_changed(event: MassEvent) -> None:
self._set_supported_features()
await self.async_on_update()
self.async_write_ha_state()
self.async_on_remove(
self.mass.subscribe(
player_config_changed, EventType.PLAYER_CONFIG_UPDATED, self.player_id
)
)
@property
def active_queue(self) -> PlayerQueue | None:
"""Return the active queue for this player (if any)."""
@@ -271,22 +276,26 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
self._attr_state = MediaPlayerState(player.state.value)
else:
self._attr_state = MediaPlayerState(STATE_OFF)
group_members_entity_ids: list[str] = []
group_members: list[str] = []
if player.group_childs:
# translate MA group_childs to HA group_members as entity id's
entity_registry = er.async_get(self.hass)
group_members_entity_ids = [
entity_id
for child_id in player.group_childs
if (
entity_id := entity_registry.async_get_entity_id(
self.platform.domain, DOMAIN, child_id
)
group_members = player.group_childs
elif player.synced_to and (parent := self.mass.players.get(player.synced_to)):
group_members = parent.group_childs
# translate MA group_childs to HA group_members as entity id's
entity_registry = er.async_get(self.hass)
group_members_entity_ids: list[str] = [
entity_id
for child_id in group_members
if (
entity_id := entity_registry.async_get_entity_id(
self.platform.domain, DOMAIN, child_id
)
]
# NOTE: we sort the group_members for now,
# until the MA API returns them sorted (group_childs is now a set)
self._attr_group_members = sorted(group_members_entity_ids)
)
]
self._attr_group_members = group_members_entity_ids
self._attr_volume_level = (
player.volume_level / 100 if player.volume_level is not None else None
)
@@ -682,3 +691,20 @@ class MusicAssistantPlayer(MusicAssistantEntity, MediaPlayerEntity):
if isinstance(queue_option, MediaPlayerEnqueue):
queue_option = QUEUE_OPTION_MAP.get(queue_option)
return queue_option
def _set_supported_features(self) -> None:
"""Set supported features based on player capabilities."""
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:
supported_features |= MediaPlayerEntityFeature.VOLUME_STEP
supported_features |= MediaPlayerEntityFeature.VOLUME_SET
if self.player.power_control != PLAYER_CONTROL_NONE:
supported_features |= MediaPlayerEntityFeature.TURN_ON
supported_features |= MediaPlayerEntityFeature.TURN_OFF
self._attr_supported_features = supported_features

View File

@@ -15,6 +15,7 @@ from .const import (
ATTR_ALBUM,
ATTR_ALBUMS,
ATTR_ARTISTS,
ATTR_AUDIOBOOKS,
ATTR_BIT_DEPTH,
ATTR_CONTENT_TYPE,
ATTR_CURRENT_INDEX,
@@ -31,6 +32,7 @@ from .const import (
ATTR_OFFSET,
ATTR_ORDER_BY,
ATTR_PLAYLISTS,
ATTR_PODCASTS,
ATTR_PROVIDER,
ATTR_QUEUE_ID,
ATTR_QUEUE_ITEM_ID,
@@ -101,6 +103,12 @@ SEARCH_RESULT_SCHEMA = vol.Schema(
vol.Required(ATTR_RADIO): vol.All(
cv.ensure_list, [vol.Schema(MEDIA_ITEM_SCHEMA)]
),
vol.Required(ATTR_AUDIOBOOKS): vol.All(
cv.ensure_list, [vol.Schema(MEDIA_ITEM_SCHEMA)]
),
vol.Required(ATTR_PODCASTS): vol.All(
cv.ensure_list, [vol.Schema(MEDIA_ITEM_SCHEMA)]
),
},
)

View File

@@ -21,7 +21,10 @@ play_media:
options:
- artist
- album
- audiobook
- folder
- playlist
- podcast
- track
- radio
artist:
@@ -118,7 +121,9 @@ search:
options:
- artist
- album
- audiobook
- playlist
- podcast
- track
- radio
artist:
@@ -160,7 +165,9 @@ get_library:
options:
- artist
- album
- audiobook
- playlist
- podcast
- track
- radio
favorite:

View File

@@ -195,8 +195,11 @@
"options": {
"artist": "Artist",
"album": "Album",
"audiobook": "Audiobook",
"folder": "Folder",
"track": "Track",
"playlist": "Playlist",
"podcast": "Podcast",
"radio": "Radio"
}
},

View File

@@ -20,7 +20,7 @@ from homeassistant.exceptions import PlatformNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import ConfigType, DiscoveryInfoType
from homeassistant.util import Throttle
from homeassistant.util import Throttle, dt as dt_util
_LOGGER = logging.getLogger(__name__)
@@ -119,6 +119,8 @@ class NSDepartureSensor(SensorEntity):
self._time = time
self._state = None
self._trips = None
self._first_trip = None
self._next_trip = None
@property
def name(self):
@@ -133,44 +135,44 @@ class NSDepartureSensor(SensorEntity):
@property
def extra_state_attributes(self):
"""Return the state attributes."""
if not self._trips:
if not self._trips or self._first_trip is None:
return None
if self._trips[0].trip_parts:
route = [self._trips[0].departure]
route.extend(k.destination for k in self._trips[0].trip_parts)
if self._first_trip.trip_parts:
route = [self._first_trip.departure]
route.extend(k.destination for k in self._first_trip.trip_parts)
# Static attributes
attributes = {
"going": self._trips[0].going,
"going": self._first_trip.going,
"departure_time_planned": None,
"departure_time_actual": None,
"departure_delay": False,
"departure_platform_planned": self._trips[0].departure_platform_planned,
"departure_platform_actual": self._trips[0].departure_platform_actual,
"departure_platform_planned": self._first_trip.departure_platform_planned,
"departure_platform_actual": self._first_trip.departure_platform_actual,
"arrival_time_planned": None,
"arrival_time_actual": None,
"arrival_delay": False,
"arrival_platform_planned": self._trips[0].arrival_platform_planned,
"arrival_platform_actual": self._trips[0].arrival_platform_actual,
"arrival_platform_planned": self._first_trip.arrival_platform_planned,
"arrival_platform_actual": self._first_trip.arrival_platform_actual,
"next": None,
"status": self._trips[0].status.lower(),
"transfers": self._trips[0].nr_transfers,
"status": self._first_trip.status.lower(),
"transfers": self._first_trip.nr_transfers,
"route": route,
"remarks": None,
}
# Planned departure attributes
if self._trips[0].departure_time_planned is not None:
attributes["departure_time_planned"] = self._trips[
0
].departure_time_planned.strftime("%H:%M")
if self._first_trip.departure_time_planned is not None:
attributes["departure_time_planned"] = (
self._first_trip.departure_time_planned.strftime("%H:%M")
)
# Actual departure attributes
if self._trips[0].departure_time_actual is not None:
attributes["departure_time_actual"] = self._trips[
0
].departure_time_actual.strftime("%H:%M")
if self._first_trip.departure_time_actual is not None:
attributes["departure_time_actual"] = (
self._first_trip.departure_time_actual.strftime("%H:%M")
)
# Delay departure attributes
if (
@@ -182,16 +184,16 @@ class NSDepartureSensor(SensorEntity):
attributes["departure_delay"] = True
# Planned arrival attributes
if self._trips[0].arrival_time_planned is not None:
attributes["arrival_time_planned"] = self._trips[
0
].arrival_time_planned.strftime("%H:%M")
if self._first_trip.arrival_time_planned is not None:
attributes["arrival_time_planned"] = (
self._first_trip.arrival_time_planned.strftime("%H:%M")
)
# Actual arrival attributes
if self._trips[0].arrival_time_actual is not None:
attributes["arrival_time_actual"] = self._trips[
0
].arrival_time_actual.strftime("%H:%M")
if self._first_trip.arrival_time_actual is not None:
attributes["arrival_time_actual"] = (
self._first_trip.arrival_time_actual.strftime("%H:%M")
)
# Delay arrival attributes
if (
@@ -202,15 +204,14 @@ class NSDepartureSensor(SensorEntity):
attributes["arrival_delay"] = True
# Next attributes
if len(self._trips) > 1:
if self._trips[1].departure_time_actual is not None:
attributes["next"] = self._trips[1].departure_time_actual.strftime(
"%H:%M"
)
elif self._trips[1].departure_time_planned is not None:
attributes["next"] = self._trips[1].departure_time_planned.strftime(
"%H:%M"
)
if self._next_trip.departure_time_actual is not None:
attributes["next"] = self._next_trip.departure_time_actual.strftime("%H:%M")
elif self._next_trip.departure_time_planned is not None:
attributes["next"] = self._next_trip.departure_time_planned.strftime(
"%H:%M"
)
else:
attributes["next"] = None
return attributes
@@ -225,6 +226,7 @@ class NSDepartureSensor(SensorEntity):
):
self._state = None
self._trips = None
self._first_trip = None
return
# Set the search parameter to search from a specific trip time
@@ -236,19 +238,51 @@ class NSDepartureSensor(SensorEntity):
.strftime("%d-%m-%Y %H:%M")
)
else:
trip_time = datetime.now().strftime("%d-%m-%Y %H:%M")
trip_time = dt_util.now().strftime("%d-%m-%Y %H:%M")
try:
self._trips = self._nsapi.get_trips(
trip_time, self._departure, self._via, self._heading, True, 0, 2
)
if self._trips:
if self._trips[0].departure_time_actual is None:
planned_time = self._trips[0].departure_time_planned
self._state = planned_time.strftime("%H:%M")
all_times = []
# If a train is delayed we can observe this through departure_time_actual.
for trip in self._trips:
if trip.departure_time_actual is None:
all_times.append(trip.departure_time_planned)
else:
all_times.append(trip.departure_time_actual)
# Remove all trains that already left.
filtered_times = [
(i, time)
for i, time in enumerate(all_times)
if time > dt_util.now()
]
if len(filtered_times) > 0:
sorted_times = sorted(filtered_times, key=lambda x: x[1])
self._first_trip = self._trips[sorted_times[0][0]]
self._state = sorted_times[0][1].strftime("%H:%M")
# Filter again to remove trains that leave at the exact same time.
filtered_times = [
(i, time)
for i, time in enumerate(all_times)
if time > sorted_times[0][1]
]
if len(filtered_times) > 0:
sorted_times = sorted(filtered_times, key=lambda x: x[1])
self._next_trip = self._trips[sorted_times[0][0]]
else:
self._next_trip = None
else:
actual_time = self._trips[0].departure_time_actual
self._state = actual_time.strftime("%H:%M")
self._first_trip = None
self._state = None
except (
requests.exceptions.ConnectionError,
requests.exceptions.HTTPError,

View File

@@ -19,5 +19,5 @@
"documentation": "https://www.home-assistant.io/integrations/nest",
"iot_class": "cloud_push",
"loggers": ["google_nest_sdm"],
"requirements": ["google-nest-sdm==7.1.3"]
"requirements": ["google-nest-sdm==7.1.4"]
}

View File

@@ -58,6 +58,7 @@
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_account%]",
"already_in_progress": "[%key:common::config_flow::abort::already_in_progress%]",
"missing_configuration": "[%key:common::config_flow::abort::oauth2_missing_configuration%]",
"missing_credentials": "[%key:common::config_flow::abort::oauth2_missing_credentials%]",
"authorize_url_timeout": "[%key:common::config_flow::abort::oauth2_authorize_url_timeout%]",

View File

@@ -12,5 +12,5 @@
"documentation": "https://www.home-assistant.io/integrations/nexia",
"iot_class": "cloud_polling",
"loggers": ["nexia"],
"requirements": ["nexia==2.0.9"]
"requirements": ["nexia==2.2.2"]
}

View File

@@ -58,6 +58,9 @@
"switch": {
"hold": {
"name": "Hold"
},
"emergency_heat": {
"name": "Emergency heat"
}
}
},

View File

@@ -486,6 +486,7 @@ DEVICE_CLASS_UNITS: dict[NumberDeviceClass, set[type[StrEnum] | str | None]] = {
NumberDeviceClass.PM25: {CONCENTRATION_MICROGRAMS_PER_CUBIC_METER},
NumberDeviceClass.POWER_FACTOR: {PERCENTAGE, None},
NumberDeviceClass.POWER: {
UnitOfPower.MILLIWATT,
UnitOfPower.WATT,
UnitOfPower.KILO_WATT,
UnitOfPower.MEGA_WATT,

View File

@@ -7,5 +7,6 @@ set_value:
fields:
value:
example: 42
required: true
selector:
text:

View File

@@ -41,14 +41,7 @@ _LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: OneDriveConfigEntry) -> bool:
"""Set up OneDrive from a config entry."""
implementation = await async_get_config_entry_implementation(hass, entry)
session = OAuth2Session(hass, entry, implementation)
async def get_access_token() -> str:
await session.async_ensure_token_valid()
return cast(str, session.token[CONF_ACCESS_TOKEN])
client = OneDriveClient(get_access_token, async_get_clientsession(hass))
client, get_access_token = await _get_onedrive_client(hass, entry)
# get approot, will be created automatically if it does not exist
approot = await _handle_item_operation(client.get_approot, "approot")
@@ -104,11 +97,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: OneDriveConfigEntry) ->
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
async def update_listener(hass: HomeAssistant, entry: OneDriveConfigEntry) -> None:
await hass.config_entries.async_reload(entry.entry_id)
entry.async_on_unload(entry.add_update_listener(update_listener))
def async_notify_backup_listeners() -> None:
for listener in hass.data.get(DATA_BACKUP_AGENT_LISTENERS, []):
listener()
@@ -164,20 +152,47 @@ async def async_migrate_entry(hass: HomeAssistant, entry: OneDriveConfigEntry) -
_LOGGER.debug(
"Migrating OneDrive config entry from version %s.%s", version, minor_version
)
client, _ = await _get_onedrive_client(hass, entry)
instance_id = await async_get_instance_id(hass)
try:
approot = await client.get_approot()
folder = await client.get_drive_item(
f"{approot.id}:/backups_{instance_id[:8]}:"
)
except OneDriveException:
_LOGGER.exception("Migration to version 1.2 failed")
return False
hass.config_entries.async_update_entry(
entry,
data={
**entry.data,
CONF_FOLDER_ID: "id", # will be updated during setup_entry
CONF_FOLDER_ID: folder.id,
CONF_FOLDER_NAME: f"backups_{instance_id[:8]}",
},
minor_version=2,
)
_LOGGER.debug("Migration to version 1.2 successful")
return True
async def _get_onedrive_client(
hass: HomeAssistant, entry: OneDriveConfigEntry
) -> tuple[OneDriveClient, Callable[[], Awaitable[str]]]:
"""Get OneDrive client."""
implementation = await async_get_config_entry_implementation(hass, entry)
session = OAuth2Session(hass, entry, implementation)
async def get_access_token() -> str:
await session.async_ensure_token_valid()
return cast(str, session.token[CONF_ACCESS_TOKEN])
return (
OneDriveClient(get_access_token, async_get_clientsession(hass)),
get_access_token,
)
async def _handle_item_operation(
func: Callable[[], Awaitable[Item]], folder: str
) -> Item:

View File

@@ -0,0 +1,33 @@
"""Diagnostics support for OneDrive."""
from __future__ import annotations
from dataclasses import asdict
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN
from homeassistant.core import HomeAssistant
from .coordinator import OneDriveConfigEntry
TO_REDACT = {"display_name", "email", CONF_ACCESS_TOKEN, CONF_TOKEN}
async def async_get_config_entry_diagnostics(
hass: HomeAssistant,
entry: OneDriveConfigEntry,
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
coordinator = entry.runtime_data.coordinator
data = {
"drive": asdict(coordinator.data),
"config": {
**entry.data,
**entry.options,
},
}
return async_redact_data(data, TO_REDACT)

View File

@@ -9,5 +9,5 @@
"iot_class": "cloud_polling",
"loggers": ["onedrive_personal_sdk"],
"quality_scale": "platinum",
"requirements": ["onedrive-personal-sdk==0.0.11"]
"requirements": ["onedrive-personal-sdk==0.0.13"]
}

View File

@@ -41,10 +41,7 @@ rules:
# Gold
devices: done
diagnostics:
status: exempt
comment: |
There is no data to diagnose.
diagnostics: done
discovery-update-info:
status: exempt
comment: |

View File

@@ -103,7 +103,7 @@ class OneDriveDriveStateSensor(
self._attr_unique_id = f"{coordinator.data.id}_{description.key}"
self._attr_device_info = DeviceInfo(
entry_type=DeviceEntryType.SERVICE,
name=coordinator.data.name,
name=coordinator.data.name or coordinator.config_entry.title,
identifiers={(DOMAIN, coordinator.data.id)},
manufacturer="Microsoft",
model=f"OneDrive {coordinator.data.drive_type.value.capitalize()}",

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