Compare commits

...

208 Commits

Author SHA1 Message Date
G Johansson
be3b18cebf Mod version 2024-09-30 20:28:38 +00:00
G Johansson
1dff94e29c Fix fritzbox tests 2024-09-30 20:28:38 +00:00
G Johansson
5b886d48cf Mod ToggleEntity and implement state in LightEntity 2024-09-30 20:28:38 +00:00
G Johansson
ddf7f08567 light returns enum 2024-09-30 20:28:37 +00:00
G Johansson
d62da432d0 Deprecate STATE_ON in light 2024-09-30 20:28:37 +00:00
G Johansson
0999f61079 Add more 2024-09-30 20:28:37 +00:00
G Johansson
c1c6e6d50e Add more tests 2024-09-30 20:28:37 +00:00
G Johansson
7f88813ba1 Add some tests 2024-09-30 20:28:37 +00:00
G Johansson
3f2292dae4 Change light state calc to use enum 2024-09-30 20:28:36 +00:00
epenet
dcb6c9a133 Adjust type hints in zwave_js config flow (#127104) 2024-09-30 21:42:16 +02:00
G Johansson
edcb4eca22 Use async_update_reload_and_abort in Trafikverket Camera (#127137) 2024-09-30 21:30:53 +02:00
G Johansson
de6ca56504 Add config flow validation that calibration factor is not zero (#127136)
* Add config flow validation that calibration factor is not zero

* Add test
2024-09-30 21:30:28 +02:00
G Johansson
fdd9fca5b3 Fix naming and docstring in yale_smart_alarm select (#127141) 2024-09-30 21:22:55 +02:00
Joost Lekkerkerker
10805805fe Add devices to Withings (#126853) 2024-09-30 21:06:51 +02:00
G Johansson
05288dad51 Allow negative calibration factor in mold_indicator (#127133) 2024-09-30 20:50:32 +02:00
Franck Nijhof
053ff33ef9 Update RestrictedPython to 7.3 (#127130) 2024-09-30 19:53:38 +02:00
Franck Nijhof
c97f1baa2b Update gotailwind to 0.2.4 (#127129) 2024-09-30 19:52:11 +02:00
starkillerOG
0f4c50e83c Mark Reolink camera entities as unavailable when camera is offline (#127127)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2024-09-30 19:00:37 +02:00
Martin Hjelmare
0a99c1c633 Bump zwave-js-server-python to 0.58.1 (#127114)
* Bump zwave-js-server-python to 0.58.1

* Update tests
2024-09-30 18:35:14 +02:00
Bram Kragten
d6ae47a0de Update frontend to 20240930.0 (#127125) 2024-09-30 18:28:03 +02:00
Darren Griffin
b258e6464d Add Open Home Foundation logo to README (#127111)
* Added Open Home Foundation logo to README

* Remove legacy reference to OHF website

* Add alt text to OHF logo
2024-09-30 16:49:30 +02:00
epenet
86a95013b6 Use start_reauth_flow helper in ezviz and netatmo tests (#127100)
* Use start_reauth_flow helper in netatmo tests

* Use start_reauth_flow helper in ezviz tests
2024-09-30 16:38:34 +02:00
Christopher Fenner
636cba5d6b Add hotwater storage sensors to ViCare integration (#126570)
add sensors for hotwater storage
2024-09-30 16:37:50 +02:00
Sven Sager
74931071de Use scheduled current preset (if set), when setting HVAC mode in AVM Fritz!Smarthome (#126044)
* Use temperature of current preset when set fritz HVAC mode to HEAT

If the HVAC mode of the Fritzbox thermostats changes from `HVACMode.OFF`
to `HVAMode.HEAT`, the current preset (COMFORT / ECO) should be
observed. Depending on the status of the current preset, the set
temperature of comfort / eco is set as the new temperature.

* fixup do not use value_scheduled_preset

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

* Add current_preset value to test_set_hvac_mode

The current_preset parameter allows the mock to be set to an active
preset. When setting HVACMode.HEAT, the respective temperature of the
ECO/COMFORT preset should be set.

* fixup Use the updated value_scheduled_preset function

To distinguish which temperature should be used when setting the
`HVAMode.HEAT`, `value_schedules_preset` is now used again, which has
been updated since the first commit. If no schedule is active, the
comfort_temperature is used. Otherwise, the respective temperature of
the current preset.

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

---------

Co-authored-by: Michael <35783820+mib1185@users.noreply.github.com>
2024-09-30 16:29:39 +02:00
epenet
c92169cb20 Use a generic string as default description placeholder in workday config_flow (#127112) 2024-09-30 15:57:37 +02:00
IceBotYT
927813ab3b Raise HomeAssistantError in entity action calls in Nice G.O. (#126439) 2024-09-30 15:36:10 +02:00
epenet
060268747c Add default description placeholder in workday config_flow (#127110) 2024-09-30 15:35:01 +02:00
epenet
47c953209d Adjust type hints in insteon config_flow (#127108) 2024-09-30 15:32:37 +02:00
epenet
16df3eb995 Adjust type hints in wilight config_flow (#127107) 2024-09-30 15:29:52 +02:00
epenet
454fb30759 Adjust type hints in enphase_envoy config_flow (#127106) 2024-09-30 15:29:25 +02:00
epenet
4e157c2999 Adjust type hints in zha config flow (#127105) 2024-09-30 15:20:20 +02:00
epenet
d96fd518e7 Use HassKey in azure_data_explorer (#127087)
* Use HassKey in azure_data_explorer

* Adjust tests

* Adjust

* Remove test
2024-09-30 14:45:37 +02:00
epenet
07fa1fa771 Move monzo test (#127101)
* Move monzo test

* Update tests/components/monzo/test_init.py
2024-09-30 14:43:23 +02:00
Christopher Fenner
404b3fcd03 Add support for room sensors in ViCare integration (#125243)
* Add room sensors

* set humidity device class

* add labels

* Create RoomSensor2.json

* Create RoomSensor1.json

* Update conftest.py

* Create test_sensor.py

* enable E3_RoomSensor

* use setup_integration

* fix ruff findings

* add test case

* fix entity id

* Apply suggestions from code review

* update

* fix findings

* reuse labels

* Apply suggestions from code review

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

* Apply suggestions from code review

* fix test snapshot

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-09-30 14:32:04 +02:00
epenet
e1db5f3cac Use start_reauth_flow helper in switcher_kis tests (#127098) 2024-09-30 15:28:33 +03:00
epenet
4bb768f39c Add test for start_reauth_flow test helper (#127093)
* Improve docstring in start_reauth_flow

* Add test

* Make private

* Make fully private until actually needed
2024-09-30 14:28:13 +02:00
Joost Lekkerkerker
730012edfd Bump yt-dlp to 2024.09.27 (#127096) 2024-09-30 13:25:17 +02:00
Joost Lekkerkerker
352987db7e Make Laundrify unique id a string (#127092) 2024-09-30 13:21:20 +02:00
G Johansson
92a6f231a9 Workday raise issues only to next year (#126997)
* Workday - raise issues only for current and next year

* variable
2024-09-30 13:08:58 +02:00
G Johansson
a44bf164e5 Add select volume to yale_smart_alarm (#127005) 2024-09-30 12:55:09 +02:00
Allen Porter
5cc8cfb209 Update local_calendar/todo to avoid blocking in the event loop (#127048) 2024-09-30 12:51:41 +02:00
Jan Bouwhuis
f99b7d8b78 Start mqtt integration discovery config flow only once if config has not changed (#126966)
* Start mqtt integration config flow only once

* Remember last config message

* Filter out instead of unsubscribing the intehration discovery topic

* Follow up comments from code review
2024-09-30 12:44:40 +02:00
Erik Montnemery
e8fd97e355 Fix stale docstring in loader.USBMatcher (#127094) 2024-09-30 12:42:04 +02:00
Simon Goodall
5e64caa225 Check "status" is present before access during device update (#127091) 2024-09-30 12:06:48 +02:00
rappenze
0672e1a1ea Add power sensor detection in fibaro integration (#126964)
* Add power sensor detection in fibaro integration

* Better solution plus test

* Update homeassistant/components/fibaro/sensor.py

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-09-30 12:01:27 +02:00
epenet
2deab9e0c2 Do not store apache kafka in hass.data (#127090) 2024-09-30 12:01:13 +02:00
Jan Schneider
ebe4ed99b5 Add is_opening and is_closing properties to VeluxCover (#127038) 2024-09-30 11:46:47 +02:00
Shai Ungar
dce51b02c8 Fix timestamp isoformat in seventeentrack (#127052)
fix timestamp isoformat
2024-09-30 11:45:54 +02:00
epenet
70d4ee93f5 Use HassKey in azure_event_hub (#127086) 2024-09-30 11:45:02 +02:00
epenet
34a4372190 Use HassKey in analytics (#127089) 2024-09-30 11:44:27 +02:00
epenet
301543d3d0 Use config entry runtime_data in atag (#127084) 2024-09-30 11:06:07 +02:00
epenet
c3c2bc51c5 Use config entry runtime_data in aussie broadband (#127083) 2024-09-30 11:04:08 +02:00
epenet
40f808e9be Use config entry runtime_data in azure event hub (#127082) 2024-09-30 10:22:12 +02:00
epenet
3caf6c0e31 Move atag coordinator to separate class (#127071) 2024-09-30 10:21:04 +02:00
Matthias Alphart
36a0c1b514 Update xknxproject to 3.8.0 (#127072) 2024-09-30 10:18:46 +02:00
epenet
e128751e64 Use config entry runtime_data in aurora_abb_powerone (#127075) 2024-09-30 10:15:18 +02:00
epenet
fae1efc237 Move aussie broadband coordinator to separate class (#127081) 2024-09-30 10:14:04 +02:00
epenet
dec03d4d25 Use config entry runtime_data in awair (#127073) 2024-09-30 10:11:21 +02:00
epenet
064bbab3f5 Use config entry runtime_data in aseko_pool_live (#127077) 2024-09-30 10:10:34 +02:00
epenet
f03e81544e Use config entry runtime_data in aprilaire (#127079) 2024-09-30 10:09:21 +02:00
epenet
4c8027aefa Use config entry runtime_data in android ip webcam (#127080) 2024-09-30 10:08:04 +02:00
epenet
dbecd7a99c Use config entry runtime_data in arve (#127078) 2024-09-30 10:06:16 +02:00
dependabot[bot]
b035649c75 Bump docker/build-push-action from 6.7.0 to 6.8.0 (#127070)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-30 09:25:28 +02:00
Shai Ungar
97ab595e20 Fix repair when integration does not exist (#127050) 2024-09-30 09:17:44 +02:00
epenet
20d4031ed4 Use HassKey in application_credentials (#127069)
Use HassKey in application_credentials
2024-09-30 09:17:33 +02:00
Allen Porter
812be801ce Bump gcal_sync to 6.1.5 (#127049) 2024-09-30 09:11:31 +02:00
Luca Dibattista
672a7ca740 Fix Roomba help URL (#127065)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2024-09-30 09:10:54 +02:00
G Johansson
0b3d69aa8e Add unique id to mold_indicator setup from yaml (#126992) 2024-09-30 09:02:43 +02:00
J. Nick Koston
e87542e091 Fix removing nulls when encoding events for PostgreSQL (#127053) 2024-09-30 09:01:41 +02:00
MarkGodwin
e9bbf773d6 Switch oamda to use a strongly typed config entry (#127044) 2024-09-30 08:58:19 +02:00
Kevin Stillhammer
68e8c968a8 Clarify excl/incl filter functionality for waze_travel_time (#127056) 2024-09-30 08:57:06 +02:00
Jon Caruana
a3f12329b3 Bump pylitejet to 0.6.3 (#127063) 2024-09-30 08:36:30 +02:00
J. Nick Koston
f5ef213842 Add missing OUI to august (#127064) 2024-09-30 08:34:41 +02:00
Jan Bouwhuis
b573e5a2b3 Allow null / None value for non numeric mqtt sensor without warnings (#127032)
Allow `null` / `None` value for mqtt sensor without warnings
2024-09-30 07:05:12 +02:00
Marc Mueller
17c3e7b238 Update grpcio constraints to 1.66.2 (#127026) 2024-09-30 07:02:00 +02:00
Michael
9921a67a05 Bump py-synologydsm-api to 2.5.3 (#127035) 2024-09-29 10:12:27 -05:00
J. Nick Koston
ad09197c00 Bump anyio to 4.6.0 (#127013) 2024-09-29 15:13:10 +02:00
YogevBokobza
be11d1cabf Add Light support for Switcher Runner S11 (#126402)
* switcher add s11 light support

* switcher fix linting

* switcher fix linting

* switcher fix linting

* switcher fix linting

* Update homeassistant/components/switcher_kis/light.py

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

* Update homeassistant/components/switcher_kis/light.py

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

* Switcher fix based on requested changes

* switcher fix light tests

* Add translations

* Remove obsolete default

* Remove obsolete default

* Update tests/components/switcher_kis/test_light.py

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

* switcher fix based on requested changes

---------

Co-authored-by: Shay Levy <levyshay1@gmail.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2024-09-29 13:20:21 +03:00
Raul Camacho
5399e2b648 Add Local Calendar ics events import on calendar creation (#117955)
* add optional config_flow step of uploading .ics file to import local calendar events

* feat: add unit test for import_ics step

* fix: remove unneeded test patch

* feat: add helper for moving ics to storage location

* move helper to config_flow

* ruff

* fix tests; add test for invalid ics content

* Update homeassistant/components/local_calendar/config_flow.py

* Update import flow with radio button and improved text

Signed-off-by: Allen Porter <allen.porter@gmail.com>

* Remove commented out code

* Update with lint fixes

* Apply suggestions from code review

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

---------

Signed-off-by: Allen Porter <allen.porter@gmail.com>
Co-authored-by: Allen Porter <allen.porter@gmail.com>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2024-09-28 22:15:24 -07:00
J. Nick Koston
a8d72cfdcf Bump aiohttp to 3.10.8 (#127009)
changelog: https://github.com/aio-libs/aiohttp/compare/v3.10.7...v3.10.8

Fixes a long standing cancellation leak on timeout
2024-09-28 21:53:54 -04:00
Michael Hansen
fbeee11fd7 Don't log voice assistant config timeout error (#127010)
Don't log config timeout error
2024-09-28 18:46:01 -04:00
Andre Lengwenus
545dae2e7f Bump pypck to 0.7.24 (#126995) 2024-09-28 21:39:48 +02:00
Paulus Schoutsen
86891351f6 Exclude Text-to-Speech cache from backups (#127001)
Text-to-speech cache doesn't need to be included in backups.
2024-09-28 18:22:57 +02:00
G Johansson
ddfe790995 Bump smhi-pkg to 1.0.18 (#126999) 2024-09-28 17:17:57 +02:00
G Johansson
85a9a8eca1 Add unique id to mold_indicator (#126990) 2024-09-28 14:53:40 +02:00
Tsvi Mostovicz
52c358e120 Add reconfigure flow for Jewish Calendar (#126773)
* Add reconfigure flow for Jewish Calendar

* Use async_update_reload_and_abort
2024-09-28 13:59:11 +02:00
Sid
f516e538a8 Include requirements_test_pre_commit.txt in pre-commit hassfest (#125388) 2024-09-28 10:48:08 +02:00
J. Nick Koston
4c28c1f556 Bump aiohttp to 3.10.7 (#126970) 2024-09-28 10:47:05 +02:00
Aindriú Mac Giolla Eoin
b996bd3e65 Updated languages.py to add Irish lang code (manually) (#126689)
* Updated languages.py to add Irish lang code (manually)

Added Irish language code manually without running the command 'python3 -m script.languages ga'.  Due to the size of the repository, I was unable to clone and run the generation script for updating languages.py

* Update homeassistant/generated/languages.py

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>

---------

Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2024-09-27 23:49:31 +02:00
Erik Montnemery
6c1167df4a Use ConfigFlow.has_matching_flow to deduplicate webostv flows (#126898) 2024-09-28 00:11:15 +03:00
J. Nick Koston
1044345587 Bump yarl to 1.13.1 (#126962) 2024-09-27 23:10:01 +02:00
Josef Zweck
d9eb419ecc Add translation for tedee exceptions (#126963) 2024-09-27 22:21:01 +02:00
Josef Zweck
8a266aac34 Add translation_domain to lamarzocco exceptions (#126959) 2024-09-27 22:01:46 +02:00
Franck Nijhof
f6ac5dab74 Update apprise to 1.9.0 (#126952) 2024-09-27 21:58:03 +02:00
Franck Nijhof
d34ba16a30 Update pyvera to 0.3.15 (#126956) 2024-09-27 21:25:27 +02:00
Franck Nijhof
5638e937b0 Update vsure to 2.6.7 (#126950) 2024-09-27 21:25:13 +02:00
G Johansson
2ff88e7baf Add preview to statistics (#122590) 2024-09-27 21:09:42 +02:00
Josef Zweck
2e1732fadf Add proper exception handling to lamarzocco (#125913) 2024-09-27 21:04:01 +02:00
G Johansson
57e041171b Add preview to mold_indicator (#125530) 2024-09-27 20:56:02 +02:00
Marc Mueller
317b73ffaf Allow passing filename to licenses script [ci] (#126951) 2024-09-27 20:52:01 +02:00
ozadr1an
39a9634a5c Bump nessclient to 1.1.2 (#125604)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2024-09-27 20:49:34 +02:00
Franck Nijhof
22ebc654a7 Update ollama to 0.3.3 (#126953) 2024-09-27 20:45:10 +02:00
Marc Mueller
20c3b9b6f9 Update grpcio constraints to 1.66.1 (#126947) 2024-09-27 13:44:31 -05:00
Franck Nijhof
7588d83c6c Update DoorBirdPy to 3.0.3 (#126949) 2024-09-27 20:29:46 +02:00
Franck Nijhof
efbb5bf9af Update debugpy to 1.8.6 (#126945) 2024-09-27 20:29:32 +02:00
Steven B.
dac69fafb8 Bump python-kasa library to 0.7.4 (#126944) 2024-09-27 13:29:28 -05:00
Franck Nijhof
8d98085873 Update ical to 8.2.0 (#126954) 2024-09-27 20:29:18 +02:00
J. Nick Koston
8950e817e0 Bump protobuf to 5.28.2 (#124936)
Co-authored-by: Marc Mueller <30130371+cdce8p@users.noreply.github.com>
2024-09-27 19:39:25 +02:00
Erwin Douna
6fb1b53039 Set DSMR Reader quality scale to Gold (#121466) 2024-09-27 19:26:51 +02:00
Joakim Plate
c81a4f8633 Translate exception from fjäråskupan (#126673) 2024-09-27 19:23:24 +02:00
Erik Montnemery
4599d1650b Use ConfigFlow.has_matching_flow to deduplicate flux_led flows (#126888) 2024-09-27 19:22:00 +02:00
Simon Lamon
4edc3872ce Add support for stop command in LinkPlay (#126941)
Add support for stop command
2024-09-27 19:13:26 +02:00
Josef Zweck
8999e9f116 Use _async_setup in tedee coordinator (#126812) 2024-09-27 19:10:52 +02:00
Erik Montnemery
c5b4892596 Adjust BaseEditConfigView.__init__ (#126729) 2024-09-27 19:08:12 +02:00
Erik Montnemery
46812777e2 Use ConfigFlow.has_matching_flow to deduplicate yalexs_ble flows (#126899) 2024-09-27 19:07:23 +02:00
Simon
616c0ebaa4 Use hass httpx client for ElevenLabs component (#126793) 2024-09-27 19:04:31 +02:00
Steven B.
33d0343089 Extend dhcp discovery flow for ring integration (#126661) 2024-09-27 19:03:43 +02:00
Raj Laud
e6af8f63f3 Squeezebox - bump pysqueezebox dependency to 0.9.3 to restore favorites support (#126929) 2024-09-27 18:50:30 +02:00
Jan Rieger
cda62a4ff3 Add missing icons to unifi (#126934) 2024-09-27 18:50:00 +02:00
Marc Mueller
f359d619cb Modify pytest workflow to support testing multiple Python versions [ci] (#126936) 2024-09-27 18:49:20 +02:00
Erik Montnemery
495faf5033 Improve statistics issue title (#126851) 2024-09-27 18:32:20 +02:00
Bram Kragten
cba2daf314 Update frontend to 20240927.0 (#126933) 2024-09-27 18:10:39 +02:00
Jan Rieger
2d68f9a986 Use icon translations in unifi (#126903)
* Use icon translations in unifi

* Update snapshots

* Add state icons

* Address feedback

* Update snapshot
2024-09-27 17:43:25 +02:00
Jan Bouwhuis
7fde2e2fe0 Do not unsubscribe mqtt integration discovery if entry is already configured (#126907)
* Do not unsubscribe mqtt integration discovery if entry is already configured

* Test cases without unsubscribe
2024-09-27 17:28:51 +02:00
Michael Hansen
bd4f3b0553 Change Assist satellite state names (#126926)
* Change state names

* Update homeassistant/components/assist_satellite/strings.json

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-09-27 11:11:23 -04:00
Michael Hansen
6373347d65 Adjust "Assist in progress" sensor in ESPHome (#126928)
Adjust sensor
2024-09-27 11:10:50 -04:00
Jon Seager
b458b204f0 Bump pytouchlinesl to 0.1.7 (#126923) 2024-09-27 16:01:59 +02:00
mvn23
dd3a3f821c Bump pyotgw to 2.2.1 (#126918) 2024-09-27 15:43:10 +02:00
Jan Bouwhuis
60e3a1fc5f Fix ruff import validation (#126917) 2024-09-27 15:17:54 +02:00
Manu
706a5680e1 Change Turkey to Türkiye per 2022 UN resolution on official name (#126779) 2024-09-27 15:11:05 +02:00
Brett Adams
cad87f51a3 Code quality improvements for Teslemetry (#123444) 2024-09-27 15:06:09 +02:00
Erik Montnemery
66ab90b518 Add EntityIDPostMigration data migrator class (#125307) 2024-09-27 14:58:34 +02:00
Jon Seager
8bdd81ff24 Update pytouchlinesl to 0.1.6 (#126912) 2024-09-27 14:56:37 +02:00
Joost Lekkerkerker
f64e542879 Fix Evohome snapshots (#126915) 2024-09-27 14:55:44 +02:00
Joost Lekkerkerker
7c58476af9 Add unique id migration to Geniushub (#122330) 2024-09-27 14:54:47 +02:00
epenet
1f3b06a9bd Refactor Trace to avoid self import (#125822) 2024-09-27 14:52:13 +02:00
epenet
e1314b6cda Use shorthand attributes in vodafone_station device tracker (#126747) 2024-09-27 14:48:30 +02:00
epenet
a6b629c392 Use shorthand attributes in traccar device tracker (#126733) 2024-09-27 14:45:41 +02:00
epenet
f9f51e2381 Use shorthand attributes in gpslogger device tracker (#126739) 2024-09-27 14:44:56 +02:00
epenet
ee75cba008 Remove unused properties in tado device tracker (#126737) 2024-09-27 14:41:47 +02:00
Manu
c768f03f71 Revert "Add support for Xiaomi airpurifier and humidifier (#117791)" (#126873) 2024-09-27 14:41:20 +02:00
Joost Lekkerkerker
20a57d6381 Fix Tado unloading (#126910) 2024-09-27 14:36:29 +02:00
Joost Lekkerkerker
308f25fe4c Migrate Nexia unique id to str (#126911) 2024-09-27 14:35:08 +02:00
Erik Montnemery
9f2ba6bc2c Use ConfigFlow.has_matching_flow to deduplicate plugwise flows (#126896) 2024-09-27 14:32:36 +02:00
rubenbe
b3b5d9602a Add RSS description to Feedreader event (#126681) 2024-09-27 13:46:48 +02:00
David Bonnes
2d16732972 Set the default time zone for evohome tests (#126679) 2024-09-27 13:44:47 +02:00
epenet
94efd3e230 Cleanup sensor tests (#126881) 2024-09-27 13:43:24 +02:00
Erik Montnemery
59a690f214 Use ConfigFlow.has_matching_flow to deduplicate homekit_controller flows (#126894) 2024-09-27 13:41:55 +02:00
Marc Mueller
6f70a52880 Update grpcio constraints to 1.62.3 (#126908) 2024-09-27 13:35:20 +02:00
Erik Montnemery
85ebe5e01a Use ConfigFlow.has_matching_flow to deduplicate hunterdouglas flows (#126895) 2024-09-27 13:22:16 +02:00
Joost Lekkerkerker
a3ec4db9cc Update airgradient device sw_version when changed (#126902) 2024-09-27 13:21:35 +02:00
Michael
ffa6b5fcb2 Use two words for Nautical miles unit (#126905) 2024-09-27 13:16:13 +02:00
Manu
b78a1f7b61 Fix blocking call in Xiaomi Miio integration (#126871) 2024-09-27 13:11:28 +02:00
epenet
1d49c5056c Use shorthand attributes in tile device tracker (#126735) 2024-09-27 13:04:19 +02:00
Erik Montnemery
83ebd601a9 Use ConfigFlow.has_matching_flow to deduplicate steamist flows (#126897) 2024-09-27 13:01:29 +02:00
Simon Lamon
a972e295ea Bump python-linkplay to 0.0.12 (#126850)
Bump dependency
2024-09-27 12:21:58 +02:00
Manu
7a0b4fc62c Add support for variant of Xiaomi Mi Air Purifier 3C (zhimi.airp.mb4a) (#126867)
Add model id zhimi.airp.mb4a
2024-09-27 12:00:19 +02:00
Joost Lekkerkerker
d78fcd2a29 Introduce base entity in Switcher (#126822) 2024-09-27 11:47:47 +02:00
Erik Montnemery
2b2f5c9353 Use ConfigFlow.has_matching_flow to deduplicate elkm1 flows (#126887) 2024-09-27 11:46:26 +02:00
Erik Montnemery
fcbb9dd8d8 Use ConfigFlow.has_matching_flow to deduplicate fritz flows (#126890) 2024-09-27 11:45:57 +02:00
Erik Montnemery
b34f3ad5c5 Use ConfigFlow.has_matching_flow to deduplicate gogogate2 flows (#126892) 2024-09-27 11:45:17 +02:00
Erik Montnemery
8bdd909351 Use ConfigFlow.has_matching_flow to deduplicate fritzbox flows (#126891) 2024-09-27 11:44:59 +02:00
Kareem ElFaramawi
d7fe7f35ad Fix Abode integration needing to reauthenticate after core update (#123035)
* bump jaraco.abode to 6.2.1

* update abode user_data path to HA config

* Move abode config call out of try block

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-09-27 11:43:29 +02:00
Joost Lekkerkerker
1ebcc34e66 Fix restoring state class in mobile app (#126868) 2024-09-27 11:37:28 +02:00
J. Nick Koston
40e83dd9e0 Bump yarl to 1.13.0 (#126872) 2024-09-27 11:35:57 +02:00
Joost Lekkerkerker
18fd00d0c2 Add diagnostics platform to airgradient (#126886) 2024-09-27 11:35:35 +02:00
J. Nick Koston
9ec26a9be5 Fix getting the current host for IPv6 urls (#126889) 2024-09-27 11:26:35 +02:00
Erik Montnemery
3c0be47d3c Add FlowManager.async_has_matching_flow (#126804)
* Add FlowManager.async_flow_has_matching_flow

* Revert changes from the future

* Apply suggested changes to apple_tv config flow

* Rename methods after discussion

* Update homeassistant/data_entry_flow.py

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

* Move deduplication functions to config_entries, add tests

* Adjust tests

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
2024-09-27 10:51:36 +02:00
Michael
26b5dab12b Add nmi (nautical miles) as valid distance unit (#124723) 2024-09-27 10:40:59 +02:00
J. Nick Koston
75ae6a8087 Fix getting the host for the current request (#126882) 2024-09-27 03:36:05 -05:00
Franck Nijhof
fb0e102d74 Mark custom panel integration as system type (#126883) 2024-09-27 10:26:19 +02:00
EnjoyingM
d777ec3267 Bump wolf-comm to 0.0.15 (#126857) 2024-09-27 09:56:33 +02:00
David Bonnes
cff9e9abab Refactor evohome test fixtures for improved testing (#126781) 2024-09-27 09:40:52 +02:00
Marc Mueller
0b19831a7a Update pytest warnings filter (#126858) 2024-09-27 09:32:50 +02:00
Alexey ALERT Rubashёff
27f3715780 Update overkiz Atlantic Water Heater away mode switching (#121801) 2024-09-27 09:30:40 +02:00
Jeef
76858f0534 Monarch Money cashflow sensor bugfix (#125774)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2024-09-27 09:18:37 +02:00
Joost Lekkerkerker
bcfdfe93f9 Fix small typo in mobile_app docstring (#126863) 2024-09-27 00:01:11 +02:00
Joost Lekkerkerker
7c6cc16ef1 Bump aiowithings to 3.1.0 (#126854) 2024-09-26 22:36:01 +02:00
Paulus Schoutsen
471c68f653 Update the Selected Pipeline entity name (#126845) 2024-09-26 20:38:51 +02:00
Joost Lekkerkerker
ae102f1318 Add logging to NYT Games setup failures (#126832) 2024-09-26 20:33:24 +02:00
Joost Lekkerkerker
2a0ad20188 Fix last played icon in NYT Games (#126837) 2024-09-26 20:23:24 +02:00
Michael Hansen
9db5b481be Fix ESPHome and VoIP Assist satellite entity names (#126229)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2024-09-26 20:22:09 +02:00
Joost Lekkerkerker
185d00c86c Fix Withings reauth title (#126838) 2024-09-26 20:19:40 +02:00
Bram Kragten
e72ec07683 Update frontend to 20240926.0 (#126843) 2024-09-26 19:48:27 +02:00
Joost Lekkerkerker
6e12726b11 Use Parametrizing in Matter tests (#126759)
* Overhaul matter tests

* Overhaul

* Remove matter_client where obsolete

* Move snapshots to the top

* Use usefixtures

* Add Valve
2024-09-26 19:40:20 +02:00
epenet
6c539cd2d8 Improve type hints in template config_flow tests (#126803)
Improve type hints in template tests
2024-09-26 19:25:33 +02:00
Sid
77642b9e3d Bump ruff to 0.6.8 (#126842) 2024-09-26 19:13:08 +02:00
Mike Degatano
86dc7111cb Bump aiohasupervisor to 0.1.0 (#126841) 2024-09-26 18:34:30 +02:00
Joost Lekkerkerker
8d428acbbb Bump nyt_games to 0.4.2 (#126834)
* Bump nyt_games to 0.4.1

* Bump nyt_games to 0.4.1

* Bump nyt_games to 0.4.2
2024-09-26 18:03:11 +02:00
Joost Lekkerkerker
1c13851858 Bump jaraco.abode to 6.2.1 (#126823) 2024-09-26 17:31:09 +02:00
Joost Lekkerkerker
a75ebc27c4 Bump knocki to 0.3.5 (#126826) 2024-09-26 15:45:20 +02:00
epenet
45f92dd981 Improve type hints in template (#126802) 2024-09-26 15:05:46 +02:00
Steven B.
5a6ce86476 Bump ring-doorbell to 0.9.6 (#126817) 2024-09-26 15:00:31 +02:00
Noah Husby
7afad1dde9 Bump aiorussound to 4.0.5 (#126774)
* Bump aiorussound to 4.0.4

* Remove unnecessary exception

* Bump aiorussound to 4.0.5

* Fixes

* Update homeassistant/components/russound_rio/media_player.py

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2024-09-26 14:38:36 +02:00
Manu
b766d91f49 Fix typo in Mealie integration (#126824) 2024-09-26 14:28:57 +02:00
Marc Mueller
22dac266c4 Update pydantic to 1.10.18 (#126821) 2024-09-26 14:17:46 +02:00
epenet
5fb9537d6d Use pytest.mark.usefixtures for start_ha in template tests (#126805) 2024-09-26 14:00:52 +02:00
Marc Mueller
c1b24e6ba2 Small typing improvements (#126818)
* Add from __future__ import annotations

* Use PEP 695 type aliases

* Fix generator typing
2024-09-26 13:51:27 +02:00
dependabot[bot]
cf803507d6 Bump actions/checkout from 4.1.7 to 4.2.0 (#126801)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-09-26 13:49:48 +02:00
Josef Zweck
16e5271cac Switch coordinator setup to _async_setup (#126810) 2024-09-26 13:08:02 +02:00
Martin Hjelmare
d5ad35630f Fix missing template alarm control panel menu string (#126791) 2024-09-26 07:37:49 +02:00
starkillerOG
1395baef01 Remove Reolink Home Hub main level switches (#126697)
Co-authored-by: Robert Resch <robert@resch.dev>
2024-09-25 21:52:26 +02:00
David Bonnes
90dcb02429 Remove unnecessary patch from evohome tests (#126760) 2024-09-25 21:52:03 +02:00
Steven B.
4f0211cdd8 Deprecate tplink alarm button entities (#126349)
Co-authored-by: J. Nick Koston <nick@koston.org>
2024-09-25 14:47:40 -05:00
epenet
a1e6d4b693 Use shorthand attributes in geofency device tracker (#126741) 2024-09-25 21:47:13 +02:00
starkillerOG
77db88ad28 Bump reolink-aio to 0.9.11 (#126778) 2024-09-25 21:43:20 +02:00
G Johansson
c6a1b9fc39 Change Climate set temp action for incorrect feature will raise (#126692)
* Change Climate set temp action for incorrect feature will raise

* Fix some tests

* Fix review comments

* Fix tesla_fleet

* Fix tests

* Fix review comment
2024-09-25 21:16:14 +02:00
Franck Nijhof
9afd270111 Bump version to 2024.11.0dev0 (#126776) 2024-09-25 21:08:07 +02:00
547 changed files with 10434 additions and 4847 deletions

View File

@@ -27,7 +27,7 @@ jobs:
publish: ${{ steps.version.outputs.publish }}
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
with:
fetch-depth: 0
@@ -90,7 +90,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Download nightly wheels of frontend
if: needs.init.outputs.channel == 'dev'
@@ -242,7 +242,7 @@ jobs:
- green
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set build additional args
run: |
@@ -279,7 +279,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Initialize git
uses: home-assistant/actions/helpers/git-init@master
@@ -321,7 +321,7 @@ jobs:
registry: ["ghcr.io/home-assistant", "docker.io/homeassistant"]
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Install Cosign
uses: sigstore/cosign-installer@v3.6.0
@@ -451,7 +451,7 @@ jobs:
if: github.repository_owner == 'home-assistant' && needs.init.outputs.publish == 'true'
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.2.0
@@ -499,7 +499,7 @@ jobs:
HASSFEST_IMAGE_TAG: ghcr.io/home-assistant/hassfest:${{ needs.init.outputs.version }}
steps:
- name: Checkout repository
uses: actions/checkout@692973e3d937129bcbf40652eb9f2f61becf3332 # v4.1.7
uses: actions/checkout@d632683dd7b4114ad314bca15554477dd762a938 # v4.2.0
- name: Login to GitHub Container Registry
uses: docker/login-action@9780b0c442fbb1117ed29e0efdff1e18412f7567 # v3.3.0
@@ -509,7 +509,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0
uses: docker/build-push-action@32945a339266b759abcbdc89316275140b0fc960 # v6.8.0
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile
@@ -522,7 +522,7 @@ jobs:
- name: Push Docker image
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
id: push
uses: docker/build-push-action@5cd11c3a4ced054e52742c5fd54dca954e0edd85 # v6.7.0
uses: docker/build-push-action@32945a339266b759abcbdc89316275140b0fc960 # v6.8.0
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile

View File

@@ -40,7 +40,7 @@ env:
CACHE_VERSION: 10
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 9
HA_SHORT_VERSION: "2024.10"
HA_SHORT_VERSION: "2024.11"
DEFAULT_PYTHON: "3.12"
ALL_PYTHON_VERSIONS: "['3.12']"
# 10.3 is the oldest supported version
@@ -93,7 +93,7 @@ jobs:
runs-on: ubuntu-24.04
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Generate partial Python venv restore key
id: generate_python_cache_key
run: |
@@ -231,7 +231,7 @@ jobs:
- info
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.2.0
@@ -277,7 +277,7 @@ jobs:
- pre-commit
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.2.0
id: python
@@ -317,7 +317,7 @@ jobs:
- pre-commit
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.2.0
id: python
@@ -357,7 +357,7 @@ jobs:
- pre-commit
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.2.0
id: python
@@ -447,7 +447,7 @@ jobs:
- script/hassfest/docker/Dockerfile
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Register hadolint problem matcher
run: |
echo "::add-matcher::.github/workflows/matchers/hadolint.json"
@@ -466,7 +466,7 @@ jobs:
python-version: ${{ fromJSON(needs.info.outputs.python_versions) }}
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.2.0
@@ -550,7 +550,7 @@ jobs:
sudo apt-get -y install \
libturbojpeg
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.2.0
@@ -583,7 +583,7 @@ jobs:
- base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.2.0
@@ -617,7 +617,7 @@ jobs:
&& needs.info.outputs.requirements == 'true'
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.2.0
@@ -645,7 +645,7 @@ jobs:
- name: Process licenses
run: |
. venv/bin/activate
python -m script.licenses
python -m script.licenses licenses.json
pylint:
name: Check pylint
@@ -660,7 +660,7 @@ jobs:
- base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.2.0
@@ -707,7 +707,7 @@ jobs:
- base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.2.0
@@ -752,7 +752,7 @@ jobs:
- base
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.2.0
@@ -815,7 +815,11 @@ jobs:
needs:
- info
- base
name: Split tests for full run
strategy:
fail-fast: false
matrix:
python-version: ${{ fromJson(needs.info.outputs.python_versions) }}
name: Split tests for full run Python ${{ matrix.python-version }}
steps:
- name: Install additional OS dependencies
run: |
@@ -827,12 +831,12 @@ jobs:
libturbojpeg \
libgammu-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.2.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
python-version: ${{ matrix.python-version }}
check-latest: true
- name: Restore base Python virtual environment
id: cache-venv
@@ -891,7 +895,7 @@ jobs:
libturbojpeg \
libgammu-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.2.0
@@ -1011,7 +1015,7 @@ jobs:
libturbojpeg \
libmariadb-dev-compat
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.2.0
@@ -1137,7 +1141,7 @@ jobs:
libturbojpeg \
postgresql-server-dev-14
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.2.0
@@ -1232,7 +1236,7 @@ jobs:
timeout-minutes: 10
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Download all coverage artifacts
uses: actions/download-artifact@v4.1.8
with:
@@ -1283,7 +1287,7 @@ jobs:
libturbojpeg \
libgammu-dev
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.2.0
@@ -1370,7 +1374,7 @@ jobs:
timeout-minutes: 10
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Download all coverage artifacts
uses: actions/download-artifact@v4.1.8
with:

View File

@@ -21,7 +21,7 @@ jobs:
steps:
- name: Check out code from GitHub
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Initialize CodeQL
uses: github/codeql-action/init@v3.26.9

View File

@@ -19,7 +19,7 @@ jobs:
runs-on: ubuntu-latest
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.2.0

View File

@@ -32,7 +32,7 @@ jobs:
architectures: ${{ steps.info.outputs.architectures }}
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
@@ -119,7 +119,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Download env_file
uses: actions/download-artifact@v4.1.8
@@ -163,7 +163,7 @@ jobs:
arch: ${{ fromJson(needs.init.outputs.architectures) }}
steps:
- name: Checkout the repository
uses: actions/checkout@v4.1.7
uses: actions/checkout@v4.2.0
- name: Download env_file
uses: actions/download-artifact@v4.1.8
@@ -205,11 +205,9 @@ jobs:
# Some dependencies still require 'cython<3'
# and don't yet use isolated build environments.
# Build these first.
# grpcio: https://github.com/grpc/grpc/issues/33918
# pydantic: https://github.com/pydantic/pydantic/issues/7689
touch requirements_old-cython.txt
cat homeassistant/package_constraints.txt | grep 'grpcio==' >> requirements_old-cython.txt
cat homeassistant/package_constraints.txt | grep 'pydantic==' >> requirements_old-cython.txt
- name: Build wheels (old cython)

View File

@@ -1,6 +1,6 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.6.6
rev: v0.6.8
hooks:
- id: ruff
args:
@@ -83,7 +83,7 @@ repos:
pass_filenames: false
language: script
types: [text]
files: ^(homeassistant/.+/(icons|manifest|strings)\.json|homeassistant/brands/.*\.json|homeassistant/.+/services\.yaml|script/hassfest/(?!metadata|mypy_config).+\.py|requirements\.txt)$
files: ^(homeassistant/.+/(icons|manifest|strings)\.json|homeassistant/brands/.*\.json|homeassistant/.+/services\.yaml|script/hassfest/(?!metadata|mypy_config).+\.py|requirements.+\.txt)$
- id: hassfest-metadata
name: hassfest-metadata
entry: script/run-in-env.sh python3 -m script.hassfest -p metadata

View File

@@ -7,8 +7,6 @@ Check out `home-assistant.io <https://home-assistant.io>`__ for `a
demo <https://demo.home-assistant.io>`__, `installation instructions <https://home-assistant.io/getting-started/>`__,
`tutorials <https://home-assistant.io/getting-started/automation/>`__ and `documentation <https://home-assistant.io/docs/>`__.
This is a project of the `Open Home Foundation <https://www.openhomefoundation.org/>`__.
|screenshot-states|
Featured integrations
@@ -22,9 +20,14 @@ components <https://developers.home-assistant.io/docs/creating_component_index/>
If you run into issues while using Home Assistant or during development
of a component, check the `Home Assistant help section <https://home-assistant.io/help/>`__ of our website for further help and information.
|ohf-logo|
.. |Chat Status| image:: https://img.shields.io/discord/330944238910963714.svg
:target: https://www.home-assistant.io/join-chat/
.. |screenshot-states| image:: https://raw.githubusercontent.com/home-assistant/core/dev/.github/assets/screenshot-states.png
:target: https://demo.home-assistant.io
.. |screenshot-integrations| image:: https://raw.githubusercontent.com/home-assistant/core/dev/.github/assets/screenshot-integrations.png
:target: https://home-assistant.io/integrations/
.. |ohf-logo| image:: https://www.openhomefoundation.org/badges/home-assistant.png
:alt: Home Assistant - A project from the Open Home Foundation
:target: https://www.openhomefoundation.org/

View File

@@ -4,8 +4,10 @@ from __future__ import annotations
from dataclasses import dataclass, field
from functools import partial
from pathlib import Path
from jaraco.abode.client import Client as Abode
import jaraco.abode.config
from jaraco.abode.exceptions import (
AuthenticationException as AbodeAuthenticationException,
Exception as AbodeException,
@@ -93,6 +95,9 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
password = entry.data[CONF_PASSWORD]
polling = entry.data[CONF_POLLING]
# Configure abode library to use config directory for storing data
jaraco.abode.config.paths.override(user_data=Path(hass.config.path("Abode")))
# For previous config entries where unique_id is None
if entry.unique_id is None:
hass.config_entries.async_update_entry(

View File

@@ -9,5 +9,5 @@
},
"iot_class": "cloud_push",
"loggers": ["jaraco.abode", "lomond"],
"requirements": ["jaraco.abode==6.2.0"]
"requirements": ["jaraco.abode==6.2.1"]
}

View File

@@ -9,9 +9,10 @@ from typing import TYPE_CHECKING
from airgradient import AirGradientClient, AirGradientError, Config, Measures
from homeassistant.core import HomeAssistant
from homeassistant.helpers import device_registry as dr
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import LOGGER
from .const import DOMAIN, LOGGER
if TYPE_CHECKING:
from . import AirGradientConfigEntry
@@ -29,6 +30,7 @@ class AirGradientCoordinator(DataUpdateCoordinator[AirGradientData]):
"""Class to manage fetching AirGradient data."""
config_entry: AirGradientConfigEntry
_current_version: str
def __init__(self, hass: HomeAssistant, client: AirGradientClient) -> None:
"""Initialize coordinator."""
@@ -42,11 +44,27 @@ class AirGradientCoordinator(DataUpdateCoordinator[AirGradientData]):
assert self.config_entry.unique_id
self.serial_number = self.config_entry.unique_id
async def _async_setup(self) -> None:
"""Set up the coordinator."""
self._current_version = (
await self.client.get_current_measures()
).firmware_version
async def _async_update_data(self) -> AirGradientData:
try:
measures = await self.client.get_current_measures()
config = await self.client.get_config()
except AirGradientError as error:
raise UpdateFailed(error) from error
else:
return AirGradientData(measures, config)
if measures.firmware_version != self._current_version:
device_registry = dr.async_get(self.hass)
device_entry = device_registry.async_get_device(
identifiers={(DOMAIN, self.serial_number)}
)
assert device_entry
device_registry.async_update_device(
device_entry.id,
sw_version=measures.firmware_version,
)
self._current_version = measures.firmware_version
return AirGradientData(measures, config)

View File

@@ -0,0 +1,18 @@
"""Diagnostics support for Airgradient."""
from __future__ import annotations
from dataclasses import asdict
from typing import Any
from homeassistant.core import HomeAssistant
from . import AirGradientConfigEntry
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, entry: AirGradientConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
return asdict(entry.runtime_data.data)

View File

@@ -10,12 +10,15 @@ from homeassistant.core import Event, HassJob, HomeAssistant, callback
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.event import async_call_later, async_track_time_interval
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.hass_dict import HassKey
from .analytics import Analytics
from .const import ATTR_ONBOARDED, ATTR_PREFERENCES, DOMAIN, INTERVAL, PREFERENCE_SCHEMA
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
DATA_COMPONENT: HassKey[Analytics] = HassKey(DOMAIN)
async def async_setup(hass: HomeAssistant, _: ConfigType) -> bool:
"""Set up the analytics integration."""
@@ -52,7 +55,7 @@ async def async_setup(hass: HomeAssistant, _: ConfigType) -> bool:
websocket_api.async_register_command(hass, websocket_analytics)
websocket_api.async_register_command(hass, websocket_analytics_preferences)
hass.data[DOMAIN] = analytics
hass.data[DATA_COMPONENT] = analytics
return True
@@ -65,7 +68,7 @@ def websocket_analytics(
msg: dict[str, Any],
) -> None:
"""Return analytics preferences."""
analytics: Analytics = hass.data[DOMAIN]
analytics = hass.data[DATA_COMPONENT]
connection.send_result(
msg["id"],
{ATTR_PREFERENCES: analytics.preferences, ATTR_ONBOARDED: analytics.onboarded},
@@ -87,7 +90,7 @@ async def websocket_analytics_preferences(
) -> None:
"""Update analytics preferences."""
preferences = msg[ATTR_PREFERENCES]
analytics: Analytics = hass.data[DOMAIN]
analytics = hass.data[DATA_COMPONENT]
await analytics.save_preferences(preferences)
await analytics.send_analytics()

View File

@@ -4,7 +4,6 @@ from __future__ import annotations
from pydroid_ipcam import PyDroidIPCam
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
@@ -15,8 +14,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
from .coordinator import AndroidIPCamDataUpdateCoordinator
from .coordinator import AndroidIPCamConfigEntry, AndroidIPCamDataUpdateCoordinator
PLATFORMS: list[Platform] = [
Platform.BINARY_SENSOR,
@@ -26,7 +24,9 @@ PLATFORMS: list[Platform] = [
]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, entry: AndroidIPCamConfigEntry
) -> bool:
"""Set up Android IP Webcam from a config entry."""
websession = async_get_clientsession(hass)
cam = PyDroidIPCam(
@@ -40,16 +40,15 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = AndroidIPCamDataUpdateCoordinator(hass, entry, cam)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, entry: AndroidIPCamConfigEntry
) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -7,12 +7,11 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN, MOTION_ACTIVE
from .coordinator import AndroidIPCamDataUpdateCoordinator
from .const import MOTION_ACTIVE
from .coordinator import AndroidIPCamConfigEntry, AndroidIPCamDataUpdateCoordinator
from .entity import AndroidIPCamBaseEntity
BINARY_SENSOR_DESCRIPTION = BinarySensorEntityDescription(
@@ -24,16 +23,12 @@ BINARY_SENSOR_DESCRIPTION = BinarySensorEntityDescription(
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AndroidIPCamConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the IP Webcam sensors from config entry."""
coordinator: AndroidIPCamDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
]
async_add_entities([IPWebcamBinarySensor(coordinator)])
async_add_entities([IPWebcamBinarySensor(config_entry.runtime_data)])
class IPWebcamBinarySensor(AndroidIPCamBaseEntity, BinarySensorEntity):

View File

@@ -3,7 +3,6 @@
from __future__ import annotations
from homeassistant.components.mjpeg import MjpegCamera, filter_urllib3_logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONF_HOST,
CONF_PASSWORD,
@@ -15,21 +14,17 @@ from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .coordinator import AndroidIPCamDataUpdateCoordinator
from .coordinator import AndroidIPCamConfigEntry, AndroidIPCamDataUpdateCoordinator
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AndroidIPCamConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the IP Webcam camera from config entry."""
filter_urllib3_logging()
coordinator: AndroidIPCamDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
]
async_add_entities([IPWebcamCamera(coordinator)])
async_add_entities([IPWebcamCamera(config_entry.runtime_data)])
class IPWebcamCamera(MjpegCamera):

View File

@@ -15,19 +15,22 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
type AndroidIPCamConfigEntry = ConfigEntry[AndroidIPCamDataUpdateCoordinator]
class AndroidIPCamDataUpdateCoordinator(DataUpdateCoordinator[None]):
"""Coordinator class for the Android IP Webcam."""
config_entry: AndroidIPCamConfigEntry
def __init__(
self,
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AndroidIPCamConfigEntry,
cam: PyDroidIPCam,
) -> None:
"""Initialize the Android IP Webcam."""
self.hass = hass
self.config_entry: ConfigEntry = config_entry
self.cam = cam
super().__init__(
self.hass,

View File

@@ -13,14 +13,12 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import DOMAIN
from .coordinator import AndroidIPCamDataUpdateCoordinator
from .coordinator import AndroidIPCamConfigEntry, AndroidIPCamDataUpdateCoordinator
from .entity import AndroidIPCamBaseEntity
@@ -120,19 +118,21 @@ SENSOR_TYPES: tuple[AndroidIPWebcamSensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AndroidIPCamConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the IP Webcam sensors from config entry."""
coordinator: AndroidIPCamDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
]
coordinator = config_entry.runtime_data
sensor_types = [
sensor
for sensor in SENSOR_TYPES
if sensor.key
in [*coordinator.cam.enabled_sensors, "audio_connections", "video_connections"]
in [
*coordinator.cam.enabled_sensors,
"audio_connections",
"video_connections",
]
]
async_add_entities(
IPWebcamSensor(coordinator, description) for description in sensor_types

View File

@@ -9,13 +9,11 @@ from typing import Any
from pydroid_ipcam import PyDroidIPCam
from homeassistant.components.switch import SwitchEntity, SwitchEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .coordinator import AndroidIPCamDataUpdateCoordinator
from .coordinator import AndroidIPCamConfigEntry, AndroidIPCamDataUpdateCoordinator
from .entity import AndroidIPCamBaseEntity
@@ -113,14 +111,12 @@ SWITCH_TYPES: tuple[AndroidIPWebcamSwitchEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AndroidIPCamConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the IP Webcam switches from config entry."""
coordinator: AndroidIPCamDataUpdateCoordinator = hass.data[DOMAIN][
config_entry.entry_id
]
coordinator = config_entry.runtime_data
switch_types = [
switch
for switch in SWITCH_TYPES

View File

@@ -53,7 +53,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Activate the Apache Kafka integration."""
conf = config[DOMAIN]
kafka = hass.data[DOMAIN] = KafkaManager(
kafka = KafkaManager(
hass,
conf[CONF_IP_ADDRESS],
conf[CONF_PORT],

View File

@@ -8,7 +8,7 @@ from collections.abc import Awaitable, Callable, Mapping
from ipaddress import ip_address
import logging
from random import randrange
from typing import Any
from typing import Any, Self
from pyatv import exceptions, pair, scan
from pyatv.const import DeviceModel, PairingRequirement, Protocol
@@ -98,8 +98,11 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
scan_filter: str | None = None
all_identifiers: set[str]
atv: BaseConfig | None = None
atv_identifiers: list[str] | None = None
_host: str # host in zeroconf discovery info, should not be accessed by other flows
host: str | None = None # set by _async_aggregate_discoveries, for other flows
protocol: Protocol | None = None
pairing: PairingHandler | None = None
protocols_to_pair: deque[Protocol] | None = None
@@ -157,7 +160,6 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
"type": "Apple TV",
}
self.scan_filter = self.unique_id
self.context["identifier"] = self.unique_id
return await self.async_step_restore_device()
async def async_step_restore_device(
@@ -192,7 +194,7 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
self.device_identifier, raise_on_progress=False
)
assert self.atv
self.context["all_identifiers"] = self.atv.all_identifiers
self.all_identifiers = set(self.atv.all_identifiers)
return await self.async_step_confirm()
return self.async_show_form(
@@ -207,7 +209,7 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle device found via zeroconf."""
if discovery_info.ip_address.version == 6:
return self.async_abort(reason="ipv6_not_supported")
host = discovery_info.host
self._host = host = discovery_info.host
service_type = discovery_info.type[:-1] # Remove leading .
name = discovery_info.name.replace(f".{service_type}.", "")
properties = discovery_info.properties
@@ -255,7 +257,7 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
# as two separate flows.
#
# To solve this, all identifiers are stored as
# "all_identifiers" in the flow context. When a new service is discovered, the
# "all_identifiers" in the flow. When a new service is discovered, the
# code below will check these identifiers for all active flows and abort if a
# match is found. Before aborting, the original flow is updated with any
# potentially new identifiers. In the example above, when service C is
@@ -277,32 +279,32 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
self._async_check_and_update_in_progress(host, unique_id)
# Host must only be set AFTER checking and updating in progress
# flows or we will have a race condition where no flows move forward.
self.context[CONF_ADDRESS] = host
self.host = host
@callback
def _async_check_and_update_in_progress(self, host: str, unique_id: str) -> None:
"""Check for in-progress flows and update them with identifiers if needed."""
for flow in self._async_in_progress(include_uninitialized=True):
context = flow["context"]
if (
context.get("source") != SOURCE_ZEROCONF
or context.get(CONF_ADDRESS) != host
):
continue
if (
"all_identifiers" in context
and unique_id not in context["all_identifiers"]
):
# Add potentially new identifiers from this device to the existing flow
context["all_identifiers"].append(unique_id)
if self.hass.config_entries.flow.async_has_matching_flow(self):
raise AbortFlow("already_in_progress")
def is_matching(self, other_flow: Self) -> bool:
"""Return True if other_flow is matching this flow."""
if (
other_flow.context.get("source") != SOURCE_ZEROCONF
or other_flow.host != self._host
):
return False
if self.unique_id is not None:
# Add potentially new identifiers from this device to the existing flow
other_flow.all_identifiers.add(self.unique_id)
return True
async def async_found_zeroconf_device(
self, user_input: dict[str, str] | None = None
) -> ConfigFlowResult:
"""Handle device found after Zeroconf discovery."""
assert self.atv
self.context["all_identifiers"] = self.atv.all_identifiers
self.all_identifiers = set(self.atv.all_identifiers)
# Also abort if an integration with this identifier already exists
await self.async_set_unique_id(self.device_identifier)
# but be sure to update the address if its changed so the scanner
@@ -310,7 +312,6 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
self._abort_if_unique_id_configured(
updates={CONF_ADDRESS: str(self.atv.address)}
)
self.context["identifier"] = self.unique_id
return await self.async_step_confirm()
async def async_find_device_wrapper(
@@ -390,7 +391,7 @@ class AppleTVConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle user-confirmation of discovered node."""
assert self.atv
if user_input is not None:
expected_identifier_count = len(self.context["all_identifiers"])
expected_identifier_count = len(self.all_identifiers)
# If number of services found during device scan mismatch number of
# identifiers collected during Zeroconf discovery, then trigger a new scan
# with hopes of finding all services.

View File

@@ -36,6 +36,7 @@ from homeassistant.loader import (
async_get_integration,
)
from homeassistant.util import slugify
from homeassistant.util.hass_dict import HassKey
__all__ = ["ClientCredential", "AuthorizationServer", "async_import_client_credential"]
@@ -45,7 +46,7 @@ DOMAIN = "application_credentials"
STORAGE_KEY = DOMAIN
STORAGE_VERSION = 1
DATA_STORAGE = "storage"
DATA_COMPONENT: HassKey[ApplicationCredentialsStorageCollection] = HassKey(DOMAIN)
CONF_AUTH_DOMAIN = "auth_domain"
DEFAULT_IMPORT_NAME = "Import from configuration.yaml"
@@ -150,7 +151,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
id_manager,
)
await storage_collection.async_load()
hass.data[DOMAIN][DATA_STORAGE] = storage_collection
hass.data[DATA_COMPONENT] = storage_collection
collection.DictStorageCollectionWebsocket(
storage_collection, DOMAIN, DOMAIN, CREATE_FIELDS, UPDATE_FIELDS
@@ -175,7 +176,6 @@ async def async_import_client_credential(
"""Import an existing credential from configuration.yaml."""
if DOMAIN not in hass.data:
raise ValueError("Integration 'application_credentials' not setup")
storage_collection = hass.data[DOMAIN][DATA_STORAGE]
item = {
CONF_DOMAIN: domain,
CONF_CLIENT_ID: credential.client_id,
@@ -183,7 +183,7 @@ async def async_import_client_credential(
CONF_AUTH_DOMAIN: auth_domain if auth_domain else domain,
}
item[CONF_NAME] = credential.name if credential.name else DEFAULT_IMPORT_NAME
await storage_collection.async_import_item(item)
await hass.data[DATA_COMPONENT].async_import_item(item)
class AuthImplementation(config_entry_oauth2_flow.LocalOAuth2Implementation):
@@ -222,8 +222,7 @@ async def _async_provide_implementation(
if not platform:
return []
storage_collection = hass.data[DOMAIN][DATA_STORAGE]
credentials = storage_collection.async_client_credentials(domain)
credentials = hass.data[DATA_COMPONENT].async_client_credentials(domain)
if hasattr(platform, "async_get_auth_implementation"):
return [
await platform.async_get_auth_implementation(hass, auth_domain, credential)
@@ -246,8 +245,7 @@ async def _async_config_entry_app_credentials(
):
return None
storage_collection = hass.data[DOMAIN][DATA_STORAGE]
for item in storage_collection.async_items():
for item in hass.data[DATA_COMPONENT].async_items():
item_id = item[CONF_ID]
if (
item[CONF_DOMAIN] == config_entry.domain

View File

@@ -5,5 +5,5 @@
"documentation": "https://www.home-assistant.io/integrations/apprise",
"iot_class": "cloud_push",
"loggers": ["apprise"],
"requirements": ["apprise==1.8.0"]
"requirements": ["apprise==1.9.0"]
}

View File

@@ -6,14 +6,12 @@ import logging
from pyaprilaire.const import Attribute
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_PORT, EVENT_HOMEASSISTANT_STOP, Platform
from homeassistant.core import Event, HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.device_registry import format_mac
from .const import DOMAIN
from .coordinator import AprilaireCoordinator
from .coordinator import AprilaireConfigEntry, AprilaireCoordinator
PLATFORMS: list[Platform] = [
Platform.CLIMATE,
@@ -25,7 +23,7 @@ PLATFORMS: list[Platform] = [
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: AprilaireConfigEntry) -> bool:
"""Set up a config entry for Aprilaire."""
host = entry.data[CONF_HOST]
@@ -34,15 +32,16 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = AprilaireCoordinator(hass, entry.unique_id, host, port)
await coordinator.start_listen()
hass.data.setdefault(DOMAIN, {})[entry.unique_id] = coordinator
async def ready_callback(ready: bool):
async def ready_callback(ready: bool) -> None:
if ready:
mac_address = format_mac(coordinator.data[Attribute.MAC_ADDRESS])
if mac_address != entry.unique_id:
raise ConfigEntryAuthFailed("Invalid MAC address")
entry.runtime_data = coordinator
entry.async_on_unload(coordinator.stop_listen)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
async def _async_close(_: Event) -> None:
@@ -63,12 +62,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: AprilaireConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
coordinator: AprilaireCoordinator = hass.data[DOMAIN].pop(entry.unique_id)
coordinator.stop_listen()
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -16,19 +16,17 @@ from homeassistant.components.climate import (
HVACAction,
HVACMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PRECISION_HALVES, PRECISION_WHOLE, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import (
DOMAIN,
FAN_CIRCULATE,
PRESET_PERMANENT_HOLD,
PRESET_TEMPORARY_HOLD,
PRESET_VACATION,
)
from .coordinator import AprilaireCoordinator
from .coordinator import AprilaireConfigEntry
from .entity import BaseAprilaireEntity
HVAC_MODE_MAP = {
@@ -64,14 +62,14 @@ FAN_MODE_MAP = {
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AprilaireConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Add climates for passed config_entry in HA."""
coordinator: AprilaireCoordinator = hass.data[DOMAIN][config_entry.unique_id]
async_add_entities([AprilaireClimate(coordinator, config_entry.unique_id)])
async_add_entities(
[AprilaireClimate(config_entry.runtime_data, config_entry.unique_id)]
)
class AprilaireClimate(BaseAprilaireEntity, ClimateEntity):

View File

@@ -9,6 +9,7 @@ from typing import Any
import pyaprilaire.client
from pyaprilaire.const import MODELS, Attribute, FunctionalDomain
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import CALLBACK_TYPE, HomeAssistant, callback
import homeassistant.helpers.device_registry as dr
from homeassistant.helpers.device_registry import DeviceInfo
@@ -22,6 +23,8 @@ WAIT_TIMEOUT = 30
_LOGGER = logging.getLogger(__name__)
type AprilaireConfigEntry = ConfigEntry[AprilaireCoordinator]
class AprilaireCoordinator(BaseDataUpdateCoordinatorProtocol):
"""Coordinator for interacting with the thermostat."""
@@ -112,7 +115,7 @@ class AprilaireCoordinator(BaseDataUpdateCoordinatorProtocol):
self.client.stop_listen()
async def wait_for_ready(
self, ready_callback: Callable[[bool], Awaitable[bool]]
self, ready_callback: Callable[[bool], Awaitable[None]]
) -> bool:
"""Wait for the client to be ready."""

View File

@@ -14,13 +14,11 @@ from homeassistant.components.humidifier import (
HumidifierEntity,
HumidifierEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import DOMAIN
from .coordinator import AprilaireCoordinator
from .coordinator import AprilaireConfigEntry, AprilaireCoordinator
from .entity import BaseAprilaireEntity
HUMIDIFIER_ACTION_MAP: dict[StateType, HumidifierAction] = {
@@ -41,12 +39,12 @@ DEHUMIDIFIER_ACTION_MAP: dict[StateType, HumidifierAction] = {
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AprilaireConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Aprilaire humidifier devices."""
coordinator: AprilaireCoordinator = hass.data[DOMAIN][config_entry.unique_id]
coordinator = config_entry.runtime_data
assert config_entry.unique_id is not None

View File

@@ -9,12 +9,10 @@ from typing import cast
from pyaprilaire.const import Attribute
from homeassistant.components.select import SelectEntity, SelectEntityDescription
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .coordinator import AprilaireCoordinator
from .coordinator import AprilaireConfigEntry, AprilaireCoordinator
from .entity import BaseAprilaireEntity
AIR_CLEANING_EVENT_MAP = {0: "off", 3: "event_clean", 4: "allergies"}
@@ -25,12 +23,12 @@ FRESH_AIR_MODE_MAP = {0: "off", 1: "automatic"}
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AprilaireConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Aprilaire select devices."""
coordinator: AprilaireCoordinator = hass.data[DOMAIN][config_entry.unique_id]
coordinator = config_entry.runtime_data
assert config_entry.unique_id is not None

View File

@@ -13,14 +13,12 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import PERCENTAGE, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import DOMAIN
from .coordinator import AprilaireCoordinator
from .coordinator import AprilaireConfigEntry, AprilaireCoordinator
from .entity import BaseAprilaireEntity
DEHUMIDIFICATION_STATUS_MAP: dict[StateType, str] = {
@@ -76,12 +74,12 @@ def get_entities(
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AprilaireConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Aprilaire sensor devices."""
coordinator: AprilaireCoordinator = hass.data[DOMAIN][config_entry.unique_id]
coordinator = config_entry.runtime_data
assert config_entry.unique_id is not None

View File

@@ -2,33 +2,28 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import ArveCoordinator
from .coordinator import ArveConfigEntry, ArveCoordinator
PLATFORMS: list[Platform] = [Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: ArveConfigEntry) -> bool:
"""Set up Arve from a config entry."""
coordinator = ArveCoordinator(hass)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ArveConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -21,11 +21,13 @@ from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, Upda
from .const import DOMAIN, LOGGER
type ArveConfigEntry = ConfigEntry[ArveCoordinator]
class ArveCoordinator(DataUpdateCoordinator[ArveSensProData]):
"""Arve coordinator."""
config_entry: ConfigEntry
config_entry: ArveConfigEntry
devices: ArveDevices
def __init__(self, hass: HomeAssistant) -> None:

View File

@@ -11,7 +11,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
@@ -21,8 +20,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .coordinator import ArveCoordinator
from .coordinator import ArveConfigEntry
from .entity import ArveDeviceEntity
@@ -85,10 +83,10 @@ SENSORS: tuple[ArveDeviceEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant, entry: ArveConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Set up Arve device based on a config entry."""
coordinator: ArveCoordinator = hass.data[DOMAIN][entry.entry_id]
coordinator = entry.runtime_data
async_add_entities(
ArveDevice(coordinator, description, sn)

View File

@@ -6,20 +6,18 @@ import logging
from aioaseko import Aseko, AsekoNotLoggedIn
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_EMAIL, CONF_PASSWORD, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed
from .const import DOMAIN
from .coordinator import AsekoDataUpdateCoordinator
from .coordinator import AsekoConfigEntry, AsekoDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
PLATFORMS: list[str] = [Platform.BINARY_SENSOR, Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: AsekoConfigEntry) -> bool:
"""Set up Aseko Pool Live from a config entry."""
aseko = Aseko(entry.data[CONF_EMAIL], entry.data[CONF_PASSWORD])
@@ -30,19 +28,19 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = AsekoDataUpdateCoordinator(hass, aseko)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: AsekoConfigEntry) -> bool:
"""Unload a config entry."""
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
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: AsekoConfigEntry
) -> bool:
"""Migrate old entry."""
_LOGGER.debug("Migrating from version %s", config_entry.version)

View File

@@ -11,12 +11,10 @@ from homeassistant.components.binary_sensor import (
BinarySensorEntity,
BinarySensorEntityDescription,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from .const import DOMAIN
from .coordinator import AsekoDataUpdateCoordinator
from .coordinator import AsekoConfigEntry
from .entity import AsekoEntity
@@ -38,11 +36,11 @@ BINARY_SENSORS: tuple[AsekoBinarySensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AsekoConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Aseko Pool Live binary sensors."""
coordinator: AsekoDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
units = coordinator.data.values()
async_add_entities(
AsekoBinarySensorEntity(unit, coordinator, description)

View File

@@ -7,6 +7,7 @@ import logging
from aioaseko import Aseko, Unit
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
@@ -14,6 +15,8 @@ from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
type AsekoConfigEntry = ConfigEntry[AsekoDataUpdateCoordinator]
class AsekoDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Unit]]):
"""Class to manage fetching Aseko unit data from single endpoint."""

View File

@@ -13,14 +13,12 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfElectricPotential, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from .const import DOMAIN
from .coordinator import AsekoDataUpdateCoordinator
from .coordinator import AsekoConfigEntry
from .entity import AsekoEntity
@@ -80,11 +78,11 @@ SENSORS: list[AsekoSensorEntityDescription] = [
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AsekoConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Aseko Pool Live sensors."""
coordinator: AsekoDataUpdateCoordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
units = coordinator.data.values()
async_add_entities(
AsekoSensorEntity(unit, coordinator, description)

View File

@@ -7,7 +7,7 @@
},
"select": {
"pipeline": {
"name": "Assist pipeline",
"name": "Assistant",
"state": {
"preferred": "Preferred"
}

View File

@@ -41,10 +41,10 @@ _LOGGER = logging.getLogger(__name__)
class AssistSatelliteState(StrEnum):
"""Valid states of an Assist satellite entity."""
LISTENING_WAKE_WORD = "listening_wake_word"
"""Device is streaming audio for wake word detection to Home Assistant."""
IDLE = "idle"
"""Device is waiting for user input, such as a wake word or a button press."""
LISTENING_COMMAND = "listening_command"
LISTENING = "listening"
"""Device is streaming audio with the voice command to Home Assistant."""
PROCESSING = "processing"
@@ -117,7 +117,7 @@ class AssistSatelliteEntity(entity.Entity):
_attr_tts_options: dict[str, Any] | None = None
_pipeline_task: asyncio.Task | None = None
__assist_satellite_state = AssistSatelliteState.LISTENING_WAKE_WORD
__assist_satellite_state = AssistSatelliteState.IDLE
@final
@property
@@ -242,7 +242,7 @@ class AssistSatelliteEntity(entity.Entity):
)
finally:
self._is_announcing = False
self._set_state(AssistSatelliteState.LISTENING_WAKE_WORD)
self._set_state(AssistSatelliteState.IDLE)
async def async_announce(self, announcement: AssistSatelliteAnnouncement) -> None:
"""Announce media on the satellite.
@@ -363,9 +363,9 @@ class AssistSatelliteEntity(entity.Entity):
def _internal_on_pipeline_event(self, event: PipelineEvent) -> None:
"""Set state based on pipeline stage."""
if event.type is PipelineEventType.WAKE_WORD_START:
self._set_state(AssistSatelliteState.LISTENING_WAKE_WORD)
self._set_state(AssistSatelliteState.IDLE)
elif event.type is PipelineEventType.STT_START:
self._set_state(AssistSatelliteState.LISTENING_COMMAND)
self._set_state(AssistSatelliteState.LISTENING)
elif event.type is PipelineEventType.INTENT_START:
self._set_state(AssistSatelliteState.PROCESSING)
elif event.type is PipelineEventType.INTENT_END:
@@ -379,7 +379,7 @@ class AssistSatelliteEntity(entity.Entity):
self._set_state(AssistSatelliteState.RESPONDING)
elif event.type is PipelineEventType.RUN_END:
if not self._run_has_tts:
self._set_state(AssistSatelliteState.LISTENING_WAKE_WORD)
self._set_state(AssistSatelliteState.IDLE)
self.on_pipeline_event(event)
@@ -392,7 +392,7 @@ class AssistSatelliteEntity(entity.Entity):
@callback
def tts_response_finished(self) -> None:
"""Tell entity that the text-to-speech response has finished playing."""
self._set_state(AssistSatelliteState.LISTENING_WAKE_WORD)
self._set_state(AssistSatelliteState.IDLE)
@callback
def _resolve_pipeline(self) -> str | None:

View File

@@ -4,8 +4,8 @@
"_": {
"name": "Assist satellite",
"state": {
"listening_wake_word": "Wake word",
"listening_command": "Voice command",
"idle": "[%key:common::state::idle%]",
"listening": "Listening",
"responding": "Responding",
"processing": "Processing"
}

View File

@@ -1,61 +1,29 @@
"""The ATAG Integration."""
from asyncio import timeout
from datetime import timedelta
import logging
from pyatag import AtagException, AtagOne
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
_LOGGER = logging.getLogger(__name__)
from .coordinator import AtagConfigEntry, AtagDataUpdateCoordinator
DOMAIN = "atag"
PLATFORMS = [Platform.CLIMATE, Platform.SENSOR, Platform.WATER_HEATER]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: AtagConfigEntry) -> bool:
"""Set up Atag integration from a config entry."""
async def _async_update_data():
"""Update data via library."""
async with timeout(20):
try:
await atag.update()
except AtagException as err:
raise UpdateFailed(err) from err
return atag
atag = AtagOne(
session=async_get_clientsession(hass), **entry.data, device=entry.unique_id
)
coordinator = DataUpdateCoordinator[AtagOne](
hass,
_LOGGER,
name=DOMAIN.title(),
update_method=_async_update_data,
update_interval=timedelta(seconds=60),
)
coordinator = AtagDataUpdateCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
entry.runtime_data = coordinator
if entry.unique_id is None:
hass.config_entries.async_update_entry(entry, unique_id=atag.id)
hass.config_entries.async_update_entry(entry, unique_id=coordinator.atag.id)
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: AtagConfigEntry) -> bool:
"""Unload Atag config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -12,13 +12,12 @@ from homeassistant.components.climate import (
HVACAction,
HVACMode,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, Platform
from homeassistant.const import ATTR_TEMPERATURE
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.util.enum import try_parse_enum
from . import DOMAIN
from .coordinator import AtagConfigEntry, AtagDataUpdateCoordinator
from .entity import AtagEntity
PRESET_MAP = {
@@ -33,11 +32,10 @@ HVAC_MODES = [HVACMode.AUTO, HVACMode.HEAT]
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant, entry: AtagConfigEntry, async_add_entities: AddEntitiesCallback
) -> None:
"""Load a config entry."""
coordinator = hass.data[DOMAIN][entry.entry_id]
async_add_entities([AtagThermostat(coordinator, Platform.CLIMATE)])
async_add_entities([AtagThermostat(entry.runtime_data, "climate")])
class AtagThermostat(AtagEntity, ClimateEntity):
@@ -50,49 +48,49 @@ class AtagThermostat(AtagEntity, ClimateEntity):
)
_enable_turn_on_off_backwards_compatibility = False
def __init__(self, coordinator, atag_id):
def __init__(self, coordinator: AtagDataUpdateCoordinator, atag_id: str) -> None:
"""Initialize an Atag climate device."""
super().__init__(coordinator, atag_id)
self._attr_temperature_unit = coordinator.data.climate.temp_unit
self._attr_temperature_unit = coordinator.atag.climate.temp_unit
@property
def hvac_mode(self) -> HVACMode | None:
"""Return hvac operation ie. heat, cool mode."""
return try_parse_enum(HVACMode, self.coordinator.data.climate.hvac_mode)
return try_parse_enum(HVACMode, self.coordinator.atag.climate.hvac_mode)
@property
def hvac_action(self) -> HVACAction | None:
"""Return the current running hvac operation."""
is_active = self.coordinator.data.climate.status
is_active = self.coordinator.atag.climate.status
return HVACAction.HEATING if is_active else HVACAction.IDLE
@property
def current_temperature(self) -> float | None:
"""Return the current temperature."""
return self.coordinator.data.climate.temperature
return self.coordinator.atag.climate.temperature
@property
def target_temperature(self) -> float | None:
"""Return the temperature we try to reach."""
return self.coordinator.data.climate.target_temperature
return self.coordinator.atag.climate.target_temperature
@property
def preset_mode(self) -> str | None:
"""Return the current preset mode, e.g., auto, manual, fireplace, extend, etc."""
preset = self.coordinator.data.climate.preset_mode
preset = self.coordinator.atag.climate.preset_mode
return PRESET_INVERTED.get(preset)
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
await self.coordinator.data.climate.set_temp(kwargs.get(ATTR_TEMPERATURE))
await self.coordinator.atag.climate.set_temp(kwargs.get(ATTR_TEMPERATURE))
self.async_write_ha_state()
async def async_set_hvac_mode(self, hvac_mode: HVACMode) -> None:
"""Set new target hvac mode."""
await self.coordinator.data.climate.set_hvac_mode(hvac_mode)
await self.coordinator.atag.climate.set_hvac_mode(hvac_mode)
self.async_write_ha_state()
async def async_set_preset_mode(self, preset_mode: str) -> None:
"""Set new preset mode."""
await self.coordinator.data.climate.set_preset_mode(PRESET_MAP[preset_mode])
await self.coordinator.atag.climate.set_preset_mode(PRESET_MAP[preset_mode])
self.async_write_ha_state()

View File

@@ -0,0 +1,41 @@
"""The ATAG Integration."""
from asyncio import timeout
from datetime import timedelta
import logging
from pyatag import AtagException, AtagOne
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
_LOGGER = logging.getLogger(__name__)
type AtagConfigEntry = ConfigEntry[AtagDataUpdateCoordinator]
class AtagDataUpdateCoordinator(DataUpdateCoordinator[None]):
"""Atag data update coordinator."""
def __init__(self, hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Initialize Atag coordinator."""
super().__init__(
hass,
_LOGGER,
name="Atag",
update_interval=timedelta(seconds=60),
)
self.atag = AtagOne(
session=async_get_clientsession(hass), **entry.data, device=entry.unique_id
)
async def _async_update_data(self) -> None:
"""Update data via library."""
async with timeout(20):
try:
await self.atag.update()
except AtagException as err:
raise UpdateFailed(err) from err

View File

@@ -1,36 +1,30 @@
"""The ATAG Integration."""
from pyatag import AtagOne
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import (
CoordinatorEntity,
DataUpdateCoordinator,
)
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import DOMAIN
from .coordinator import AtagDataUpdateCoordinator
class AtagEntity(CoordinatorEntity[DataUpdateCoordinator[AtagOne]]):
class AtagEntity(CoordinatorEntity[AtagDataUpdateCoordinator]):
"""Defines a base Atag entity."""
def __init__(
self, coordinator: DataUpdateCoordinator[AtagOne], atag_id: str
) -> None:
def __init__(self, coordinator: AtagDataUpdateCoordinator, atag_id: str) -> None:
"""Initialize the Atag entity."""
super().__init__(coordinator)
self._id = atag_id
self._attr_name = DOMAIN.title()
self._attr_unique_id = f"{coordinator.data.id}-{atag_id}"
self._attr_unique_id = f"{coordinator.atag.id}-{atag_id}"
@property
def device_info(self) -> DeviceInfo:
"""Return info for device registry."""
return DeviceInfo(
identifiers={(DOMAIN, self.coordinator.data.id)},
identifiers={(DOMAIN, self.coordinator.atag.id)},
manufacturer="Atag",
model="Atag One",
name="Atag Thermostat",
sw_version=self.coordinator.data.apiversion,
sw_version=self.coordinator.atag.apiversion,
)

View File

@@ -1,7 +1,6 @@
"""Initialization of ATAG One sensor platform."""
from homeassistant.components.sensor import SensorDeviceClass, SensorEntity
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
PERCENTAGE,
UnitOfPressure,
@@ -11,7 +10,7 @@ from homeassistant.const import (
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import DOMAIN
from .coordinator import AtagConfigEntry, AtagDataUpdateCoordinator
from .entity import AtagEntity
SENSORS = {
@@ -28,43 +27,43 @@ SENSORS = {
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AtagConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Initialize sensor platform from config entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
async_add_entities([AtagSensor(coordinator, sensor) for sensor in SENSORS])
class AtagSensor(AtagEntity, SensorEntity):
"""Representation of a AtagOne Sensor."""
def __init__(self, coordinator, sensor):
def __init__(self, coordinator: AtagDataUpdateCoordinator, sensor: str) -> None:
"""Initialize Atag sensor."""
super().__init__(coordinator, SENSORS[sensor])
self._attr_name = sensor
if coordinator.data.report[self._id].sensorclass in (
if coordinator.atag.report[self._id].sensorclass in (
SensorDeviceClass.PRESSURE,
SensorDeviceClass.TEMPERATURE,
):
self._attr_device_class = coordinator.data.report[self._id].sensorclass
if coordinator.data.report[self._id].measure in (
self._attr_device_class = coordinator.atag.report[self._id].sensorclass
if coordinator.atag.report[self._id].measure in (
UnitOfPressure.BAR,
UnitOfTemperature.CELSIUS,
UnitOfTemperature.FAHRENHEIT,
PERCENTAGE,
UnitOfTime.HOURS,
):
self._attr_native_unit_of_measurement = coordinator.data.report[
self._attr_native_unit_of_measurement = coordinator.atag.report[
self._id
].measure
@property
def native_value(self):
"""Return the state of the sensor."""
return self.coordinator.data.report[self._id].state
return self.coordinator.atag.report[self._id].state
@property
def icon(self):
"""Return icon."""
return self.coordinator.data.report[self._id].icon
return self.coordinator.atag.report[self._id].icon

View File

@@ -7,12 +7,11 @@ from homeassistant.components.water_heater import (
STATE_PERFORMANCE,
WaterHeaterEntity,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_TEMPERATURE, STATE_OFF, Platform, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddEntitiesCallback
from . import DOMAIN
from .coordinator import AtagConfigEntry
from .entity import AtagEntity
OPERATION_LIST = [STATE_OFF, STATE_ECO, STATE_PERFORMANCE]
@@ -20,12 +19,13 @@ OPERATION_LIST = [STATE_OFF, STATE_ECO, STATE_PERFORMANCE]
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AtagConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Initialize DHW device from config entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
async_add_entities([AtagWaterHeater(coordinator, Platform.WATER_HEATER)])
async_add_entities(
[AtagWaterHeater(config_entry.runtime_data, Platform.WATER_HEATER)]
)
class AtagWaterHeater(AtagEntity, WaterHeaterEntity):
@@ -37,30 +37,30 @@ class AtagWaterHeater(AtagEntity, WaterHeaterEntity):
@property
def current_temperature(self):
"""Return the current temperature."""
return self.coordinator.data.dhw.temperature
return self.coordinator.atag.dhw.temperature
@property
def current_operation(self):
"""Return current operation."""
operation = self.coordinator.data.dhw.current_operation
operation = self.coordinator.atag.dhw.current_operation
return operation if operation in self.operation_list else STATE_OFF
async def async_set_temperature(self, **kwargs: Any) -> None:
"""Set new target temperature."""
if await self.coordinator.data.dhw.set_temp(kwargs.get(ATTR_TEMPERATURE)):
if await self.coordinator.atag.dhw.set_temp(kwargs.get(ATTR_TEMPERATURE)):
self.async_write_ha_state()
@property
def target_temperature(self):
"""Return the setpoint if water demand, otherwise return base temp (comfort level)."""
return self.coordinator.data.dhw.target_temperature
return self.coordinator.atag.dhw.target_temperature
@property
def max_temp(self) -> float:
"""Return the maximum temperature."""
return self.coordinator.data.dhw.max_temp
return self.coordinator.atag.dhw.max_temp
@property
def min_temp(self) -> float:
"""Return the minimum temperature."""
return self.coordinator.data.dhw.min_temp
return self.coordinator.atag.dhw.min_temp

View File

@@ -16,6 +16,10 @@
"hostname": "connect",
"macaddress": "2C9FFB*"
},
{
"hostname": "connect",
"macaddress": "789C85*"
},
{
"hostname": "august*",
"macaddress": "E076D0*"

View File

@@ -10,21 +10,15 @@
# and add the following to the end of script/bootstrap:
# sudo chmod 777 /dev/ttyUSB0
import logging
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ADDRESS, CONF_PORT, Platform
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import AuroraAbbDataUpdateCoordinator
from .coordinator import AuroraAbbConfigEntry, AuroraAbbDataUpdateCoordinator
PLATFORMS = [Platform.SENSOR]
_LOGGER = logging.getLogger(__name__)
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(hass: HomeAssistant, entry: AuroraAbbConfigEntry) -> bool:
"""Set up Aurora ABB PowerOne from a config entry."""
comport = entry.data[CONF_PORT]
@@ -32,19 +26,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
coordinator = AuroraAbbDataUpdateCoordinator(hass, comport, address)
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = coordinator
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: AuroraAbbConfigEntry) -> bool:
"""Unload a config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
# It should not be necessary to close the serial port because we close
# it after every use in sensor.py, i.e. no need to do entry["client"].close()
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -6,6 +6,7 @@ from time import sleep
from aurorapy.client import AuroraError, AuroraSerialClient, AuroraTimeoutError
from serial import SerialException
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
@@ -14,6 +15,9 @@ from .const import DOMAIN, SCAN_INTERVAL
_LOGGER = logging.getLogger(__name__)
type AuroraAbbConfigEntry = ConfigEntry[AuroraAbbDataUpdateCoordinator]
class AuroraAbbDataUpdateCoordinator(DataUpdateCoordinator[dict[str, float]]):
"""Class to manage fetching AuroraAbbPowerone data."""

View File

@@ -14,7 +14,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_SERIAL_NUMBER,
EntityCategory,
@@ -31,7 +30,6 @@ from homeassistant.helpers.entity_platform import AddEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import AuroraAbbDataUpdateCoordinator
from .const import (
ATTR_DEVICE_NAME,
ATTR_FIRMWARE,
@@ -40,6 +38,7 @@ from .const import (
DOMAIN,
MANUFACTURER,
)
from .coordinator import AuroraAbbConfigEntry, AuroraAbbDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
ALARM_STATES = list(AuroraMapping.ALARM_STATES.values())
@@ -130,12 +129,12 @@ SENSOR_TYPES = [
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AuroraAbbConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up aurora_abb_powerone sensor based on a config entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
data = config_entry.data
entities = [AuroraSensor(coordinator, data, sens) for sens in SENSOR_TYPES]

View File

@@ -2,28 +2,27 @@
from __future__ import annotations
from datetime import timedelta
import logging
from aiohttp import ClientError
from aussiebb.asyncio import AussieBB
from aussiebb.const import FETCH_TYPES
from aussiebb.exceptions import AuthenticationException, UnrecognisedServiceType
from aussiebb.exceptions import AuthenticationException
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DEFAULT_UPDATE_INTERVAL, DOMAIN, SERVICE_ID
from .coordinator import (
AussieBroadbandConfigEntry,
AussieBroadbandDataUpdateCoordinator,
)
_LOGGER = logging.getLogger(__name__)
PLATFORMS = [Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, entry: AussieBroadbandConfigEntry
) -> bool:
"""Set up Aussie Broadband from a config entry."""
# Login to the Aussie Broadband API and retrieve the current service list
client = AussieBB(
@@ -43,41 +42,22 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except ClientError as exc:
raise ConfigEntryNotReady from exc
# Create an appropriate refresh function
def update_data_factory(service_id):
async def async_update_data():
try:
return await client.get_usage(service_id)
except UnrecognisedServiceType as err:
raise UpdateFailed(f"Service {service_id} was unrecognised") from err
return async_update_data
# Initiate a Data Update Coordinator for each service
for service in services:
service["coordinator"] = DataUpdateCoordinator(
hass,
_LOGGER,
name=service["service_id"],
update_interval=timedelta(minutes=DEFAULT_UPDATE_INTERVAL),
update_method=update_data_factory(service[SERVICE_ID]),
service["coordinator"] = AussieBroadbandDataUpdateCoordinator(
hass, client, service["service_id"]
)
await service["coordinator"].async_config_entry_first_refresh()
# Setup the integration
hass.data.setdefault(DOMAIN, {})[entry.entry_id] = {
"client": client,
"services": services,
}
entry.runtime_data = services
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, entry: AussieBroadbandConfigEntry
) -> bool:
"""Unload the config entry."""
unload_ok = await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
if unload_ok:
hass.data[DOMAIN].pop(entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -1,6 +1,8 @@
"""Constants for the Aussie Broadband integration."""
from typing import Final
DEFAULT_UPDATE_INTERVAL = 30
DOMAIN = "aussie_broadband"
SERVICE_ID = "service_id"
SERVICE_ID: Final = "service_id"
CONF_SERVICES = "services"

View File

@@ -0,0 +1,53 @@
"""Coordinator for the Aussie Broadband integration."""
from __future__ import annotations
from datetime import timedelta
import logging
from typing import Any, TypedDict
from aussiebb.asyncio import AussieBB
from aussiebb.exceptions import UnrecognisedServiceType
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DEFAULT_UPDATE_INTERVAL
_LOGGER = logging.getLogger(__name__)
class AussieBroadbandServiceData(TypedDict, total=False):
"""Aussie Broadband service information, extended with the coordinator."""
coordinator: AussieBroadbandDataUpdateCoordinator
description: str
name: str
service_id: str
type: str
type AussieBroadbandConfigEntry = ConfigEntry[list[AussieBroadbandServiceData]]
class AussieBroadbandDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
"""Aussie Broadand data update coordinator."""
def __init__(self, hass: HomeAssistant, client: AussieBB, service_id: str) -> None:
"""Initialize Atag coordinator."""
super().__init__(
hass,
_LOGGER,
name=f"Aussie Broadband {service_id}",
update_interval=timedelta(minutes=DEFAULT_UPDATE_INTERVAL),
)
self._client = client
self._service_id = service_id
async def _async_update_data(self) -> dict[str, Any]:
"""Update data via library."""
try:
return await self._client.get_usage(self._service_id)
except UnrecognisedServiceType as err:
raise UpdateFailed(f"Service {self._service_id} was unrecognised") from err

View File

@@ -5,16 +5,15 @@ from __future__ import annotations
from typing import Any
from homeassistant.components.diagnostics import async_redact_data
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from .const import DOMAIN
from .coordinator import AussieBroadbandConfigEntry
TO_REDACT = ["address", "ipAddresses", "description", "discounts", "coordinator"]
async def async_get_config_entry_diagnostics(
hass: HomeAssistant, config_entry: ConfigEntry
hass: HomeAssistant, config_entry: AussieBroadbandConfigEntry
) -> dict[str, Any]:
"""Return diagnostics for a config entry."""
return {
@@ -23,6 +22,6 @@ async def async_get_config_entry_diagnostics(
"service": async_redact_data(service, TO_REDACT),
"usage": async_redact_data(service["coordinator"].data, ["historical"]),
}
for service in hass.data[DOMAIN][config_entry.entry_id]["services"]
for service in config_entry.runtime_data
]
}

View File

@@ -5,7 +5,7 @@ from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
import re
from typing import Any, cast
from typing import cast
from homeassistant.components.sensor import (
SensorDeviceClass,
@@ -13,7 +13,6 @@ from homeassistant.components.sensor import (
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import UnitOfInformation, UnitOfTime
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceEntryType, DeviceInfo
@@ -22,6 +21,11 @@ from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from .const import DOMAIN, SERVICE_ID
from .coordinator import (
AussieBroadbandConfigEntry,
AussieBroadbandDataUpdateCoordinator,
AussieBroadbandServiceData,
)
@dataclass(frozen=True)
@@ -117,28 +121,34 @@ SENSOR_DESCRIPTIONS: tuple[SensorValueEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant, entry: ConfigEntry, async_add_entities: AddEntitiesCallback
hass: HomeAssistant,
entry: AussieBroadbandConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up the Aussie Broadband sensor platform from a config entry."""
async_add_entities(
[
AussieBroadandSensorEntity(service, description)
for service in hass.data[DOMAIN][entry.entry_id]["services"]
for service in entry.runtime_data
for description in SENSOR_DESCRIPTIONS
if description.key in service["coordinator"].data
]
)
class AussieBroadandSensorEntity(CoordinatorEntity, SensorEntity):
class AussieBroadandSensorEntity(
CoordinatorEntity[AussieBroadbandDataUpdateCoordinator], SensorEntity
):
"""Base class for Aussie Broadband metric sensors."""
_attr_has_entity_name = True
entity_description: SensorValueEntityDescription
def __init__(
self, service: dict[str, Any], description: SensorValueEntityDescription
self,
service: AussieBroadbandServiceData,
description: SensorValueEntityDescription,
) -> None:
"""Initialize the sensor."""
super().__init__(service["coordinator"])

View File

@@ -2,14 +2,13 @@
from __future__ import annotations
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
from .coordinator import (
AwairCloudDataUpdateCoordinator,
AwairConfigEntry,
AwairDataUpdateCoordinator,
AwairLocalDataUpdateCoordinator,
)
@@ -17,7 +16,9 @@ from .coordinator import (
PLATFORMS = [Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, config_entry: AwairConfigEntry
) -> bool:
"""Set up Awair integration from a config entry."""
session = async_get_clientsession(hass)
@@ -33,28 +34,21 @@ async def async_setup_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> b
await coordinator.async_config_entry_first_refresh()
hass.data.setdefault(DOMAIN, {})
hass.data[DOMAIN][config_entry.entry_id] = coordinator
config_entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(config_entry, PLATFORMS)
return True
async def _async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
async def _async_update_listener(hass: HomeAssistant, entry: AwairConfigEntry) -> None:
"""Handle options update."""
coordinator: AwairLocalDataUpdateCoordinator = hass.data[DOMAIN][entry.entry_id]
if entry.title != coordinator.title:
if entry.title != entry.runtime_data.title:
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, config_entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, config_entry: AwairConfigEntry
) -> bool:
"""Unload Awair configuration."""
unload_ok = await hass.config_entries.async_unload_platforms(
config_entry, PLATFORMS
)
if unload_ok:
hass.data[DOMAIN].pop(config_entry.entry_id)
return unload_ok
return await hass.config_entries.async_unload_platforms(config_entry, PLATFORMS)

View File

@@ -26,6 +26,8 @@ from .const import (
UPDATE_INTERVAL_LOCAL,
)
type AwairConfigEntry = ConfigEntry[AwairDataUpdateCoordinator]
@dataclass
class AwairResult:

View File

@@ -46,7 +46,7 @@ from .const import (
ATTRIBUTION,
DOMAIN,
)
from .coordinator import AwairDataUpdateCoordinator, AwairResult
from .coordinator import AwairConfigEntry, AwairDataUpdateCoordinator
DUST_ALIASES = [API_PM25, API_PM10]
@@ -132,15 +132,14 @@ SENSOR_TYPES_DUST: tuple[AwairSensorEntityDescription, ...] = (
async def async_setup_entry(
hass: HomeAssistant,
config_entry: ConfigEntry,
config_entry: AwairConfigEntry,
async_add_entities: AddEntitiesCallback,
) -> None:
"""Set up Awair sensor entity based on a config entry."""
coordinator = hass.data[DOMAIN][config_entry.entry_id]
coordinator = config_entry.runtime_data
entities = []
data: list[AwairResult] = coordinator.data.values()
for result in data:
for result in coordinator.data.values():
if result.air_data:
entities.append(AwairSensor(result.device, coordinator, SENSOR_TYPE_SCORE))
device_sensors = result.air_data.sensors.keys()

View File

@@ -16,19 +16,18 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import MATCH_ALL
from homeassistant.core import Event, HomeAssistant, State
from homeassistant.exceptions import ConfigEntryError
from homeassistant.helpers.entityfilter import FILTER_SCHEMA
from homeassistant.helpers.entityfilter import FILTER_SCHEMA, EntityFilter
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.json import ExtendedJSONEncoder
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.dt import utcnow
from homeassistant.util.hass_dict import HassKey
from .client import AzureDataExplorerClient
from .const import (
CONF_APP_REG_SECRET,
CONF_FILTER,
CONF_SEND_INTERVAL,
DATA_FILTER,
DATA_HUB,
DEFAULT_MAX_DELAY,
DOMAIN,
FILTER_STATES,
@@ -46,6 +45,7 @@ CONFIG_SCHEMA = vol.Schema(
},
extra=vol.ALLOW_EXTRA,
)
DATA_COMPONENT: HassKey[EntityFilter] = HassKey(DOMAIN)
# fixtures for both init and config flow tests
@@ -63,10 +63,10 @@ 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.
"""
hass.data.setdefault(DOMAIN, {DATA_FILTER: FILTER_SCHEMA({})})
if DOMAIN in yaml_config:
hass.data[DOMAIN][DATA_FILTER] = yaml_config[DOMAIN].pop(CONF_FILTER)
hass.data[DATA_COMPONENT] = yaml_config[DOMAIN].pop(CONF_FILTER)
else:
hass.data[DATA_COMPONENT] = FILTER_SCHEMA({})
return True
@@ -83,15 +83,13 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
except KustoAuthenticationError:
return False
hass.data[DOMAIN][DATA_HUB] = adx
entry.async_on_unload(adx.async_stop)
await adx.async_start()
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
adx = hass.data[DOMAIN].pop(DATA_HUB)
await adx.async_stop()
return True
@@ -107,7 +105,7 @@ class AzureDataExplorer:
self.hass = hass
self._entry = entry
self._entities_filter = hass.data[DOMAIN][DATA_FILTER]
self._entities_filter = hass.data[DATA_COMPONENT]
self._client = AzureDataExplorerClient(entry.data)

View File

@@ -16,9 +16,8 @@ CONF_APP_REG_SECRET = "client_secret"
CONF_AUTHORITY_ID = "authority_id"
CONF_SEND_INTERVAL = "send_interval"
CONF_MAX_DELAY = "max_delay"
CONF_FILTER = DATA_FILTER = "filter"
CONF_FILTER = "filter"
CONF_USE_QUEUED_CLIENT = "use_queued_ingestion"
DATA_HUB = "hub"
STEP_USER = "user"

View File

@@ -7,6 +7,7 @@ from collections.abc import Callable
from datetime import datetime
import json
import logging
from types import MappingProxyType
from typing import Any
from azure.eventhub import EventData, EventDataBatch
@@ -19,11 +20,12 @@ from homeassistant.const import MATCH_ALL
from homeassistant.core import Event, HomeAssistant, State
from homeassistant.exceptions import ConfigEntryNotReady
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.entityfilter import FILTER_SCHEMA
from homeassistant.helpers.entityfilter import FILTER_SCHEMA, EntityFilter
from homeassistant.helpers.event import async_call_later
from homeassistant.helpers.json import JSONEncoder
from homeassistant.helpers.typing import ConfigType
from homeassistant.util.dt import utcnow
from homeassistant.util.hass_dict import HassKey
from .client import AzureEventHubClient
from .const import (
@@ -35,13 +37,13 @@ from .const import (
CONF_FILTER,
CONF_MAX_DELAY,
CONF_SEND_INTERVAL,
DATA_FILTER,
DATA_HUB,
DEFAULT_MAX_DELAY,
DOMAIN,
FILTER_STATES,
)
type AzureEventHubConfigEntry = ConfigEntry[AzureEventHub]
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = vol.Schema(
@@ -61,6 +63,7 @@ CONFIG_SCHEMA = vol.Schema(
},
extra=vol.ALLOW_EXTRA,
)
DATA_COMPONENT: HassKey[EntityFilter] = HassKey(DOMAIN)
async def async_setup(hass: HomeAssistant, yaml_config: ConfigType) -> bool:
@@ -71,10 +74,10 @@ async def async_setup(hass: HomeAssistant, yaml_config: ConfigType) -> bool:
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: FILTER_SCHEMA({})})
if DOMAIN not in yaml_config:
hass.data[DATA_COMPONENT] = FILTER_SCHEMA({})
return True
hass.data[DOMAIN][DATA_FILTER] = yaml_config[DOMAIN].pop(CONF_FILTER)
hass.data[DATA_COMPONENT] = yaml_config[DOMAIN].pop(CONF_FILTER)
if not yaml_config[DOMAIN]:
return True
@@ -92,33 +95,37 @@ async def async_setup(hass: HomeAssistant, yaml_config: ConfigType) -> bool:
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_setup_entry(
hass: HomeAssistant, entry: AzureEventHubConfigEntry
) -> bool:
"""Do the setup based on the config entry and the filter from yaml."""
hass.data.setdefault(DOMAIN, {DATA_FILTER: FILTER_SCHEMA({})})
hub = AzureEventHub(
hass,
entry,
hass.data[DOMAIN][DATA_FILTER],
hass.data[DATA_COMPONENT],
)
try:
await hub.async_test_connection()
except EventHubError as err:
raise ConfigEntryNotReady("Could not connect to Azure Event Hub") from err
hass.data[DOMAIN][DATA_HUB] = hub
entry.runtime_data = hub
entry.async_on_unload(hub.async_stop)
entry.async_on_unload(entry.add_update_listener(async_update_listener))
await hub.async_start()
return True
async def async_update_listener(hass: HomeAssistant, entry: ConfigEntry) -> None:
async def async_update_listener(
hass: HomeAssistant, entry: AzureEventHubConfigEntry
) -> None:
"""Update listener for options."""
hass.data[DOMAIN][DATA_HUB].update_options(entry.options)
entry.runtime_data.update_options(entry.options)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, entry: AzureEventHubConfigEntry
) -> bool:
"""Unload a config entry."""
hub = hass.data[DOMAIN].pop(DATA_HUB)
await hub.async_stop()
return True
@@ -129,7 +136,7 @@ class AzureEventHub:
self,
hass: HomeAssistant,
entry: ConfigEntry,
entities_filter: vol.Schema,
entities_filter: EntityFilter,
) -> None:
"""Initialize the listener."""
self.hass = hass
@@ -172,7 +179,7 @@ class AzureEventHub:
await self.async_send(None)
await self._queue.join()
def update_options(self, new_options: dict[str, Any]) -> None:
def update_options(self, new_options: MappingProxyType[str, Any]) -> None:
"""Update options."""
self._send_interval = new_options[CONF_SEND_INTERVAL]

View File

@@ -16,8 +16,7 @@ CONF_EVENT_HUB_SAS_KEY = "event_hub_sas_key"
CONF_EVENT_HUB_CON_STRING = "event_hub_connection_string"
CONF_SEND_INTERVAL = "send_interval"
CONF_MAX_DELAY = "max_delay"
CONF_FILTER = DATA_FILTER = "filter"
DATA_HUB = "hub"
CONF_FILTER = "filter"
STEP_USER = "user"
STEP_SAS = "sas"

View File

@@ -13,4 +13,5 @@ EXCLUDE_FROM_BACKUP = [
"*.log",
"backups/*.tar",
"OZW_Log.txt",
"tts/*",
]

View File

@@ -59,7 +59,7 @@
"pt-br": "Portugal",
"ru-ru": "Russia",
"sv-se": "Sweden",
"tr-tr": "Turkey"
"tr-tr": "Türkiye"
}
}
}

View File

@@ -965,46 +965,18 @@ async def async_service_temperature_set(
ATTR_TEMPERATURE in service_call.data
and not entity.supported_features & ClimateEntityFeature.TARGET_TEMPERATURE
):
# Warning implemented in 2024.10 and will be changed to raising
# a ServiceValidationError in 2025.4
report_issue = async_suggest_report_issue(
entity.hass,
integration_domain=entity.platform.platform_name,
module=type(entity).__module__,
)
_LOGGER.warning(
(
"%s::%s set_temperature action was used with temperature but the entity does not "
"implement the ClimateEntityFeature.TARGET_TEMPERATURE feature. "
"This will stop working in 2025.4 and raise an error instead. "
"Please %s"
),
entity.platform.platform_name,
entity.__class__.__name__,
report_issue,
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="missing_target_temperature_entity_feature",
)
if (
ATTR_TARGET_TEMP_LOW in service_call.data
and not entity.supported_features
& ClimateEntityFeature.TARGET_TEMPERATURE_RANGE
):
# Warning implemented in 2024.10 and will be changed to raising
# a ServiceValidationError in 2025.4
report_issue = async_suggest_report_issue(
entity.hass,
integration_domain=entity.platform.platform_name,
module=type(entity).__module__,
)
_LOGGER.warning(
(
"%s::%s set_temperature action was used with target_temp_low but the entity does not "
"implement the ClimateEntityFeature.TARGET_TEMPERATURE_RANGE feature. "
"This will stop working in 2025.4 and raise an error instead. "
"Please %s"
),
entity.platform.platform_name,
entity.__class__.__name__,
report_issue,
raise ServiceValidationError(
translation_domain=DOMAIN,
translation_key="missing_target_temperature_range_entity_feature",
)
hass = entity.hass

View File

@@ -275,6 +275,12 @@
},
"humidity_out_of_range": {
"message": "Provided humidity {humidity} is not valid. Accepted range is {min_humidity} to {max_humidity}."
},
"missing_target_temperature_entity_feature": {
"message": "Set temperature action was used with the target temperature parameter but the entity does not support it."
},
"missing_target_temperature_range_entity_feature": {
"message": "Set temperature action was used with the target temperature low/high parameter but the entity does not support it."
}
}
}

View File

@@ -6,10 +6,7 @@ from typing import Any
import uuid
from homeassistant.components.automation import DOMAIN as AUTOMATION_DOMAIN
from homeassistant.components.automation.config import (
PLATFORM_SCHEMA,
async_validate_config_item,
)
from homeassistant.components.automation.config import async_validate_config_item
from homeassistant.config import AUTOMATION_CONFIG_PATH
from homeassistant.const import CONF_ID, SERVICE_RELOAD
from homeassistant.core import HomeAssistant, callback
@@ -48,7 +45,6 @@ def async_setup(hass: HomeAssistant) -> bool:
"config",
AUTOMATION_CONFIG_PATH,
cv.string,
PLATFORM_SCHEMA,
post_write_hook=hook,
data_validator=async_validate_config_item,
)

View File

@@ -47,7 +47,7 @@ def async_setup(hass: HomeAssistant) -> bool:
"config",
SCENE_CONFIG_PATH,
cv.string,
PLATFORM_SCHEMA,
data_schema=PLATFORM_SCHEMA,
post_write_hook=hook,
)
)

View File

@@ -5,10 +5,7 @@ from __future__ import annotations
from typing import Any
from homeassistant.components.script import DOMAIN as SCRIPT_DOMAIN
from homeassistant.components.script.config import (
SCRIPT_ENTITY_SCHEMA,
async_validate_config_item,
)
from homeassistant.components.script.config import async_validate_config_item
from homeassistant.config import SCRIPT_CONFIG_PATH
from homeassistant.const import SERVICE_RELOAD
from homeassistant.core import HomeAssistant, callback
@@ -45,7 +42,6 @@ def async_setup(hass: HomeAssistant) -> bool:
"config",
SCRIPT_CONFIG_PATH,
cv.slug,
SCRIPT_ENTITY_SCHEMA,
post_write_hook=hook,
data_validator=async_validate_config_item,
)

View File

@@ -33,9 +33,9 @@ class BaseEditConfigView[_DataT: (dict[str, dict[str, Any]], list[dict[str, Any]
config_type: str,
path: str,
key_schema: Callable[[Any], str],
data_schema: Callable[[dict[str, Any]], Any],
*,
post_write_hook: Callable[[str, str], Coroutine[Any, Any, None]] | None = None,
data_schema: Callable[[dict[str, Any]], Any] | None = None,
data_validator: Callable[
[HomeAssistant, str, dict[str, Any]],
Coroutine[Any, Any, dict[str, Any] | None],
@@ -51,6 +51,12 @@ class BaseEditConfigView[_DataT: (dict[str, dict[str, Any]], list[dict[str, Any]
self.post_write_hook = post_write_hook
self.data_validator = data_validator
self.mutation_lock = asyncio.Lock()
if (self.data_schema is None and self.data_validator is None) or (
self.data_schema is not None and self.data_validator is not None
):
raise ValueError(
"Must specify exactly one of data_schema or data_validator"
)
def _empty_config(self) -> _DataT:
"""Empty config if file not found."""
@@ -112,7 +118,8 @@ class BaseEditConfigView[_DataT: (dict[str, dict[str, Any]], list[dict[str, Any]
if self.data_validator:
await self.data_validator(hass, config_key, data)
else:
self.data_schema(data)
# We either have a data_schema or a data_validator, ignore mypy
self.data_schema(data) # type: ignore[misc]
except (vol.Invalid, HomeAssistantError) as err:
return self.json_message(
f"Message malformed: {err}", HTTPStatus.BAD_REQUEST

View File

@@ -6,5 +6,5 @@
"integration_type": "service",
"iot_class": "local_push",
"quality_scale": "internal",
"requirements": ["debugpy==1.8.1"]
"requirements": ["debugpy==1.8.6"]
}

View File

@@ -7,7 +7,7 @@
"documentation": "https://www.home-assistant.io/integrations/doorbird",
"iot_class": "local_push",
"loggers": ["doorbirdpy"],
"requirements": ["DoorBirdPy==3.0.2"],
"requirements": ["DoorBirdPy==3.0.3"],
"zeroconf": [
{
"type": "_axis-video._tcp.local.",

View File

@@ -6,5 +6,6 @@
"dependencies": ["mqtt"],
"documentation": "https://www.home-assistant.io/integrations/dsmr_reader",
"iot_class": "local_push",
"mqtt": ["dsmr/#"]
"mqtt": ["dsmr/#"],
"quality_scale": "gold"
}

View File

@@ -12,6 +12,7 @@ from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_API_KEY, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryError
from homeassistant.helpers.httpx_client import get_async_client
from .const import CONF_MODEL
@@ -41,7 +42,10 @@ type EleventLabsConfigEntry = ConfigEntry[ElevenLabsData]
async def async_setup_entry(hass: HomeAssistant, entry: EleventLabsConfigEntry) -> bool:
"""Set up ElevenLabs text-to-speech from a config entry."""
entry.add_update_listener(update_listener)
client = AsyncElevenLabs(api_key=entry.data[CONF_API_KEY])
httpx_client = get_async_client(hass)
client = AsyncElevenLabs(
api_key=entry.data[CONF_API_KEY], httpx_client=httpx_client
)
model_id = entry.options[CONF_MODEL]
try:
model = await get_model_by_id(client, model_id)

View File

@@ -17,6 +17,8 @@ from homeassistant.config_entries import (
OptionsFlowWithConfigEntry,
)
from homeassistant.const import CONF_API_KEY
from homeassistant.core import HomeAssistant
from homeassistant.helpers.httpx_client import get_async_client
from homeassistant.helpers.selector import (
SelectOptionDict,
SelectSelector,
@@ -47,9 +49,12 @@ USER_STEP_SCHEMA = vol.Schema({vol.Required(CONF_API_KEY): str})
_LOGGER = logging.getLogger(__name__)
async def get_voices_models(api_key: str) -> tuple[dict[str, str], dict[str, str]]:
async def get_voices_models(
hass: HomeAssistant, api_key: str
) -> tuple[dict[str, str], dict[str, str]]:
"""Get available voices and models as dicts."""
client = AsyncElevenLabs(api_key=api_key)
httpx_client = get_async_client(hass)
client = AsyncElevenLabs(api_key=api_key, httpx_client=httpx_client)
voices = (await client.voices.get_all()).voices
models = await client.models.get_all()
voices_dict = {
@@ -77,7 +82,7 @@ class ElevenLabsConfigFlow(ConfigFlow, domain=DOMAIN):
errors: dict[str, str] = {}
if user_input is not None:
try:
voices, _ = await get_voices_models(user_input[CONF_API_KEY])
voices, _ = await get_voices_models(self.hass, user_input[CONF_API_KEY])
except ApiError:
errors["base"] = "invalid_api_key"
else:
@@ -116,7 +121,7 @@ class ElevenLabsOptionsFlow(OptionsFlowWithConfigEntry):
) -> ConfigFlowResult:
"""Manage the options."""
if not self.voices or not self.models:
self.voices, self.models = await get_voices_models(self.api_key)
self.voices, self.models = await get_voices_models(self.hass, self.api_key)
assert self.models and self.voices

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
import logging
from typing import Any
from typing import Any, Self
from elkm1_lib.discovery import ElkSystem
from elkm1_lib.elk import Elk
@@ -132,6 +132,8 @@ class Elkm1ConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
host: str | None = None
def __init__(self) -> None:
"""Initialize the elkm1 config flow."""
self._discovered_device: ElkSystem | None = None
@@ -176,10 +178,9 @@ class Elkm1ConfigFlow(ConfigFlow, domain=DOMAIN):
if async_update_entry_from_discovery(self.hass, entry, device):
self.hass.config_entries.async_schedule_reload(entry.entry_id)
return self.async_abort(reason="already_configured")
self.context[CONF_HOST] = host
for progress in self._async_in_progress():
if progress.get("context", {}).get(CONF_HOST) == host:
return self.async_abort(reason="already_in_progress")
self.host = host
if self.hass.config_entries.flow.async_has_matching_flow(self):
return self.async_abort(reason="already_in_progress")
# Handled ignored case since _async_current_entries
# is called with include_ignore=False
self._abort_if_unique_id_configured()
@@ -190,6 +191,10 @@ class Elkm1ConfigFlow(ConfigFlow, domain=DOMAIN):
return self.async_abort(reason="cannot_connect")
return await self.async_step_discovery_confirm()
def is_matching(self, other_flow: Self) -> bool:
"""Return True if other_flow is matching this flow."""
return other_flow.host == self.host
async def async_step_discovery_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:

View File

@@ -5,7 +5,7 @@ from __future__ import annotations
from collections.abc import Mapping
import logging
from types import MappingProxyType
from typing import Any
from typing import TYPE_CHECKING, Any
from awesomeversion import AwesomeVersion
from pyenphase import AUTH_TOKEN_MIN_VERSION, Envoy, EnvoyError
@@ -311,6 +311,9 @@ class EnvoyOptionsFlowHandler(OptionsFlowWithConfigEntry):
if user_input is not None:
return self.async_create_entry(title="", data=user_input)
if TYPE_CHECKING:
assert self.config_entry.unique_id is not None
return self.async_show_form(
step_id="init",
data_schema=vol.Schema(
@@ -326,6 +329,6 @@ class EnvoyOptionsFlowHandler(OptionsFlowWithConfigEntry):
),
description_placeholders={
CONF_SERIAL: self.config_entry.unique_id,
CONF_HOST: self.config_entry.data.get("host"),
CONF_HOST: self.config_entry.data[CONF_HOST],
},
)

View File

@@ -133,7 +133,7 @@ class EsphomeAssistSatellite(
# Empty config. Updated when added to HA.
self._satellite_config = assist_satellite.AssistSatelliteConfiguration(
available_wake_words=[], active_wake_words=[], max_active_wake_words=0
available_wake_words=[], active_wake_words=[], max_active_wake_words=1
)
@property
@@ -179,7 +179,13 @@ class EsphomeAssistSatellite(
async def _update_satellite_config(self) -> None:
"""Get the latest satellite configuration from the device."""
config = await self.cli.get_voice_assistant_configuration(_CONFIG_TIMEOUT_SEC)
try:
config = await self.cli.get_voice_assistant_configuration(
_CONFIG_TIMEOUT_SEC
)
except TimeoutError:
# Placeholder config will be used
return
# Update available/active wake words
self._satellite_config.available_wake_words = [
@@ -315,6 +321,10 @@ class EsphomeAssistSatellite(
"code": event.data["code"],
"message": event.data["message"],
}
elif event_type == VoiceAssistantEventType.VOICE_ASSISTANT_RUN_END:
if self._tts_streaming_task is None:
# No TTS
self.entry_data.async_set_assist_pipeline_state(False)
self.cli.send_voice_assistant_event(event_type, data_to_send)
@@ -413,7 +423,6 @@ class EsphomeAssistSatellite(
# Run the pipeline
_LOGGER.debug("Running pipeline from %s to %s", start_stage, end_stage)
self.entry_data.async_set_assist_pipeline_state(True)
self._pipeline_task = self.config_entry.async_create_background_task(
self.hass,
self.async_accept_pipeline_from_satellite(
@@ -443,7 +452,6 @@ class EsphomeAssistSatellite(
def handle_pipeline_finished(self) -> None:
"""Handle when pipeline has finished running."""
self.entry_data.async_set_assist_pipeline_state(False)
self._stop_udp_server()
_LOGGER.debug("Pipeline finished")
@@ -561,6 +569,7 @@ class EsphomeAssistSatellite(
# State change
self.tts_response_finished()
self.entry_data.async_set_assist_pipeline_state(False)
async def _wrap_audio_stream(self) -> AsyncIterable[bytes]:
"""Yield audio chunks from the queue until None."""

View File

@@ -68,7 +68,7 @@ class EsphomeLock(EsphomeEntity[LockInfo, LockEntityState], LockEntity):
@convert_api_error_ha_error
async def async_unlock(self, **kwargs: Any) -> None:
"""Unlock the lock."""
code = kwargs.get(ATTR_CODE, None)
code = kwargs.get(ATTR_CODE)
self._client.lock_command(self._key, LockCommand.UNLOCK, code)
@convert_api_error_ha_error

View File

@@ -59,6 +59,11 @@
}
},
"entity": {
"assist_satellite": {
"assist_satellite": {
"name": "[%key:component::assist_satellite::entity_component::_::name%]"
}
},
"binary_sensor": {
"assist_in_progress": {
"name": "[%key:component::assist_pipeline::entity::binary_sensor::assist_in_progress::name%]"

View File

@@ -19,6 +19,7 @@ from .coordinator import FeedReaderCoordinator
LOGGER = logging.getLogger(__name__)
ATTR_CONTENT = "content"
ATTR_DESCRIPTION = "description"
ATTR_LINK = "link"
ATTR_TITLE = "title"
@@ -40,7 +41,9 @@ class FeedReaderEvent(CoordinatorEntity[FeedReaderCoordinator], EventEntity):
_attr_event_types = [EVENT_FEEDREADER]
_attr_name = None
_attr_has_entity_name = True
_unrecorded_attributes = frozenset({ATTR_CONTENT, ATTR_TITLE, ATTR_LINK})
_unrecorded_attributes = frozenset(
{ATTR_CONTENT, ATTR_DESCRIPTION, ATTR_TITLE, ATTR_LINK}
)
coordinator: FeedReaderCoordinator
def __init__(self, coordinator: FeedReaderCoordinator) -> None:
@@ -80,6 +83,7 @@ class FeedReaderEvent(CoordinatorEntity[FeedReaderCoordinator], EventEntity):
self._trigger_event(
EVENT_FEEDREADER,
{
ATTR_DESCRIPTION: feed_data.get("description"),
ATTR_TITLE: feed_data.get("title"),
ATTR_LINK: feed_data.get("link"),
ATTR_CONTENT: content,

View File

@@ -241,11 +241,14 @@ class FibaroController:
platform = Platform.LOCK
elif device.has_central_scene_event:
platform = Platform.EVENT
elif device.value.has_value:
if device.value.is_bool_value:
platform = Platform.BINARY_SENSOR
else:
platform = Platform.SENSOR
elif device.value.has_value and device.value.is_bool_value:
platform = Platform.BINARY_SENSOR
elif (
device.value.has_value
or "power" in device.properties
or "energy" in device.properties
):
platform = Platform.SENSOR
# Switches that control lights should show up as lights
if platform == Platform.SWITCH and device.properties.get("isLight", False):

View File

@@ -112,6 +112,11 @@ async def async_setup_entry(
entities: list[SensorEntity] = [
FibaroSensor(device, MAIN_SENSOR_TYPES.get(device.type))
for device in controller.fibaro_devices[Platform.SENSOR]
# Some sensor devices do not have a value but report power or energy.
# These sensors are added to the sensor list but need to be excluded
# here as the FibaroSensor expects a value. One example is the
# Qubino 3 phase power meter.
if device.value.has_value
]
entities.extend(

View File

@@ -3,11 +3,18 @@
from __future__ import annotations
from collections.abc import AsyncIterator
from contextlib import asynccontextmanager
from contextlib import asynccontextmanager, contextmanager
from datetime import timedelta
import logging
from fjaraskupan import Device, State
from fjaraskupan import (
Device,
FjaraskupanConnectionError,
FjaraskupanError,
FjaraskupanReadError,
FjaraskupanWriteError,
State,
)
from homeassistant.components.bluetooth import (
BluetoothServiceInfoBleak,
@@ -19,9 +26,37 @@ from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
@contextmanager
def exception_converter():
"""Convert exception so home assistant translated ones."""
try:
yield
except FjaraskupanWriteError as exception:
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="write_error"
) from exception
except FjaraskupanReadError as exception:
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="read_error"
) from exception
except FjaraskupanConnectionError as exception:
raise HomeAssistantError(
translation_domain=DOMAIN, translation_key="connection_error"
) from exception
except FjaraskupanError as exception:
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="unexpected_error",
translation_placeholders={"msg": str(exception)},
) from exception
class UnableToConnect(HomeAssistantError):
"""Exception to indicate that we cannot connect to device."""
@@ -71,8 +106,11 @@ class FjaraskupanCoordinator(DataUpdateCoordinator[State]):
)
) is None:
raise UpdateFailed("No connectable path to device")
async with self.device.connect(ble_device) as device:
await device.update()
with exception_converter():
async with self.device.connect(ble_device) as device:
await device.update()
return self.device.state
def detection_callback(self, service_info: BluetoothServiceInfoBleak) -> None:
@@ -90,7 +128,8 @@ class FjaraskupanCoordinator(DataUpdateCoordinator[State]):
) is None:
raise UnableToConnect("No connectable path to device")
async with self.device.connect(ble_device) as device:
yield device
with exception_converter():
async with self.device.connect(ble_device) as device:
yield device
self.async_set_updated_data(self.device.state)

View File

@@ -24,5 +24,19 @@
"name": "Periodic venting"
}
}
},
"exceptions": {
"write_error": {
"message": "Failed to write data to device"
},
"read_error": {
"message": "Failed to read data from device"
},
"connection_error": {
"message": "Failed to connect to device"
},
"unexpected_error": {
"message": "Unexpected error occurred: {msg}"
}
}
}

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
import contextlib
from typing import Any, cast
from typing import Any, Self, cast
from flux_led.const import (
ATTR_ID,
@@ -61,6 +61,8 @@ class FluxLedConfigFlow(ConfigFlow, domain=DOMAIN):
VERSION = 1
host: str | None = None
def __init__(self) -> None:
"""Initialize the config flow."""
self._discovered_devices: dict[str, FluxLEDDiscovery] = {}
@@ -149,10 +151,9 @@ class FluxLedConfigFlow(ConfigFlow, domain=DOMAIN):
assert device is not None
await self._async_set_discovered_mac(device, self._allow_update_mac)
host = device[ATTR_IPADDR]
self.context[CONF_HOST] = host
for progress in self._async_in_progress():
if progress.get("context", {}).get(CONF_HOST) == host:
return self.async_abort(reason="already_in_progress")
self.host = host
if self.hass.config_entries.flow.async_has_matching_flow(self):
return self.async_abort(reason="already_in_progress")
if not device[ATTR_MODEL_DESCRIPTION]:
mac_address = device[ATTR_ID]
assert mac_address is not None
@@ -173,6 +174,10 @@ class FluxLedConfigFlow(ConfigFlow, domain=DOMAIN):
await self._async_set_discovered_mac(device, True)
return await self.async_step_discovery_confirm()
def is_matching(self, other_flow: Self) -> bool:
"""Return True if other_flow is matching this flow."""
return other_flow.host == self.host
async def async_step_discovery_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:

View File

@@ -6,7 +6,7 @@ from collections.abc import Mapping
import ipaddress
import logging
import socket
from typing import Any
from typing import Any, Self
from urllib.parse import ParseResult, urlparse
from fritzconnection import FritzConnection
@@ -155,7 +155,6 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
discovery_info.upnp.get(ssdp.ATTR_UPNP_FRIENDLY_NAME)
or discovery_info.upnp[ssdp.ATTR_UPNP_MODEL_NAME]
)
self.context[CONF_HOST] = self._host
if not self._host or ipaddress.ip_address(self._host).is_link_local:
return self.async_abort(reason="ignore_ip6_link_local")
@@ -166,9 +165,8 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(uuid)
self._abort_if_unique_id_configured({CONF_HOST: self._host})
for progress in self._async_in_progress():
if progress.get("context", {}).get(CONF_HOST) == self._host:
return self.async_abort(reason="already_in_progress")
if self.hass.config_entries.flow.async_has_matching_flow(self):
return self.async_abort(reason="already_in_progress")
if entry := await self.async_check_configured_entry():
if uuid and not entry.unique_id:
@@ -184,6 +182,10 @@ class FritzBoxToolsFlowHandler(ConfigFlow, domain=DOMAIN):
return await self.async_step_confirm()
def is_matching(self, other_flow: Self) -> bool:
"""Return True if other_flow is matching this flow."""
return other_flow._host == self._host # noqa: SLF001
async def async_step_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:

View File

@@ -33,6 +33,7 @@ from .const import (
from .coordinator import FritzboxConfigEntry, FritzboxDataUpdateCoordinator
from .entity import FritzBoxDeviceEntity
from .model import ClimateExtraAttributes
from .sensor import value_scheduled_preset
HVAC_MODES = [HVACMode.HEAT, HVACMode.OFF]
PRESET_HOLIDAY = "holiday"
@@ -177,7 +178,11 @@ class FritzboxThermostat(FritzBoxDeviceEntity, ClimateEntity):
if hvac_mode == HVACMode.OFF:
await self.async_set_temperature(temperature=OFF_REPORT_SET_TEMPERATURE)
else:
await self.async_set_temperature(temperature=self.data.comfort_temperature)
if value_scheduled_preset(self.data) == PRESET_ECO:
target_temp = self.data.eco_temperature
else:
target_temp = self.data.comfort_temperature
await self.async_set_temperature(temperature=target_temp)
@property
def preset_mode(self) -> str | None:

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
from collections.abc import Mapping
import ipaddress
from typing import Any
from typing import Any, Self
from urllib.parse import urlparse
from pyfritzhome import Fritzhome, LoginError
@@ -122,7 +122,6 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a flow initialized by discovery."""
host = urlparse(discovery_info.ssdp_location).hostname
assert isinstance(host, str)
self.context[CONF_HOST] = host
if (
ipaddress.ip_address(host).version == 6
@@ -136,9 +135,9 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN):
await self.async_set_unique_id(uuid)
self._abort_if_unique_id_configured({CONF_HOST: host})
for progress in self._async_in_progress():
if progress.get("context", {}).get(CONF_HOST) == host:
return self.async_abort(reason="already_in_progress")
self._host = host
if self.hass.config_entries.flow.async_has_matching_flow(self):
return self.async_abort(reason="already_in_progress")
# update old and user-configured config entries
for entry in self._async_current_entries(include_ignore=False):
@@ -147,12 +146,15 @@ class FritzboxConfigFlow(ConfigFlow, domain=DOMAIN):
self.hass.config_entries.async_update_entry(entry, unique_id=uuid)
return self.async_abort(reason="already_configured")
self._host = host
self._name = str(discovery_info.upnp.get(ssdp.ATTR_UPNP_FRIENDLY_NAME) or host)
self.context["title_placeholders"] = {"name": self._name}
return await self.async_step_confirm()
def is_matching(self, other_flow: Self) -> bool:
"""Return True if other_flow is matching this flow."""
return other_flow._host == self._host # noqa: SLF001
async def async_step_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:

View File

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

View File

@@ -155,8 +155,22 @@ type GeniusHubConfigEntry = ConfigEntry[GeniusBroker]
async def async_setup_entry(hass: HomeAssistant, entry: GeniusHubConfigEntry) -> bool:
"""Create a Genius Hub system."""
if CONF_TOKEN in entry.data and CONF_MAC in entry.data:
entity_registry = er.async_get(hass)
registry_entries = er.async_entries_for_config_entry(
entity_registry, entry.entry_id
)
for reg_entry in registry_entries:
if reg_entry.unique_id.startswith(entry.data[CONF_MAC]):
entity_registry.async_update_entity(
reg_entry.entity_id,
new_unique_id=reg_entry.unique_id.replace(
entry.data[CONF_MAC], entry.entry_id
),
)
session = async_get_clientsession(hass)
unique_id: str
if CONF_HOST in entry.data:
client = GeniusHub(
entry.data[CONF_HOST],
@@ -164,14 +178,12 @@ async def async_setup_entry(hass: HomeAssistant, entry: GeniusHubConfigEntry) ->
password=entry.data[CONF_PASSWORD],
session=session,
)
unique_id = entry.data[CONF_MAC]
else:
client = GeniusHub(entry.data[CONF_TOKEN], session=session)
unique_id = entry.entry_id
unique_id = entry.unique_id or entry.entry_id
broker = entry.runtime_data = GeniusBroker(
hass, client, entry.data.get(CONF_MAC, unique_id)
)
broker = entry.runtime_data = GeniusBroker(hass, client, unique_id)
try:
await client.update()

View File

@@ -57,44 +57,17 @@ class GeofencyEntity(TrackerEntity, RestoreEntity):
def __init__(self, device, gps=None, location_name=None, attributes=None):
"""Set up Geofency entity."""
self._attributes = attributes or {}
self._attr_extra_state_attributes = attributes or {}
self._name = device
self._location_name = location_name
self._gps = gps
self._attr_location_name = location_name
if gps:
self._attr_latitude = gps[0]
self._attr_longitude = gps[1]
self._unsub_dispatcher = None
self._unique_id = device
@property
def extra_state_attributes(self):
"""Return device specific attributes."""
return self._attributes
@property
def latitude(self):
"""Return latitude value of the device."""
return self._gps[0]
@property
def longitude(self):
"""Return longitude value of the device."""
return self._gps[1]
@property
def location_name(self):
"""Return a location name for the current location of the device."""
return self._location_name
@property
def unique_id(self):
"""Return the unique ID."""
return self._unique_id
@property
def device_info(self) -> DeviceInfo:
"""Return the device info."""
return DeviceInfo(
identifiers={(GF_DOMAIN, self._unique_id)},
name=self._name,
self._attr_unique_id = device
self._attr_device_info = DeviceInfo(
identifiers={(GF_DOMAIN, device)},
name=device,
)
async def async_added_to_hass(self) -> None:
@@ -104,21 +77,23 @@ class GeofencyEntity(TrackerEntity, RestoreEntity):
self.hass, TRACKER_UPDATE, self._async_receive_data
)
if self._attributes:
if self._attr_extra_state_attributes:
return
if (state := await self.async_get_last_state()) is None:
self._gps = (None, None)
self._attr_latitude = None
self._attr_longitude = None
return
attr = state.attributes
self._gps = (attr.get(ATTR_LATITUDE), attr.get(ATTR_LONGITUDE))
self._attr_latitude = attr.get(ATTR_LATITUDE)
self._attr_longitude = attr.get(ATTR_LONGITUDE)
async def async_will_remove_from_hass(self) -> None:
"""Clean up after entity before removal."""
await super().async_will_remove_from_hass()
self._unsub_dispatcher()
self.hass.data[GF_DOMAIN]["devices"].remove(self._unique_id)
self.hass.data[GF_DOMAIN]["devices"].remove(self.unique_id)
@callback
def _async_receive_data(self, device, gps, location_name, attributes):
@@ -126,7 +101,8 @@ class GeofencyEntity(TrackerEntity, RestoreEntity):
if device != self._name:
return
self._attributes.update(attributes)
self._location_name = location_name
self._gps = gps
self._attr_extra_state_attributes.update(attributes)
self._attr_location_name = location_name
self._attr_latitude = gps[0]
self._attr_longitude = gps[1]
self.async_write_ha_state()

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
import dataclasses
import re
from typing import Any
from typing import Any, Self
from ismartgate.common import AbstractInfoResponse, ApiError
from ismartgate.const import GogoGate2ApiErrorCode, ISmartGateApiErrorCode
@@ -57,19 +57,21 @@ class Gogogate2FlowHandler(ConfigFlow, domain=DOMAIN):
async def _async_discovery_handler(self, ip_address: str) -> ConfigFlowResult:
"""Start the user flow from any discovery."""
self.context[CONF_IP_ADDRESS] = ip_address
self._abort_if_unique_id_configured({CONF_IP_ADDRESS: ip_address})
self._async_abort_entries_match({CONF_IP_ADDRESS: ip_address})
self._ip_address = ip_address
for progress in self._async_in_progress():
if progress.get("context", {}).get(CONF_IP_ADDRESS) == self._ip_address:
raise AbortFlow("already_in_progress")
if self.hass.config_entries.flow.async_has_matching_flow(self):
raise AbortFlow("already_in_progress")
self._device_type = DEVICE_TYPE_ISMARTGATE
return await self.async_step_user()
def is_matching(self, other_flow: Self) -> bool:
"""Return True if other_flow is matching this flow."""
return other_flow._ip_address == self._ip_address # noqa: SLF001
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:

View File

@@ -7,5 +7,5 @@
"documentation": "https://www.home-assistant.io/integrations/calendar.google",
"iot_class": "cloud_polling",
"loggers": ["googleapiclient"],
"requirements": ["gcal-sync==6.1.4", "oauth2client==4.1.3", "ical==8.1.1"]
"requirements": ["gcal-sync==6.1.5", "oauth2client==4.1.3", "ical==8.2.0"]
}

View File

@@ -1,5 +1,7 @@
"""Media source for Google Photos."""
from __future__ import annotations
from dataclasses import dataclass
from enum import StrEnum
import logging
@@ -46,7 +48,7 @@ class PhotosIdentifierType(StrEnum):
ALBUM = "a"
@classmethod
def of(cls, name: str) -> "PhotosIdentifierType":
def of(cls, name: str) -> PhotosIdentifierType:
"""Parse a PhotosIdentifierType by string value."""
for enum in PhotosIdentifierType:
if enum.value == name:

View File

@@ -71,52 +71,25 @@ class GPSLoggerEntity(TrackerEntity, RestoreEntity):
def __init__(self, device, location, battery, accuracy, attributes):
"""Set up GPSLogger entity."""
self._accuracy = accuracy
self._attributes = attributes
self._attr_location_accuracy = accuracy
self._attr_extra_state_attributes = attributes
self._name = device
self._battery = battery
self._location = location
if location:
self._attr_latitude = location[0]
self._attr_longitude = location[1]
self._unsub_dispatcher = None
self._unique_id = device
self._attr_unique_id = device
self._attr_device_info = DeviceInfo(
identifiers={(GPL_DOMAIN, device)},
name=device,
)
@property
def battery_level(self):
"""Return battery value of the device."""
return self._battery
@property
def extra_state_attributes(self):
"""Return device specific attributes."""
return self._attributes
@property
def latitude(self):
"""Return latitude value of the device."""
return self._location[0]
@property
def longitude(self):
"""Return longitude value of the device."""
return self._location[1]
@property
def location_accuracy(self):
"""Return the gps accuracy of the device."""
return self._accuracy
@property
def unique_id(self):
"""Return the unique ID."""
return self._unique_id
@property
def device_info(self) -> DeviceInfo:
"""Return the device info."""
return DeviceInfo(
identifiers={(GPL_DOMAIN, self._unique_id)},
name=self._name,
)
async def async_added_to_hass(self) -> None:
"""Register state update callback."""
await super().async_added_to_hass()
@@ -125,13 +98,14 @@ class GPSLoggerEntity(TrackerEntity, RestoreEntity):
)
# don't restore if we got created with data
if self._location is not None:
if self.latitude is not None:
return
if (state := await self.async_get_last_state()) is None:
self._location = (None, None)
self._accuracy = None
self._attributes = {
self._attr_latitude = None
self._attr_longitude = None
self._attr_location_accuracy = 0
self._attr_extra_state_attributes = {
ATTR_ALTITUDE: None,
ATTR_ACTIVITY: None,
ATTR_DIRECTION: None,
@@ -142,9 +116,10 @@ class GPSLoggerEntity(TrackerEntity, RestoreEntity):
return
attr = state.attributes
self._location = (attr.get(ATTR_LATITUDE), attr.get(ATTR_LONGITUDE))
self._accuracy = attr.get(ATTR_GPS_ACCURACY)
self._attributes = {
self._attr_latitude = attr.get(ATTR_LATITUDE)
self._attr_longitude = attr.get(ATTR_LONGITUDE)
self._attr_location_accuracy = attr.get(ATTR_GPS_ACCURACY, 0)
self._attr_extra_state_attributes = {
ATTR_ALTITUDE: attr.get(ATTR_ALTITUDE),
ATTR_ACTIVITY: attr.get(ATTR_ACTIVITY),
ATTR_DIRECTION: attr.get(ATTR_DIRECTION),
@@ -164,8 +139,9 @@ class GPSLoggerEntity(TrackerEntity, RestoreEntity):
if device != self._name:
return
self._location = location
self._attr_latitude = location[0]
self._attr_longitude = location[1]
self._battery = battery
self._accuracy = accuracy
self._attributes.update(attributes)
self._attr_location_accuracy = accuracy
self._attr_extra_state_attributes.update(attributes)
self.async_write_ha_state()

View File

@@ -31,6 +31,7 @@ from homeassistant.components.light import (
ColorMode,
LightEntity,
LightEntityFeature,
LightState,
filter_supported_color_modes,
)
from homeassistant.config_entries import ConfigEntry
@@ -42,7 +43,6 @@ from homeassistant.const import (
CONF_UNIQUE_ID,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_ON,
STATE_UNAVAILABLE,
STATE_UNKNOWN,
)
@@ -207,7 +207,7 @@ class LightGroup(GroupEntity, LightEntity):
for entity_id in self._entity_ids
if (state := self.hass.states.get(entity_id)) is not None
]
on_states = [state for state in states if state.state == STATE_ON]
on_states = [state for state in states if state.state == LightState.ON]
valid_state = self.mode(
state.state not in (STATE_UNKNOWN, STATE_UNAVAILABLE) for state in states
@@ -218,7 +218,9 @@ class LightGroup(GroupEntity, LightEntity):
self._attr_is_on = None
else:
# Set as ON if any / all member is ON
self._attr_is_on = self.mode(state.state == STATE_ON for state in states)
self._attr_is_on = self.mode(
state.state == LightState.ON for state in states
)
self._attr_available = any(state.state != STATE_UNAVAILABLE for state in states)
self._attr_brightness = reduce_attribute(on_states, ATTR_BRIGHTNESS)

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/hassio",
"iot_class": "local_polling",
"quality_scale": "internal",
"requirements": ["aiohasupervisor==0.1.0b1"]
"requirements": ["aiohasupervisor==0.1.0"]
}

View File

@@ -127,5 +127,5 @@ class HiveSensorEntity(HiveEntity, SensorEntity):
await self.hive.session.updateData(self.device)
self.device = await self.hive.sensor.getSensor(self.device)
self._attr_native_value = self.entity_description.fn(
self.device["status"]["state"]
self.device.get("status", {}).get("state")
)

View File

@@ -22,16 +22,12 @@ from homeassistant.components.light import (
ATTR_WHITE,
DOMAIN as LIGHT_DOMAIN,
ColorMode,
LightState,
brightness_supported,
color_supported,
color_temp_supported,
)
from homeassistant.const import (
ATTR_ENTITY_ID,
SERVICE_TURN_OFF,
SERVICE_TURN_ON,
STATE_ON,
)
from homeassistant.const import ATTR_ENTITY_ID, SERVICE_TURN_OFF, SERVICE_TURN_ON
from homeassistant.core import CALLBACK_TYPE, State, callback
from homeassistant.helpers.event import async_call_later
from homeassistant.util.color import (
@@ -244,7 +240,7 @@ class Light(HomeAccessory):
state = new_state.state
attributes = new_state.attributes
color_mode = attributes.get(ATTR_COLOR_MODE)
self.char_on.set_value(int(state == STATE_ON))
self.char_on.set_value(int(state == LightState.ON))
color_mode_changed = self._previous_color_mode != color_mode
self._previous_color_mode = color_mode
@@ -265,7 +261,7 @@ class Light(HomeAccessory):
# Therefore, if the brightness is 0 and the device is still on,
# the brightness is mapped to 1 otherwise the update is ignored in
# order to avoid this incorrect behavior.
if brightness == 0 and state == STATE_ON:
if brightness == 0 and state == LightState.ON:
brightness = 1
self.char_brightness.set_value(brightness)
if color_mode_changed:

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
import logging
import re
from typing import TYPE_CHECKING, Any, cast
from typing import TYPE_CHECKING, Any, Self, cast
import aiohomekit
from aiohomekit import Controller, const as aiohomekit_const
@@ -111,6 +111,8 @@ class HomekitControllerFlowHandler(ConfigFlow, domain=DOMAIN):
self.devices: dict[str, AbstractDiscovery] = {}
self.controller: Controller | None = None
self.finish_pairing: FinishPairing | None = None
self.pairing = False
self._device_paired = False
async def _async_setup_controller(self) -> None:
"""Create the controller."""
@@ -300,18 +302,10 @@ class HomekitControllerFlowHandler(ConfigFlow, domain=DOMAIN):
# Set unique-id and error out if it's already configured
self._abort_if_unique_id_configured(updates=updated_ip_port)
for progress in self._async_in_progress(include_uninitialized=True):
context = progress["context"]
if context.get("unique_id") == normalized_hkid and not context.get(
"pairing"
):
if paired:
# If the device gets paired, we want to dismiss
# an existing discovery since we can no longer
# pair with it
self.hass.config_entries.flow.async_abort(progress["flow_id"])
else:
raise AbortFlow("already_in_progress")
self.hkid = normalized_hkid
self._device_paired = paired
if self.hass.config_entries.flow.async_has_matching_flow(self):
raise AbortFlow("already_in_progress")
if paired:
# Device is paired but not to us - ignore it
@@ -332,13 +326,24 @@ class HomekitControllerFlowHandler(ConfigFlow, domain=DOMAIN):
self.name = name
self.model = model
self.category = Categories(int(properties.get("ci", 0)))
self.hkid = normalized_hkid
# We want to show the pairing form - but don't call async_step_pair
# directly as it has side effects (will ask the device to show a
# pairing code)
return self._async_step_pair_show_form()
def is_matching(self, other_flow: Self) -> bool:
"""Return True if other_flow is matching this flow."""
if other_flow.context.get("unique_id") == self.hkid and not other_flow.pairing:
if self._device_paired:
# If the device gets paired, we want to dismiss
# an existing discovery since we can no longer
# pair with it
self.hass.config_entries.flow.async_abort(other_flow.flow_id)
else:
return True
return False
async def async_step_bluetooth(
self, discovery_info: bluetooth.BluetoothServiceInfoBleak
) -> ConfigFlowResult:
@@ -419,7 +424,7 @@ class HomekitControllerFlowHandler(ConfigFlow, domain=DOMAIN):
assert self.controller
if pair_info and self.finish_pairing:
self.context["pairing"] = True
self.pairing = True
code = pair_info["pairing_code"]
try:
code = ensure_pin_format(

View File

@@ -1,5 +1,7 @@
"""Config flow for the html5 component."""
from __future__ import annotations
import binascii
from typing import Any, cast
@@ -42,7 +44,7 @@ class HTML5ConfigFlow(ConfigFlow, domain=DOMAIN):
@callback
def _async_create_html5_entry(
self: "HTML5ConfigFlow", data: dict[str, str]
self: HTML5ConfigFlow, data: dict[str, str]
) -> tuple[dict[str, str], ConfigFlowResult | None]:
"""Create an HTML5 entry."""
errors = {}
@@ -68,7 +70,7 @@ class HTML5ConfigFlow(ConfigFlow, domain=DOMAIN):
return errors, flow_result
async def async_step_user(
self: "HTML5ConfigFlow", user_input: dict[str, Any] | None = None
self: HTML5ConfigFlow, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle a flow initialized by the user."""
errors: dict[str, str] = {}
@@ -92,7 +94,7 @@ class HTML5ConfigFlow(ConfigFlow, domain=DOMAIN):
)
async def async_step_import(
self: "HTML5ConfigFlow", import_config: dict
self: HTML5ConfigFlow, import_config: dict
) -> ConfigFlowResult:
"""Handle config import from yaml."""
_, flow_result = self._async_create_html5_entry(import_config)

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