Compare commits

..

213 Commits

Author SHA1 Message Date
Franck Nijhof
394dafd980 2024.6.3 (#119742) 2024-06-15 21:05:26 +02:00
Franck Nijhof
eba429dc54 Temporary pin CI to Python 3.12.3 (#119261) 2024-06-15 20:36:35 +02:00
Franck Nijhof
89ce8478de Bump version to 2024.6.3 2024-06-15 18:23:39 +02:00
Franck Nijhof
a4a8315376 Ensure workday issues are not persistent (#119732) 2024-06-15 18:23:29 +02:00
Franck Nijhof
3a705fd668 Ensure UniFi Protect EA warning is not persistent (#119730) 2024-06-15 18:23:25 +02:00
TheJulianJES
dc0fc318b8 Bump ZHA dependencies (#119713)
* Bump bellows to 0.39.1

* Bump zigpy to 0.64.1
2024-06-15 18:23:22 +02:00
J. Nick Koston
5ceb8537eb Bump uiprotect to 1.7.2 (#119705)
changelog: https://github.com/uilibs/uiprotect/compare/v1.7.1...v1.7.2
2024-06-15 18:23:19 +02:00
J. Nick Koston
d7d7782a69 Bump uiprotect to 1.7.1 (#119694)
changelog: https://github.com/uilibs/uiprotect/compare/v1.6.0...v1.7.0
2024-06-15 18:23:16 +02:00
G Johansson
2d4176d581 Fix alarm default code in concord232 (#119691) 2024-06-15 18:23:12 +02:00
J. Nick Koston
204e9a79c5 Bump uiprotect to 1.6.0 (#119661) 2024-06-15 18:23:09 +02:00
J. Nick Koston
ace7da2328 Bump uiprotect to 1.4.1 (#119653) 2024-06-15 18:21:52 +02:00
mletenay
dfe25ff804 Bump goodwe to 0.3.6 (#119646) 2024-06-15 18:21:49 +02:00
J. Nick Koston
2b44cf898e Soften unifiprotect EA channel message (#119641) 2024-06-15 18:21:45 +02:00
Paul Bottein
c77ed921de Update frontend to 20240610.1 (#119634) 2024-06-15 18:21:03 +02:00
Jan Bouwhuis
78e13d138f Fix group enabled platforms are preloaded if they have alternative states (#119621) 2024-06-15 18:20:05 +02:00
J. Nick Koston
4e394597bd Bump uiprotect to 1.2.1 (#119620)
* Bump uiprotect to 1.2.0

changelog: https://github.com/uilibs/uiprotect/compare/v1.1.0...v1.2.0

* bump
2024-06-15 18:20:02 +02:00
starkillerOG
78c2dc708c Fix error for Reolink snapshot streams (#119572) 2024-06-15 18:19:58 +02:00
Ethem Cem Özkan
4c1d2e7ac8 Revert "Revert Use integration fallback configuration for tado water fallback" (#119526)
* Revert "Revert Use integration fallback configuration for tado water heater fallback (#119466)"

This reverts commit ade936e6d5.

* add decide method for duration

* add repair issue to let users know

* test module for repairs

* Update strings.json

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

* repair issue should not be persistent

* use issue_registery fixture instead of mocking

* fix comment

* parameterize repair issue created test case

---------

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2024-06-15 18:19:55 +02:00
Jan-Philipp Benecke
7b809a8e55 Partially revert "Add more debug logging to Ping integration" (#119487) 2024-06-15 18:19:52 +02:00
Erwin Douna
4eea448f9d Revert Use integration fallback configuration for tado water heater fallback (#119466) 2024-06-15 18:19:48 +02:00
Joakim Plate
f58882c878 Add loggers to gardena bluetooth (#119460) 2024-06-15 18:19:45 +02:00
J. Nick Koston
4e6e9f35b5 Bump uiprotect to 1.1.0 (#119449) 2024-06-15 18:19:42 +02:00
Sebastian Goscik
d5e9976b2c Bump uiprotect to v1.0.1 (#119436) 2024-06-15 18:19:39 +02:00
MJJ
8d547d4599 Bump buieradar to 1.0.6 (#119433) 2024-06-15 18:19:32 +02:00
J. Nick Koston
94d79440a0 Fix incorrect key name in unifiprotect options strings (#119417) 2024-06-15 18:19:29 +02:00
J. Nick Koston
d602b7d19b Bump uiprotect to 1.0.0 (#119415) 2024-06-15 18:19:26 +02:00
J. Nick Koston
fb5de55c3e Bump uiprotect to 0.13.0 (#119344) 2024-06-15 18:19:23 +02:00
J. Nick Koston
5cf0ee936d Bump uiprotect to 0.10.1 (#119327)
Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
2024-06-15 18:19:19 +02:00
tronikos
7443878333 Make remaining time of timers available to LLMs (#118696)
* Include speech_slots in IntentResponse.as_dict

* Populate speech_slots only if available

* fix typo

* Add test

* test all fields

* Fix another test

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2024-06-15 18:19:16 +02:00
Franck Nijhof
090d296135 2024.6.2 (#119376) 2024-06-11 14:41:12 +02:00
Franck Nijhof
415bfb40a7 Bump version to 2024.6.2 2024-06-11 11:21:51 +02:00
Maciej Bieniek
7ced4e981e Bump imgw-pib backend library to version 1.0.5 (#119360)
Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com>
2024-06-11 11:17:29 +02:00
swcloudgenie
b656ef4d4f Fix AladdinConnect OAuth domain (#119336)
fix aladdin connect oauth domain
2024-06-11 11:17:26 +02:00
Erik Montnemery
6ea18a7b24 Fix statistic_during_period after core restart (#119323) 2024-06-11 11:17:22 +02:00
Bram Kragten
a0ac9fe6c9 Update frontend to 20240610.0 (#119320) 2024-06-11 11:16:04 +02:00
Jan-Philipp Benecke
135735126a Add more debug logging to Ping integration (#119318) 2024-06-11 11:10:36 +02:00
J. Nick Koston
3bc6cf666a Bump uiprotect to 0.4.1 (#119308) 2024-06-11 11:10:33 +02:00
Franck Nijhof
1929e103c0 Fix persistence on OpenWeatherMap raised repair issue (#119289) 2024-06-11 11:10:30 +02:00
J. Nick Koston
74b49556f9 Improve workday test coverage (#119259) 2024-06-11 11:10:27 +02:00
J. Nick Koston
8d40f4d39f Bump uiprotect to 0.4.0 (#119256) 2024-06-11 11:10:24 +02:00
Allen Porter
eed126c6d4 Bump google-nest-sdm to 4.0.5 (#119255) 2024-06-11 11:10:21 +02:00
J. Nick Koston
38cd84fa5f Fix climate on/off in nexia (#119254) 2024-06-11 11:10:18 +02:00
Abílio Costa
a28f5baeeb Fix wrong arg name in Idasen Desk config flow (#119247) 2024-06-11 11:10:14 +02:00
J. Nick Koston
f9352dfe8f Switch unifiprotect lib to use uiprotect (#119243) 2024-06-11 11:09:20 +02:00
J. Nick Koston
5beff34069 Remove myself as codeowner for unifiprotect (#118824) 2024-06-11 10:59:51 +02:00
Joakim Plate
119d4c2316 Always provide a currentArmLevel in Google assistant (#119238) 2024-06-11 10:29:34 +02:00
epenet
1e7ab07d9e Revert SamsungTV migration (#119234) 2024-06-11 10:29:31 +02:00
Ethem Cem Özkan
7896e7675c Bump python-roborock to 2.3.0 (#119228) 2024-06-11 10:29:28 +02:00
wittypluck
8b415a0376 Fix Glances v4 network and container issues (glances-api 0.8.0) (#119226) 2024-06-11 10:29:25 +02:00
Angel Nunez Mencias
6a656c5d49 Fixes crashes when receiving malformed decoded payloads (#119216)
Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
2024-06-11 10:29:21 +02:00
G Johansson
8d094bf12e Fix envisalink alarm (#119212) 2024-06-11 10:29:18 +02:00
Sid
c71b6bdac9 Add fallback to entry_id when no mac address is retrieved in enigma2 (#119185) 2024-06-11 10:29:15 +02:00
tronikos
57cc1f841b Bump opower to 0.4.7 (#119183) 2024-06-11 10:29:12 +02:00
Quentin
d8f3778d77 Fix elgato light color detection (#119177) 2024-06-11 10:29:08 +02:00
Shay Levy
9a8e3ad5cc Handle Shelly BLE errors during connect and disconnect (#119174) 2024-06-11 10:29:05 +02:00
Michael
019d33c06c Use more conservative timeout values in Synology DSM (#119169)
use ClientTimeout object
2024-06-11 10:29:02 +02:00
Michael
40ebf3b2a9 Bump py-synologydsm-api to 2.4.4 (#119156)
bump py-synologydsm-api to 2.4.4
2024-06-11 10:28:58 +02:00
Tom Brien
7912c9e95c Fix workday timezone (#119148) 2024-06-11 10:28:55 +02:00
Paulus Schoutsen
4bb1ea1da1 Ensure intent tools have safe names (#119144) 2024-06-11 10:28:51 +02:00
Joost Lekkerkerker
a696ea18d3 Bump aiowaqi to 3.1.0 (#119124) 2024-06-11 10:28:48 +02:00
Shay Levy
df96b94985 Bump aioshelly to 10.0.1 (#119123) 2024-06-11 10:28:45 +02:00
tronikos
0f8ed4e73d Catch GoogleAPICallError in Google Generative AI (#119118) 2024-06-11 10:28:41 +02:00
tronikos
34477d3559 Properly handle escaped unicode characters passed to tools in Google Generative AI (#119117) 2024-06-11 10:28:38 +02:00
Austin Drummond
96ac566032 Fix control 4 on os 2 (#119104) 2024-06-11 10:28:35 +02:00
J. Nick Koston
87f48b15d1 Ensure multiple executions of a restart automation in the same event loop iteration are allowed (#119100)
* Add test for restarting automation

related issue #119097

* fix

* add a delay since restart is an infinite loop

* tests
2024-06-11 10:28:31 +02:00
kaareseras
a1f2140ed7 Fix Azure data explorer (#119089)
Co-authored-by: Robert Resch <robert@resch.dev>
2024-06-11 10:28:28 +02:00
tronikos
db7a9321be Bump google-generativeai to 0.6.0 (#119062) 2024-06-11 10:28:24 +02:00
G Johansson
ebb0a453f4 Calculate attributes when entity information available in Group sensor (#119021) 2024-06-11 10:28:21 +02:00
Joakim Plate
7da10794a8 Update gardena library to 1.4.2 (#119010) 2024-06-11 10:28:18 +02:00
Ruben Bokobza
461f0865af Bump pyElectra to 1.2.1 (#118958) 2024-06-11 10:28:15 +02:00
karwosts
fc83bb1737 Fix statistic_during_period wrongly prioritizing ST statistics over LT (#115291)
* Fix statistic_during_period wrongly prioritizing ST statistics over LT

* comment

* start of a test

* more testcases

* fix sts insertion range

* update from review

* remove unneeded comments

* update logic

* min/mean/max testing
2024-06-11 10:28:09 +02:00
Franck Nijhof
b28cdcfc49 2024.6.1 (#119096) 2024-06-07 21:20:44 +02:00
Franck Nijhof
3f70e2b6f0 Bump version to 2024.6.1 2024-06-07 20:26:53 +02:00
Joost Lekkerkerker
ed22e98861 Fix Azure Data Explorer strings (#119067) 2024-06-07 20:24:03 +02:00
Marc Mueller
093f07c04e Add type ignore comments (#119052) 2024-06-07 20:24:00 +02:00
Joost Lekkerkerker
b5693ca604 Fix AirGradient name (#119046) 2024-06-07 20:23:57 +02:00
J. Nick Koston
20b77aa15f Fix remember_the_milk calling configurator async api from the wrong thread (#119029) 2024-06-07 20:23:53 +02:00
J. Nick Koston
1cbd3ab930 Fix refactoring error in snmp switch (#119028) 2024-06-07 20:23:50 +02:00
Matthias Alphart
31b44b7846 Fix KNX climate.set_hvac_mode not turning on (#119012) 2024-06-07 20:23:47 +02:00
Matthias Alphart
de3a0841d8 Increase test coverage for KNX Climate (#117903)
* Increase test coverage fro KNX Climate

* fix test type annotation
2024-06-07 20:23:40 +02:00
Mike Degatano
581fb2f9f4 Always have addon url in detached_addon_missing (#119011) 2024-06-07 20:21:25 +02:00
Shay Levy
5bb4e4f5d9 Hold connection lock in Shelly RPC reconnect (#119009) 2024-06-07 20:21:22 +02:00
J. Nick Koston
cfa619b67e Remove isal from after_dependencies in http (#119000) 2024-06-07 20:21:18 +02:00
Michael Hansen
56db7fc7dc Fix exposure checks on some intents (#118988)
* Check exposure in climate intent

* Check exposure in todo list

* Check exposure for weather

* Check exposure in humidity intents

* Add extra checks to weather tests

* Add more checks to todo intent test

* Move climate intents to async_match_targets

* Update test_intent.py

* Update test_intent.py

* Remove patch
2024-06-07 20:20:41 +02:00
Joost Lekkerkerker
1f6be7b4d1 Fix unit of measurement for airgradient sensor (#118981) 2024-06-07 20:18:45 +02:00
Maciej Bieniek
52d1432d81 Bump imgw-pib library to version 1.0.4 (#118978)
Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com>
2024-06-07 20:18:41 +02:00
David Knowles
14da1e9b23 Bump pydrawise to 2024.6.3 (#118977) 2024-06-07 20:18:38 +02:00
G Johansson
d6e1d05e87 Bump python-holidays to 0.50 (#118965) 2024-06-07 20:18:35 +02:00
G Johansson
62f73cfcca Fix Alarm control panel not require code in several integrations (#118961) 2024-06-07 20:18:32 +02:00
Maciej Bieniek
6e9a53d02e Bump imgw-pib backend library to version 1.0.2 (#118953)
Bump imgw-pib to version 1.0.2

Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com>
2024-06-07 20:18:28 +02:00
J. Nick Koston
394c13af1d Revert "Bump orjson to 3.10.3 (#116945)" (#118920)
This reverts commit dc50095d06.
2024-06-07 20:17:01 +02:00
Jan-Philipp Benecke
86b13e8ae3 Fix flaky Google Assistant test (#118914)
* Fix flaky Google Assistant test

* Trigger full ci
2024-06-07 20:16:58 +02:00
Rami Mosleh
5a7332a135 Check if imap message text has a value instead of checking if its not None (#118901)
* Check if message_text has a value instead of checking if its not None

* Strip message_text to ensure that its actually empty or not

* Add test with multipart payload having empty plain text
2024-06-07 20:16:55 +02:00
Michael Hansen
0f9a91d369 Prioritize literal text with name slots in sentence matching (#118900)
Prioritize literal text with name slots
2024-06-07 20:16:52 +02:00
Marc Mueller
00dd86fb4b Update requests to 2.32.3 (#118868)
Co-authored-by: Robert Resch <robert@resch.dev>
2024-06-07 20:16:47 +02:00
Franck Nijhof
460909a7f6 2024.6.0 (#118400) 2024-06-05 20:06:01 +02:00
Franck Nijhof
21fd012447 Bump version to 2024.6.0 2024-06-05 19:00:08 +02:00
Robert Resch
c27f0c560e Replace slave by meter in v2c (#118893) 2024-06-05 18:59:52 +02:00
Michael Hansen
0f4a1b421e Bump intents to 2024.6.5 (#118890) 2024-06-05 18:59:49 +02:00
Erik Montnemery
5e35ce2996 Improve WS command validate_config (#118864)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
Co-authored-by: Robert Resch <robert@resch.dev>
2024-06-05 18:59:44 +02:00
Franck Nijhof
e5804307e7 Bump version to 2024.6.0b9 2024-06-05 15:51:19 +02:00
Bram Kragten
3b74b63b23 Update frontend to 20240605.0 (#118875) 2024-06-05 15:51:08 +02:00
Marc Mueller
06df32d9d4 Fix TypeAliasType not callable in senz (#118872) 2024-06-05 15:51:05 +02:00
Jan Bouwhuis
63947e4980 Improve repair issue when notify service is still being used (#118855)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2024-06-05 15:51:02 +02:00
Ethem Cem Özkan
ac6a377478 Bump python-roborock to 2.2.3 (#118853)
Co-authored-by: G Johansson <goran.johansson@shiftit.se>
2024-06-05 15:50:59 +02:00
Paulus Schoutsen
18af423a78 Fix the radio browser doing I/O in the event loop (#118842) 2024-06-05 15:50:56 +02:00
Franck Nijhof
f1445bc8f5 Fix capitalization of protocols in Reolink option flow (#118839) 2024-06-05 15:50:53 +02:00
starkillerOG
3784c99305 Conserve Reolink battery by not waking the camera on each update (#118773)
* update to new cmd_list type

* Wake battery cams each 1 hour

* fix styling

* fix epoch

* fix timezone

* force full update when using generic update service

* improve comment

* Use time.time() instead of datetime

* fix import order
2024-06-05 15:50:50 +02:00
Pete Sage
0084d6c5bd Fix Hydrawise sensor availability (#118669)
Co-authored-by: Robert Resch <robert@resch.dev>
2024-06-05 15:50:47 +02:00
Franck Nijhof
f1e6375406 Bump version to 2024.6.0b8 2024-06-04 21:32:36 +02:00
Jan-Philipp Benecke
9157905f80 Initialize the Sentry SDK within an import executor job to not block event loop (#118830) 2024-06-04 21:31:49 +02:00
J. Nick Koston
b02c9aa2ef Ensure name of task is logged for unhandled loop exceptions (#118822) 2024-06-04 21:31:45 +02:00
Bram Kragten
6e30fd7633 Update frontend to 20240604.0 (#118811) 2024-06-04 21:31:42 +02:00
Stefan Agner
74b29c2e54 Bump Python Matter Server library to 6.1.0 (#118806) 2024-06-04 21:31:38 +02:00
Maciej Bieniek
b1b26af92b Check if Shelly entry.runtime_data is available (#118805)
* Check if runtime_data is available

* Add tests

* Use `is` operator

---------

Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com>
2024-06-04 21:31:33 +02:00
arturyak
b107ffd30d Add missing FAN_ONLY mode to ccm15 (#118804) 2024-06-04 21:31:28 +02:00
Joost Lekkerkerker
776675404a Set unique id in aladdin connect config flow (#118798) 2024-06-04 21:30:08 +02:00
Paulus Schoutsen
38ee32fed2 Include script description in LLM exposed entities (#118749)
* Include script description in LLM exposed entities

* Fix race in test

* Fix type

* Expose script

* Remove fields
2024-06-04 21:21:26 +02:00
Tsvi Mostovicz
111d11aaca Fix updating options in Jewish Calendar (#118643) 2024-06-04 21:21:23 +02:00
Jack Boswell
ff8752ea4f Fix calculation of Starlink sleep end setting (#115507)
Co-authored-by: J. Nick Koston <nick@koston.org>
2024-06-04 21:21:13 +02:00
Franck Nijhof
2151f7ebf3 Bump version to 2024.6.0b7 2024-06-04 12:20:22 +02:00
Richard Kroegel
50efce4e53 Allow per-sensor unit conversion on BMW sensors (#110272)
* Update BMW sensors to use device_class

* Test adjustments

* Trigger CI

* Remove unneeded cast

* Set suggested_display_precision to 0

* Rebase for climate_status

* Change charging_status to ENUM device class

* Add test for Enum translations

* Pin Enum sensor values

* Use snapshot_platform helper

* Remove translation tests

* Formatting

* Remove comment

* Use const.STATE_UNKOWN

* Fix typo

* Update strings

* Loop through Enum sensors

* Revert enum sensor changes

---------

Co-authored-by: Richard <rikroe@users.noreply.github.com>
2024-06-04 12:20:08 +02:00
Richard Kroegel
c8538f3c08 Use snapshot_platform helper for BMW tests (#118735)
* Use snapshot_platform helper

* Remove comments

---------

Co-authored-by: Richard <rikroe@users.noreply.github.com>
2024-06-04 12:19:01 +02:00
Richard Kroegel
4bfff12570 Set lock state to unkown on BMW API error (#118559)
* Revert to previous lock state on BMW API error

* Set lock state to unkown on error and force refresh from API

---------

Co-authored-by: Richard <rikroe@users.noreply.github.com>
2024-06-04 12:02:47 +02:00
Richard Kroegel
f2b1635969 Refactor fixture calling for BMW tests (#118708)
* Refactor BMW tests to use pytest.mark.usefixtures

* Fix freeze_time

---------

Co-authored-by: Richard <rikroe@users.noreply.github.com>
2024-06-04 12:01:40 +02:00
Joost Lekkerkerker
b3b8ae31fd Move Aladdin stale device removal to init module (#118784) 2024-06-04 11:58:35 +02:00
Joost Lekkerkerker
ba96fc272b Re-enable sensor platform for Aladdin Connect (#118782) 2024-06-04 11:58:32 +02:00
Joost Lekkerkerker
c702174fa0 Add coordinator to Aladdin Connect (#118781) 2024-06-04 11:58:29 +02:00
Joost Lekkerkerker
5d6fe7387e Use model from Aladdin Connect lib (#118778)
* Use model from Aladdin Connect lib

* Fix
2024-06-04 11:58:25 +02:00
Joost Lekkerkerker
c76b7a48d3 Initial cleanup for Aladdin connect (#118777) 2024-06-04 11:58:22 +02:00
Joost Lekkerkerker
954e8ff9b3 Bump airgradient to 0.4.3 (#118776) 2024-06-04 11:58:18 +02:00
Joakim Sørensen
8c332ddbdb Update hass-nabucasa to version 0.81.1 (#118768) 2024-06-04 11:58:15 +02:00
Jan Bouwhuis
01c4ca2749 Recover mqtt abbrevations optimizations (#118762)
Co-authored-by: J. Nick Koston <nick@koston.org>
2024-06-04 11:58:12 +02:00
Michael Hansen
4b4b5362d9 Clean up exposed domains (#118753)
* Remove lock and script

* Add media player

* Fix tests
2024-06-04 11:58:08 +02:00
Jan Bouwhuis
70d7cedf08 Do not log mqtt origin info if the log level does not allow it (#118752) 2024-06-04 11:58:05 +02:00
Michael Hansen
7bbfb1a22b Bump intents to 2024.6.3 (#118748) 2024-06-04 11:58:02 +02:00
Paulus Schoutsen
d68d871054 Update OpenAI prompt on each interaction (#118747) 2024-06-04 11:57:58 +02:00
Jan Bouwhuis
69bdefb02d Revert "Allow MQTT device based auto discovery" (#118746)
Revert "Allow MQTT device based auto discovery (#109030)"

This reverts commit 585892f067.
2024-06-04 11:57:55 +02:00
Paulus Schoutsen
ebaec6380f Google Gen AI: Copy messages to avoid changing the trace data (#118745) 2024-06-04 11:57:51 +02:00
starkillerOG
9cf6e9b21a Bump reolink-aio to 0.9.1 (#118655)
Co-authored-by: J. Nick Koston <nick@koston.org>
2024-06-04 11:55:34 +02:00
David Bonnes
eb1a9eda60 Harden evohome against failures to retrieve zone schedules (#118517) 2024-06-04 11:55:21 +02:00
Franck Nijhof
26344ffd74 Bump version to 2024.6.0b6 2024-06-03 21:27:31 +02:00
Paulus Schoutsen
2940104008 Remove dispatcher from Tag entity (#118671)
* Remove dispatcher from Tag entity

* type

* Don't use  helper

* Del is faster than pop

* Use id in update

---------

Co-authored-by: G Johansson <goran.johansson@shiftit.se>
2024-06-03 21:27:08 +02:00
Joost Lekkerkerker
8072a268a1 Require firmware version 3.1.1 for airgradient (#118744) 2024-06-03 21:26:19 +02:00
Bram Kragten
b5f557ad73 Update frontend to 20240603.0 (#118736) 2024-06-03 21:26:16 +02:00
Michael Hansen
f977b54312 Resolve areas/floors to ids in intent_script (#118734) 2024-06-03 21:26:13 +02:00
Jan-Philipp Benecke
11b2f201f3 Rename Discovergy to inexogy (#118724) 2024-06-03 21:26:10 +02:00
Erik Montnemery
8cc3c147fe Tweak light service schema (#118720) 2024-06-03 21:26:07 +02:00
epenet
fd9ea2f224 Bump renault-api to 0.2.3 (#118718) 2024-06-03 21:26:04 +02:00
Diogo Gomes
f064f44a09 Address reviews comments in #117147 (#118714) 2024-06-03 21:26:01 +02:00
Erik Montnemery
f3d1157bc4 Remove tag_id from tag store (#118713) 2024-06-03 21:25:58 +02:00
mkmer
85982d2b87 Remove unintended translation key from blink (#118712) 2024-06-03 21:25:55 +02:00
Erik Montnemery
cc83443ad1 Don't store tag_id in tag storage (#118707) 2024-06-03 21:25:52 +02:00
tronikos
8a516207e9 Use ISO format when passing date to LLMs (#118705) 2024-06-03 21:25:49 +02:00
Mick Vleeshouwer
f805df8390 Bump pyoverkiz to 1.13.11 (#118703) 2024-06-03 21:25:46 +02:00
Joost Lekkerkerker
ea85ed6992 Disable both option in Airgradient select (#118702) 2024-06-03 21:25:43 +02:00
Joost Lekkerkerker
54425b756e Configure device in airgradient config flow (#118699) 2024-06-03 21:25:40 +02:00
Paul Bottein
7b43b587a7 Bump python-roborock to 2.2.2 (#118697) 2024-06-03 21:25:37 +02:00
Matrix
7e71975358 Fixing device model compatibility issues. (#118686) 2024-06-03 21:25:34 +02:00
J. Nick Koston
e0232510d7 Revert "Add websocket API to get list of recorded entities (#92640)" (#118644)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2024-06-03 21:21:45 +02:00
Paulus Schoutsen
84f9bb1d63 Automatically fill in slots based on LLM context (#118619)
* Automatically fill in slots from LLM context

* Add tests

* Apply suggestions from code review

Co-authored-by: Allen Porter <allen@thebends.org>

---------

Co-authored-by: Allen Porter <allen@thebends.org>
2024-06-03 21:21:41 +02:00
David Knowles
b436fe94ae Bump pydrawise to 2024.6.2 (#118608) 2024-06-03 21:21:38 +02:00
epenet
aff5da5762 Address late review comment in samsungtv (#118539)
Address late comment in samsungtv
2024-06-03 21:21:29 +02:00
Paulus Schoutsen
b5783e6f5c Bump version to 2024.6.0b5 2024-06-03 01:10:10 +00:00
G Johansson
1708b60ecf Fix entity state dispatching for Tag entities (#118662) 2024-06-03 01:10:06 +00:00
puddly
3c012c497b Bump ZHA dependencies (#118658)
* Bump bellows to 0.39.0

* Do not create a backup if there is no active ZHA gateway object

* Bump universal-silabs-flasher as well
2024-06-03 01:10:05 +00:00
Joost Lekkerkerker
4d2dc9a40e Fix incorrect placeholder in SharkIQ (#118640)
Update strings.json
2024-06-03 01:10:05 +00:00
Jan Bouwhuis
3653a51288 Fix handling undecoded mqtt sensor payloads (#118633) 2024-06-03 01:10:04 +00:00
J. Nick Koston
9366a4e69b Include a traceback for non-strict event loop blocking detection (#118620) 2024-06-03 01:10:03 +00:00
Luca Angemi
1d1af7ec11 Fix telegram bot send_document (#118616) 2024-06-03 01:10:02 +00:00
tronikos
236b19c5b3 Use gemini-1.5-flash-latest in google_generative_ai_conversation.generate_content (#118594) 2024-06-03 01:10:02 +00:00
tronikos
1afbfd687f Strip Google AI text responses (#118593)
* Strip Google AI test responses

* strip each part
2024-06-03 01:10:01 +00:00
Paulus Schoutsen
20159d0277 Add base prompt for LLMs (#118592) 2024-06-03 01:10:00 +00:00
tronikos
4df3d43e45 Stop instructing LLM to not pass the domain as a list (#118590) 2024-06-03 01:10:00 +00:00
Michael
1a588760b9 Avoid future exception during setup of Synology DSM (#118583)
* avoid future exception during integration setup

* clear future flag during setup

* always clear the flag (with comment)
2024-06-03 01:09:58 +00:00
Jan-Philipp Benecke
6ba9e7d5fd Run ruff format for device registry (#118582) 2024-06-03 01:09:58 +00:00
epenet
4b06c5d2fb Update device connections in samsungtv (#118556) 2024-06-03 01:09:57 +00:00
Adam Pasztor
bfc1c62a49 Bump pyads to 3.4.0 (#116934)
Co-authored-by: J. Nick Koston <nick@koston.org>
2024-06-03 01:09:57 +00:00
Thomas Ytterdal
c52fabcf77 Ignore myuplink sensors without a description that provide non-numeric values (#115525)
Ignore sensors without a description that provide non-numeric values

Co-authored-by: Jan-Philipp Benecke <jan-philipp@bnck.me>
2024-06-03 01:09:56 +00:00
Paulus Schoutsen
b39d7b39e1 Bump version to 2024.6.0b4 2024-05-31 19:34:58 +00:00
Paulus Schoutsen
c01c155037 Fix openAI tool calls (#118577) 2024-05-31 19:34:38 +00:00
epenet
b459559c8b Add ability to replace connections in DeviceRegistry (#118555)
* Add ability to replace connections in DeviceRegistry

* Add more tests

* Improve coverage

* Apply suggestion

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>

---------

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2024-05-31 19:34:38 +00:00
Maciej Bieniek
d823e56659 In Brother integration use SnmpEngine from SNMP integration (#118554)
Co-authored-by: Maciej Bieniek <478555+bieniu@users.noreply.github.com>
Co-authored-by: J. Nick Koston <nick@koston.org>
2024-05-31 19:34:37 +00:00
Michael Chisholm
e401a0da7f Fix KeyError in dlna_dmr SSDP config flow when checking existing config entries (#118549)
Fix KeyError checking existing dlna_dmr config entries
2024-05-31 19:34:36 +00:00
Tsvi Mostovicz
3f6df28ef3 Fix YAML deprecation breaking version in jewish calendar and media extractor (#118546)
* Fix YAML deprecation breaking version

* Update

* fix media extractor deprecation as well

* Add issue_domain
2024-05-31 19:34:35 +00:00
Joost Lekkerkerker
9b63779063 Fix typo in OWM strings (#118538)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2024-05-31 19:34:35 +00:00
Josef Zweck
4998fe5e6d Migrate openai_conversation to entry.runtime_data (#118535)
* switch to entry.runtime_data

* check for missing config entry

* Update homeassistant/components/openai_conversation/__init__.py

---------

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2024-05-31 19:34:34 +00:00
Brett Adams
a59c890779 Fix off_grid_vehicle_charging_reserve_percent in Teselemetry (#118532) 2024-05-31 19:34:34 +00:00
Luca Angemi
a2cdb349f4 Fix telegram doing blocking I/O in the event loop (#118531) 2024-05-31 19:34:33 +00:00
J. Nick Koston
267228cae0 Fix openweathermap config entry migration (#118526)
* Fix openweathermap config entry migration

The options keys were accidentally migrated to data so
they could no longer be changed in the options flow

* more fixes

* adjust

* reduce

* fix

* adjust
2024-05-31 19:34:32 +00:00
J. Nick Koston
ba769f4d9f Fix snmp doing blocking I/O in the event loop (#118521) 2024-05-31 19:34:31 +00:00
Denis Shulyaka
c09bc726d1 Add OpenAI Conversation system prompt user_name and llm_context variables (#118512)
* OpenAI Conversation: Add variables to the system prompt

* User name and llm_context

* test for user name

* test for user id

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2024-05-31 19:34:31 +00:00
Josef Zweck
c441f689bf Add typing for OpenAI client and fallout (#118514)
* typing for client and consequences

* Update homeassistant/components/openai_conversation/conversation.py

---------

Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2024-05-31 19:34:07 +00:00
Denis Shulyaka
395e1ae31e Add Google Generative AI Conversation system prompt user_name and llm_context variables (#118510)
* Google Generative AI Conversation: Add variables to the system prompt

* User name and llm_context

* test for template variables

* test for template variables

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2024-05-31 19:32:47 +00:00
Bas Brussee
2e45d678b8 Revert "Fix Tibber sensors state class" (#118409)
Revert "Fix Tibber sensors state class (#117085)"

This reverts commit 658c1f3d97.
2024-05-31 19:32:46 +00:00
Paulus Schoutsen
17cb25a5b6 Rename llm.ToolContext to llm.LLMContext (#118566) 2024-05-31 19:32:07 +00:00
Paulus Schoutsen
e5e26de06f Bump version to 2024.6.0b3 2024-05-31 02:20:10 +00:00
tronikos
7dab255c15 Fix unnecessary single quotes escaping in Google AI (#118522) 2024-05-31 02:19:58 +00:00
tronikos
cea7347ed9 Improve LLM prompt (#118520) 2024-05-31 02:19:56 +00:00
tronikos
f4a876c590 Fix LLMs asking which area when there is only one device (#118518)
* Ignore deprecated open and close cover intents for LLMs

* Fix LLMs asking which area when there is only one device

* remove unrelated changed

* remove unrelated changes
2024-05-31 02:19:55 +00:00
tronikos
117a02972d Ignore deprecated open and close cover intents for LLMs (#118515) 2024-05-31 02:19:54 +00:00
G Johansson
3fb40deacb Fix key issue in config entry options in Openweathermap (#118506) 2024-05-31 02:19:54 +00:00
G Johansson
38c88c576b Fix tado non-string unique id for device trackers (#118505)
* Fix tado none string unique id for device trackers

* Add comment

* Fix comment
2024-05-31 02:19:53 +00:00
Paulus Schoutsen
e95b63bc89 Intent script: allow setting description and platforms (#118500)
* Add description to intent_script

* Allow setting platforms
2024-05-31 02:19:51 +00:00
Jan Bouwhuis
ea44b534e6 Fix group platform dependencies (#118499) 2024-05-31 02:19:50 +00:00
G Johansson
7646d853f4 Remove not needed hass object from Tag (#118498) 2024-05-31 02:19:49 +00:00
G Johansson
248c7c33b2 Fix blocking call in holiday (#118496) 2024-05-31 02:19:48 +00:00
Paulus Schoutsen
eb887a707c Ignore the toggle intent (#118491) 2024-05-31 02:19:46 +00:00
David Bonnes
e3ddbb2768 Fix evohome so it doesn't retrieve schedules unnecessarily (#118478) 2024-05-31 02:19:45 +00:00
Jan-Philipp Benecke
008aec5670 Log aiohttp error in rest_command (#118453) 2024-05-31 02:19:45 +00:00
Tsvi Mostovicz
d93d7159db Fix Jewish calendar unique id's (#117985)
* Initial commit

* Fix updating of unique id

* Add testing to check the unique id is being updated correctly

* Reload the config entry and confirm the unique id has not been changed

* Move updating unique_id to __init__.py as suggested

* Change the config_entry variable's name back from config to config_entry

* Move the loop into the update_unique_ids method

* Move test from test_config_flow to test_init

* Try an early optimization to check if we need to update the unique ids

* Mention the correct version

* Implement suggestions

* Ensure all entities are migrated correctly

* Just to be sure keep the previous assertion as well
2024-05-31 02:19:44 +00:00
Diogo Gomes
e6e017dab7 Add support for V2C Trydan 2.1.7 (#117147)
* Support for firmware 2.1.7

* add device ID as unique_id

* add device ID as unique_id

* add test device id as unique_id

* backward compatibility

* move outside try

* Sensor return type

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

* not needed

* make slave error enum state

* fix enum

* Update homeassistant/components/v2c/sensor.py

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

* Update homeassistant/components/v2c/strings.json

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

* Update homeassistant/components/v2c/strings.json

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

* simplify tests

* fix misspellings from upstream library

* add sensor tests

* just enough coverage for enum sensor

* Refactor V2C tests (#117264)

* Refactor V2C tests

* fix rebase issues

* ruff

* review

* fix https://github.com/home-assistant/core/issues/117296

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-05-31 02:19:43 +00:00
dontinelli
486c72db73 Adjustment of unit of measurement for light (#116695) 2024-05-31 02:19:42 +00:00
355 changed files with 8779 additions and 3796 deletions

View File

@@ -62,7 +62,6 @@ omit =
homeassistant/components/aladdin_connect/api.py
homeassistant/components/aladdin_connect/application_credentials.py
homeassistant/components/aladdin_connect/cover.py
homeassistant/components/aladdin_connect/model.py
homeassistant/components/aladdin_connect/sensor.py
homeassistant/components/alarmdecoder/__init__.py
homeassistant/components/alarmdecoder/alarm_control_panel.py
@@ -1534,7 +1533,6 @@ omit =
homeassistant/components/v2c/coordinator.py
homeassistant/components/v2c/entity.py
homeassistant/components/v2c/number.py
homeassistant/components/v2c/sensor.py
homeassistant/components/v2c/switch.py
homeassistant/components/vallox/__init__.py
homeassistant/components/vallox/coordinator.py

View File

@@ -10,7 +10,7 @@ on:
env:
BUILD_TYPE: core
DEFAULT_PYTHON: "3.12"
DEFAULT_PYTHON: "3.12.3"
PIP_TIMEOUT: 60
UV_HTTP_TIMEOUT: 60
UV_SYSTEM_PYTHON: "true"

View File

@@ -37,8 +37,8 @@ env:
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 8
HA_SHORT_VERSION: "2024.6"
DEFAULT_PYTHON: "3.12"
ALL_PYTHON_VERSIONS: "['3.12']"
DEFAULT_PYTHON: "3.12.3"
ALL_PYTHON_VERSIONS: "['3.12.3']"
# 10.3 is the oldest supported version
# - 10.3.32 is the version currently shipped with Synology (as of 17 Feb 2022)
# 10.6 is the current long-term-support

View File

@@ -10,7 +10,7 @@ on:
- "**strings.json"
env:
DEFAULT_PYTHON: "3.11"
DEFAULT_PYTHON: "3.12.3"
jobs:
upload:

View File

@@ -17,7 +17,7 @@ on:
- "script/gen_requirements_all.py"
env:
DEFAULT_PYTHON: "3.12"
DEFAULT_PYTHON: "3.12.3"
concurrency:
group: ${{ github.workflow }}-${{ github.ref_name}}

View File

@@ -163,7 +163,6 @@ homeassistant.components.easyenergy.*
homeassistant.components.ecovacs.*
homeassistant.components.ecowitt.*
homeassistant.components.efergy.*
homeassistant.components.electrasmart.*
homeassistant.components.electric_kiwi.*
homeassistant.components.elgato.*
homeassistant.components.elkm1.*

View File

@@ -1486,8 +1486,6 @@ build.json @home-assistant/supervisor
/tests/components/unifi/ @Kane610
/homeassistant/components/unifi_direct/ @tofuSCHNITZEL
/homeassistant/components/unifiled/ @florisvdk
/homeassistant/components/unifiprotect/ @bdraco
/tests/components/unifiprotect/ @bdraco
/homeassistant/components/upb/ @gwww
/tests/components/upb/ @gwww
/homeassistant/components/upc_connect/ @pvizeli @fabaff

View File

@@ -134,8 +134,15 @@ COOLDOWN_TIME = 60
DEBUGGER_INTEGRATIONS = {"debugpy"}
# Core integrations are unconditionally loaded
CORE_INTEGRATIONS = {"homeassistant", "persistent_notification"}
LOGGING_INTEGRATIONS = {
# Integrations that are loaded right after the core is set up
LOGGING_AND_HTTP_DEPS_INTEGRATIONS = {
# isal is loaded right away before `http` to ensure if its
# enabled, that `isal` is up to date.
"isal",
# Set log levels
"logger",
# Error logging
@@ -214,8 +221,8 @@ CRITICAL_INTEGRATIONS = {
}
SETUP_ORDER = (
# Load logging as soon as possible
("logging", LOGGING_INTEGRATIONS),
# Load logging and http deps as soon as possible
("logging, http deps", LOGGING_AND_HTTP_DEPS_INTEGRATIONS),
# Setup frontend and recorder
("frontend, recorder", {*FRONTEND_INTEGRATIONS, *RECORDER_INTEGRATIONS}),
# Start up debuggers. Start these first in case they want to wait.

View File

@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/ads",
"iot_class": "local_push",
"loggers": ["pyads"],
"requirements": ["pyads==3.2.2"]
"requirements": ["pyads==3.4.0"]
}

View File

@@ -43,6 +43,7 @@ class AgentBaseStation(AlarmControlPanelEntity):
| AlarmControlPanelEntityFeature.ARM_AWAY
| AlarmControlPanelEntityFeature.ARM_NIGHT
)
_attr_code_arm_required = False
_attr_has_entity_name = True
_attr_name = None

View File

@@ -2,7 +2,9 @@
from typing import Any
from airgradient import AirGradientClient, AirGradientError
from airgradient import AirGradientClient, AirGradientError, ConfigurationControl
from awesomeversion import AwesomeVersion
from mashumaro import MissingField
import voluptuous as vol
from homeassistant.components import zeroconf
@@ -12,6 +14,8 @@ from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
MIN_VERSION = AwesomeVersion("3.1.1")
class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
"""AirGradient config flow."""
@@ -19,6 +23,14 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
def __init__(self) -> None:
"""Initialize the config flow."""
self.data: dict[str, Any] = {}
self.client: AirGradientClient | None = None
async def set_configuration_source(self) -> None:
"""Set configuration source to local if it hasn't been set yet."""
assert self.client
config = await self.client.get_config()
if config.configuration_control is ConfigurationControl.NOT_INITIALIZED:
await self.client.set_configuration_control(ConfigurationControl.LOCAL)
async def async_step_zeroconf(
self, discovery_info: zeroconf.ZeroconfServiceInfo
@@ -30,9 +42,12 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(discovery_info.properties["serialno"])
self._abort_if_unique_id_configured(updates={CONF_HOST: host})
if AwesomeVersion(discovery_info.properties["fw_ver"]) < MIN_VERSION:
return self.async_abort(reason="invalid_version")
session = async_get_clientsession(self.hass)
air_gradient = AirGradientClient(host, session=session)
await air_gradient.get_current_measures()
self.client = AirGradientClient(host, session=session)
await self.client.get_current_measures()
self.context["title_placeholders"] = {
"model": self.data[CONF_MODEL],
@@ -44,6 +59,7 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult:
"""Confirm discovery."""
if user_input is not None:
await self.set_configuration_source()
return self.async_create_entry(
title=self.data[CONF_MODEL],
data={CONF_HOST: self.data[CONF_HOST]},
@@ -64,14 +80,17 @@ class AirGradientConfigFlow(ConfigFlow, domain=DOMAIN):
errors: dict[str, str] = {}
if user_input:
session = async_get_clientsession(self.hass)
air_gradient = AirGradientClient(user_input[CONF_HOST], session=session)
self.client = AirGradientClient(user_input[CONF_HOST], session=session)
try:
current_measures = await air_gradient.get_current_measures()
current_measures = await self.client.get_current_measures()
except AirGradientError:
errors["base"] = "cannot_connect"
except MissingField:
return self.async_abort(reason="invalid_version")
else:
await self.async_set_unique_id(current_measures.serial_number)
self._abort_if_unique_id_configured()
await self.set_configuration_source()
return self.async_create_entry(
title=current_measures.model,
data={CONF_HOST: user_input[CONF_HOST]},

View File

@@ -1,11 +1,11 @@
{
"domain": "airgradient",
"name": "Airgradient",
"name": "AirGradient",
"codeowners": ["@airgradienthq", "@joostlek"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/airgradient",
"integration_type": "device",
"iot_class": "local_polling",
"requirements": ["airgradient==0.4.2"],
"requirements": ["airgradient==0.4.3"],
"zeroconf": ["_airgradient._tcp.local."]
}

View File

@@ -22,7 +22,7 @@ from .entity import AirGradientEntity
class AirGradientSelectEntityDescription(SelectEntityDescription):
"""Describes AirGradient select entity."""
value_fn: Callable[[Config], str]
value_fn: Callable[[Config], str | None]
set_value_fn: Callable[[AirGradientClient, str], Awaitable[None]]
requires_display: bool = False
@@ -30,9 +30,11 @@ class AirGradientSelectEntityDescription(SelectEntityDescription):
CONFIG_CONTROL_ENTITY = AirGradientSelectEntityDescription(
key="configuration_control",
translation_key="configuration_control",
options=[x.value for x in ConfigurationControl],
options=[ConfigurationControl.CLOUD.value, ConfigurationControl.LOCAL.value],
entity_category=EntityCategory.CONFIG,
value_fn=lambda config: config.configuration_control,
value_fn=lambda config: config.configuration_control
if config.configuration_control is not ConfigurationControl.NOT_INITIALIZED
else None,
set_value_fn=lambda client, value: client.set_configuration_control(
ConfigurationControl(value)
),
@@ -96,7 +98,7 @@ class AirGradientSelect(AirGradientEntity, SelectEntity):
self._attr_unique_id = f"{coordinator.serial_number}-{description.key}"
@property
def current_option(self) -> str:
def current_option(self) -> str | None:
"""Return the state of the select."""
return self.entity_description.value_fn(self.coordinator.data)

View File

@@ -103,6 +103,7 @@ SENSOR_TYPES: tuple[AirGradientSensorEntityDescription, ...] = (
AirGradientSensorEntityDescription(
key="pm003",
translation_key="pm003_count",
native_unit_of_measurement="particles/dL",
state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda status: status.pm003_count,
),

View File

@@ -15,7 +15,8 @@
}
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"invalid_version": "This firmware version is unsupported. Please upgrade the firmware of the device to at least version 3.1.1."
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
@@ -28,8 +29,7 @@
"name": "Configuration source",
"state": {
"cloud": "Cloud",
"local": "Local",
"both": "Both"
"local": "Local"
}
},
"display_temperature_unit": {
@@ -48,7 +48,7 @@
"name": "Nitrogen index"
},
"pm003_count": {
"name": "PM0.3 count"
"name": "PM0.3"
},
"raw_total_volatile_organic_component": {
"name": "Raw total VOC"

View File

@@ -2,52 +2,93 @@
from __future__ import annotations
from genie_partner_sdk.client import AladdinConnectClient
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers import aiohttp_client, config_entry_oauth2_flow
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.config_entry_oauth2_flow import (
OAuth2Session,
async_get_config_entry_implementation,
)
from . import api
from .const import CONFIG_FLOW_MINOR_VERSION, CONFIG_FLOW_VERSION
from .api import AsyncConfigEntryAuth
from .const import DOMAIN
from .coordinator import AladdinConnectCoordinator
PLATFORMS: list[Platform] = [Platform.COVER]
PLATFORMS: list[Platform] = [Platform.COVER, Platform.SENSOR]
type AladdinConnectConfigEntry = ConfigEntry[AladdinConnectCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, entry: AladdinConnectConfigEntry
) -> bool:
"""Set up Aladdin Connect Genie from a config entry."""
implementation = (
await config_entry_oauth2_flow.async_get_config_entry_implementation(
hass, entry
)
)
implementation = await async_get_config_entry_implementation(hass, entry)
session = config_entry_oauth2_flow.OAuth2Session(hass, entry, implementation)
session = OAuth2Session(hass, entry, implementation)
auth = AsyncConfigEntryAuth(async_get_clientsession(hass), session)
coordinator = AladdinConnectCoordinator(hass, AladdinConnectClient(auth))
# If using an aiohttp-based API lib
entry.runtime_data = api.AsyncConfigEntryAuth(
aiohttp_client.async_get_clientsession(hass), session
)
await coordinator.async_setup()
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
async_remove_stale_devices(hass, entry)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, entry: AladdinConnectConfigEntry
) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_migrate_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_migrate_entry(
hass: HomeAssistant, config_entry: AladdinConnectConfigEntry
) -> bool:
"""Migrate old config."""
if config_entry.version < CONFIG_FLOW_VERSION:
if config_entry.version < 2:
config_entry.async_start_reauth(hass)
new_data = {**config_entry.data}
hass.config_entries.async_update_entry(
config_entry,
data=new_data,
version=CONFIG_FLOW_VERSION,
minor_version=CONFIG_FLOW_MINOR_VERSION,
version=2,
minor_version=1,
)
return True
def async_remove_stale_devices(
hass: HomeAssistant, config_entry: AladdinConnectConfigEntry
) -> None:
"""Remove stale devices from device registry."""
device_registry = dr.async_get(hass)
device_entries = dr.async_entries_for_config_entry(
device_registry, config_entry.entry_id
)
all_device_ids = {door.unique_id for door in config_entry.runtime_data.doors}
for device_entry in device_entries:
device_id: str | None = None
for identifier in device_entry.identifiers:
if identifier[0] == DOMAIN:
device_id = identifier[1]
break
if device_id is None or device_id not in all_device_ids:
# If device_id is None an invalid device entry was found for this config entry.
# If the device_id is not in existing device ids it's a stale device entry.
# Remove config entry from this device entry in either case.
device_registry.async_update_device(
device_entry.id, remove_config_entry_id=config_entry.entry_id
)

View File

@@ -1,9 +1,11 @@
"""API for Aladdin Connect Genie bound to Home Assistant OAuth."""
from typing import cast
from aiohttp import ClientSession
from genie_partner_sdk.auth import Auth
from homeassistant.helpers import config_entry_oauth2_flow
from homeassistant.helpers.config_entry_oauth2_flow import OAuth2Session
API_URL = "https://twdvzuefzh.execute-api.us-east-2.amazonaws.com/v1"
API_KEY = "k6QaiQmcTm2zfaNns5L1Z8duBtJmhDOW8JawlCC3"
@@ -15,7 +17,7 @@ class AsyncConfigEntryAuth(Auth): # type: ignore[misc]
def __init__(
self,
websession: ClientSession,
oauth_session: config_entry_oauth2_flow.OAuth2Session,
oauth_session: OAuth2Session,
) -> None:
"""Initialize Aladdin Connect Genie auth."""
super().__init__(
@@ -25,7 +27,6 @@ class AsyncConfigEntryAuth(Auth): # type: ignore[misc]
async def async_get_access_token(self) -> str:
"""Return a valid access token."""
if not self._oauth_session.valid_token:
await self._oauth_session.async_ensure_token_valid()
await self._oauth_session.async_ensure_token_valid()
return str(self._oauth_session.token["access_token"])
return cast(str, self._oauth_session.token["access_token"])

View File

@@ -4,22 +4,21 @@ from collections.abc import Mapping
import logging
from typing import Any
import voluptuous as vol
import jwt
from homeassistant.config_entries import ConfigEntry, ConfigFlowResult
from homeassistant.helpers import config_entry_oauth2_flow
from homeassistant.const import CONF_ACCESS_TOKEN, CONF_TOKEN
from homeassistant.helpers.config_entry_oauth2_flow import AbstractOAuth2FlowHandler
from .const import CONFIG_FLOW_MINOR_VERSION, CONFIG_FLOW_VERSION, DOMAIN
from .const import DOMAIN
class OAuth2FlowHandler(
config_entry_oauth2_flow.AbstractOAuth2FlowHandler, domain=DOMAIN
):
class AladdinConnectOAuth2FlowHandler(AbstractOAuth2FlowHandler, domain=DOMAIN):
"""Config flow to handle Aladdin Connect Genie OAuth2 authentication."""
DOMAIN = DOMAIN
VERSION = CONFIG_FLOW_VERSION
MINOR_VERSION = CONFIG_FLOW_MINOR_VERSION
VERSION = 2
MINOR_VERSION = 1
reauth_entry: ConfigEntry | None = None
@@ -37,20 +36,33 @@ class OAuth2FlowHandler(
) -> ConfigFlowResult:
"""Dialog that informs the user that reauth is required."""
if user_input is None:
return self.async_show_form(
step_id="reauth_confirm",
data_schema=vol.Schema({}),
)
return self.async_show_form(step_id="reauth_confirm")
return await self.async_step_user()
async def async_oauth_create_entry(self, data: dict) -> ConfigFlowResult:
async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult:
"""Create an oauth config entry or update existing entry for reauth."""
if self.reauth_entry:
token_payload = jwt.decode(
data[CONF_TOKEN][CONF_ACCESS_TOKEN], options={"verify_signature": False}
)
if not self.reauth_entry:
await self.async_set_unique_id(token_payload["sub"])
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=token_payload["username"],
data=data,
)
if self.reauth_entry.unique_id == token_payload["username"]:
return self.async_update_reload_and_abort(
self.reauth_entry,
data=data,
unique_id=token_payload["sub"],
)
return await super().async_oauth_create_entry(data)
if self.reauth_entry.unique_id == token_payload["sub"]:
return self.async_update_reload_and_abort(self.reauth_entry, data=data)
return self.async_abort(reason="wrong_account")
@property
def logger(self) -> logging.Logger:

View File

@@ -1,14 +1,6 @@
"""Constants for the Aladdin Connect Genie integration."""
from typing import Final
from homeassistant.components.cover import CoverEntityFeature
DOMAIN = "aladdin_connect"
CONFIG_FLOW_VERSION = 2
CONFIG_FLOW_MINOR_VERSION = 1
OAUTH2_AUTHORIZE = "https://app.aladdinconnect.com/login.html"
OAUTH2_AUTHORIZE = "https://app.aladdinconnect.net/login.html"
OAUTH2_TOKEN = "https://twdvzuefzh.execute-api.us-east-2.amazonaws.com/v1/oauth2/token"
SUPPORTED_FEATURES: Final = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE

View File

@@ -0,0 +1,38 @@
"""Define an object to coordinate fetching Aladdin Connect data."""
from datetime import timedelta
import logging
from genie_partner_sdk.client import AladdinConnectClient
from genie_partner_sdk.model import GarageDoor
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
class AladdinConnectCoordinator(DataUpdateCoordinator[None]):
"""Aladdin Connect Data Update Coordinator."""
def __init__(self, hass: HomeAssistant, acc: AladdinConnectClient) -> None:
"""Initialize."""
super().__init__(
hass,
logger=_LOGGER,
name=DOMAIN,
update_interval=timedelta(seconds=15),
)
self.acc = acc
self.doors: list[GarageDoor] = []
async def async_setup(self) -> None:
"""Fetch initial data."""
self.doors = await self.acc.get_doors()
async def _async_update_data(self) -> None:
"""Fetch data from API endpoint."""
for door in self.doors:
await self.acc.update_door(door.device_id, door.door_number)

View File

@@ -1,115 +1,64 @@
"""Cover Entity for Genie Garage Door."""
from datetime import timedelta
from typing import Any
from genie_partner_sdk.client import AladdinConnectClient
from genie_partner_sdk.model import GarageDoor
from homeassistant.components.cover import CoverDeviceClass, CoverEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.components.cover import (
CoverDeviceClass,
CoverEntity,
CoverEntityFeature,
)
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import PlatformNotReady
import homeassistant.helpers.device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import api
from .const import DOMAIN, SUPPORTED_FEATURES
from .model import GarageDoor
SCAN_INTERVAL = timedelta(seconds=15)
from . import AladdinConnectConfigEntry, AladdinConnectCoordinator
from .entity import AladdinConnectEntity
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AladdinConnectConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Aladdin Connect platform."""
session: api.AsyncConfigEntryAuth = config_entry.runtime_data
acc = AladdinConnectClient(session)
doors = await acc.get_doors()
if doors is None:
raise PlatformNotReady("Error from Aladdin Connect getting doors")
device_registry = dr.async_get(hass)
doors_to_add = []
for door in doors:
existing = device_registry.async_get(door.unique_id)
if existing is None:
doors_to_add.append(door)
coordinator = config_entry.runtime_data
async_add_entities(
(AladdinDevice(acc, door, config_entry) for door in doors_to_add),
)
remove_stale_devices(hass, config_entry, doors)
async_add_entities(AladdinDevice(coordinator, door) for door in coordinator.doors)
def remove_stale_devices(
hass: HomeAssistant, config_entry: ConfigEntry, devices: list[GarageDoor]
) -> None:
"""Remove stale devices from device registry."""
device_registry = dr.async_get(hass)
device_entries = dr.async_entries_for_config_entry(
device_registry, config_entry.entry_id
)
all_device_ids = {door.unique_id for door in devices}
for device_entry in device_entries:
device_id: str | None = None
for identifier in device_entry.identifiers:
if identifier[0] == DOMAIN:
device_id = identifier[1]
break
if device_id is None or device_id not in all_device_ids:
# If device_id is None an invalid device entry was found for this config entry.
# If the device_id is not in existing device ids it's a stale device entry.
# Remove config entry from this device entry in either case.
device_registry.async_update_device(
device_entry.id, remove_config_entry_id=config_entry.entry_id
)
class AladdinDevice(CoverEntity):
class AladdinDevice(AladdinConnectEntity, CoverEntity):
"""Representation of Aladdin Connect cover."""
_attr_device_class = CoverDeviceClass.GARAGE
_attr_supported_features = SUPPORTED_FEATURES
_attr_has_entity_name = True
_attr_supported_features = CoverEntityFeature.OPEN | CoverEntityFeature.CLOSE
_attr_name = None
def __init__(
self, acc: AladdinConnectClient, device: GarageDoor, entry: ConfigEntry
self, coordinator: AladdinConnectCoordinator, device: GarageDoor
) -> None:
"""Initialize the Aladdin Connect cover."""
self._acc = acc
self._device_id = device.device_id
self._number = device.door_number
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, device.unique_id)},
name=device.name,
manufacturer="Overhead Door",
)
super().__init__(coordinator, device)
self._attr_unique_id = device.unique_id
async def async_open_cover(self, **kwargs: Any) -> None:
"""Issue open command to cover."""
await self._acc.open_door(self._device_id, self._number)
await self.coordinator.acc.open_door(
self._device.device_id, self._device.door_number
)
async def async_close_cover(self, **kwargs: Any) -> None:
"""Issue close command to cover."""
await self._acc.close_door(self._device_id, self._number)
async def async_update(self) -> None:
"""Update status of cover."""
await self._acc.update_door(self._device_id, self._number)
await self.coordinator.acc.close_door(
self._device.device_id, self._device.door_number
)
@property
def is_closed(self) -> bool | None:
"""Update is closed attribute."""
value = self._acc.get_door_status(self._device_id, self._number)
value = self.coordinator.acc.get_door_status(
self._device.device_id, self._device.door_number
)
if value is None:
return None
return bool(value == "closed")
@@ -117,7 +66,9 @@ class AladdinDevice(CoverEntity):
@property
def is_closing(self) -> bool | None:
"""Update is closing attribute."""
value = self._acc.get_door_status(self._device_id, self._number)
value = self.coordinator.acc.get_door_status(
self._device.device_id, self._device.door_number
)
if value is None:
return None
return bool(value == "closing")
@@ -125,7 +76,9 @@ class AladdinDevice(CoverEntity):
@property
def is_opening(self) -> bool | None:
"""Update is opening attribute."""
value = self._acc.get_door_status(self._device_id, self._number)
value = self.coordinator.acc.get_door_status(
self._device.device_id, self._device.door_number
)
if value is None:
return None
return bool(value == "opening")

View File

@@ -0,0 +1,27 @@
"""Defines a base Aladdin Connect entity."""
from genie_partner_sdk.model import GarageDoor
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN
from .coordinator import AladdinConnectCoordinator
class AladdinConnectEntity(CoordinatorEntity[AladdinConnectCoordinator]):
"""Defines a base Aladdin Connect entity."""
_attr_has_entity_name = True
def __init__(
self, coordinator: AladdinConnectCoordinator, device: GarageDoor
) -> None:
"""Initialize the entity."""
super().__init__(coordinator)
self._device = device
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, device.unique_id)},
name=device.name,
manufacturer="Overhead Door",
)

View File

@@ -1,30 +0,0 @@
"""Models for Aladdin connect cover platform."""
from __future__ import annotations
from typing import TypedDict
class GarageDoorData(TypedDict):
"""Aladdin door data."""
device_id: str
door_number: int
name: str
status: str
link_status: str
battery_level: int
class GarageDoor:
"""Aladdin Garage Door Entity."""
def __init__(self, data: GarageDoorData) -> None:
"""Create `GarageDoor` from dictionary of data."""
self.device_id = data["device_id"]
self.door_number = data["door_number"]
self.unique_id = f"{self.device_id}-{self.door_number}"
self.name = data["name"]
self.status = data["status"]
self.link_status = data["link_status"]
self.battery_level = data["battery_level"]

View File

@@ -4,9 +4,9 @@ from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import cast
from genie_partner_sdk.client import AladdinConnectClient
from genie_partner_sdk.model import GarageDoor
from homeassistant.components.sensor import (
SensorDeviceClass,
@@ -14,22 +14,19 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import api
from .const import DOMAIN
from .model import GarageDoor
from . import AladdinConnectConfigEntry, AladdinConnectCoordinator
from .entity import AladdinConnectEntity
@dataclass(frozen=True, kw_only=True)
class AccSensorEntityDescription(SensorEntityDescription):
"""Describes AladdinConnect sensor entity."""
value_fn: Callable
value_fn: Callable[[AladdinConnectClient, str, int], float | None]
SENSORS: tuple[AccSensorEntityDescription, ...] = (
@@ -45,52 +42,39 @@ SENSORS: tuple[AccSensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant,
entry: AladdinConnectConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Aladdin Connect sensor devices."""
coordinator = entry.runtime_data
session: api.AsyncConfigEntryAuth = hass.data[DOMAIN][entry.entry_id]
acc = AladdinConnectClient(session)
entities = []
doors = await acc.get_doors()
for door in doors:
entities.extend(
[AladdinConnectSensor(acc, door, description) for description in SENSORS]
)
async_add_entities(entities)
async_add_entities(
AladdinConnectSensor(coordinator, door, description)
for description in SENSORS
for door in coordinator.doors
)
class AladdinConnectSensor(SensorEntity):
class AladdinConnectSensor(AladdinConnectEntity, SensorEntity):
"""A sensor implementation for Aladdin Connect devices."""
entity_description: AccSensorEntityDescription
_attr_has_entity_name = True
def __init__(
self,
acc: AladdinConnectClient,
coordinator: AladdinConnectCoordinator,
device: GarageDoor,
description: AccSensorEntityDescription,
) -> None:
"""Initialize a sensor for an Aladdin Connect device."""
self._device_id = device.device_id
self._number = device.door_number
self._acc = acc
super().__init__(coordinator, device)
self.entity_description = description
self._attr_unique_id = f"{device.unique_id}-{description.key}"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, device.unique_id)},
name=device.name,
manufacturer="Overhead Door",
)
@property
def native_value(self) -> float | None:
"""Return the state of the sensor."""
return cast(
float,
self.entity_description.value_fn(self._acc, self._device_id, self._number),
return self.entity_description.value_fn(
self.coordinator.acc, self._device.device_id, self._device.door_number
)

View File

@@ -62,13 +62,12 @@ async def async_setup(hass: HomeAssistant, yaml_config: ConfigType) -> bool:
Adds an empty filter to hass data.
Tries to get a filter from yaml, if present set to hass data.
If config is empty after getting the filter, return, otherwise emit
deprecated warning and pass the rest to the config flow.
"""
hass.data.setdefault(DOMAIN, {DATA_FILTER: {}})
hass.data.setdefault(DOMAIN, {DATA_FILTER: FILTER_SCHEMA({})})
if DOMAIN in yaml_config:
hass.data[DOMAIN][DATA_FILTER] = yaml_config[DOMAIN][CONF_FILTER]
hass.data[DOMAIN][DATA_FILTER] = yaml_config[DOMAIN].pop(CONF_FILTER)
return True
@@ -207,6 +206,6 @@ class AzureDataExplorer:
if "\n" in state.state:
return None, dropped + 1
json_event = str(json.dumps(obj=state, cls=JSONEncoder).encode("utf-8"))
json_event = json.dumps(obj=state, cls=JSONEncoder)
return (json_event, dropped)

View File

@@ -23,7 +23,7 @@ from .const import (
CONF_APP_REG_ID,
CONF_APP_REG_SECRET,
CONF_AUTHORITY_ID,
CONF_USE_FREE,
CONF_USE_QUEUED_CLIENT,
)
_LOGGER = logging.getLogger(__name__)
@@ -35,7 +35,6 @@ class AzureDataExplorerClient:
def __init__(self, data: Mapping[str, Any]) -> None:
"""Create the right class."""
self._cluster_ingest_uri = data[CONF_ADX_CLUSTER_INGEST_URI]
self._database = data[CONF_ADX_DATABASE_NAME]
self._table = data[CONF_ADX_TABLE_NAME]
self._ingestion_properties = IngestionProperties(
@@ -45,24 +44,36 @@ class AzureDataExplorerClient:
ingestion_mapping_reference="ha_json_mapping",
)
# Create cLient for ingesting and querying data
kcsb = KustoConnectionStringBuilder.with_aad_application_key_authentication(
self._cluster_ingest_uri,
data[CONF_APP_REG_ID],
data[CONF_APP_REG_SECRET],
data[CONF_AUTHORITY_ID],
# Create client for ingesting data
kcsb_ingest = (
KustoConnectionStringBuilder.with_aad_application_key_authentication(
data[CONF_ADX_CLUSTER_INGEST_URI],
data[CONF_APP_REG_ID],
data[CONF_APP_REG_SECRET],
data[CONF_AUTHORITY_ID],
)
)
if data[CONF_USE_FREE] is True:
# Queded is the only option supported on free tear of ADX
self.write_client = QueuedIngestClient(kcsb)
else:
self.write_client = ManagedStreamingIngestClient.from_dm_kcsb(kcsb)
# Create client for querying data
kcsb_query = (
KustoConnectionStringBuilder.with_aad_application_key_authentication(
data[CONF_ADX_CLUSTER_INGEST_URI].replace("ingest-", ""),
data[CONF_APP_REG_ID],
data[CONF_APP_REG_SECRET],
data[CONF_AUTHORITY_ID],
)
)
self.query_client = KustoClient(kcsb)
if data[CONF_USE_QUEUED_CLIENT] is True:
# Queded is the only option supported on free tear of ADX
self.write_client = QueuedIngestClient(kcsb_ingest)
else:
self.write_client = ManagedStreamingIngestClient.from_dm_kcsb(kcsb_ingest)
self.query_client = KustoClient(kcsb_query)
def test_connection(self) -> None:
"""Test connection, will throw Exception when it cannot connect."""
"""Test connection, will throw Exception if it cannot connect."""
query = f"{self._table} | take 1"

View File

@@ -10,6 +10,7 @@ import voluptuous as vol
from homeassistant import config_entries
from homeassistant.config_entries import ConfigFlowResult
from homeassistant.helpers.selector import BooleanSelector
from . import AzureDataExplorerClient
from .const import (
@@ -19,7 +20,7 @@ from .const import (
CONF_APP_REG_ID,
CONF_APP_REG_SECRET,
CONF_AUTHORITY_ID,
CONF_USE_FREE,
CONF_USE_QUEUED_CLIENT,
DEFAULT_OPTIONS,
DOMAIN,
)
@@ -34,7 +35,7 @@ STEP_USER_DATA_SCHEMA = vol.Schema(
vol.Required(CONF_APP_REG_ID): str,
vol.Required(CONF_APP_REG_SECRET): str,
vol.Required(CONF_AUTHORITY_ID): str,
vol.Optional(CONF_USE_FREE, default=False): bool,
vol.Required(CONF_USE_QUEUED_CLIENT, default=False): BooleanSelector(),
}
)

View File

@@ -17,7 +17,7 @@ CONF_AUTHORITY_ID = "authority_id"
CONF_SEND_INTERVAL = "send_interval"
CONF_MAX_DELAY = "max_delay"
CONF_FILTER = DATA_FILTER = "filter"
CONF_USE_FREE = "use_queued_ingestion"
CONF_USE_QUEUED_CLIENT = "use_queued_ingestion"
DATA_HUB = "hub"
STEP_USER = "user"

View File

@@ -3,14 +3,19 @@
"step": {
"user": {
"title": "Setup your Azure Data Explorer integration",
"description": "Enter connection details.",
"description": "Enter connection details",
"data": {
"clusteringesturi": "Cluster Ingest URI",
"database": "Database name",
"table": "Table name",
"cluster_ingest_uri": "Cluster Ingest URI",
"authority_id": "Authority ID",
"client_id": "Client ID",
"client_secret": "Client secret",
"authority_id": "Authority ID"
"database": "Database name",
"table": "Table name",
"use_queued_ingestion": "Use queued ingestion"
},
"data_description": {
"cluster_ingest_uri": "Ingest-URI of the cluster",
"use_queued_ingestion": "Must be enabled when using ADX free cluster"
}
}
},

View File

@@ -46,6 +46,7 @@ class BlinkSyncModuleHA(
"""Representation of a Blink Alarm Control Panel."""
_attr_supported_features = AlarmControlPanelEntityFeature.ARM_AWAY
_attr_code_arm_required = False
_attr_has_entity_name = True
_attr_name = None

View File

@@ -85,7 +85,7 @@
},
"save_recent_clips": {
"name": "Save recent clips",
"description": "Saves all recent video clips to local directory with file pattern \"%Y%m%d_%H%M%S_{name}.mp4\".",
"description": "Saves all recent video clips to local directory with file pattern \"%Y%m%d_%H%M%S_[camera name].mp4\".",
"fields": {
"file_path": {
"name": "Output directory",

View File

@@ -65,11 +65,13 @@ class BMWLock(BMWBaseEntity, LockEntity):
try:
await self.vehicle.remote_services.trigger_remote_door_lock()
except MyBMWAPIError as ex:
self._attr_is_locked = False
# Set the state to unknown if the command fails
self._attr_is_locked = None
self.async_write_ha_state()
raise HomeAssistantError(ex) from ex
self.coordinator.async_update_listeners()
finally:
# Always update the listeners to get the latest state
self.coordinator.async_update_listeners()
async def async_unlock(self, **kwargs: Any) -> None:
"""Unlock the car."""
@@ -83,11 +85,13 @@ class BMWLock(BMWBaseEntity, LockEntity):
try:
await self.vehicle.remote_services.trigger_remote_door_unlock()
except MyBMWAPIError as ex:
self._attr_is_locked = True
# Set the state to unknown if the command fails
self._attr_is_locked = None
self.async_write_ha_state()
raise HomeAssistantError(ex) from ex
self.coordinator.async_update_listeners()
finally:
# Always update the listeners to get the latest state
self.coordinator.async_update_listeners()
@callback
def _handle_coordinator_update(self) -> None:

View File

@@ -6,9 +6,8 @@ from collections.abc import Callable
from dataclasses import dataclass
import datetime
import logging
from typing import cast
from bimmer_connected.models import ValueWithUnit
from bimmer_connected.models import StrEnum, ValueWithUnit
from bimmer_connected.vehicle import MyBMWVehicle
from homeassistant.components.sensor import (
@@ -18,14 +17,19 @@ from homeassistant.components.sensor import (
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import LENGTH, PERCENTAGE, VOLUME, UnitOfElectricCurrent
from homeassistant.const import (
PERCENTAGE,
STATE_UNKNOWN,
UnitOfElectricCurrent,
UnitOfLength,
UnitOfVolume,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.util import dt as dt_util
from . import BMWBaseEntity
from .const import CLIMATE_ACTIVITY_STATE, DOMAIN, UNIT_MAP
from .const import CLIMATE_ACTIVITY_STATE, DOMAIN
from .coordinator import BMWDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
@@ -36,34 +40,18 @@ class BMWSensorEntityDescription(SensorEntityDescription):
"""Describes BMW sensor entity."""
key_class: str | None = None
unit_type: str | None = None
value: Callable = lambda x, y: x
is_available: Callable[[MyBMWVehicle], bool] = lambda v: v.is_lsc_enabled
def convert_and_round(
state: ValueWithUnit,
converter: Callable[[float | None, str], float],
precision: int,
) -> float | None:
"""Safely convert and round a value from ValueWithUnit."""
if state.value and state.unit:
return round(
converter(state.value, UNIT_MAP.get(state.unit, state.unit)), precision
)
if state.value:
return state.value
return None
SENSOR_TYPES: list[BMWSensorEntityDescription] = [
# --- Generic ---
BMWSensorEntityDescription(
key="ac_current_limit",
translation_key="ac_current_limit",
key_class="charging_profile",
unit_type=UnitOfElectricCurrent.AMPERE,
device_class=SensorDeviceClass.CURRENT,
native_unit_of_measurement=UnitOfElectricCurrent.AMPERE,
entity_registry_enabled_default=False,
suggested_display_precision=0,
is_available=lambda v: v.is_lsc_enabled and v.has_electric_drivetrain,
),
BMWSensorEntityDescription(
@@ -85,74 +73,81 @@ SENSOR_TYPES: list[BMWSensorEntityDescription] = [
key="charging_status",
translation_key="charging_status",
key_class="fuel_and_battery",
value=lambda x, y: x.value,
is_available=lambda v: v.is_lsc_enabled and v.has_electric_drivetrain,
),
BMWSensorEntityDescription(
key="charging_target",
translation_key="charging_target",
key_class="fuel_and_battery",
unit_type=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=0,
is_available=lambda v: v.is_lsc_enabled and v.has_electric_drivetrain,
),
BMWSensorEntityDescription(
key="remaining_battery_percent",
translation_key="remaining_battery_percent",
key_class="fuel_and_battery",
unit_type=PERCENTAGE,
device_class=SensorDeviceClass.BATTERY,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
is_available=lambda v: v.is_lsc_enabled and v.has_electric_drivetrain,
),
# --- Specific ---
BMWSensorEntityDescription(
key="mileage",
translation_key="mileage",
unit_type=LENGTH,
value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2),
device_class=SensorDeviceClass.DISTANCE,
native_unit_of_measurement=UnitOfLength.KILOMETERS,
state_class=SensorStateClass.TOTAL_INCREASING,
suggested_display_precision=0,
),
BMWSensorEntityDescription(
key="remaining_range_total",
translation_key="remaining_range_total",
key_class="fuel_and_battery",
unit_type=LENGTH,
value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2),
device_class=SensorDeviceClass.DISTANCE,
native_unit_of_measurement=UnitOfLength.KILOMETERS,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
BMWSensorEntityDescription(
key="remaining_range_electric",
translation_key="remaining_range_electric",
key_class="fuel_and_battery",
unit_type=LENGTH,
value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2),
device_class=SensorDeviceClass.DISTANCE,
native_unit_of_measurement=UnitOfLength.KILOMETERS,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
is_available=lambda v: v.is_lsc_enabled and v.has_electric_drivetrain,
),
BMWSensorEntityDescription(
key="remaining_range_fuel",
translation_key="remaining_range_fuel",
key_class="fuel_and_battery",
unit_type=LENGTH,
value=lambda x, hass: convert_and_round(x, hass.config.units.length, 2),
device_class=SensorDeviceClass.DISTANCE,
native_unit_of_measurement=UnitOfLength.KILOMETERS,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
is_available=lambda v: v.is_lsc_enabled and v.has_combustion_drivetrain,
),
BMWSensorEntityDescription(
key="remaining_fuel",
translation_key="remaining_fuel",
key_class="fuel_and_battery",
unit_type=VOLUME,
value=lambda x, hass: convert_and_round(x, hass.config.units.volume, 2),
device_class=SensorDeviceClass.VOLUME,
native_unit_of_measurement=UnitOfVolume.LITERS,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
is_available=lambda v: v.is_lsc_enabled and v.has_combustion_drivetrain,
),
BMWSensorEntityDescription(
key="remaining_fuel_percent",
translation_key="remaining_fuel_percent",
key_class="fuel_and_battery",
unit_type=PERCENTAGE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
is_available=lambda v: v.is_lsc_enabled and v.has_combustion_drivetrain,
),
BMWSensorEntityDescription(
@@ -161,7 +156,6 @@ SENSOR_TYPES: list[BMWSensorEntityDescription] = [
key_class="climate",
device_class=SensorDeviceClass.ENUM,
options=CLIMATE_ACTIVITY_STATE,
value=lambda x, _: x.lower() if x != "UNKNOWN" else None,
is_available=lambda v: v.is_remote_climate_stop_enabled,
),
]
@@ -201,13 +195,6 @@ class BMWSensor(BMWBaseEntity, SensorEntity):
self.entity_description = description
self._attr_unique_id = f"{vehicle.vin}-{description.key}"
# Set the correct unit of measurement based on the unit_type
if description.unit_type:
self._attr_native_unit_of_measurement = (
coordinator.hass.config.units.as_dict().get(description.unit_type)
or description.unit_type
)
@callback
def _handle_coordinator_update(self) -> None:
"""Handle updated data from the coordinator."""
@@ -225,8 +212,18 @@ class BMWSensor(BMWBaseEntity, SensorEntity):
# For datetime without tzinfo, we assume it to be the same timezone as the HA instance
if isinstance(state, datetime.datetime) and state.tzinfo is None:
state = state.replace(tzinfo=dt_util.get_default_time_zone())
# For enum types, we only want the value
elif isinstance(state, ValueWithUnit):
state = state.value
# Get lowercase values from StrEnum
elif isinstance(state, StrEnum):
state = state.value.lower()
if state == STATE_UNKNOWN:
state = None
self._attr_native_value = cast(
StateType, self.entity_description.value(state, self.hass)
)
# special handling for charging_status to avoid a breaking change
if self.entity_description.key == "charging_status" and state:
state = state.upper()
self._attr_native_value = state
super()._handle_coordinator_update()

View File

@@ -3,16 +3,14 @@
from __future__ import annotations
from brother import Brother, SnmpError
from pysnmp.hlapi.asyncio.cmdgen import lcd
from homeassistant.config_entries import ConfigEntry, ConfigEntryState
from homeassistant.components.snmp import async_get_snmp_engine
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_TYPE, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from .const import DOMAIN, SNMP_ENGINE
from .coordinator import BrotherDataUpdateCoordinator
from .utils import get_snmp_engine
PLATFORMS = [Platform.SENSOR]
@@ -24,7 +22,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: BrotherConfigEntry) -> b
host = entry.data[CONF_HOST]
printer_type = entry.data[CONF_TYPE]
snmp_engine = get_snmp_engine(hass)
snmp_engine = await async_get_snmp_engine(hass)
try:
brother = await Brother.create(
host, printer_type=printer_type, snmp_engine=snmp_engine
@@ -44,16 +42,4 @@ async def async_setup_entry(hass: HomeAssistant, entry: BrotherConfigEntry) -> b
async def async_unload_entry(hass: HomeAssistant, entry: BrotherConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
loaded_entries = [
entry
for entry in hass.config_entries.async_entries(DOMAIN)
if entry.state == ConfigEntryState.LOADED
]
# We only want to remove the SNMP engine when unloading the last config entry
if unload_ok and len(loaded_entries) == 1:
lcd.unconfigure(hass.data[SNMP_ENGINE], None)
hass.data.pop(SNMP_ENGINE)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -8,13 +8,13 @@ from brother import Brother, SnmpError, UnsupportedModelError
import voluptuous as vol
from homeassistant.components import zeroconf
from homeassistant.components.snmp import async_get_snmp_engine
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_HOST, CONF_TYPE
from homeassistant.exceptions import HomeAssistantError
from homeassistant.util.network import is_host_valid
from .const import DOMAIN, PRINTER_TYPES
from .utils import get_snmp_engine
DATA_SCHEMA = vol.Schema(
{
@@ -45,7 +45,7 @@ class BrotherConfigFlow(ConfigFlow, domain=DOMAIN):
if not is_host_valid(user_input[CONF_HOST]):
raise InvalidHost
snmp_engine = get_snmp_engine(self.hass)
snmp_engine = await async_get_snmp_engine(self.hass)
brother = await Brother.create(
user_input[CONF_HOST], snmp_engine=snmp_engine
@@ -79,7 +79,7 @@ class BrotherConfigFlow(ConfigFlow, domain=DOMAIN):
# Do not probe the device if the host is already configured
self._async_abort_entries_match({CONF_HOST: self.host})
snmp_engine = get_snmp_engine(self.hass)
snmp_engine = await async_get_snmp_engine(self.hass)
model = discovery_info.properties.get("product")
try:

View File

@@ -9,6 +9,4 @@ DOMAIN: Final = "brother"
PRINTER_TYPES: Final = ["laser", "ink"]
SNMP_ENGINE: Final = "snmp_engine"
UPDATE_INTERVAL = timedelta(seconds=30)

View File

@@ -1,6 +1,7 @@
{
"domain": "brother",
"name": "Brother Printer",
"after_dependencies": ["snmp"],
"codeowners": ["@bieniu"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/brother",

View File

@@ -1,33 +0,0 @@
"""Brother helpers functions."""
from __future__ import annotations
import logging
import pysnmp.hlapi.asyncio as hlapi
from pysnmp.hlapi.asyncio.cmdgen import lcd
from homeassistant.const import EVENT_HOMEASSISTANT_STOP
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.helpers import singleton
from .const import SNMP_ENGINE
_LOGGER = logging.getLogger(__name__)
@singleton.singleton(SNMP_ENGINE)
def get_snmp_engine(hass: HomeAssistant) -> hlapi.SnmpEngine:
"""Get SNMP engine."""
_LOGGER.debug("Creating SNMP engine")
snmp_engine = hlapi.SnmpEngine()
@callback
def shutdown_listener(ev: Event) -> None:
if hass.data.get(SNMP_ENGINE):
_LOGGER.debug("Unconfiguring SNMP engine")
lcd.unconfigure(hass.data[SNMP_ENGINE], None)
hass.bus.async_listen_once(EVENT_HOMEASSISTANT_STOP, shutdown_listener)
return snmp_engine

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/buienradar",
"iot_class": "cloud_polling",
"loggers": ["buienradar", "vincenty"],
"requirements": ["buienradar==1.0.5"]
"requirements": ["buienradar==1.0.6"]
}

View File

@@ -57,6 +57,7 @@ class CCM15Climate(CoordinatorEntity[CCM15Coordinator], ClimateEntity):
HVACMode.HEAT,
HVACMode.COOL,
HVACMode.DRY,
HVACMode.FAN_ONLY,
HVACMode.AUTO,
]
_attr_fan_modes = [FAN_AUTO, FAN_LOW, FAN_MEDIUM, FAN_HIGH]

View File

@@ -4,11 +4,10 @@ from __future__ import annotations
import voluptuous as vol
from homeassistant.core import HomeAssistant, State
from homeassistant.core import HomeAssistant
from homeassistant.helpers import intent
from homeassistant.helpers.entity_component import EntityComponent
from . import DOMAIN, ClimateEntity
from . import DOMAIN
INTENT_GET_TEMPERATURE = "HassClimateGetTemperature"
@@ -23,7 +22,10 @@ class GetTemperatureIntent(intent.IntentHandler):
intent_type = INTENT_GET_TEMPERATURE
description = "Gets the current temperature of a climate device or entity"
slot_schema = {vol.Optional("area"): str, vol.Optional("name"): str}
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:
@@ -31,74 +33,24 @@ class GetTemperatureIntent(intent.IntentHandler):
hass = intent_obj.hass
slots = self.async_validate_slots(intent_obj.slots)
component: EntityComponent[ClimateEntity] = hass.data[DOMAIN]
entities: list[ClimateEntity] = list(component.entities)
climate_entity: ClimateEntity | None = None
climate_state: State | None = None
name: str | None = None
if "name" in slots:
name = slots["name"]["value"]
if not entities:
raise intent.IntentHandleError("No climate entities")
area: str | None = None
if "area" in slots:
area = slots["area"]["value"]
name_slot = slots.get("name", {})
entity_name: str | None = name_slot.get("value")
entity_text: str | None = name_slot.get("text")
area_slot = slots.get("area", {})
area_id = area_slot.get("value")
if area_id:
# Filter by area and optionally name
area_name = area_slot.get("text")
for maybe_climate in intent.async_match_states(
hass, name=entity_name, area_name=area_id, domains=[DOMAIN]
):
climate_state = maybe_climate
break
if climate_state is None:
raise intent.NoStatesMatchedError(
reason=intent.MatchFailedReason.AREA,
name=entity_text or entity_name,
area=area_name or area_id,
floor=None,
domains={DOMAIN},
device_classes=None,
)
climate_entity = component.get_entity(climate_state.entity_id)
elif entity_name:
# Filter by name
for maybe_climate in intent.async_match_states(
hass, name=entity_name, domains=[DOMAIN]
):
climate_state = maybe_climate
break
if climate_state is None:
raise intent.NoStatesMatchedError(
reason=intent.MatchFailedReason.NAME,
name=entity_name,
area=None,
floor=None,
domains={DOMAIN},
device_classes=None,
)
climate_entity = component.get_entity(climate_state.entity_id)
else:
# First entity
climate_entity = entities[0]
climate_state = hass.states.get(climate_entity.entity_id)
assert climate_entity is not None
if climate_state is None:
raise intent.IntentHandleError(f"No state for {climate_entity.name}")
assert climate_state is not None
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=[climate_state])
response.async_set_states(matched_states=match_result.states)
return response

View File

@@ -8,5 +8,5 @@
"integration_type": "system",
"iot_class": "cloud_push",
"loggers": ["hass_nabucasa"],
"requirements": ["hass-nabucasa==0.81.0"]
"requirements": ["hass-nabucasa==0.81.1"]
}

View File

@@ -86,6 +86,7 @@ class Concord232Alarm(AlarmControlPanelEntity):
self._attr_name = name
self._code = code
self._alarm_control_panel_option_default_code = code
self._mode = mode
self._url = url
self._alarm = concord232_client.Client(self._url)

View File

@@ -120,7 +120,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
director_all_items = json.loads(director_all_items)
entry_data[CONF_DIRECTOR_ALL_ITEMS] = director_all_items
entry_data[CONF_UI_CONFIGURATION] = json.loads(await director.getUiConfiguration())
# Check if OS version is 3 or higher to get UI configuration
entry_data[CONF_UI_CONFIGURATION] = None
if int(entry_data[CONF_DIRECTOR_SW_VERSION].split(".")[0]) >= 3:
entry_data[CONF_UI_CONFIGURATION] = json.loads(
await director.getUiConfiguration()
)
# Load options from config entry
entry_data[CONF_SCAN_INTERVAL] = entry.options.get(

View File

@@ -81,11 +81,18 @@ async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Control4 rooms from a config entry."""
entry_data = hass.data[DOMAIN][entry.entry_id]
ui_config = entry_data[CONF_UI_CONFIGURATION]
# OS 2 will not have a ui_configuration
if not ui_config:
_LOGGER.debug("No UI Configuration found for Control4")
return
all_rooms = await get_rooms(hass, entry)
if not all_rooms:
return
entry_data = hass.data[DOMAIN][entry.entry_id]
scan_interval = entry_data[CONF_SCAN_INTERVAL]
_LOGGER.debug("Scan interval = %s", scan_interval)
@@ -119,8 +126,6 @@ async def async_setup_entry(
if "parentId" in item and k > 1
}
ui_config = entry_data[CONF_UI_CONFIGURATION]
entity_list = []
for room in all_rooms:
room_id = room["id"]

View File

@@ -429,8 +429,15 @@ class DefaultAgent(ConversationEntity):
intent_context=intent_context,
language=language,
):
if ("name" in result.entities) and (
not result.entities["name"].is_wildcard
# Prioritize results with a "name" slot, but still prefer ones with
# more literal text matched.
if (
("name" in result.entities)
and (not result.entities["name"].is_wildcard)
and (
(name_result is None)
or (result.text_chunks_matched > name_result.text_chunks_matched)
)
):
name_result = result
@@ -871,7 +878,7 @@ class DefaultAgent(ConversationEntity):
if device_area is None:
return None
return {"area": {"value": device_area.id, "text": device_area.name}}
return {"area": {"value": device_area.name, "text": device_area.name}}
def _get_error_text(
self,

View File

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

View File

@@ -19,6 +19,7 @@ async def async_setup_intents(hass: HomeAssistant) -> None:
DOMAIN,
SERVICE_OPEN_COVER,
"Opened {}",
description="Opens a cover",
platforms={DOMAIN},
),
)
@@ -29,6 +30,7 @@ async def async_setup_intents(hass: HomeAssistant) -> None:
DOMAIN,
SERVICE_CLOSE_COVER,
"Closed {}",
description="Closes a cover",
platforms={DOMAIN},
),
)

View File

@@ -3,4 +3,4 @@
from __future__ import annotations
DOMAIN = "discovergy"
MANUFACTURER = "Discovergy"
MANUFACTURER = "inexogy"

View File

@@ -1,6 +1,6 @@
{
"domain": "discovergy",
"name": "Discovergy",
"name": "inexogy",
"codeowners": ["@jpbede"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/discovergy",

View File

@@ -26,7 +26,7 @@
},
"system_health": {
"info": {
"api_endpoint_reachable": "Discovergy API endpoint reachable"
"api_endpoint_reachable": "inexogy API endpoint reachable"
}
},
"entity": {

View File

@@ -149,7 +149,7 @@ class DlnaDmrFlowHandler(ConfigFlow, domain=DOMAIN):
# case the device doesn't have a static and unique UDN (breaking the
# UPnP spec).
for entry in self._async_current_entries(include_ignore=True):
if self._location == entry.data[CONF_URL]:
if self._location == entry.data.get(CONF_URL):
return self.async_abort(reason="already_configured")
if self._mac and self._mac == entry.data.get(CONF_MAC):
return self.async_abort(reason="already_configured")

View File

@@ -43,7 +43,9 @@ class EcobeeNotificationService(BaseNotificationService):
async def async_send_message(self, message: str = "", **kwargs: Any) -> None:
"""Send a message and raise issue."""
migrate_notify_issue(self.hass, DOMAIN, "Ecobee", "2024.11.0")
migrate_notify_issue(
self.hass, DOMAIN, "Ecobee", "2024.11.0", service_name=self._service_name
)
await self.hass.async_add_executor_job(
partial(self.send_message, message, **kwargs)
)

View File

@@ -67,6 +67,7 @@ class EgardiaAlarm(AlarmControlPanelEntity):
"""Representation of a Egardia alarm."""
_attr_state: str | None
_attr_code_arm_required = False
_attr_supported_features = (
AlarmControlPanelEntityFeature.ARM_HOME
| AlarmControlPanelEntityFeature.ARM_AWAY

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/electrasmart",
"iot_class": "cloud_polling",
"requirements": ["pyElectra==1.2.0"]
"requirements": ["pyElectra==1.2.1"]
}

View File

@@ -59,7 +59,15 @@ class ElgatoLight(ElgatoEntity, LightEntity):
self._attr_unique_id = coordinator.data.info.serial_number
# Elgato Light supporting color, have a different temperature range
if self.coordinator.data.settings.power_on_hue is not None:
if (
self.coordinator.data.info.product_name
in (
"Elgato Light Strip",
"Elgato Light Strip Pro",
)
or self.coordinator.data.settings.power_on_hue
or self.coordinator.data.state.hue is not None
):
self._attr_supported_color_modes = {ColorMode.COLOR_TEMP, ColorMode.HS}
self._attr_min_mireds = 153
self._attr_max_mireds = 285

View File

@@ -141,10 +141,10 @@ class Enigma2Device(MediaPlayerEntity):
self._device: OpenWebIfDevice = device
self._entry = entry
self._attr_unique_id = device.mac_address
self._attr_unique_id = device.mac_address or entry.entry_id
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, device.mac_address)},
identifiers={(DOMAIN, self._attr_unique_id)},
manufacturer=about["info"]["brand"],
model=about["info"]["model"],
configuration_url=device.base,

View File

@@ -116,8 +116,9 @@ class EnvisalinkAlarm(EnvisalinkDevice, AlarmControlPanelEntity):
):
"""Initialize the alarm panel."""
self._partition_number = partition_number
self._code = code
self._panic_type = panic_type
self._alarm_control_panel_option_default_code = code
self._attr_code_format = CodeFormat.NUMBER
_LOGGER.debug("Setting up alarm: %s", alarm_name)
super().__init__(alarm_name, info, controller)
@@ -141,13 +142,6 @@ class EnvisalinkAlarm(EnvisalinkDevice, AlarmControlPanelEntity):
if partition is None or int(partition) == self._partition_number:
self.async_write_ha_state()
@property
def code_format(self) -> CodeFormat | None:
"""Regex for code format or None if no code is required."""
if self._code:
return None
return CodeFormat.NUMBER
@property
def state(self) -> str:
"""Return the state of the device."""
@@ -169,34 +163,15 @@ class EnvisalinkAlarm(EnvisalinkDevice, AlarmControlPanelEntity):
async def async_alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command."""
if code:
self.hass.data[DATA_EVL].disarm_partition(str(code), self._partition_number)
else:
self.hass.data[DATA_EVL].disarm_partition(
str(self._code), self._partition_number
)
self.hass.data[DATA_EVL].disarm_partition(code, self._partition_number)
async def async_alarm_arm_home(self, code: str | None = None) -> None:
"""Send arm home command."""
if code:
self.hass.data[DATA_EVL].arm_stay_partition(
str(code), self._partition_number
)
else:
self.hass.data[DATA_EVL].arm_stay_partition(
str(self._code), self._partition_number
)
self.hass.data[DATA_EVL].arm_stay_partition(code, self._partition_number)
async def async_alarm_arm_away(self, code: str | None = None) -> None:
"""Send arm away command."""
if code:
self.hass.data[DATA_EVL].arm_away_partition(
str(code), self._partition_number
)
else:
self.hass.data[DATA_EVL].arm_away_partition(
str(self._code), self._partition_number
)
self.hass.data[DATA_EVL].arm_away_partition(code, self._partition_number)
async def async_alarm_trigger(self, code: str | None = None) -> None:
"""Alarm trigger command. Will be used to trigger a panic alarm."""
@@ -204,9 +179,7 @@ class EnvisalinkAlarm(EnvisalinkDevice, AlarmControlPanelEntity):
async def async_alarm_arm_night(self, code: str | None = None) -> None:
"""Send arm night command."""
self.hass.data[DATA_EVL].arm_night_partition(
str(code) if code else str(self._code), self._partition_number
)
self.hass.data[DATA_EVL].arm_night_partition(code, self._partition_number)
@callback
def async_alarm_keypress(self, keypress=None):

View File

@@ -6,7 +6,7 @@ Such systems include evohome, Round Thermostat, and others.
from __future__ import annotations
from collections.abc import Awaitable
from datetime import datetime, timedelta
from datetime import datetime, timedelta, timezone
from http import HTTPStatus
import logging
import re
@@ -452,7 +452,7 @@ class EvoBroker:
self.config = client.installation_info[loc_idx][GWS][0][TCS][0]
self.tcs: evo.ControlSystem = self._location._gateways[0]._control_systems[0] # noqa: SLF001
self.tcs_utc_offset = timedelta(minutes=self._location.timeZone[UTC_OFFSET])
self.loc_utc_offset = timedelta(minutes=self._location.timeZone[UTC_OFFSET])
self.temps: dict[str, float | None] = {}
async def save_auth_tokens(self) -> None:
@@ -685,7 +685,8 @@ class EvoChild(EvoDevice):
if not (schedule := self._schedule.get("DailySchedules")):
return {} # no scheduled setpoints when {'DailySchedules': []}
day_time = dt_util.now()
# get dt in the same TZ as the TCS location, so we can compare schedule times
day_time = dt_util.now().astimezone(timezone(self._evo_broker.loc_utc_offset))
day_of_week = day_time.weekday() # for evohome, 0 is Monday
time_of_day = day_time.strftime("%H:%M:%S")
@@ -699,7 +700,7 @@ class EvoChild(EvoDevice):
else:
break
# Did the current SP start yesterday? Does the next start SP tomorrow?
# Did this setpoint start yesterday? Does the next setpoint start tomorrow?
this_sp_day = -1 if sp_idx == -1 else 0
next_sp_day = 1 if sp_idx + 1 == len(day["Switchpoints"]) else 0
@@ -716,7 +717,7 @@ class EvoChild(EvoDevice):
)
assert switchpoint_time_of_day is not None # mypy check
dt_aware = _dt_evo_to_aware(
switchpoint_time_of_day, self._evo_broker.tcs_utc_offset
switchpoint_time_of_day, self._evo_broker.loc_utc_offset
)
self._setpoints[f"{key}_sp_from"] = dt_aware.isoformat()
@@ -740,16 +741,18 @@ class EvoChild(EvoDevice):
assert isinstance(self._evo_device, evo.HotWater | evo.Zone) # mypy check
try:
self._schedule = await self._evo_broker.call_client_api( # type: ignore[assignment]
schedule = await self._evo_broker.call_client_api(
self._evo_device.get_schedule(), update_state=False
)
except evo.InvalidSchedule as err:
_LOGGER.warning(
"%s: Unable to retrieve the schedule: %s",
"%s: Unable to retrieve a valid schedule: %s",
self._evo_device,
err,
)
self._schedule = {}
else:
self._schedule = schedule or {}
_LOGGER.debug("Schedule['%s'] = %s", self.name, self._schedule)

View File

@@ -69,7 +69,9 @@ class FileNotificationService(BaseNotificationService):
"""Send a message to a file."""
# The use of the legacy notify service was deprecated with HA Core 2024.6.0
# and will be removed with HA Core 2024.12
migrate_notify_issue(self.hass, DOMAIN, "File", "2024.12.0")
migrate_notify_issue(
self.hass, DOMAIN, "File", "2024.12.0", service_name=self._service_name
)
await self.hass.async_add_executor_job(
partial(self.send_message, message, **kwargs)
)

View File

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

View File

@@ -93,7 +93,7 @@ SENSORS: Final[list[FytaSensorEntityDescription]] = [
FytaSensorEntityDescription(
key="light",
translation_key="light",
native_unit_of_measurement="mol/d",
native_unit_of_measurement="μmol/s⋅m²",
state_class=SensorStateClass.MEASUREMENT,
),
FytaSensorEntityDescription(

View File

@@ -13,5 +13,6 @@
"dependencies": ["bluetooth_adapters"],
"documentation": "https://www.home-assistant.io/integrations/gardena_bluetooth",
"iot_class": "local_polling",
"requirements": ["gardena-bluetooth==1.4.1"]
"loggers": ["bleak", "bleak_esphome", "gardena_bluetooth"],
"requirements": ["gardena-bluetooth==1.4.2"]
}

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/glances",
"iot_class": "local_polling",
"loggers": ["glances_api"],
"requirements": ["glances-api==0.7.0"]
"requirements": ["glances-api==0.8.0"]
}

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/goodwe",
"iot_class": "local_polling",
"loggers": ["goodwe"],
"requirements": ["goodwe==0.3.5"]
"requirements": ["goodwe==0.3.6"]
}

View File

@@ -1586,6 +1586,17 @@ class ArmDisArmTrait(_Trait):
if features & required_feature != 0
]
def _default_arm_state(self):
states = self._supported_states()
if STATE_ALARM_TRIGGERED in states:
states.remove(STATE_ALARM_TRIGGERED)
if len(states) != 1:
raise SmartHomeError(ERR_NOT_SUPPORTED, "ArmLevel missing")
return states[0]
def sync_attributes(self):
"""Return ArmDisarm attributes for a sync request."""
response = {}
@@ -1609,10 +1620,13 @@ class ArmDisArmTrait(_Trait):
def query_attributes(self):
"""Return ArmDisarm query attributes."""
armed_state = self.state.attributes.get("next_state", self.state.state)
response = {"isArmed": armed_state in self.state_to_service}
if response["isArmed"]:
response.update({"currentArmLevel": armed_state})
return response
if armed_state in self.state_to_service:
return {"isArmed": True, "currentArmLevel": armed_state}
return {
"isArmed": False,
"currentArmLevel": self._default_arm_state(),
}
async def execute(self, command, data, params, challenge):
"""Execute an ArmDisarm command."""
@@ -1620,15 +1634,7 @@ class ArmDisArmTrait(_Trait):
# If no arm level given, we can only arm it if there is
# only one supported arm type. We never default to triggered.
if not (arm_level := params.get("armLevel")):
states = self._supported_states()
if STATE_ALARM_TRIGGERED in states:
states.remove(STATE_ALARM_TRIGGERED)
if len(states) != 1:
raise SmartHomeError(ERR_NOT_SUPPORTED, "ArmLevel missing")
arm_level = states[0]
arm_level = self._default_arm_state()
if self.state.state == arm_level:
raise SmartHomeError(ERR_ALREADY_ARMED, "System is already armed")

View File

@@ -165,7 +165,7 @@ class GoogleAssistantConversationAgent(conversation.AbstractConversationAgent):
await session.async_ensure_token_valid()
self.assistant = None
if not self.assistant or user_input.language != self.language:
credentials = Credentials(session.token[CONF_ACCESS_TOKEN])
credentials = Credentials(session.token[CONF_ACCESS_TOKEN]) # type: ignore[no-untyped-call]
self.language = user_input.language
self.assistant = TextAssistant(credentials, self.language)

View File

@@ -72,7 +72,7 @@ async def async_send_text_commands(
entry.async_start_reauth(hass)
raise
credentials = Credentials(session.token[CONF_ACCESS_TOKEN])
credentials = Credentials(session.token[CONF_ACCESS_TOKEN]) # type: ignore[no-untyped-call]
language_code = entry.options.get(CONF_LANGUAGE_CODE, default_language_code(hass))
with TextAssistant(
credentials, language_code, audio_out=bool(media_players)

View File

@@ -66,13 +66,12 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
}
)
model_name = "gemini-pro-vision" if image_filenames else "gemini-pro"
model = genai.GenerativeModel(model_name=model_name)
model = genai.GenerativeModel(model_name=RECOMMENDED_CHAT_MODEL)
try:
response = await model.generate_content_async(prompt_parts)
except (
ClientError,
GoogleAPICallError,
ValueError,
genai_types.BlockedPromptException,
genai_types.StopCandidateException,

View File

@@ -2,12 +2,14 @@
from __future__ import annotations
import codecs
from typing import Any, Literal
import google.ai.generativelanguage as glm
from google.api_core.exceptions import GoogleAPICallError
import google.generativeai as genai
from google.generativeai import protos
import google.generativeai.types as genai_types
from google.protobuf.json_format import MessageToDict
import voluptuous as vol
from voluptuous_openapi import convert
@@ -92,7 +94,7 @@ def _format_tool(tool: llm.Tool) -> dict[str, Any]:
parameters = _format_schema(convert(tool.parameters))
return glm.Tool(
return protos.Tool(
{
"function_declarations": [
{
@@ -105,6 +107,17 @@ def _format_tool(tool: llm.Tool) -> dict[str, Any]:
)
def _escape_decode(value: Any) -> Any:
"""Recursively call codecs.escape_decode on all values."""
if isinstance(value, str):
return codecs.escape_decode(bytes(value, "utf-8"))[0].decode("utf-8") # type: ignore[attr-defined]
if isinstance(value, list):
return [_escape_decode(item) for item in value]
if isinstance(value, dict):
return {k: _escape_decode(v) for k, v in value.items()}
return value
class GoogleGenerativeAIConversationEntity(
conversation.ConversationEntity, conversation.AbstractConversationAgent
):
@@ -151,20 +164,22 @@ class GoogleGenerativeAIConversationEntity(
intent_response = intent.IntentResponse(language=user_input.language)
llm_api: llm.APIInstance | None = None
tools: list[dict[str, Any]] | None = None
user_name: str | None = None
llm_context = llm.LLMContext(
platform=DOMAIN,
context=user_input.context,
user_prompt=user_input.text,
language=user_input.language,
assistant=conversation.DOMAIN,
device_id=user_input.device_id,
)
if self.entry.options.get(CONF_LLM_HASS_API):
try:
llm_api = await llm.async_get_api(
self.hass,
self.entry.options[CONF_LLM_HASS_API],
llm.ToolContext(
platform=DOMAIN,
context=user_input.context,
user_prompt=user_input.text,
language=user_input.language,
assistant=conversation.DOMAIN,
device_id=user_input.device_id,
),
llm_context,
)
except HomeAssistantError as err:
LOGGER.error("Error getting LLM API: %s", err)
@@ -211,7 +226,16 @@ class GoogleGenerativeAIConversationEntity(
messages = self.history[conversation_id]
else:
conversation_id = ulid.ulid_now()
messages = [{}, {}]
messages = [{}, {"role": "model", "parts": "Ok"}]
if (
user_input.context
and user_input.context.user_id
and (
user := await self.hass.auth.async_get_user(user_input.context.user_id)
)
):
user_name = user.name
try:
if llm_api:
@@ -222,13 +246,16 @@ class GoogleGenerativeAIConversationEntity(
prompt = "\n".join(
(
template.Template(
self.entry.options.get(
llm.BASE_PROMPT
+ self.entry.options.get(
CONF_PROMPT, llm.DEFAULT_INSTRUCTIONS_PROMPT
),
self.hass,
).async_render(
{
"ha_name": self.hass.config.location_name,
"user_name": user_name,
"llm_context": llm_context,
},
parse_result=False,
),
@@ -246,8 +273,11 @@ class GoogleGenerativeAIConversationEntity(
response=intent_response, conversation_id=conversation_id
)
messages[0] = {"role": "user", "parts": prompt}
messages[1] = {"role": "model", "parts": "Ok"}
# Make a copy, because we attach it to the trace event.
messages = [
{"role": "user", "parts": prompt},
*messages[1:],
]
LOGGER.debug("Input: '%s' with history: %s", user_input.text, messages)
trace.async_conversation_trace_append(
@@ -295,21 +325,19 @@ class GoogleGenerativeAIConversationEntity(
response=intent_response, conversation_id=conversation_id
)
self.history[conversation_id] = chat.history
tool_calls = [
function_calls = [
part.function_call for part in chat_response.parts if part.function_call
]
if not tool_calls or not llm_api:
if not function_calls or not llm_api:
break
tool_responses = []
for tool_call in tool_calls:
tool_input = llm.ToolInput(
tool_name=tool_call.name,
tool_args=dict(tool_call.args),
)
LOGGER.debug(
"Tool call: %s(%s)", tool_input.tool_name, tool_input.tool_args
)
for function_call in function_calls:
tool_call = MessageToDict(function_call._pb) # noqa: SLF001
tool_name = tool_call["name"]
tool_args = _escape_decode(tool_call["args"])
LOGGER.debug("Tool call: %s(%s)", tool_name, tool_args)
tool_input = llm.ToolInput(tool_name=tool_name, tool_args=tool_args)
try:
function_response = await llm_api.async_call_tool(tool_input)
except (HomeAssistantError, vol.Invalid) as e:
@@ -319,16 +347,16 @@ class GoogleGenerativeAIConversationEntity(
LOGGER.debug("Tool response: %s", function_response)
tool_responses.append(
glm.Part(
function_response=glm.FunctionResponse(
name=tool_call.name, response=function_response
protos.Part(
function_response=protos.FunctionResponse(
name=tool_name, response=function_response
)
)
)
chat_request = glm.Content(parts=tool_responses)
chat_request = protos.Content(parts=tool_responses)
intent_response.async_set_speech(
" ".join([part.text for part in chat_response.parts if part.text])
" ".join([part.text.strip() for part in chat_response.parts if part.text])
)
return conversation.ConversationResult(
response=intent_response, conversation_id=conversation_id

View File

@@ -9,5 +9,5 @@
"integration_type": "service",
"iot_class": "cloud_polling",
"quality_scale": "platinum",
"requirements": ["google-generativeai==0.5.4", "voluptuous-openapi==0.0.4"]
"requirements": ["google-generativeai==0.6.0", "voluptuous-openapi==0.0.4"]
}

View File

@@ -93,7 +93,9 @@ async def async_setup_service(hass: HomeAssistant) -> None:
def _append_to_sheet(call: ServiceCall, entry: ConfigEntry) -> None:
"""Run append in the executor."""
service = Client(Credentials(entry.data[CONF_TOKEN][CONF_ACCESS_TOKEN]))
service = Client(
Credentials(entry.data[CONF_TOKEN][CONF_ACCESS_TOKEN]) # type: ignore[no-untyped-call]
)
try:
sheet = service.open_by_key(entry.unique_id)
except RefreshError:

View File

@@ -61,7 +61,9 @@ class OAuth2FlowHandler(
async def async_oauth_create_entry(self, data: dict[str, Any]) -> ConfigFlowResult:
"""Create an entry for the flow, or update existing entry."""
service = Client(Credentials(data[CONF_TOKEN][CONF_ACCESS_TOKEN]))
service = Client(
Credentials(data[CONF_TOKEN][CONF_ACCESS_TOKEN]) # type: ignore[no-untyped-call]
)
if self.reauth_entry:
_LOGGER.debug("service.open_by_key")

View File

@@ -1,6 +1,18 @@
{
"domain": "group",
"name": "Group",
"after_dependencies": [
"alarm_control_panel",
"climate",
"cover",
"device_tracker",
"lock",
"media_player",
"person",
"plant",
"vacuum",
"water_heater"
],
"codeowners": ["@home-assistant/core"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/group",

View File

@@ -36,7 +36,14 @@ from homeassistant.const import (
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
from homeassistant.core import HomeAssistant, State, callback
from homeassistant.core import (
CALLBACK_TYPE,
Event,
EventStateChangedData,
HomeAssistant,
State,
callback,
)
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv, entity_registry as er
from homeassistant.helpers.entity import (
@@ -45,6 +52,7 @@ from homeassistant.helpers.entity import (
get_unit_of_measurement,
)
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.event import async_track_state_change_event
from homeassistant.helpers.issue_registry import (
IssueSeverity,
async_create_issue,
@@ -329,6 +337,7 @@ class SensorGroup(GroupEntity, SensorEntity):
self._native_unit_of_measurement = unit_of_measurement
self._valid_units: set[str | None] = set()
self._can_convert: bool = False
self.calculate_attributes_later: CALLBACK_TYPE | None = None
self._attr_name = name
if name == DEFAULT_NAME:
self._attr_name = f"{DEFAULT_NAME} {sensor_type}".capitalize()
@@ -345,13 +354,32 @@ class SensorGroup(GroupEntity, SensorEntity):
async def async_added_to_hass(self) -> None:
"""When added to hass."""
for entity_id in self._entity_ids:
if self.hass.states.get(entity_id) is None:
self.calculate_attributes_later = async_track_state_change_event(
self.hass, self._entity_ids, self.calculate_state_attributes
)
break
if not self.calculate_attributes_later:
await self.calculate_state_attributes()
await super().async_added_to_hass()
async def calculate_state_attributes(
self, event: Event[EventStateChangedData] | None = None
) -> None:
"""Calculate state attributes."""
for entity_id in self._entity_ids:
if self.hass.states.get(entity_id) is None:
return
if self.calculate_attributes_later:
self.calculate_attributes_later()
self.calculate_attributes_later = None
self._attr_state_class = self._calculate_state_class(self._state_class)
self._attr_device_class = self._calculate_device_class(self._device_class)
self._attr_native_unit_of_measurement = self._calculate_unit_of_measurement(
self._native_unit_of_measurement
)
self._valid_units = self._get_valid_units()
await super().async_added_to_hass()
@callback
def async_update_group_state(self) -> None:

View File

@@ -267,15 +267,14 @@ class SupervisorIssues:
placeholders = {PLACEHOLDER_KEY_REFERENCE: issue.reference}
if issue.key == ISSUE_KEY_ADDON_DETACHED_ADDON_MISSING:
placeholders[PLACEHOLDER_KEY_ADDON_URL] = (
f"/hassio/addon/{issue.reference}"
)
addons = get_addons_info(self._hass)
if addons and issue.reference in addons:
placeholders[PLACEHOLDER_KEY_ADDON] = addons[issue.reference][
"name"
]
if "url" in addons[issue.reference]:
placeholders[PLACEHOLDER_KEY_ADDON_URL] = addons[
issue.reference
]["url"]
else:
placeholders[PLACEHOLDER_KEY_ADDON] = issue.reference

View File

@@ -51,6 +51,7 @@ class HiveAlarmControlPanelEntity(HiveEntity, AlarmControlPanelEntity):
| AlarmControlPanelEntityFeature.ARM_AWAY
| AlarmControlPanelEntityFeature.TRIGGER
)
_attr_code_arm_required = False
async def async_alarm_disarm(self, code: str | None = None) -> None:
"""Send disarm command."""

View File

@@ -18,16 +18,10 @@ from homeassistant.util import dt as dt_util
from .const import CONF_PROVINCE, DOMAIN
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Holiday Calendar config entry."""
country: str = config_entry.data[CONF_COUNTRY]
province: str | None = config_entry.data.get(CONF_PROVINCE)
language = hass.config.language
def _get_obj_holidays_and_language(
country: str, province: str | None, language: str
) -> tuple[HolidayBase, str]:
"""Get the object for the requested country and year."""
obj_holidays = country_holidays(
country,
subdiv=province,
@@ -58,6 +52,23 @@ async def async_setup_entry(
)
language = default_language
return (obj_holidays, language)
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Holiday Calendar config entry."""
country: str = config_entry.data[CONF_COUNTRY]
province: str | None = config_entry.data.get(CONF_PROVINCE)
language = hass.config.language
obj_holidays, language = await hass.async_add_executor_job(
_get_obj_holidays_and_language, country, province, language
)
async_add_entities(
[
HolidayCalendarEntity(

View File

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

View File

@@ -35,9 +35,8 @@ DEFAULT_EXPOSED_DOMAINS = {
"fan",
"humidifier",
"light",
"lock",
"media_player",
"scene",
"script",
"switch",
"todo",
"vacuum",

View File

@@ -1,7 +1,6 @@
{
"domain": "http",
"name": "HTTP",
"after_dependencies": ["isal"],
"codeowners": ["@home-assistant/core"],
"documentation": "https://www.home-assistant.io/integrations/http",
"integration_type": "system",

View File

@@ -35,7 +35,7 @@ class HumidityHandler(intent.IntentHandler):
intent_type = INTENT_HUMIDITY
description = "Set desired humidity level"
slot_schema = {
vol.Required("name"): cv.string,
vol.Required("name"): intent.non_empty_string,
vol.Required("humidity"): vol.All(vol.Coerce(int), vol.Range(0, 100)),
}
platforms = {DOMAIN}
@@ -44,18 +44,19 @@ class HumidityHandler(intent.IntentHandler):
"""Handle the hass intent."""
hass = intent_obj.hass
slots = self.async_validate_slots(intent_obj.slots)
states = list(
intent.async_match_states(
hass,
name=slots["name"]["value"],
states=hass.states.async_all(DOMAIN),
)
match_constraints = intent.MatchTargetsConstraints(
name=slots["name"]["value"],
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
)
if not states:
raise intent.IntentHandleError("No entities matched")
state = states[0]
state = match_result.states[0]
service_data = {ATTR_ENTITY_ID: state.entity_id}
humidity = slots["humidity"]["value"]
@@ -89,7 +90,7 @@ class SetModeHandler(intent.IntentHandler):
intent_type = INTENT_MODE
description = "Set humidifier mode"
slot_schema = {
vol.Required("name"): cv.string,
vol.Required("name"): intent.non_empty_string,
vol.Required("mode"): cv.string,
}
platforms = {DOMAIN}
@@ -98,18 +99,18 @@ class SetModeHandler(intent.IntentHandler):
"""Handle the hass intent."""
hass = intent_obj.hass
slots = self.async_validate_slots(intent_obj.slots)
states = list(
intent.async_match_states(
hass,
name=slots["name"]["value"],
states=hass.states.async_all(DOMAIN),
)
match_constraints = intent.MatchTargetsConstraints(
name=slots["name"]["value"],
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
)
if not states:
raise intent.IntentHandleError("No entities matched")
state = states[0]
state = match_result.states[0]
service_data = {ATTR_ENTITY_ID: state.entity_id}
intent.async_test_feature(state, HumidifierEntityFeature.MODES, "modes")

View File

@@ -24,13 +24,17 @@ class HydrawiseBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Describes Hydrawise binary sensor."""
value_fn: Callable[[HydrawiseBinarySensor], bool | None]
always_available: bool = False
CONTROLLER_BINARY_SENSORS: tuple[HydrawiseBinarySensorEntityDescription, ...] = (
HydrawiseBinarySensorEntityDescription(
key="status",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
value_fn=lambda status_sensor: status_sensor.coordinator.last_update_success,
value_fn=lambda status_sensor: status_sensor.coordinator.last_update_success
and status_sensor.controller.online,
# Connectivtiy sensor is always available
always_available=True,
),
)
@@ -98,3 +102,10 @@ class HydrawiseBinarySensor(HydrawiseEntity, BinarySensorEntity):
def _update_attrs(self) -> None:
"""Update state attributes."""
self._attr_is_on = self.entity_description.value_fn(self)
@property
def available(self) -> bool:
"""Set the entity availability."""
if self.entity_description.always_available:
return True
return super().available

View File

@@ -70,3 +70,8 @@ class HydrawiseEntity(CoordinatorEntity[HydrawiseDataUpdateCoordinator]):
self.controller = self.coordinator.data.controllers[self.controller.id]
self._update_attrs()
super()._handle_coordinator_update()
@property
def available(self) -> bool:
"""Set the entity availability."""
return super().available and self.controller.online

View File

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

View File

@@ -37,6 +37,7 @@ class IAlarmPanel(
AlarmControlPanelEntityFeature.ARM_HOME
| AlarmControlPanelEntityFeature.ARM_AWAY
)
_attr_code_arm_required = False
def __init__(self, coordinator: IAlarmDataUpdateCoordinator) -> None:
"""Create the entity with a DataUpdateCoordinator."""

View File

@@ -64,7 +64,7 @@ class IdasenDeskConfigFlow(ConfigFlow, domain=DOMAIN):
desk = Desk(None, monitor_height=False)
try:
await desk.connect(discovery_info.device, auto_reconnect=False)
await desk.connect(discovery_info.device, retry=False)
except AuthFailedError:
errors["base"] = "auth_failed"
except TimeoutError:

View File

@@ -195,13 +195,13 @@ class ImapMessage:
):
message_untyped_text = str(part.get_payload())
if message_text is not None:
if message_text is not None and message_text.strip():
return message_text
if message_html is not None:
if message_html:
return message_html
if message_untyped_text is not None:
if message_untyped_text:
return message_untyped_text
return str(self.email_message.get_payload())

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/imgw_pib",
"iot_class": "cloud_polling",
"quality_scale": "platinum",
"requirements": ["imgw_pib==1.0.1"]
"requirements": ["imgw_pib==1.0.5"]
}

View File

@@ -8,7 +8,7 @@ from typing import Any, TypedDict
import voluptuous as vol
from homeassistant.components.script import CONF_MODE
from homeassistant.const import CONF_TYPE, SERVICE_RELOAD
from homeassistant.const import CONF_DESCRIPTION, CONF_TYPE, SERVICE_RELOAD
from homeassistant.core import HomeAssistant, ServiceCall
from homeassistant.helpers import (
config_validation as cv,
@@ -24,6 +24,7 @@ _LOGGER = logging.getLogger(__name__)
DOMAIN = "intent_script"
CONF_PLATFORMS = "platforms"
CONF_INTENTS = "intents"
CONF_SPEECH = "speech"
CONF_REPROMPT = "reprompt"
@@ -41,6 +42,8 @@ CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: {
cv.string: {
vol.Optional(CONF_DESCRIPTION): cv.string,
vol.Optional(CONF_PLATFORMS): vol.All([cv.string], vol.Coerce(set)),
vol.Optional(CONF_ACTION): cv.SCRIPT_SCHEMA,
vol.Optional(
CONF_ASYNC_ACTION, default=DEFAULT_CONF_ASYNC_ACTION
@@ -146,6 +149,8 @@ class ScriptIntentHandler(intent.IntentHandler):
"""Initialize the script intent handler."""
self.intent_type = intent_type
self.config = config
self.description = config.get(CONF_DESCRIPTION)
self.platforms = config.get(CONF_PLATFORMS)
async def async_handle(self, intent_obj: intent.Intent) -> intent.IntentResponse:
"""Handle the intent."""

View File

@@ -16,11 +16,13 @@ from homeassistant.const import (
CONF_TIME_ZONE,
Platform,
)
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant
from homeassistant.core import DOMAIN as HOMEASSISTANT_DOMAIN, HomeAssistant, callback
import homeassistant.helpers.config_validation as cv
import homeassistant.helpers.entity_registry as er
from homeassistant.helpers.issue_registry import IssueSeverity, async_create_issue
from homeassistant.helpers.typing import ConfigType
from .binary_sensor import BINARY_SENSORS
from .const import (
CONF_CANDLE_LIGHT_MINUTES,
CONF_DIASPORA,
@@ -32,6 +34,7 @@ from .const import (
DEFAULT_NAME,
DOMAIN,
)
from .sensor import INFO_SENSORS, TIME_SENSORS
PLATFORMS: list[Platform] = [Platform.BINARY_SENSOR, Platform.SENSOR]
@@ -93,7 +96,8 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
HOMEASSISTANT_DOMAIN,
f"deprecated_yaml_{DOMAIN}",
is_fixable=False,
breaks_in_ha_version="2024.10.0",
issue_domain=DOMAIN,
breaks_in_ha_version="2024.12.0",
severity=IssueSeverity.WARNING,
translation_key="deprecated_yaml",
translation_placeholders={
@@ -115,10 +119,10 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
"""Set up a configuration entry for Jewish calendar."""
language = config_entry.data.get(CONF_LANGUAGE, DEFAULT_LANGUAGE)
diaspora = config_entry.data.get(CONF_DIASPORA, DEFAULT_DIASPORA)
candle_lighting_offset = config_entry.data.get(
candle_lighting_offset = config_entry.options.get(
CONF_CANDLE_LIGHT_MINUTES, DEFAULT_CANDLE_LIGHT
)
havdalah_offset = config_entry.data.get(
havdalah_offset = config_entry.options.get(
CONF_HAVDALAH_OFFSET_MINUTES, DEFAULT_HAVDALAH_OFFSET_MINUTES
)
@@ -131,19 +135,31 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
timezone=config_entry.data.get(CONF_TIME_ZONE, hass.config.time_zone),
)
prefix = get_unique_prefix(
location, language, candle_lighting_offset, havdalah_offset
)
hass.data.setdefault(DOMAIN, {})[config_entry.entry_id] = {
CONF_LANGUAGE: language,
CONF_DIASPORA: diaspora,
CONF_LOCATION: location,
CONF_CANDLE_LIGHT_MINUTES: candle_lighting_offset,
CONF_HAVDALAH_OFFSET_MINUTES: havdalah_offset,
"prefix": prefix,
}
# Update unique ID to be unrelated to user defined options
old_prefix = get_unique_prefix(
location, language, candle_lighting_offset, havdalah_offset
)
ent_reg = er.async_get(hass)
entries = er.async_entries_for_config_entry(ent_reg, config_entry.entry_id)
if not entries or any(entry.unique_id.startswith(old_prefix) for entry in entries):
async_update_unique_ids(ent_reg, config_entry.entry_id, old_prefix)
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
async def update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
# Trigger update of states for all platforms
await hass.config_entries.async_reload(config_entry.entry_id)
config_entry.async_on_unload(config_entry.add_update_listener(update_listener))
return True
@@ -157,3 +173,25 @@ async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) ->
hass.data[DOMAIN].pop(config_entry.entry_id)
return unload_ok
@callback
def async_update_unique_ids(
ent_reg: er.EntityRegistry, new_prefix: str, old_prefix: str
) -> None:
"""Update unique ID to be unrelated to user defined options.
Introduced with release 2024.6
"""
platform_descriptions = {
Platform.BINARY_SENSOR: BINARY_SENSORS,
Platform.SENSOR: (*INFO_SENSORS, *TIME_SENSORS),
}
for platform, descriptions in platform_descriptions.items():
for description in descriptions:
new_unique_id = f"{new_prefix}-{description.key}"
old_unique_id = f"{old_prefix}_{description.key}"
if entity_id := ent_reg.async_get_entity_id(
platform, DOMAIN, old_unique_id
):
ent_reg.async_update_entity(entity_id, new_unique_id=new_unique_id)

View File

@@ -70,10 +70,10 @@ async def async_setup_entry(
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Jewish Calendar binary sensors."""
entry = hass.data[DOMAIN][config_entry.entry_id]
async_add_entities(
JewishCalendarBinarySensor(
hass.data[DOMAIN][config_entry.entry_id], description
)
JewishCalendarBinarySensor(config_entry.entry_id, entry, description)
for description in BINARY_SENSORS
)
@@ -86,13 +86,14 @@ class JewishCalendarBinarySensor(BinarySensorEntity):
def __init__(
self,
entry_id: str,
data: dict[str, Any],
description: JewishCalendarBinarySensorEntityDescription,
) -> None:
"""Initialize the binary sensor."""
self.entity_description = description
self._attr_name = f"{DEFAULT_NAME} {description.name}"
self._attr_unique_id = f'{data["prefix"]}_{description.key}'
self._attr_unique_id = f"{entry_id}-{description.key}"
self._location = data[CONF_LOCATION]
self._hebrew = data[CONF_LANGUAGE] == "hebrew"
self._candle_lighting_offset = data[CONF_CANDLE_LIGHT_MINUTES]

View File

@@ -100,10 +100,23 @@ class JewishCalendarConfigFlow(ConfigFlow, domain=DOMAIN):
) -> ConfigFlowResult:
"""Handle the initial step."""
if user_input is not None:
_options = {}
if CONF_CANDLE_LIGHT_MINUTES in user_input:
_options[CONF_CANDLE_LIGHT_MINUTES] = user_input[
CONF_CANDLE_LIGHT_MINUTES
]
del user_input[CONF_CANDLE_LIGHT_MINUTES]
if CONF_HAVDALAH_OFFSET_MINUTES in user_input:
_options[CONF_HAVDALAH_OFFSET_MINUTES] = user_input[
CONF_HAVDALAH_OFFSET_MINUTES
]
del user_input[CONF_HAVDALAH_OFFSET_MINUTES]
if CONF_LOCATION in user_input:
user_input[CONF_LATITUDE] = user_input[CONF_LOCATION][CONF_LATITUDE]
user_input[CONF_LONGITUDE] = user_input[CONF_LOCATION][CONF_LONGITUDE]
return self.async_create_entry(title=DEFAULT_NAME, data=user_input)
return self.async_create_entry(
title=DEFAULT_NAME, data=user_input, options=_options
)
return self.async_show_form(
step_id="user",

View File

@@ -155,9 +155,13 @@ async def async_setup_entry(
) -> None:
"""Set up the Jewish calendar sensors ."""
entry = hass.data[DOMAIN][config_entry.entry_id]
sensors = [JewishCalendarSensor(entry, description) for description in INFO_SENSORS]
sensors = [
JewishCalendarSensor(config_entry.entry_id, entry, description)
for description in INFO_SENSORS
]
sensors.extend(
JewishCalendarTimeSensor(entry, description) for description in TIME_SENSORS
JewishCalendarTimeSensor(config_entry.entry_id, entry, description)
for description in TIME_SENSORS
)
async_add_entities(sensors)
@@ -168,13 +172,14 @@ class JewishCalendarSensor(SensorEntity):
def __init__(
self,
entry_id: str,
data: dict[str, Any],
description: SensorEntityDescription,
) -> None:
"""Initialize the Jewish calendar sensor."""
self.entity_description = description
self._attr_name = f"{DEFAULT_NAME} {description.name}"
self._attr_unique_id = f'{data["prefix"]}_{description.key}'
self._attr_unique_id = f"{entry_id}-{description.key}"
self._location = data[CONF_LOCATION]
self._hebrew = data[CONF_LANGUAGE] == "hebrew"
self._candle_lighting_offset = data[CONF_CANDLE_LIGHT_MINUTES]

View File

@@ -283,16 +283,13 @@ class KNXClimate(KnxEntity, ClimateEntity):
)
if knx_controller_mode in self._device.mode.controller_modes:
await self._device.mode.set_controller_mode(knx_controller_mode)
self.async_write_ha_state()
return
if self._device.supports_on_off:
if hvac_mode == HVACMode.OFF:
await self._device.turn_off()
elif not self._device.is_on:
# for default hvac mode, otherwise above would have triggered
await self._device.turn_on()
self.async_write_ha_state()
self.async_write_ha_state()
@property
def preset_mode(self) -> str | None:

View File

@@ -60,7 +60,9 @@ class KNXNotificationService(BaseNotificationService):
async def async_send_message(self, message: str = "", **kwargs: Any) -> None:
"""Send a notification to knx bus."""
migrate_notify_issue(self.hass, DOMAIN, "KNX", "2024.11.0")
migrate_notify_issue(
self.hass, DOMAIN, "KNX", "2024.11.0", service_name=self._service_name
)
if "target" in kwargs:
await self._async_send_to_device(message, kwargs["target"])
else:

View File

@@ -23,6 +23,7 @@ turn_on:
- light.ColorMode.RGB
- light.ColorMode.RGBW
- light.ColorMode.RGBWW
example: "[255, 100, 100]"
selector:
color_rgb:
rgbw_color:
@@ -250,6 +251,7 @@ turn_on:
- light.ColorMode.RGB
- light.ColorMode.RGBW
- light.ColorMode.RGBWW
advanced: true
selector:
color_temp:
unit: "mired"
@@ -265,7 +267,6 @@ turn_on:
- light.ColorMode.RGB
- light.ColorMode.RGBW
- light.ColorMode.RGBWW
advanced: true
selector:
color_temp:
unit: "kelvin"
@@ -419,10 +420,35 @@ toggle:
- light.ColorMode.RGB
- light.ColorMode.RGBW
- light.ColorMode.RGBWW
advanced: true
example: "[255, 100, 100]"
selector:
color_rgb:
rgbw_color:
filter:
attribute:
supported_color_modes:
- light.ColorMode.HS
- light.ColorMode.XY
- light.ColorMode.RGB
- light.ColorMode.RGBW
- light.ColorMode.RGBWW
advanced: true
example: "[255, 100, 100, 50]"
selector:
object:
rgbww_color:
filter:
attribute:
supported_color_modes:
- light.ColorMode.HS
- light.ColorMode.XY
- light.ColorMode.RGB
- light.ColorMode.RGBW
- light.ColorMode.RGBWW
advanced: true
example: "[255, 100, 100, 50, 70]"
selector:
object:
color_name:
filter:
attribute:
@@ -625,6 +651,9 @@ toggle:
advanced: true
selector:
color_temp:
unit: "mired"
min: 153
max: 500
kelvin:
filter:
attribute:
@@ -635,7 +664,6 @@ toggle:
- light.ColorMode.RGB
- light.ColorMode.RGBW
- light.ColorMode.RGBWW
advanced: true
selector:
color_temp:
unit: "kelvin"

View File

@@ -342,6 +342,14 @@
"name": "[%key:component::light::services::turn_on::fields::rgb_color::name%]",
"description": "[%key:component::light::services::turn_on::fields::rgb_color::description%]"
},
"rgbw_color": {
"name": "[%key:component::light::services::turn_on::fields::rgbw_color::name%]",
"description": "[%key:component::light::services::turn_on::fields::rgbw_color::description%]"
},
"rgbww_color": {
"name": "[%key:component::light::services::turn_on::fields::rgbww_color::name%]",
"description": "[%key:component::light::services::turn_on::fields::rgbww_color::description%]"
},
"color_name": {
"name": "[%key:component::light::services::turn_on::fields::color_name::name%]",
"description": "[%key:component::light::services::turn_on::fields::color_name::description%]"

View File

@@ -48,6 +48,7 @@ class LupusecAlarm(LupusecDevice, AlarmControlPanelEntity):
AlarmControlPanelEntityFeature.ARM_HOME
| AlarmControlPanelEntityFeature.ARM_AWAY
)
_attr_code_arm_required = False
def __init__(
self, data: lupupy.Lupusec, device: lupupy.devices.LupusecAlarm, entry_id: str

View File

@@ -6,6 +6,6 @@
"dependencies": ["websocket_api"],
"documentation": "https://www.home-assistant.io/integrations/matter",
"iot_class": "local_push",
"requirements": ["python-matter-server==6.1.0b1"],
"requirements": ["python-matter-server==6.1.0"],
"zeroconf": ["_matter._tcp.local.", "_matterc._udp.local."]
}

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