Compare commits

...

287 Commits

Author SHA1 Message Date
Abílio Costa
a109f2dcdf Update homeassistant/components/websocket_api/commands.py
Co-authored-by: Artur Pragacz <49985303+arturpragacz@users.noreply.github.com>
2025-09-15 14:20:21 +01:00
Abílio Costa
b61b9bb606 Merge branch 'dev' into target_websocket_api 2025-09-08 16:11:44 +01:00
epenet
56c865dcfe Fix _is_valid_suggested_unit in sensor platform (#151912) 2025-09-08 16:35:33 +02:00
karwosts
b7360dfad8 Allow deleting kitchen_sink devices (#151826) 2025-09-08 15:01:08 +01:00
dependabot[bot]
f2204e97ab Bump github/codeql-action from 3.30.0 to 3.30.1 (#151890)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-08 14:49:05 +02:00
Artur Pragacz
98df5f5f0c Validate selectors in the condition helper (#151884) 2025-09-08 14:20:11 +02:00
Simone Chemelli
064d43480d Bump aiovodafone to 1.2.1 (#151901) 2025-09-08 12:58:26 +02:00
Jan Bouwhuis
1536375e82 Fix update of the entity ID does not clean up an old restored state (#151696)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-09-08 12:48:23 +02:00
Avi Miller
39e9ffff29 Bump aiolifx-themes to 1.0.2 to support newer LIFX devices (#151898)
Signed-off-by: Avi Miller <me@dje.li>
2025-09-08 13:45:42 +03:00
puddly
12f152d6e4 Home Assistant Connect ZBT-2 integration (#151015)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-09-08 11:18:31 +02:00
Jan Bouwhuis
c7f0560208 Deprecate object_id and instead suggest to use default_entity_id to set the suggested entity_id in MQTT entity configurations (#151775) 2025-09-08 09:22:12 +02:00
Denis Shulyaka
65603a3829 Add signature to ai_task generated images URL (#151882) 2025-09-07 16:58:51 -04:00
Robert Resch
38ea5c6813 Bump aioecowitt to 2025.9.1 (#151859) 2025-09-07 19:52:05 +02:00
Norbert Rittel
5b1fd8f58b Fix sentence-casing in volvooncall (#151863) 2025-09-07 19:51:16 +02:00
Maciej Bieniek
e5f99a617f Mark Tractive switches as unavailable when tacker is in the enegy saving zone (#151817) 2025-09-07 13:34:31 +02:00
Martins Sipenko
7f8b5f2288 Update pysmarty2 to 0.10.3 (#151855) 2025-09-07 09:36:45 +02:00
J. Nick Koston
75f69cd5b6 Bump aioharmony to 0.5.3 (#151853) 2025-09-06 23:55:27 -04:00
Paulus Schoutsen
d7fab27351 Remove myself as code owner of integrations (#151851) 2025-09-06 20:42:25 -05:00
David Knowles
6c6ec7534f Bump pydrawise to 2025.9.0 (#151842) 2025-09-06 23:07:56 +03:00
Norbert Rittel
8e3780264a Capitalize "AC" in nut (#151831) 2025-09-06 20:25:42 +02:00
jan iversen
78b009dd8f Allow delay > 1 in modbus. (#151832) 2025-09-06 21:21:12 +03:00
jan iversen
76d72ad280 max_temp / min_temp in modbus light could only be int, otherwise an assert was provoked. (#151833) 2025-09-06 21:20:49 +03:00
jan iversen
e5be9426a4 removed assert fron entity in modbus. (#151834) 2025-09-06 21:20:21 +03:00
Norbert Rittel
0922f12ec0 Use "credentials" only for username and password in overkiz (#151837) 2025-09-06 21:19:52 +03:00
Norbert Rittel
89f424e1d3 Fix sentence-casing of "Application credentials" in common strings (#151828) 2025-09-06 19:21:33 +02:00
Norbert Rittel
143eb20d99 Fix sentence-casing of two tesla_fleet user-facing strings (#151829) 2025-09-06 17:56:54 +02:00
Artur Pragacz
3187506eb9 Ignore incorrect themes (#151794) 2025-09-06 13:49:03 +02:00
Matthias Alphart
a328b23437 Fix KNX BinarySensor config_store data (#151808) 2025-09-06 13:02:35 +02:00
Norbert Rittel
7e6a949559 Fix exceptions of climate.set_temperature action to use friendly names (#151811) 2025-09-06 14:00:15 +03:00
J. Nick Koston
da7db5e22b Bump habluetooth to 5.3.1 (#151803) 2025-09-06 13:57:44 +03:00
Jan-Philipp Benecke
ec58943c8c Bump zeroconf to 0.147.2 (#151809) 2025-09-06 13:56:52 +03:00
Norbert Rittel
61a05490e9 Fix missing sentence-casing of "temperature" in bsblan (#151810) 2025-09-06 13:55:13 +03:00
Joakim Plate
6a1629d2ed Update philips_js to 3.2.4 (#151796) 2025-09-06 08:16:06 +02:00
Paulus Schoutsen
106e1ce224 Gen translations in script/bootstrap (#151806) 2025-09-06 07:22:47 +02:00
Abílio Costa
601d63e3b7 Add top-level target support to condition schema (#149634)
Co-authored-by: Artur Pragacz <49985303+arturpragacz@users.noreply.github.com>
2025-09-05 22:55:28 +01:00
Jan Bouwhuis
6a5f5b9adc Mock discovery in lifx sensor tests to avoid socket access in tests (#151787) 2025-09-05 21:46:07 +02:00
karwosts
8ecf5a98a5 Catch more invalid themes in validation (#151719) 2025-09-05 20:09:33 +02:00
Tsvi Mostovicz
1728c577f7 Revert "Jewish Calendar add coordinator " (#151780) 2025-09-05 17:47:29 +02:00
wollew
1006d5e0ba Use position percentage for closed status in Velux (#151679)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-05 16:45:29 +01:00
Artur Pragacz
6c29d5dc49 Add entity info to device database analytics (#151670) 2025-09-05 16:12:35 +02:00
Carlos Morán
a4e086f0d9 Add manual mode to the map of Overkiz to HVAC modes (#151438) 2025-09-05 15:43:54 +02:00
Marko Todorić
0fecf012e6 SFTP/SSH as remote Backup location (#135844)
Co-authored-by: Josef Zweck <josef@zweck.dev>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-09-05 15:43:26 +02:00
blotus
63c8bfaa9b Fix support for Ecowitt soil moisture sensors (#151685) 2025-09-05 14:40:14 +02:00
Mark Adkins
435926fd41 Update SharkIQ authentication method (#151046) 2025-09-05 13:14:28 +02:00
Ludovic BOUÉ
c621f0c139 Matter RVC ServiceArea EstimatedEndTime attribute (#151384) 2025-09-05 12:49:32 +02:00
Artur Pragacz
fa9007777d Add myself as codeowner to Voice components (#151764) 2025-09-05 12:45:17 +02:00
jan iversen
e1afadb28c Fix enable/disable entity in modbus (#151626) 2025-09-05 12:31:33 +02:00
tronikos
34c45eae56 Translate exceptions in Android TV Remote media player (#151744) 2025-09-05 12:29:06 +02:00
tronikos
71b8da6497 Limit the scope of try except blocks in Android TV Remote (#151746) 2025-09-05 12:28:46 +02:00
tronikos
caa0e357ee Improve Android TV Remote tests by testing we can recover from errors (#151752) 2025-09-05 12:26:31 +02:00
Imeon-Energy
0721ac6c73 Fix, entities stay unavailable after timeout error, Imeon inverter integration (#151671)
Co-authored-by: TheBushBoy <theodavid@icloud.com>
2025-09-05 12:11:44 +02:00
Marcel van der Veldt
783c742e09 Add support for migrated Hue bridge (#151411)
Co-authored-by: Joostlek <joostlek@outlook.com>
2025-09-05 12:09:37 +02:00
tronikos
d2324086af Remove unused class variables in Android TV Remote entity (#151743) 2025-09-05 12:04:14 +02:00
Marc Mueller
2ffd5f4c97 Update types packages (#151760) 2025-09-05 11:54:14 +02:00
Norbert Rittel
aa4a110923 Improve action descriptions in homematic (#151751) 2025-09-05 11:46:28 +02:00
Artur Pragacz
0a3032e766 Fix recognition of entity names in default agent with interpunction (#151759) 2025-09-05 11:42:56 +02:00
Richard Kroegel
c4db422355 Bump bimmer_connected to 0.17.3 (#151756) 2025-09-05 11:37:51 +02:00
Michael Hansen
f4e0b9ba15 Handle match failures in intent HTTP API (#151726) 2025-09-05 10:28:37 +02:00
dependabot[bot]
f3b997720d Bump actions/github-script from 7 to 8 (#151747)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 10:18:31 +02:00
dependabot[bot]
f5d3a89f90 Bump codecov/codecov-action from 5.5.0 to 5.5.1 (#151748)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-05 10:17:12 +02:00
Norbert Rittel
b90296d853 Remove trailing periods from title strings in sia (#151754) 2025-09-05 10:07:37 +02:00
tronikos
c6d6349908 Remove extra whitespace in Android TV Remote strings (#151741) 2025-09-05 09:25:00 +02:00
J. Nick Koston
2be6f17505 Bump aioesphomeapi to 40.0.1 (#151737) 2025-09-05 09:24:28 +02:00
epenet
4c953f36c8 Bump tuya-device-sharing-sdk to 0.2.4 (#151742) 2025-09-05 09:21:34 +02:00
Dan Raper
ec2fa202e9 Require OhmeAdvancedSettingsCoordinator to run regardless of entities (#151701) 2025-09-05 07:37:40 +02:00
G Johansson
1a970e6c88 Make sensor startup code more dry in System monitor (#151164) 2025-09-05 07:36:37 +02:00
Dan Raper
b9db828df3 Bump ohmepy version to 1.5.2 (#151707) 2025-09-05 04:23:57 +02:00
Daniel Hjelseth Høyer
50c0f41e8f Update Mill library 0.13.1 (#151712) 2025-09-05 04:23:08 +02:00
David Knowles
8cc66ee96c Bump pyschlage to 2025.9.0 (#151731) 2025-09-05 04:21:09 +02:00
Louis Christ
71981975a4 Update pyblu to 2.0.5 and fix code (#151728) 2025-09-04 23:34:59 +01:00
Manu
b875af9667 Bump pyotp to v2.9.0 (#151721) 2025-09-05 00:09:23 +02:00
Manu
89cd55c878 Bump habiticalib to v0.4.5 (#151720) 2025-09-04 22:58:28 +01:00
Daniel Hjelseth Høyer
b25708cec2 Update Tibber library 0.31.7 (#151711)
Signed-off-by: Daniel Hjelseth Høyer <github@dahoiv.net>
2025-09-05 00:21:26 +03:00
Franck Nijhof
bb9c65bc4b Update debugpy to v1.8.16 (#151716) 2025-09-05 00:17:58 +03:00
Franck Nijhof
447c7b64a9 Update cryptography to 45.0.7 (#151715) 2025-09-04 23:40:08 +03:00
Marc Mueller
b111a33b8c Update ciso8601 to 2.3.3 (#151704) 2025-09-04 13:54:49 -05:00
Marc Mueller
4fcd02bc5d Update requests to 2.32.5 (#151705) 2025-09-04 20:53:16 +02:00
Marc Mueller
80d26b8d2e Update pytest to 8.4.2 (#151706) 2025-09-04 20:52:48 +02:00
flonou
a475ecb342 Shelly cover position update when moving (#139008)
Co-authored-by: Shay Levy <levyshay1@gmail.com>
2025-09-04 20:22:07 +03:00
Simone Chemelli
42aec9cd91 Remove attributes from all Shelly entities (#140386) 2025-09-04 20:21:32 +03:00
karwosts
5409181b79 Add missing device trigger duration localizations (#151578)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-09-04 17:25:25 +02:00
Marc Mueller
c1945211fa [ci] Add timeout to install os dependencies step (#151682) 2025-09-04 17:20:17 +02:00
Abílio Costa
29537dc87d Add tests for hassfest conditions module (#151646)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-04 17:18:51 +02:00
Bram Kragten
72e1a8f912 Update frontend to 20250903.3 (#151694) 2025-09-04 17:04:49 +02:00
epenet
b742e4898c Ensure Tuya fixtures are correctly referenced (#151691) 2025-09-04 16:53:48 +02:00
Marcel van der Veldt
e5565c75f6 Bump aiohue to 4.7.5 (#151684) 2025-09-04 14:44:10 +02:00
epenet
a7ca618327 Check badly formatted dhcp addresses in tests (#147814) 2025-09-04 13:43:51 +01:00
epenet
6cbb881647 Drop Tuya compatibility code for mqtt (#151666) 2025-09-04 12:10:58 +02:00
Kevin McCormack
eae1fe4a56 Add strict typing, shared constants, and fix OPNsense name casing (#151599) 2025-09-04 11:20:16 +02:00
epenet
8d945d89de Bump tuya-device-sharing-sdk to 0.2.3 (#151659) 2025-09-04 10:37:25 +02:00
dependabot[bot]
86e7f3713f Bump actions/stale from 9.1.0 to 10.0.0 (#151660)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 10:11:50 +02:00
Pete Sage
3bc772a196 Fix Sonos Dialog Select type conversion (#151649) 2025-09-04 09:33:43 +02:00
Felipe Santos
c2290d6edb Fix WebSocket proxy for add-ons not forwarding ping/pong frame data (#151654) 2025-09-04 09:30:23 +02:00
dependabot[bot]
2a458dcec9 Bump actions/setup-python from 5.6.0 to 6.0.0 (#151662)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 09:22:28 +02:00
dependabot[bot]
ab5ef3674f Bump pypa/gh-action-pypi-publish from 1.12.4 to 1.13.0 (#151661)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-04 09:22:22 +02:00
Norbert Rittel
f28251bc76 Small fixes of user-facing strings in fritz (#151663) 2025-09-04 09:20:15 +02:00
Mike Kelly
1cca65b5c5 Add MCF (1000 Cubic Feet) as an alternate unit of measure for volume (#150015) 2025-09-04 09:29:37 +03:00
karwosts
ed134e22f9 Allow defining the start weekday for statistic_during_period (#149033) 2025-09-04 09:27:12 +03:00
Paulus Schoutsen
300c582ea0 Devcontainer fixes for Debian 13 (#151655) 2025-09-04 06:23:45 +02:00
Franck Nijhof
52f7e20b5c Merge branch 'master' into dev 2025-09-03 21:32:40 +00:00
epenet
813098cb1a Use correctly formatted MAC in esphome tests (#151622) 2025-09-03 15:07:03 -05:00
Manu
000df08bca Correct capitalization of "FRITZ!Box" in FRITZ!Box Tools integration (#151637) 2025-09-03 21:23:48 +02:00
J. Nick Koston
9b80cf7d94 Prevent multiple Home Assistant instances from running with the same config directory (#151631) 2025-09-03 13:13:02 -05:00
karwosts
3385151c26 Test for async_show_menu sort (#151630) 2025-09-03 18:46:57 +02:00
Michael Hansen
111fa78c57 Bump intents (#151627) 2025-09-03 18:11:37 +02:00
Erik Montnemery
e67df73c4e Clarify behavior of ConfigEntry.async_on_state_change (#151628) 2025-09-03 17:21:28 +02:00
Bram Kragten
b9f24bbb2a Update frontend to 20250903.2 (#151629) 2025-09-03 16:54:37 +02:00
karwosts
18ca9590f0 Sort template config menu step by user language (#151596) 2025-09-03 17:02:45 +03:00
Paulus Schoutsen
eccadd4a11 script/bootstrap to update core deps (#151624) 2025-09-03 09:58:29 -04:00
BenJewell
aeff62faea Correct critical notification variable name in Flo (#151523)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-09-03 14:15:13 +01:00
Norbert Rittel
e5a44e5966 Fix naming of "State of charge" sensor in growatt_server (#151619) 2025-09-03 15:13:04 +02:00
mattreim
1369a98fa3 Fix for deCONZ issue - Detected that integration 'deconz' calls device_registry.async_get_or_create referencing a non existing via_device - #134539 (#150355) 2025-09-03 14:28:52 +02:00
jan iversen
0e1dd04083 Simplify Modbus update methods (#151494) 2025-09-03 14:23:36 +02:00
G Johansson
df46816b2f Add reload support to schema options flow handler (#151260) 2025-09-03 12:55:21 +01:00
Jack
955ef3b5e7 Remove deprecated target position attributes from ZHA covers (#142534)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-09-03 13:43:29 +02:00
Bram Kragten
5fc6fb9cf3 Update frontend to 20250903.1 (#151617) 2025-09-03 13:31:33 +02:00
Erik Montnemery
9ee9e1775d Bump device registry version to 1.12 (#151616) 2025-09-03 13:12:49 +02:00
jan iversen
712c9b9edc Fix racing bug in slave entities in Modbus (#151522) 2025-09-03 13:09:42 +02:00
Erik Montnemery
d571857770 Handle colliding aliases for floors (#151614) 2025-09-03 12:43:03 +02:00
Bram Kragten
de90922297 Update frontend to 20250903.0 (#151612) 2025-09-03 12:42:27 +02:00
Erik Montnemery
e0b3a5337c Handle colliding aliases for areas (#151613) 2025-09-03 12:39:03 +02:00
Yevhenii Vaskivskyi
215603fae1 Bump asusrouter to 1.21.0 (#151607) 2025-09-03 12:16:00 +02:00
Krisjanis Lejejs
1a12c619e9 Bump hass-nabucasa from 1.0.0 to 1.1.0 (#151606) 2025-09-03 12:12:29 +02:00
yufeng
34c061df19 Add energy consumption/production for Tuya kg category (smart switches) (#149234) 2025-09-03 11:47:23 +02:00
yufeng
c12b638b3d Adds initial support for tuya category xnyjcn (solar inverter) (#151549)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2025-09-03 11:39:54 +02:00
Robert Resch
b9427deed2 Bump aioecowitt to 2025.9.0 (#151608) 2025-09-03 11:34:45 +02:00
yufeng
d66016588b Add support for new energy sensor entities for DLQ (circuit breaker) devices in the Tuya integration (#151551)
Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2025-09-03 11:34:08 +02:00
Stefan Agner
229d0bdc77 Update Home Assistant base image to 2025.09.0 (#151582) 2025-09-03 11:12:18 +02:00
Erik Montnemery
078425918e Improve migration to device registry version 1.10 (#151571) 2025-09-03 10:22:29 +02:00
Erik Montnemery
da2f154111 Improve migration to entity registry version 1.18 (#151570) 2025-09-03 10:08:59 +02:00
yufeng
270a9a5a98 Add support for new power sensor entities for ZNDB (smart energy meter) devices in the Tuya integration (#151554) 2025-09-03 09:22:20 +02:00
Lucas Mindêllo de Andrade
9f953c2e35 Tuya add missing sensors for Metering_3PN_ZB (dlq) device (#151601) 2025-09-03 09:13:24 +02:00
Artur Pragacz
8f16b09751 Accept None directly in the selector schemas (#151510)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-09-03 08:37:43 +02:00
dependabot[bot]
73ab041051 Bump github/codeql-action from 3.29.11 to 3.30.0 (#151600)
Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-09-03 08:10:07 +02:00
Paul Bottein
4bb76c6d94 Update frontend to 20250902.1 (#151593) 2025-09-02 22:55:03 -03:00
Brandon Rothweiler
7378d3607c Update py-aosmith to 1.0.14 (#151597) 2025-09-02 22:34:37 +01:00
Florian von Garrel
7d1e36af7f Raise paperless to platinum (#151588) 2025-09-02 22:27:56 +01:00
Petar Petrov
8b03a23ed8 Add option descriptions to Z-Wave reconfigure flow (#151558)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-09-02 22:06:07 +01:00
Michael Hansen
a023dfc013 Add required features to vacuum intents (#151581) 2025-09-02 14:10:31 -05:00
J. Nick Koston
fa0f707872 Add bluetooth websocket_api to subscribe to scanner state (#151452)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-02 15:17:27 -03:00
Joost Lekkerkerker
a8f56e4b96 Fix Slide local tests (#151569)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-09-02 18:21:38 +02:00
Marc Mueller
61c904d225 Update pytest-rerunfailures to 16.0.1 (#151573) 2025-09-02 17:11:15 +01:00
Thomas D
a8ff14ecb8 Bump volvocarsapi to v0.4.2 (#151579) 2025-09-02 17:09:21 +01:00
Michael Hansen
3909906823 Add required features for mowing intents (#151580) 2025-09-02 11:02:38 -05:00
Blear
e0bf7749e6 Adjust Zhong_Hong climate set_fan_mode to lowercase (#151559) 2025-09-02 17:00:05 +02:00
Paul Bottein
72128e9708 Add start mowing and dock intents for lawn mower (#140525) 2025-09-02 09:49:24 -05:00
cdnninja
1b9acdc233 Convert Vesync to 3.X version of library (#148239)
Co-authored-by: SapuSeven <sapuseven@gmail.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2025-09-02 15:31:38 +02:00
Maciej Bieniek
0f530485d1 Record current IQS for NextDNS (#146895)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-02 15:07:25 +02:00
Christopher Fenner
0928e9a6ee Add sensor for DHW storage temperature in ViCare integration (#151128) 2025-09-02 13:24:42 +02:00
Marc Mueller
75d792207a Add missing pychromecast imports (#151544) 2025-09-02 13:24:00 +02:00
yufeng
ceda62f6ea Add support for new energy sensor entities for TDQ (socket/outlet) devices in the Tuya integration (#151553) 2025-09-02 13:10:36 +02:00
Avi Miller
12ab84a5d9 Expose the transition field to the UI config of effect_colorloop (#151124)
Signed-off-by: Avi Miller <me@dje.li>
2025-09-02 13:07:48 +02:00
Simone Chemelli
8e85faf997 Update SamsungTV quality scale (#151552) 2025-09-02 13:06:33 +02:00
Simone Chemelli
b514a14c10 Remove config entry from device instead of deleting in Uptime robot (#151557) 2025-09-02 13:06:07 +02:00
Erik Montnemery
6b609b019e Exclude non mowers from husqvarna_automower_ble discovery (#151507) 2025-09-02 12:57:39 +02:00
Erik Montnemery
10baae92a0 Revert "Improve migration to device registry version 1.11" (#151563) 2025-09-02 11:58:27 +02:00
Erik Montnemery
8e1ee32190 Revert "Improve migration to entity registry version 1.18" (#151561) 2025-09-02 11:54:37 +02:00
Abílio Costa
814b98c2a3 Add tests for hassfest triggers module (#151318) 2025-09-02 10:45:55 +02:00
Abílio Costa
243569f6b8 Add back missing controller cleanup to Govee Light Local (#151541) 2025-09-02 10:37:43 +02:00
Abílio Costa
4b7817f1df Filter out IPv6 addresses in Govee Light Local (#151540) 2025-09-02 10:27:38 +02:00
Antoni Czaplicki
180f898bfa Remove the vulcan integration (#151504) 2025-09-02 01:10:07 +02:00
Mathis Dirksen-Thedens
e9dcde1bb5 Allow overriding default recipient in Signal messenger (#145654)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-09-01 23:20:26 +01:00
Bram Kragten
f44b6a3a39 Update frontend to 20250901.0 (#151529) 2025-09-01 23:37:49 +02:00
Lukas
9b6b8003ec Remove mac address from Pooldose device (#151536)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2025-09-01 23:19:24 +02:00
Jan-Philipp Benecke
2503157282 Deprecate LANnouncer integration (#151531) 2025-09-01 22:31:25 +02:00
Sab44
7b2b3e9e33 Add Libre Hardware Monitor integration (#140449) 2025-09-01 22:12:12 +02:00
Abílio Costa
2d5f228308 Use MockConfigEntry.start_reauth_flow in Roborock's tests (#151528) 2025-09-01 21:28:42 +02:00
Nolan Stover
19f36fc630 Bump zabbix-utils to 2.0.3 (#149450)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-09-01 20:07:11 +01:00
Andrea Turri
6b6553dae3 Miele time sensors 2/3 - Provide consistent behavior with appliance status (#146053)
Co-authored-by: Robert Resch <robert@resch.dev>
2025-09-01 20:20:24 +02:00
Robert Resch
0865d3f749 Add support for stream orientation in go2rtc (#148832) 2025-09-01 19:06:01 +01:00
Ravaka Razafimanantsoa
095f73d84f Add Switchbot Cloud AC Off (#138648)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-09-01 19:52:43 +02:00
Andrea Turri
3b60961f02 Miele refrigerators cause index out of range errors when offline (#151299) 2025-09-01 19:28:08 +02:00
Yevhenii Vaskivskyi
0d9079ea72 Add model_id and serial_number to the device description (asuswrt) (#151516) 2025-09-01 18:08:53 +02:00
Andrew Jackson
f17db80428 Bump aiomealie to 0.10.2 (#151514) 2025-09-01 18:07:36 +02:00
Imeon-Energy
2d4b2e822a Fix typo in const.py for Imeon inverter integration (#151515)
Co-authored-by: TheBushBoy <theodavid@icloud.com>
2025-09-01 17:54:16 +02:00
Yuxin Wang
b08a72a53d Move APC UPS Daemon integration to platinum (#151335) 2025-09-01 17:26:31 +02:00
dontinelli
1e4fa40a77 Extend effect of invert_position to cover status for slide_local (#150418) 2025-09-01 17:25:59 +02:00
Jan Bouwhuis
ac0ff96f26 Sort MQTT test cases for subentry config flow (#151426) 2025-09-01 17:23:27 +02:00
Jan Bouwhuis
2e50cee555 Sort globals and helpers in MQTT config flow (#151419) 2025-09-01 17:22:58 +02:00
Artur Pragacz
51c6c1b0d2 Allow ignored Onkyo devices to be set up from the user flow (#150921) 2025-09-01 17:04:06 +02:00
Artur Pragacz
c4fce1c793 Improve unpair schema in homekit (#150235) 2025-09-01 09:57:24 -05:00
Willem-Jan van Rootselaar
581f8a9378 Fix add checks for None values and check if DHW is available (#151376) 2025-09-01 16:47:59 +02:00
Artur Pragacz
d7e6f84d28 Fix empty selector validation (#151340) 2025-09-01 16:22:41 +02:00
Artur Pragacz
5e22533fc0 Code quality improvements of the selector helper (#151505) 2025-09-01 16:18:17 +02:00
Fabian Leutgeb
ecae074dd7 Homekit valve duration properties (#150273)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-09-01 08:24:50 -05:00
Joost Lekkerkerker
55b0406960 Update Pooldose quality scale (#151499) 2025-09-01 15:15:21 +02:00
Phil Male
36483dd785 Use average color for Hue light group state (#149499) 2025-09-01 15:14:50 +02:00
Ludovic BOUÉ
ad154dce40 Add Matter occupancy sensing hold time (#150745) 2025-09-01 15:14:08 +02:00
G Johansson
3abf91af3a Use OptionsFlowWithReload in google (#151257) 2025-09-01 15:13:19 +02:00
Jozef Kruszynski
579d217c6b Fix sort order in media browser for music assistant integration (#150910) 2025-09-01 14:27:48 +02:00
Joakim Plate
7322bee4dd Add select entity to ToGrill (#151114)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-09-01 14:24:43 +02:00
Joost Lekkerkerker
8a36ec88f4 Add AC fixture to smartthings (#150891)
Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
2025-09-01 14:24:33 +02:00
Michel van de Wetering
864f908257 Remove Hue Bridge v1 image in config flow (#151112) 2025-09-01 14:14:43 +02:00
alexqzd
5d86d8b380 SmartThings: Expose the entity to control the AC display light (#151404)
Co-authored-by: Joostlek <joostlek@outlook.com>
2025-09-01 14:12:18 +02:00
Iskra kranj
15245707a5 Bump pyiskra to 0.1.26 (#151489) 2025-09-01 14:09:19 +02:00
Martin Hjelmare
80e4451a3f Freeze development of alert integration (#151486) 2025-09-01 13:50:22 +02:00
Yuxin Wang
f051f4ea99 Add more test logic to APCUPSD (#151336) 2025-09-01 13:15:11 +02:00
starkillerOG
7717b5aca6 Add Reolink Home Hub siren (#151196) 2025-09-01 12:46:46 +02:00
Imeon-Energy
81d2bcdeb9 Missing state for inverter state sensor in Imeon inverter (#151493)
Co-authored-by: TheBushBoy <theodavid@icloud.com>
2025-09-01 12:34:27 +02:00
Marc Mueller
9934de18ae Remove unused code in bayesian binary_sensor (#151492) 2025-09-01 12:33:53 +02:00
Marc Mueller
aac015e822 Fix backup manager delete backup error filter (#151490) 2025-09-01 12:22:23 +02:00
Denis Shulyaka
1f584f011e Allow structure field of ai_task.generate_data for non-advanced users (#151481) 2025-09-01 07:06:09 -03:00
Joost Lekkerkerker
2106c4cfb9 Set Aladdin Connect integration type to hub (#151491) 2025-09-01 11:59:25 +02:00
jan iversen
a053142601 modbus: Do not modify registers (return wrong data). (#151131) 2025-09-01 11:48:19 +02:00
starkillerOG
dd0dce7968 Add Reolink encoding select entity (#151195) 2025-09-01 11:20:20 +02:00
Tom
bdfff6df2d Bump airOS to 0.5.1 (#151458) 2025-09-01 10:40:09 +02:00
J. Nick Koston
671c4e1eab Reduce log spam from unauthenticated websocket connections (#151388) 2025-09-01 10:35:22 +02:00
ChristianKuehnel
8aae2a935a Replace string literal in lacrosse (#151484) 2025-09-01 10:32:05 +02:00
Yevhenii Vaskivskyi
9e64f18439 Fix bug with the wrong temperature scale on new router firmware (asuswrt) (#151011) 2025-09-01 10:30:41 +02:00
Paul Bottein
e8a6f2f098 Update frontend to 20250829.0 (#151390) 2025-09-01 10:20:03 +02:00
Russell VanderMey
8faeb1fe98 Avoid blocking IO in TRIGGERcmd (#151396) 2025-09-01 10:19:35 +02:00
J. Nick Koston
edc48e0604 Fix Yale Access Bluetooth key discovery timing issues (#151433)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-09-01 10:18:53 +02:00
Manu
eab77f11b0 Rename brand Fritz!Box to FRITZ! (#151389) 2025-09-01 10:15:09 +02:00
Simone Chemelli
edb79b0337 Change sounds list source for Alexa Devices (#151317) 2025-09-01 09:50:57 +02:00
Brett Adams
41f33a106f Fix history startup failures (#151439) 2025-09-01 09:48:49 +02:00
Arjan
cf31401cc2 Fix typo in Meteo France mappings (#151344) 2025-09-01 09:46:21 +02:00
Niccolò Maggioni
8679c8e40c Expose MAC address in SNMP device_tracker entity attributes (#139941)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-09-01 09:40:50 +02:00
David Rapan
e675d0e8ed Starlink's Energy, Download and Upload accumulation after restart fix (#137855)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-09-01 09:00:09 +02:00
J. Nick Koston
c73289aed9 Bump bluetooth-adapters to 2.1.0 and habluetooth to 5.3.0 (#151465) 2025-08-31 17:13:03 -05:00
Marc Mueller
4420776977 Update anyio to 4.10.0 (#151455) 2025-08-31 21:14:37 +02:00
jan iversen
b77d6e7b59 Modbus: Ignore unknown parameters. (#151451) 2025-08-31 16:48:12 +02:00
Thomas55555
8f074e5724 Bump aioautomower to 2.2.1 (#151427) 2025-08-31 15:16:19 +02:00
tronikos
b1e46bcde4 Bump opower to 0.15.4 (#151443) 2025-08-31 12:14:14 +02:00
stephan-carstens
fc4b5f66ff Extend UnitOfApparentPower with 'kVA' (#151420) 2025-08-31 00:05:07 +01:00
Maciej Bieniek
55978f2827 Allow integration to initialize when BraviaTV is offline (#151415) 2025-08-30 23:06:01 +03:00
Yuxin Wang
010a8cc693 Attach serial_number to devices in APC UPS Daemon (#151421) 2025-08-30 18:20:46 +02:00
Joakim Plate
d31eadc8cd feat: bump fjaraskupan to 2.3.3 (#151408) 2025-08-30 10:35:30 +02:00
Manu
3190a523aa Remove device class from Habitica binary sensor quest status (#151338) 2025-08-30 09:40:36 +02:00
karwosts
8f82e451cd Fix play media example data (#151394) 2025-08-30 09:37:41 +02:00
Joakim Plate
5bbd71e594 Add icons to different temperatures for the ToGrill integration (#151392) 2025-08-30 07:11:33 +02:00
Aaron Bach
33257b8422 Bump aiopurpleair to 2025.08.1 (#151398) 2025-08-29 23:38:34 +02:00
Michael Hansen
b3a4cd5b76 Bump intents to 2025.8.29 (#151397) 2025-08-29 16:17:42 -05:00
J. Nick Koston
dcfa466dd4 Bump habluetooth to 5.2.1 (#151391) 2025-08-29 12:14:22 -05:00
Joakim Plate
846e6d96a4 Add minimum and maximum targets (#151387) 2025-08-29 17:42:28 +02:00
Erik Montnemery
d72b35a0cd Improve comment on disabled_by + hidden_by flag in registries (#151290)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-08-29 17:27:39 +02:00
Foscam-wangzhengyu
5e003627b2 Update Foscam codeowners (#150972) 2025-08-29 16:59:46 +02:00
Manu
a4f71f37f6 Use subentry title as display name in ntfy integration (#151370) 2025-08-29 16:56:37 +02:00
Manu
c37b2f86b1 Change manufacturer name AVM to FRITZ! in FRITZ!Box Tools integration (#151371) 2025-08-29 16:54:53 +02:00
Manu
926aeef156 Change manufacturer name AVM to FRITZ! in FRITZ!Box Call Monitor integration (#151374) 2025-08-29 16:53:57 +02:00
Maciej Bieniek
ee86671d39 Bump brother to version 5.1.0 (#151368)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-08-29 16:53:26 +02:00
Manu
dc371cf46d Ignore errors when PlayStation Network group fetch is blocked by parental controls (#150364) 2025-08-29 16:53:13 +02:00
J. Nick Koston
c76e26508d Bump aioesphomeapi to 39.0.1 (#151385) 2025-08-29 16:52:27 +02:00
J. Nick Koston
a5cd316fa3 Bump bleak-esphome to 3.2.0 (#151380) 2025-08-29 16:51:56 +02:00
starkillerOG
736cc8a17d Bump reolink-aio to 0.15.0 (#151367)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-08-29 16:51:20 +02:00
J. Nick Koston
5278fce218 Bump nexia to 2.11.1 (#151379) 2025-08-29 16:49:57 +02:00
Erik Montnemery
8f04f22c65 Improve migration to device registry version 1.11 (#151315)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-08-29 16:48:29 +02:00
Manu
a01f638fc6 Change manufacturer name AVM to FRITZ! in FRITZ!SmartHome integration (#151373) 2025-08-29 16:17:32 +02:00
Marc Mueller
22005dd48a Pin pytest-rerunfailures to 15.1 (#151383) 2025-08-29 16:03:32 +02:00
Thomas55555
fff60b3863 Use _async_setup in Huqvarna Automower (#151325) 2025-08-29 13:16:24 +02:00
Manu
5cb5fe5b67 Fix direct message notifiers in PlayStation Network (#150548) 2025-08-29 11:37:01 +02:00
J. Nick Koston
24ea5eb9b5 Bump habluetooth to 5.2.0 (#151333) 2025-08-29 11:23:50 +02:00
dependabot[bot]
673c2a77e0 Bump actions/attest-build-provenance from 2.4.0 to 3.0.0 (#151347)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-08-29 11:21:42 +02:00
Yevhenii Vaskivskyi
ad3014e711 Bump asusrouter to 1.20.1 (#151311) 2025-08-29 11:21:18 +02:00
Tom
c19ae81cbc Bump airOS to 0.4.4 (#151345) 2025-08-29 11:20:20 +02:00
J. Nick Koston
959d99f333 Bump bleak-retry-connector to 4.4.3 (#151341) 2025-08-29 11:19:44 +02:00
Manu
7cfe6bf427 Add sensors for boss rage to Habitica (#151334) 2025-08-29 00:01:23 +01:00
Manu
765e2c1b6c Bump habiticalib to v0.4.4 (#151332) 2025-08-29 00:04:37 +02:00
Paul Bottein
0fd63df123 Update frontend to 20250828.0 (#151321) 2025-08-28 22:27:46 +02:00
Robert Resch
862fbd551a Bump deebot-client to 13.7.0 (#151327) 2025-08-28 22:23:46 +02:00
J. Nick Koston
6e79b76d15 Bump nexia to 2.11.0 (#151319) 2025-08-28 21:48:41 +02:00
Martin Hjelmare
f85307d86c Fix Z-Wave duplicate notification binary sensors (#151304) 2025-08-28 20:46:43 +02:00
Erik Montnemery
b01f93119f Fix restoring disabled_by flag of deleted devices (#151313) 2025-08-28 20:10:10 +02:00
Erik Montnemery
4130f3db2f Improve migration to entity registry version 1.18 (#151308)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-08-28 18:52:51 +02:00
Yuxin Wang
ffcd5167b5 Use fixtures instead of helper functions for APCUPSD tests (#151172)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
2025-08-28 16:11:31 +02:00
Ludovic BOUÉ
e94a7b2ec1 Add product_id support to Matter discovery schemas (#151307) 2025-08-28 15:57:40 +02:00
Ludovic BOUÉ
6b3f2e9b7b Aqara door window p2 fixture (#151294) 2025-08-28 15:04:23 +02:00
Roland Moers
56545dacb0 Bump fritzconnection to 1.15.0 (#151252) 2025-08-28 14:26:07 +02:00
Norbert Rittel
cbf061183e Fix wrong description for numeric_state observation in bayesian (#151291) 2025-08-28 15:24:24 +03:00
Jamie Magee
5dcb5f4926 Remove uv.lock (#151282) 2025-08-28 12:33:30 +02:00
Andrew Jackson
5fbb99a79a Fix endpoint deprecation warning in Mastodon (#151275) 2025-08-28 11:59:37 +02:00
Felipe Santos
da65c52f2d Fix ONVIF not displaying sensor and binary_sensor entity names (#151285) 2025-08-28 11:58:13 +02:00
Simone Chemelli
08a850cfc7 Fix exception countries migration for Alexa Devices (#151292) 2025-08-28 11:57:32 +02:00
Simone Chemelli
12978092f7 Add missing state class to Alexa Devices sensors (#151296) 2025-08-28 11:53:46 +02:00
starkillerOG
210a9ad2de Fix Reolink duplicates due to wrong merge (#151298) 2025-08-28 11:38:08 +02:00
Artur Pragacz
61328129fc Remove is_new from device entry (#149835)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-08-28 08:07:54 +02:00
Ian Tewksbury
f4673f44ee Add multiple NICs in govee_light_local (#128123) 2025-08-28 08:04:50 +02:00
Arjan
3bdd532dcd Adding missing: Averses de grèle (#151288) 2025-08-28 07:50:48 +02:00
G Johansson
e23d3c8ab4 Use OptionsFlowWithReload in google_cloud (#151259) 2025-08-27 22:19:44 -07:00
Florent Thoumie
a7cb66c592 Iaqualink: create parent device manually and link entities (#151215) 2025-08-27 23:05:15 +01:00
Norbert Rittel
8544d1ebec Fix broken translation key for "update_percentage" in template (#151272) 2025-08-27 22:57:32 +01:00
G Johansson
240afd80c1 Fix spelling in bayesian strings (#151265) 2025-08-28 00:08:39 +03:00
Franck Nijhof
ccb1da3a97 Bump version to 2025.10.0dev0 (#151262) 2025-08-27 21:53:39 +02:00
Denis Shulyaka
de62991e5b OpenAI ai_task image generation support (#151238) 2025-08-27 14:43:27 -04:00
G Johansson
bad75222ed Use OptionsFlowWithReload in yalexs_ble (#151256) 2025-08-27 13:14:31 -05:00
abmantis
86e7ca9790 Rename to extract_from_target 2025-08-07 22:02:41 +01:00
Abílio Costa
f8f4c7ddeb copilot: Update homeassistant/components/websocket_api/commands.py
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-08-06 15:57:16 +01:00
abmantis
8308be185e Add expand_target websocket command 2025-08-06 15:50:16 +01:00
541 changed files with 26304 additions and 4590 deletions

View File

@@ -8,6 +8,8 @@
"PYTHONASYNCIODEBUG": "1"
},
"features": {
// Node feature required for Claude Code until fixed https://github.com/anthropics/devcontainer-features/issues/28
"ghcr.io/devcontainers/features/node:1": {},
"ghcr.io/anthropics/devcontainer-features/claude-code:1.0": {},
"ghcr.io/devcontainers/features/github-cli:1": {}
},

View File

@@ -32,7 +32,7 @@ jobs:
fetch-depth: 0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -116,7 +116,7 @@ jobs:
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
if: needs.init.outputs.channel == 'dev'
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -457,7 +457,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -480,7 +480,7 @@ jobs:
python -m build
- name: Upload package to PyPI
uses: pypa/gh-action-pypi-publish@v1.12.4
uses: pypa/gh-action-pypi-publish@v1.13.0
with:
skip-existing: true
@@ -531,7 +531,7 @@ jobs:
- name: Generate artifact attestation
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
uses: actions/attest-build-provenance@977bb373ede98d70efdf65b84cb5f73e068dcc2a # v3.0.0
with:
subject-name: ${{ env.HASSFEST_IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}

View File

@@ -40,7 +40,7 @@ env:
CACHE_VERSION: 7
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 1
HA_SHORT_VERSION: "2025.9"
HA_SHORT_VERSION: "2025.10"
DEFAULT_PYTHON: "3.13"
ALL_PYTHON_VERSIONS: "['3.13']"
# 10.3 is the oldest supported version
@@ -249,7 +249,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -294,7 +294,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -334,7 +334,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -374,7 +374,7 @@ jobs:
- name: Check out code from GitHub
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
id: python
with:
python-version: ${{ env.DEFAULT_PYTHON }}
@@ -484,7 +484,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -517,6 +517,7 @@ jobs:
env.HA_SHORT_VERSION }}-
- name: Install additional OS dependencies
if: steps.cache-venv.outputs.cache-hit != 'true'
timeout-minutes: 5
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
@@ -578,6 +579,7 @@ jobs:
- base
steps:
- name: Install additional OS dependencies
timeout-minutes: 5
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
@@ -587,7 +589,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -620,7 +622,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -677,7 +679,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -720,7 +722,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -767,7 +769,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -812,7 +814,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -877,6 +879,7 @@ jobs:
name: Split tests for full run
steps:
- name: Install additional OS dependencies
timeout-minutes: 5
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
@@ -889,7 +892,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true
@@ -937,6 +940,7 @@ jobs:
Run tests Python ${{ matrix.python-version }} (${{ matrix.group }})
steps:
- name: Install additional OS dependencies
timeout-minutes: 5
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
@@ -950,7 +954,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -1070,6 +1074,7 @@ jobs:
Run ${{ matrix.mariadb-group }} tests Python ${{ matrix.python-version }}
steps:
- name: Install additional OS dependencies
timeout-minutes: 5
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
@@ -1083,7 +1088,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -1210,6 +1215,7 @@ jobs:
Run ${{ matrix.postgresql-group }} tests Python ${{ matrix.python-version }}
steps:
- name: Install additional OS dependencies
timeout-minutes: 5
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
@@ -1225,7 +1231,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -1341,7 +1347,7 @@ jobs:
pattern: coverage-*
- name: Upload coverage to Codecov
if: needs.info.outputs.test_full_suite == 'true'
uses: codecov/codecov-action@v5.5.0
uses: codecov/codecov-action@v5.5.1
with:
fail_ci_if_error: true
flags: full-suite
@@ -1371,6 +1377,7 @@ jobs:
Run tests Python ${{ matrix.python-version }} (${{ matrix.group }})
steps:
- name: Install additional OS dependencies
timeout-minutes: 5
run: |
sudo rm /etc/apt/sources.list.d/microsoft-prod.list
sudo apt-get update
@@ -1384,7 +1391,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ matrix.python-version }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ matrix.python-version }}
check-latest: true
@@ -1491,7 +1498,7 @@ jobs:
pattern: coverage-*
- name: Upload coverage to Codecov
if: needs.info.outputs.test_full_suite == 'false'
uses: codecov/codecov-action@v5.5.0
uses: codecov/codecov-action@v5.5.1
with:
fail_ci_if_error: true
token: ${{ secrets.CODECOV_TOKEN }}

View File

@@ -24,11 +24,11 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Initialize CodeQL
uses: github/codeql-action/init@v3.29.11
uses: github/codeql-action/init@v3.30.1
with:
languages: python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3.29.11
uses: github/codeql-action/analyze@v3.30.1
with:
category: "/language:python"

View File

@@ -16,7 +16,7 @@ jobs:
steps:
- name: Check if integration label was added and extract details
id: extract
uses: actions/github-script@v7.0.1
uses: actions/github-script@v8
with:
script: |
// Debug: Log the event payload
@@ -113,7 +113,7 @@ jobs:
- name: Fetch similar issues
id: fetch_similar
if: steps.extract.outputs.should_continue == 'true'
uses: actions/github-script@v7.0.1
uses: actions/github-script@v8
env:
INTEGRATION_LABELS: ${{ steps.extract.outputs.integration_labels }}
CURRENT_NUMBER: ${{ steps.extract.outputs.current_number }}
@@ -280,7 +280,7 @@ jobs:
- name: Post duplicate detection results
id: post_results
if: steps.extract.outputs.should_continue == 'true' && steps.fetch_similar.outputs.has_similar == 'true'
uses: actions/github-script@v7.0.1
uses: actions/github-script@v8
env:
AI_RESPONSE: ${{ steps.ai_detection.outputs.response }}
SIMILAR_ISSUES: ${{ steps.fetch_similar.outputs.similar_issues }}

View File

@@ -16,7 +16,7 @@ jobs:
steps:
- name: Check issue language
id: detect_language
uses: actions/github-script@v7.0.1
uses: actions/github-script@v8
env:
ISSUE_NUMBER: ${{ github.event.issue.number }}
ISSUE_TITLE: ${{ github.event.issue.title }}
@@ -90,7 +90,7 @@ jobs:
- name: Process non-English issues
if: steps.detect_language.outputs.should_continue == 'true'
uses: actions/github-script@v7.0.1
uses: actions/github-script@v8
env:
AI_RESPONSE: ${{ steps.ai_language_detection.outputs.response }}
ISSUE_NUMBER: ${{ steps.detect_language.outputs.issue_number }}

View File

@@ -12,7 +12,7 @@ jobs:
if: github.event.issue.type.name == 'Task'
steps:
- name: Check if user is authorized
uses: actions/github-script@v7
uses: actions/github-script@v8
with:
script: |
const issueAuthor = context.payload.issue.user.login;

View File

@@ -17,7 +17,7 @@ jobs:
# - No PRs marked as no-stale
# - No issues (-1)
- name: 60 days stale PRs policy
uses: actions/stale@v9.1.0
uses: actions/stale@v10.0.0
with:
repo-token: ${{ secrets.GITHUB_TOKEN }}
days-before-stale: 60
@@ -57,7 +57,7 @@ jobs:
# - No issues marked as no-stale or help-wanted
# - No PRs (-1)
- name: 90 days stale issues
uses: actions/stale@v9.1.0
uses: actions/stale@v10.0.0
with:
repo-token: ${{ steps.token.outputs.token }}
days-before-stale: 90
@@ -87,7 +87,7 @@ jobs:
# - No Issues marked as no-stale or help-wanted
# - No PRs (-1)
- name: Needs more information stale issues policy
uses: actions/stale@v9.1.0
uses: actions/stale@v10.0.0
with:
repo-token: ${{ steps.token.outputs.token }}
only-labels: "needs-more-information"

View File

@@ -22,7 +22,7 @@ jobs:
uses: actions/checkout@v5.0.0
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}

View File

@@ -36,7 +36,7 @@ jobs:
- name: Set up Python ${{ env.DEFAULT_PYTHON }}
id: python
uses: actions/setup-python@v5.6.0
uses: actions/setup-python@v6.0.0
with:
python-version: ${{ env.DEFAULT_PYTHON }}
check-latest: true

View File

@@ -307,6 +307,7 @@ homeassistant.components.ld2410_ble.*
homeassistant.components.led_ble.*
homeassistant.components.lektrico.*
homeassistant.components.letpot.*
homeassistant.components.libre_hardware_monitor.*
homeassistant.components.lidarr.*
homeassistant.components.lifx.*
homeassistant.components.light.*
@@ -382,6 +383,7 @@ homeassistant.components.openai_conversation.*
homeassistant.components.openexchangerates.*
homeassistant.components.opensky.*
homeassistant.components.openuv.*
homeassistant.components.opnsense.*
homeassistant.components.opower.*
homeassistant.components.oralb.*
homeassistant.components.otbr.*
@@ -458,6 +460,7 @@ homeassistant.components.sensorpush_cloud.*
homeassistant.components.sensoterra.*
homeassistant.components.senz.*
homeassistant.components.sfr_box.*
homeassistant.components.sftp_storage.*
homeassistant.components.shell_command.*
homeassistant.components.shelly.*
homeassistant.components.shopping_list.*

64
CODEOWNERS generated
View File

@@ -154,10 +154,10 @@ build.json @home-assistant/supervisor
/tests/components/arve/ @ikalnyi
/homeassistant/components/aseko_pool_live/ @milanmeu
/tests/components/aseko_pool_live/ @milanmeu
/homeassistant/components/assist_pipeline/ @balloob @synesthesiam
/tests/components/assist_pipeline/ @balloob @synesthesiam
/homeassistant/components/assist_satellite/ @home-assistant/core @synesthesiam
/tests/components/assist_satellite/ @home-assistant/core @synesthesiam
/homeassistant/components/assist_pipeline/ @synesthesiam @arturpragacz
/tests/components/assist_pipeline/ @synesthesiam @arturpragacz
/homeassistant/components/assist_satellite/ @home-assistant/core @synesthesiam @arturpragacz
/tests/components/assist_satellite/ @home-assistant/core @synesthesiam @arturpragacz
/homeassistant/components/asuswrt/ @kennedyshead @ollo69 @Vaskivskyi
/tests/components/asuswrt/ @kennedyshead @ollo69 @Vaskivskyi
/homeassistant/components/atag/ @MatsNL
@@ -298,8 +298,8 @@ build.json @home-assistant/supervisor
/tests/components/configurator/ @home-assistant/core
/homeassistant/components/control4/ @lawtancool
/tests/components/control4/ @lawtancool
/homeassistant/components/conversation/ @home-assistant/core @synesthesiam
/tests/components/conversation/ @home-assistant/core @synesthesiam
/homeassistant/components/conversation/ @home-assistant/core @synesthesiam @arturpragacz
/tests/components/conversation/ @home-assistant/core @synesthesiam @arturpragacz
/homeassistant/components/cookidoo/ @miaucl
/tests/components/cookidoo/ @miaucl
/homeassistant/components/coolmaster/ @OnFreund
@@ -464,8 +464,6 @@ build.json @home-assistant/supervisor
/tests/components/eufylife_ble/ @bdr99
/homeassistant/components/event/ @home-assistant/core
/tests/components/event/ @home-assistant/core
/homeassistant/components/evil_genius_labs/ @balloob
/tests/components/evil_genius_labs/ @balloob
/homeassistant/components/evohome/ @zxdavb
/tests/components/evohome/ @zxdavb
/homeassistant/components/ezviz/ @RenierM26
@@ -515,8 +513,8 @@ build.json @home-assistant/supervisor
/homeassistant/components/forked_daapd/ @uvjustin
/tests/components/forked_daapd/ @uvjustin
/homeassistant/components/fortios/ @kimfrellsen
/homeassistant/components/foscam/ @krmarien
/tests/components/foscam/ @krmarien
/homeassistant/components/foscam/ @Foscam-wangzhengyu
/tests/components/foscam/ @Foscam-wangzhengyu
/homeassistant/components/freebox/ @hacf-fr @Quentame
/tests/components/freebox/ @hacf-fr @Quentame
/homeassistant/components/freedompro/ @stefano055415
@@ -650,6 +648,8 @@ build.json @home-assistant/supervisor
/tests/components/homeassistant/ @home-assistant/core
/homeassistant/components/homeassistant_alerts/ @home-assistant/core
/tests/components/homeassistant_alerts/ @home-assistant/core
/homeassistant/components/homeassistant_connect_zbt2/ @home-assistant/core
/tests/components/homeassistant_connect_zbt2/ @home-assistant/core
/homeassistant/components/homeassistant_green/ @home-assistant/core
/tests/components/homeassistant_green/ @home-assistant/core
/homeassistant/components/homeassistant_hardware/ @home-assistant/core
@@ -678,8 +678,8 @@ build.json @home-assistant/supervisor
/tests/components/http/ @home-assistant/core
/homeassistant/components/huawei_lte/ @scop @fphammerle
/tests/components/huawei_lte/ @scop @fphammerle
/homeassistant/components/hue/ @balloob @marcelveldt
/tests/components/hue/ @balloob @marcelveldt
/homeassistant/components/hue/ @marcelveldt
/tests/components/hue/ @marcelveldt
/homeassistant/components/huisbaasje/ @dennisschroer
/tests/components/huisbaasje/ @dennisschroer
/homeassistant/components/humidifier/ @home-assistant/core @Shulyaka
@@ -751,8 +751,8 @@ build.json @home-assistant/supervisor
/tests/components/integration/ @dgomes
/homeassistant/components/intellifire/ @jeeftor
/tests/components/intellifire/ @jeeftor
/homeassistant/components/intent/ @home-assistant/core @synesthesiam
/tests/components/intent/ @home-assistant/core @synesthesiam
/homeassistant/components/intent/ @home-assistant/core @synesthesiam @arturpragacz
/tests/components/intent/ @home-assistant/core @synesthesiam @arturpragacz
/homeassistant/components/intesishome/ @jnimmo
/homeassistant/components/iometer/ @MaestroOnICe
/tests/components/iometer/ @MaestroOnICe
@@ -860,6 +860,8 @@ build.json @home-assistant/supervisor
/tests/components/lg_netcast/ @Drafteed @splinter98
/homeassistant/components/lg_thinq/ @LG-ThinQ-Integration
/tests/components/lg_thinq/ @LG-ThinQ-Integration
/homeassistant/components/libre_hardware_monitor/ @Sab44
/tests/components/libre_hardware_monitor/ @Sab44
/homeassistant/components/lidarr/ @tkdrob
/tests/components/lidarr/ @tkdrob
/homeassistant/components/lifx/ @Djelibeybi
@@ -1108,8 +1110,6 @@ build.json @home-assistant/supervisor
/tests/components/open_meteo/ @frenck
/homeassistant/components/open_router/ @joostlek
/tests/components/open_router/ @joostlek
/homeassistant/components/openai_conversation/ @balloob
/tests/components/openai_conversation/ @balloob
/homeassistant/components/openerz/ @misialq
/tests/components/openerz/ @misialq
/homeassistant/components/openexchangerates/ @MartinHjelmare
@@ -1208,8 +1208,6 @@ build.json @home-assistant/supervisor
/homeassistant/components/proximity/ @mib1185
/tests/components/proximity/ @mib1185
/homeassistant/components/proxmoxve/ @jhollowe @Corbeno
/homeassistant/components/prusalink/ @balloob
/tests/components/prusalink/ @balloob
/homeassistant/components/ps4/ @ktnrg45
/tests/components/ps4/ @ktnrg45
/homeassistant/components/pterodactyl/ @elmurato
@@ -1303,8 +1301,8 @@ build.json @home-assistant/supervisor
/tests/components/rflink/ @javicalle
/homeassistant/components/rfxtrx/ @danielhiversen @elupus @RobBie1221
/tests/components/rfxtrx/ @danielhiversen @elupus @RobBie1221
/homeassistant/components/rhasspy/ @balloob @synesthesiam
/tests/components/rhasspy/ @balloob @synesthesiam
/homeassistant/components/rhasspy/ @synesthesiam
/tests/components/rhasspy/ @synesthesiam
/homeassistant/components/ridwell/ @bachya
/tests/components/ridwell/ @bachya
/homeassistant/components/ring/ @sdb9696
@@ -1392,12 +1390,14 @@ build.json @home-assistant/supervisor
/tests/components/seventeentrack/ @shaiu
/homeassistant/components/sfr_box/ @epenet
/tests/components/sfr_box/ @epenet
/homeassistant/components/sftp_storage/ @maretodoric
/tests/components/sftp_storage/ @maretodoric
/homeassistant/components/sharkiq/ @JeffResc @funkybunch
/tests/components/sharkiq/ @JeffResc @funkybunch
/homeassistant/components/shell_command/ @home-assistant/core
/tests/components/shell_command/ @home-assistant/core
/homeassistant/components/shelly/ @balloob @bieniu @thecode @chemelli74 @bdraco
/tests/components/shelly/ @balloob @bieniu @thecode @chemelli74 @bdraco
/homeassistant/components/shelly/ @bieniu @thecode @chemelli74 @bdraco
/tests/components/shelly/ @bieniu @thecode @chemelli74 @bdraco
/homeassistant/components/shodan/ @fabaff
/homeassistant/components/sia/ @eavanvalkenburg
/tests/components/sia/ @eavanvalkenburg
@@ -1544,8 +1544,8 @@ build.json @home-assistant/supervisor
/tests/components/systemmonitor/ @gjohansson-ST
/homeassistant/components/tado/ @erwindouna
/tests/components/tado/ @erwindouna
/homeassistant/components/tag/ @balloob @dmulcahey
/tests/components/tag/ @balloob @dmulcahey
/homeassistant/components/tag/ @home-assistant/core
/tests/components/tag/ @home-assistant/core
/homeassistant/components/tailscale/ @frenck
/tests/components/tailscale/ @frenck
/homeassistant/components/tailwind/ @frenck
@@ -1690,15 +1690,15 @@ build.json @home-assistant/supervisor
/tests/components/vegehub/ @ghowevege
/homeassistant/components/velbus/ @Cereal2nd @brefra
/tests/components/velbus/ @Cereal2nd @brefra
/homeassistant/components/velux/ @Julius2342 @DeerMaximum @pawlizio
/tests/components/velux/ @Julius2342 @DeerMaximum @pawlizio
/homeassistant/components/velux/ @Julius2342 @DeerMaximum @pawlizio @wollew
/tests/components/velux/ @Julius2342 @DeerMaximum @pawlizio @wollew
/homeassistant/components/venstar/ @garbled1 @jhollowe
/tests/components/venstar/ @garbled1 @jhollowe
/homeassistant/components/versasense/ @imstevenxyz
/homeassistant/components/version/ @ludeeus
/tests/components/version/ @ludeeus
/homeassistant/components/vesync/ @markperdue @webdjoe @thegardenmonkey @cdnninja @iprak
/tests/components/vesync/ @markperdue @webdjoe @thegardenmonkey @cdnninja @iprak
/homeassistant/components/vesync/ @markperdue @webdjoe @thegardenmonkey @cdnninja @iprak @sapuseven
/tests/components/vesync/ @markperdue @webdjoe @thegardenmonkey @cdnninja @iprak @sapuseven
/homeassistant/components/vicare/ @CFenner
/tests/components/vicare/ @CFenner
/homeassistant/components/vilfo/ @ManneW
@@ -1710,8 +1710,8 @@ build.json @home-assistant/supervisor
/tests/components/vlc_telnet/ @rodripf @MartinHjelmare
/homeassistant/components/vodafone_station/ @paoloantinori @chemelli74
/tests/components/vodafone_station/ @paoloantinori @chemelli74
/homeassistant/components/voip/ @balloob @synesthesiam @jaminh
/tests/components/voip/ @balloob @synesthesiam @jaminh
/homeassistant/components/voip/ @synesthesiam @jaminh
/tests/components/voip/ @synesthesiam @jaminh
/homeassistant/components/volumio/ @OnFreund
/tests/components/volumio/ @OnFreund
/homeassistant/components/volvo/ @thomasddn
@@ -1782,8 +1782,8 @@ build.json @home-assistant/supervisor
/tests/components/worldclock/ @fabaff
/homeassistant/components/ws66i/ @ssaenger
/tests/components/ws66i/ @ssaenger
/homeassistant/components/wyoming/ @balloob @synesthesiam
/tests/components/wyoming/ @balloob @synesthesiam
/homeassistant/components/wyoming/ @synesthesiam
/tests/components/wyoming/ @synesthesiam
/homeassistant/components/xbox/ @hunterjm
/tests/components/xbox/ @hunterjm
/homeassistant/components/xiaomi_aqara/ @danielhiversen @syssi

View File

@@ -3,8 +3,7 @@ FROM mcr.microsoft.com/vscode/devcontainers/base:debian
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
RUN \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& apt-get update \
apt-get update \
&& DEBIAN_FRONTEND=noninteractive apt-get install -y --no-install-recommends \
# Additional library needed by some tests and accordingly by VScode Tests Discovery
bluez \

View File

@@ -187,36 +187,42 @@ def main() -> int:
from . import config, runner # noqa: PLC0415
safe_mode = config.safe_mode_enabled(config_dir)
# Ensure only one instance runs per config directory
with runner.ensure_single_execution(config_dir) as single_execution_lock:
# Check if another instance is already running
if single_execution_lock.exit_code is not None:
return single_execution_lock.exit_code
runtime_conf = runner.RuntimeConfig(
config_dir=config_dir,
verbose=args.verbose,
log_rotate_days=args.log_rotate_days,
log_file=args.log_file,
log_no_color=args.log_no_color,
skip_pip=args.skip_pip,
skip_pip_packages=args.skip_pip_packages,
recovery_mode=args.recovery_mode,
debug=args.debug,
open_ui=args.open_ui,
safe_mode=safe_mode,
)
safe_mode = config.safe_mode_enabled(config_dir)
fault_file_name = os.path.join(config_dir, FAULT_LOG_FILENAME)
with open(fault_file_name, mode="a", encoding="utf8") as fault_file:
faulthandler.enable(fault_file)
exit_code = runner.run(runtime_conf)
faulthandler.disable()
runtime_conf = runner.RuntimeConfig(
config_dir=config_dir,
verbose=args.verbose,
log_rotate_days=args.log_rotate_days,
log_file=args.log_file,
log_no_color=args.log_no_color,
skip_pip=args.skip_pip,
skip_pip_packages=args.skip_pip_packages,
recovery_mode=args.recovery_mode,
debug=args.debug,
open_ui=args.open_ui,
safe_mode=safe_mode,
)
# It's possible for the fault file to disappear, so suppress obvious errors
with suppress(FileNotFoundError):
if os.path.getsize(fault_file_name) == 0:
os.remove(fault_file_name)
fault_file_name = os.path.join(config_dir, FAULT_LOG_FILENAME)
with open(fault_file_name, mode="a", encoding="utf8") as fault_file:
faulthandler.enable(fault_file)
exit_code = runner.run(runtime_conf)
faulthandler.disable()
check_threads()
# It's possible for the fault file to disappear, so suppress obvious errors
with suppress(FileNotFoundError):
if os.path.getsize(fault_file_name) == 0:
os.remove(fault_file_name)
return exit_code
check_threads()
return exit_code
if __name__ == "__main__":

View File

@@ -27,7 +27,7 @@ from . import (
SetupFlow,
)
REQUIREMENTS = ["pyotp==2.8.0"]
REQUIREMENTS = ["pyotp==2.9.0"]
CONF_MESSAGE = "message"

View File

@@ -20,7 +20,7 @@ from . import (
SetupFlow,
)
REQUIREMENTS = ["pyotp==2.8.0", "PyQRCode==1.2.1"]
REQUIREMENTS = ["pyotp==2.9.0", "PyQRCode==1.2.1"]
CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({}, extra=vol.PREVENT_EXTRA)

View File

@@ -1,5 +1,5 @@
{
"domain": "fritzbox",
"name": "FRITZ!Box",
"name": "FRITZ!",
"integrations": ["fritz", "fritzbox", "fritzbox_callmonitor"]
}

View File

@@ -211,7 +211,6 @@ class ImageView(HomeAssistantView):
url = f"/api/{DOMAIN}/images/{{filename}}"
name = f"api:{DOMAIN}/images"
requires_auth = False
async def get(
self,

View File

@@ -2,8 +2,10 @@
from __future__ import annotations
from datetime import timedelta
import logging
from homeassistant.components.http.auth import async_sign_path
from homeassistant.components.media_player import BrowseError, MediaClass
from homeassistant.components.media_source import (
BrowseMediaSource,
@@ -14,7 +16,7 @@ from homeassistant.components.media_source import (
)
from homeassistant.core import HomeAssistant
from .const import DATA_IMAGES, DOMAIN
from .const import DATA_IMAGES, DOMAIN, IMAGE_EXPIRY_TIME
_LOGGER = logging.getLogger(__name__)
@@ -43,7 +45,14 @@ class ImageMediaSource(MediaSource):
if image is None:
raise Unresolvable(f"Could not resolve media item: {item.identifier}")
return PlayMedia(f"/api/{DOMAIN}/images/{item.identifier}", image.mime_type)
return PlayMedia(
async_sign_path(
self.hass,
f"/api/{DOMAIN}/images/{item.identifier}",
timedelta(seconds=IMAGE_EXPIRY_TIME or 1800),
),
image.mime_type,
)
async def async_browse_media(
self,

View File

@@ -3,7 +3,7 @@
from __future__ import annotations
from dataclasses import dataclass
from datetime import datetime
from datetime import datetime, timedelta
from functools import partial
import mimetypes
from pathlib import Path
@@ -13,6 +13,7 @@ from typing import Any
import voluptuous as vol
from homeassistant.components import camera, conversation, media_source
from homeassistant.components.http.auth import async_sign_path
from homeassistant.core import HomeAssistant, ServiceResponse, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.chat_session import ChatSession, async_get_chat_session
@@ -239,7 +240,11 @@ async def async_generate_image(
if IMAGE_EXPIRY_TIME > 0:
async_call_later(hass, IMAGE_EXPIRY_TIME, partial(_purge_image, filename))
service_result["url"] = get_url(hass) + f"/api/{DOMAIN}/images/{filename}"
service_result["url"] = get_url(hass) + async_sign_path(
hass,
f"/api/{DOMAIN}/images/{filename}",
timedelta(seconds=IMAGE_EXPIRY_TIME or 1800),
)
service_result["media_source_id"] = f"media-source://{DOMAIN}/images/{filename}"
return service_result

View File

@@ -2,7 +2,7 @@
from __future__ import annotations
from airos.airos8 import AirOS
from airos.airos8 import AirOS8
from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant
@@ -23,7 +23,7 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirOSConfigEntry) -> boo
# with no option in the web UI to change or upload a custom certificate.
session = async_get_clientsession(hass, verify_ssl=False)
airos_device = AirOS(
airos_device = AirOS8(
host=entry.data[CONF_HOST],
username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD],

View File

@@ -15,7 +15,7 @@ from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import AirOSConfigEntry, AirOSData, AirOSDataUpdateCoordinator
from .coordinator import AirOS8Data, AirOSConfigEntry, AirOSDataUpdateCoordinator
from .entity import AirOSEntity
_LOGGER = logging.getLogger(__name__)
@@ -27,7 +27,7 @@ PARALLEL_UPDATES = 0
class AirOSBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Describe an AirOS binary sensor."""
value_fn: Callable[[AirOSData], bool]
value_fn: Callable[[AirOS8Data], bool]
BINARY_SENSORS: tuple[AirOSBinarySensorEntityDescription, ...] = (

View File

@@ -19,7 +19,7 @@ from homeassistant.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN
from .coordinator import AirOS
from .coordinator import AirOS8
_LOGGER = logging.getLogger(__name__)
@@ -48,7 +48,7 @@ class AirOSConfigFlow(ConfigFlow, domain=DOMAIN):
# with no option in the web UI to change or upload a custom certificate.
session = async_get_clientsession(self.hass, verify_ssl=False)
airos_device = AirOS(
airos_device = AirOS8(
host=user_input[CONF_HOST],
username=user_input[CONF_USERNAME],
password=user_input[CONF_PASSWORD],

View File

@@ -4,7 +4,7 @@ from __future__ import annotations
import logging
from airos.airos8 import AirOS, AirOSData
from airos.airos8 import AirOS8, AirOS8Data
from airos.exceptions import (
AirOSConnectionAuthenticationError,
AirOSConnectionSetupError,
@@ -24,13 +24,13 @@ _LOGGER = logging.getLogger(__name__)
type AirOSConfigEntry = ConfigEntry[AirOSDataUpdateCoordinator]
class AirOSDataUpdateCoordinator(DataUpdateCoordinator[AirOSData]):
class AirOSDataUpdateCoordinator(DataUpdateCoordinator[AirOS8Data]):
"""Class to manage fetching AirOS data from single endpoint."""
config_entry: AirOSConfigEntry
def __init__(
self, hass: HomeAssistant, config_entry: AirOSConfigEntry, airos_device: AirOS
self, hass: HomeAssistant, config_entry: AirOSConfigEntry, airos_device: AirOS8
) -> None:
"""Initialize the coordinator."""
self.airos_device = airos_device
@@ -42,7 +42,7 @@ class AirOSDataUpdateCoordinator(DataUpdateCoordinator[AirOSData]):
update_interval=SCAN_INTERVAL,
)
async def _async_update_data(self) -> AirOSData:
async def _async_update_data(self) -> AirOS8Data:
"""Fetch data from AirOS."""
try:
await self.airos_device.login()

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/airos",
"iot_class": "local_polling",
"quality_scale": "bronze",
"requirements": ["airos==0.4.4"]
"requirements": ["airos==0.5.1"]
}

View File

@@ -26,7 +26,7 @@ from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from .coordinator import AirOSConfigEntry, AirOSData, AirOSDataUpdateCoordinator
from .coordinator import AirOS8Data, AirOSConfigEntry, AirOSDataUpdateCoordinator
from .entity import AirOSEntity
_LOGGER = logging.getLogger(__name__)
@@ -42,7 +42,7 @@ PARALLEL_UPDATES = 0
class AirOSSensorEntityDescription(SensorEntityDescription):
"""Describe an AirOS sensor."""
value_fn: Callable[[AirOSData], StateType]
value_fn: Callable[[AirOS8Data], StateType]
SENSORS: tuple[AirOSSensorEntityDescription, ...] = (

View File

@@ -1,4 +1,7 @@
"""Support for repeating alerts when conditions are met."""
"""Support for repeating alerts when conditions are met.
DEVELOPMENT OF THE ALERT INTEGRATION IS FROZEN.
"""
from __future__ import annotations
@@ -63,7 +66,10 @@ CONFIG_SCHEMA = vol.Schema(
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Alert component."""
"""Set up the Alert component.
DEVELOPMENT OF THE ALERT INTEGRATION IS FROZEN.
"""
component = EntityComponent[AlertEntity](LOGGER, DOMAIN, hass)
entities: list[AlertEntity] = []

View File

@@ -1,4 +1,7 @@
"""Support for repeating alerts when conditions are met."""
"""Support for repeating alerts when conditions are met.
DEVELOPMENT OF THE ALERT INTEGRATION IS FROZEN.
"""
from __future__ import annotations
@@ -27,7 +30,10 @@ from .const import DOMAIN, LOGGER
class AlertEntity(Entity):
"""Representation of an alert."""
"""Representation of an alert.
DEVELOPMENT OF THE ALERT INTEGRATION IS FROZEN.
"""
_attr_should_poll = False

View File

@@ -1,4 +1,7 @@
"""Reproduce an Alert state."""
"""Reproduce an Alert state.
DEVELOPMENT OF THE ALERT INTEGRATION IS FROZEN.
"""
from __future__ import annotations

View File

@@ -24,7 +24,12 @@ from homeassistant.components.recorder import (
get_instance as get_recorder_instance,
)
from homeassistant.config_entries import SOURCE_IGNORE
from homeassistant.const import ATTR_DOMAIN, BASE_PLATFORMS, __version__ as HA_VERSION
from homeassistant.const import (
ATTR_ASSUMED_STATE,
ATTR_DOMAIN,
BASE_PLATFORMS,
__version__ as HA_VERSION,
)
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, entity_registry as er
@@ -389,66 +394,117 @@ def _domains_from_yaml_config(yaml_configuration: dict[str, Any]) -> set[str]:
async def async_devices_payload(hass: HomeAssistant) -> dict:
"""Return the devices payload."""
devices: list[dict[str, Any]] = []
"""Return detailed information about entities and devices."""
integrations_info: dict[str, dict[str, Any]] = {}
dev_reg = dr.async_get(hass)
# Devices that need via device info set
new_indexes: dict[str, int] = {}
via_devices: dict[str, str] = {}
seen_integrations = set()
# We need to refer to other devices, for example in `via_device` field.
# We don't however send the original device ids outside of Home Assistant,
# instead we refer to devices by (integration_domain, index_in_integration_device_list).
device_id_mapping: dict[str, tuple[str, int]] = {}
for device in dev_reg.devices.values():
if not device.primary_config_entry:
for device_entry in dev_reg.devices.values():
if not device_entry.primary_config_entry:
continue
config_entry = hass.config_entries.async_get_entry(device.primary_config_entry)
config_entry = hass.config_entries.async_get_entry(
device_entry.primary_config_entry
)
if config_entry is None:
continue
seen_integrations.add(config_entry.domain)
integration_domain = config_entry.domain
integration_info = integrations_info.setdefault(
integration_domain, {"devices": [], "entities": []}
)
new_indexes[device.id] = len(devices)
devices.append(
devices_info = integration_info["devices"]
device_id_mapping[device_entry.id] = (integration_domain, len(devices_info))
devices_info.append(
{
"integration": config_entry.domain,
"manufacturer": device.manufacturer,
"model_id": device.model_id,
"model": device.model,
"sw_version": device.sw_version,
"hw_version": device.hw_version,
"has_configuration_url": device.configuration_url is not None,
"via_device": None,
"entry_type": device.entry_type.value if device.entry_type else None,
"entities": [],
"entry_type": device_entry.entry_type,
"has_configuration_url": device_entry.configuration_url is not None,
"hw_version": device_entry.hw_version,
"manufacturer": device_entry.manufacturer,
"model": device_entry.model,
"model_id": device_entry.model_id,
"sw_version": device_entry.sw_version,
"via_device": device_entry.via_device_id,
}
)
if device.via_device_id:
via_devices[device.id] = device.via_device_id
# Fill out via_device with new device ids
for integration_info in integrations_info.values():
for device_info in integration_info["devices"]:
if device_info["via_device"] is None:
continue
device_info["via_device"] = device_id_mapping.get(device_info["via_device"])
for from_device, via_device in via_devices.items():
if via_device not in new_indexes:
continue
devices[new_indexes[from_device]]["via_device"] = new_indexes[via_device]
ent_reg = er.async_get(hass)
for entity_entry in ent_reg.entities.values():
integration_domain = entity_entry.platform
integration_info = integrations_info.setdefault(
integration_domain, {"devices": [], "entities": []}
)
devices_info = integration_info["devices"]
entities_info = integration_info["entities"]
entity_state = hass.states.get(entity_entry.entity_id)
entity_info = {
# LIMITATION: `assumed_state` can be overridden by users;
# we should replace it with the original value in the future.
# It is also not present, if entity is not in the state machine,
# which can happen for disabled entities.
"assumed_state": entity_state.attributes.get(ATTR_ASSUMED_STATE, False)
if entity_state is not None
else None,
"capabilities": entity_entry.capabilities,
"domain": entity_entry.domain,
"entity_category": entity_entry.entity_category,
"has_entity_name": entity_entry.has_entity_name,
"original_device_class": entity_entry.original_device_class,
# LIMITATION: `unit_of_measurement` can be overridden by users;
# we should replace it with the original value in the future.
"unit_of_measurement": entity_entry.unit_of_measurement,
}
if (
((device_id := entity_entry.device_id) is not None)
and ((new_device_id := device_id_mapping.get(device_id)) is not None)
and (new_device_id[0] == integration_domain)
):
device_info = devices_info[new_device_id[1]]
device_info["entities"].append(entity_info)
else:
entities_info.append(entity_info)
integrations = {
domain: integration
for domain, integration in (
await async_get_integrations(hass, seen_integrations)
await async_get_integrations(hass, integrations_info.keys())
).items()
if isinstance(integration, Integration)
}
for device_info in devices:
if integration := integrations.get(device_info["integration"]):
device_info["is_custom_integration"] = not integration.is_built_in
for domain, integration_info in integrations_info.items():
if integration := integrations.get(domain):
integration_info["is_custom_integration"] = not integration.is_built_in
# Include version for custom integrations
if not integration.is_built_in and integration.version:
device_info["custom_integration_version"] = str(integration.version)
integration_info["custom_integration_version"] = str(
integration.version
)
return {
"version": "home-assistant:1",
"home_assistant": HA_VERSION,
"devices": devices,
"integrations": integrations_info,
}

View File

@@ -66,9 +66,14 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
if user_input is not None:
self.host = user_input[CONF_HOST]
api = create_api(self.hass, self.host, enable_ime=False)
await api.async_generate_cert_if_missing()
try:
await api.async_generate_cert_if_missing()
self.name, self.mac = await api.async_get_name_and_mac()
except CannotConnect:
# Likely invalid IP address or device is network unreachable. Stay
# in the user step allowing the user to enter a different host.
errors["base"] = "cannot_connect"
else:
await self.async_set_unique_id(format_mac(self.mac))
if self.source == SOURCE_RECONFIGURE:
self._abort_if_unique_id_mismatch()
@@ -81,11 +86,10 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
},
)
self._abort_if_unique_id_configured(updates={CONF_HOST: self.host})
return await self._async_start_pair()
except (CannotConnect, ConnectionClosed):
# Likely invalid IP address or device is network unreachable. Stay
# in the user step allowing the user to enter a different host.
errors["base"] = "cannot_connect"
try:
return await self._async_start_pair()
except (CannotConnect, ConnectionClosed):
errors["base"] = "cannot_connect"
else:
user_input = {}
default_host = user_input.get(CONF_HOST, vol.UNDEFINED)
@@ -112,22 +116,9 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle the pair step."""
errors: dict[str, str] = {}
if user_input is not None:
pin = user_input["pin"]
try:
pin = user_input["pin"]
await self.api.async_finish_pairing(pin)
if self.source == SOURCE_REAUTH:
return self.async_update_reload_and_abort(
self._get_reauth_entry(), reload_even_if_entry_is_unchanged=True
)
return self.async_create_entry(
title=self.name,
data={
CONF_HOST: self.host,
CONF_NAME: self.name,
CONF_MAC: self.mac,
},
)
except InvalidAuth:
# Invalid PIN. Stay in the pair step allowing the user to enter
# a different PIN.
@@ -145,6 +136,20 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
# them to enter a new IP address but we cannot do that for the zeroconf
# flow. Simpler to abort for both flows.
return self.async_abort(reason="cannot_connect")
else:
if self.source == SOURCE_REAUTH:
return self.async_update_reload_and_abort(
self._get_reauth_entry(), reload_even_if_entry_is_unchanged=True
)
return self.async_create_entry(
title=self.name,
data={
CONF_HOST: self.host,
CONF_NAME: self.name,
CONF_MAC: self.mac,
},
)
return self.async_show_form(
step_id="pair",
data_schema=STEP_PAIR_DATA_SCHEMA,

View File

@@ -6,7 +6,7 @@ from typing import Any
from androidtvremote2 import AndroidTVRemote, ConnectionClosed
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME
from homeassistant.const import CONF_MAC, CONF_NAME
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
@@ -28,8 +28,6 @@ class AndroidTVRemoteBaseEntity(Entity):
) -> None:
"""Initialize the entity."""
self._api = api
self._host = config_entry.data[CONF_HOST]
self._name = config_entry.data[CONF_NAME]
self._apps: dict[str, Any] = config_entry.options.get(CONF_APPS, {})
self._attr_unique_id = config_entry.unique_id
self._attr_is_on = api.is_on
@@ -39,7 +37,7 @@ class AndroidTVRemoteBaseEntity(Entity):
self._attr_device_info = DeviceInfo(
connections={(CONNECTION_NETWORK_MAC, config_entry.data[CONF_MAC])},
identifiers={(DOMAIN, config_entry.unique_id)},
name=self._name,
name=config_entry.data[CONF_NAME],
manufacturer=device_info["manufacturer"],
model=device_info["model"],
)

View File

@@ -175,7 +175,11 @@ class AndroidTVRemoteMediaPlayerEntity(AndroidTVRemoteBaseEntity, MediaPlayerEnt
"""Play a piece of media."""
if media_type == MediaType.CHANNEL:
if not media_id.isnumeric():
raise ValueError(f"Channel must be numeric: {media_id}")
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="invalid_channel",
translation_placeholders={"media_id": media_id},
)
if self._channel_set_task:
self._channel_set_task.cancel()
self._channel_set_task = asyncio.create_task(
@@ -188,7 +192,11 @@ class AndroidTVRemoteMediaPlayerEntity(AndroidTVRemoteBaseEntity, MediaPlayerEnt
self._send_launch_app_command(media_id)
return
raise ValueError(f"Invalid media type: {media_type}")
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="invalid_media_type",
translation_placeholders={"media_type": media_type},
)
async def async_browse_media(
self,

View File

@@ -22,7 +22,7 @@
},
"zeroconf_confirm": {
"title": "Discovered Android TV",
"description": "Do you want to add the Android TV ({name}) to Home Assistant? It will turn on and a pairing code will be displayed on it that you will need to enter in the next screen."
"description": "Do you want to add the Android TV ({name}) to Home Assistant? It will turn on and a pairing code will be displayed on it that you will need to enter in the next screen."
},
"pair": {
"description": "Enter the pairing code displayed on the Android TV ({name}).",
@@ -85,6 +85,12 @@
"exceptions": {
"connection_closed": {
"message": "Connection to the Android TV device is closed"
},
"invalid_channel": {
"message": "Channel must be numeric: {media_id}"
},
"invalid_media_type": {
"message": "Invalid media type: {media_type}"
}
}
}

View File

@@ -5,5 +5,5 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/aosmith",
"iot_class": "cloud_polling",
"requirements": ["py-aosmith==1.0.12"]
"requirements": ["py-aosmith==1.0.14"]
}

View File

@@ -9,7 +9,7 @@ from homeassistant.core import HomeAssistant
from .coordinator import APCUPSdConfigEntry, APCUPSdCoordinator
PLATFORMS: Final = (Platform.BINARY_SENSOR, Platform.SENSOR)
PLATFORMS: Final = [Platform.BINARY_SENSOR, Platform.SENSOR]
async def async_setup_entry(

View File

@@ -100,6 +100,7 @@ class APCUPSdCoordinator(DataUpdateCoordinator[APCUPSdData]):
name=self.data.name or "APC UPS",
hw_version=self.data.get("FIRMWARE"),
sw_version=self.data.get("VERSION"),
serial_number=self.data.serial_no,
)
async def _async_update_data(self) -> APCUPSdData:

View File

@@ -6,6 +6,6 @@
"documentation": "https://www.home-assistant.io/integrations/apcupsd",
"iot_class": "local_polling",
"loggers": ["apcaccess"],
"quality_scale": "bronze",
"quality_scale": "platinum",
"requirements": ["aioapcaccess==0.4.2"]
}

View File

@@ -43,10 +43,7 @@ rules:
status: exempt
comment: |
The integration does not require authentication.
test-coverage:
status: todo
comment: |
Patch `aioapcaccess.request_status` where we use it.
test-coverage: done
# Gold
devices: done
diagnostics: done

View File

@@ -2,7 +2,7 @@
"domain": "assist_pipeline",
"name": "Assist pipeline",
"after_dependencies": ["repairs"],
"codeowners": ["@balloob", "@synesthesiam"],
"codeowners": ["@synesthesiam", "@arturpragacz"],
"dependencies": ["conversation", "stt", "tts", "wake_word"],
"documentation": "https://www.home-assistant.io/integrations/assist_pipeline",
"integration_type": "system",

View File

@@ -1,7 +1,7 @@
{
"domain": "assist_satellite",
"name": "Assist Satellite",
"codeowners": ["@home-assistant/core", "@synesthesiam"],
"codeowners": ["@home-assistant/core", "@synesthesiam", "@arturpragacz"],
"dependencies": ["assist_pipeline", "http", "stt", "tts"],
"documentation": "https://www.home-assistant.io/integrations/assist_satellite",
"integration_type": "entity",

View File

@@ -124,6 +124,8 @@ class AsusWrtBridge(ABC):
self._firmware: str | None = None
self._label_mac: str | None = None
self._model: str | None = None
self._model_id: str | None = None
self._serial_number: str | None = None
@property
def host(self) -> str:
@@ -145,6 +147,16 @@ class AsusWrtBridge(ABC):
"""Return model information."""
return self._model
@property
def model_id(self) -> str | None:
"""Return model_id information."""
return self._model_id
@property
def serial_number(self) -> str | None:
"""Return serial number information."""
return self._serial_number
@property
@abstractmethod
def is_connected(self) -> bool:
@@ -361,6 +373,8 @@ class AsusWrtHttpBridge(AsusWrtBridge):
self._label_mac = format_mac(mac)
self._firmware = str(_identity.firmware)
self._model = _identity.model
self._model_id = _identity.product_id
self._serial_number = _identity.serial
async def async_disconnect(self) -> None:
"""Disconnect to the device."""

View File

@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["aioasuswrt", "asusrouter", "asyncssh"],
"requirements": ["aioasuswrt==1.4.0", "asusrouter==1.20.1"]
"requirements": ["aioasuswrt==1.4.0", "asusrouter==1.21.0"]
}

View File

@@ -391,6 +391,8 @@ class AsusWrtRouter:
identifiers={(DOMAIN, self._entry.unique_id or "AsusWRT")},
name=self.host,
model=self._api.model or "Asus Router",
model_id=self._api.model_id,
serial_number=self._api.serial_number,
manufacturer="Asus",
configuration_url=f"http://{self.host}",
)

View File

@@ -555,10 +555,6 @@ class BayesianBinarySensor(BinarySensorEntity):
for observation in self._observations:
if observation.value_template is None:
continue
if isinstance(observation.value_template, str):
observation.value_template = Template(
observation.value_template, hass=self.hass
)
template = observation.value_template
observations_by_template.setdefault(template, []).append(observation)

View File

@@ -6,7 +6,7 @@
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/bluesound",
"iot_class": "local_polling",
"requirements": ["pyblu==2.0.4"],
"requirements": ["pyblu==2.0.5"],
"zeroconf": [
{
"type": "_musc._tcp.local."

View File

@@ -321,8 +321,14 @@ class BluesoundPlayer(CoordinatorEntity[BluesoundCoordinator], MediaPlayerEntity
if self.available is False or (self.is_grouped and not self.is_leader):
return None
sources = [x.text for x in self._inputs]
sources += [x.name for x in self._presets]
sources = [x.name for x in self._presets]
# ignore if both id and text are None
for input_ in self._inputs:
if input_.text is not None:
sources.append(input_.text)
elif input_.id is not None:
sources.append(input_.id)
return sources
@@ -340,7 +346,7 @@ class BluesoundPlayer(CoordinatorEntity[BluesoundCoordinator], MediaPlayerEntity
input_.id == self._status.input_id
or input_.url == self._status.stream_url
):
return input_.text
return input_.text if input_.text is not None else input_.id
for preset in self._presets:
if preset.url == self._status.stream_url:
@@ -537,7 +543,7 @@ class BluesoundPlayer(CoordinatorEntity[BluesoundCoordinator], MediaPlayerEntity
# presets and inputs might have the same name; presets have priority
for input_ in self._inputs:
if input_.text == source:
if source in (input_.text, input_.id):
await self._player.play_url(input_.url)
return
for preset in self._presets:

View File

@@ -21,6 +21,6 @@
"bluetooth-auto-recovery==1.5.2",
"bluetooth-data-tools==1.28.2",
"dbus-fast==2.44.3",
"habluetooth==5.3.0"
"habluetooth==5.3.1"
]
}

View File

@@ -8,8 +8,10 @@ import time
from typing import Any
from habluetooth import (
BaseHaScanner,
BluetoothScanningMode,
HaBluetoothSlotAllocations,
HaScannerModeChange,
HaScannerRegistration,
HaScannerRegistrationEvent,
)
@@ -27,12 +29,54 @@ from .models import BluetoothChange
from .util import InvalidConfigEntryID, InvalidSource, config_entry_id_to_source
@callback
def _async_get_source_from_config_entry(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg_id: int,
config_entry_id: str | None,
validate_source: bool = True,
) -> str | None:
"""Get source from config entry id.
Returns None if no config_entry_id provided or on error (after sending error response).
If validate_source is True, also validates that the scanner exists.
"""
if not config_entry_id:
return None
if validate_source:
# Use the full validation that checks if scanner exists
try:
return config_entry_id_to_source(hass, config_entry_id)
except InvalidConfigEntryID as err:
connection.send_error(msg_id, "invalid_config_entry_id", str(err))
return None
except InvalidSource as err:
connection.send_error(msg_id, "invalid_source", str(err))
return None
# Just check if config entry exists and belongs to bluetooth
if (
not (entry := hass.config_entries.async_get_entry(config_entry_id))
or entry.domain != DOMAIN
):
connection.send_error(
msg_id,
"invalid_config_entry_id",
f"Config entry {config_entry_id} not found",
)
return None
return entry.unique_id
@callback
def async_setup(hass: HomeAssistant) -> None:
"""Set up the bluetooth websocket API."""
websocket_api.async_register_command(hass, ws_subscribe_advertisements)
websocket_api.async_register_command(hass, ws_subscribe_connection_allocations)
websocket_api.async_register_command(hass, ws_subscribe_scanner_details)
websocket_api.async_register_command(hass, ws_subscribe_scanner_state)
@lru_cache(maxsize=1024)
@@ -180,16 +224,12 @@ async def ws_subscribe_connection_allocations(
) -> None:
"""Handle subscribe advertisements websocket command."""
ws_msg_id = msg["id"]
source: str | None = None
if config_entry_id := msg.get("config_entry_id"):
try:
source = config_entry_id_to_source(hass, config_entry_id)
except InvalidConfigEntryID as err:
connection.send_error(ws_msg_id, "invalid_config_entry_id", str(err))
return
except InvalidSource as err:
connection.send_error(ws_msg_id, "invalid_source", str(err))
return
config_entry_id = msg.get("config_entry_id")
source = _async_get_source_from_config_entry(
hass, connection, ws_msg_id, config_entry_id
)
if config_entry_id and source is None:
return # Error already sent by helper
def _async_allocations_changed(allocations: HaBluetoothSlotAllocations) -> None:
connection.send_message(
@@ -220,20 +260,12 @@ async def ws_subscribe_scanner_details(
) -> None:
"""Handle subscribe scanner details websocket command."""
ws_msg_id = msg["id"]
source: str | None = None
if config_entry_id := msg.get("config_entry_id"):
if (
not (entry := hass.config_entries.async_get_entry(config_entry_id))
or entry.domain != DOMAIN
):
connection.send_error(
ws_msg_id,
"invalid_config_entry_id",
f"Invalid config entry id: {config_entry_id}",
)
return
source = entry.unique_id
assert source is not None
config_entry_id = msg.get("config_entry_id")
source = _async_get_source_from_config_entry(
hass, connection, ws_msg_id, config_entry_id, validate_source=False
)
if config_entry_id and source is None:
return # Error already sent by helper
def _async_event_message(message: dict[str, Any]) -> None:
connection.send_message(
@@ -260,3 +292,70 @@ async def ws_subscribe_scanner_details(
]
):
_async_event_message({"add": matching_scanners})
@websocket_api.require_admin
@websocket_api.websocket_command(
{
vol.Required("type"): "bluetooth/subscribe_scanner_state",
vol.Optional("config_entry_id"): str,
}
)
@websocket_api.async_response
async def ws_subscribe_scanner_state(
hass: HomeAssistant, connection: websocket_api.ActiveConnection, msg: dict[str, Any]
) -> None:
"""Handle subscribe scanner state websocket command."""
ws_msg_id = msg["id"]
config_entry_id = msg.get("config_entry_id")
source = _async_get_source_from_config_entry(
hass, connection, ws_msg_id, config_entry_id, validate_source=False
)
if config_entry_id and source is None:
return # Error already sent by helper
@callback
def _async_send_scanner_state(
scanner: BaseHaScanner,
current_mode: BluetoothScanningMode | None,
requested_mode: BluetoothScanningMode | None,
) -> None:
payload = {
"source": scanner.source,
"adapter": scanner.adapter,
"current_mode": current_mode.value if current_mode else None,
"requested_mode": requested_mode.value if requested_mode else None,
}
connection.send_message(
json_bytes(
websocket_api.event_message(
ws_msg_id,
payload,
)
)
)
@callback
def _async_scanner_state_changed(mode_change: HaScannerModeChange) -> None:
_async_send_scanner_state(
mode_change.scanner,
mode_change.current_mode,
mode_change.requested_mode,
)
manager = _get_manager(hass)
connection.subscriptions[ws_msg_id] = (
manager.async_register_scanner_mode_change_callback(
_async_scanner_state_changed, source
)
)
connection.send_message(json_bytes(websocket_api.result_message(ws_msg_id)))
# Send initial state for all matching scanners
for scanner in manager.async_current_scanners():
if source is None or scanner.source == source:
_async_send_scanner_state(
scanner,
scanner.current_mode,
scanner.requested_mode,
)

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive",
"iot_class": "cloud_polling",
"loggers": ["bimmer_connected"],
"requirements": ["bimmer-connected[china]==0.17.2"]
"requirements": ["bimmer-connected[china]==0.17.3"]
}

View File

@@ -8,7 +8,7 @@
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["brother", "pyasn1", "pysmi", "pysnmp"],
"requirements": ["brother==5.0.1"],
"requirements": ["brother==5.1.0"],
"zeroconf": [
{
"type": "_printer._tcp.local.",

View File

@@ -85,10 +85,10 @@
"entity": {
"sensor": {
"current_temperature": {
"name": "Current Temperature"
"name": "Current temperature"
},
"outside_temperature": {
"name": "Outside Temperature"
"name": "Outside temperature"
}
}
}

View File

@@ -81,7 +81,11 @@ from .const import (
)
from .helper import get_camera_from_entity_id
from .img_util import scale_jpeg_camera_image
from .prefs import CameraPreferences, DynamicStreamSettings # noqa: F401
from .prefs import (
CameraPreferences,
DynamicStreamSettings, # noqa: F401
get_dynamic_camera_stream_settings,
)
from .webrtc import (
DATA_ICE_SERVERS,
CameraWebRTCProvider,
@@ -550,9 +554,9 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
self.hass,
source,
options=self.stream_options,
dynamic_stream_settings=await self.hass.data[
DATA_CAMERA_PREFS
].get_dynamic_stream_settings(self.entity_id),
dynamic_stream_settings=await get_dynamic_camera_stream_settings(
self.hass, self.entity_id
),
stream_label=self.entity_id,
)
self.stream.set_update_callback(self.async_write_ha_state)
@@ -942,9 +946,7 @@ async def websocket_get_prefs(
hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
) -> None:
"""Handle request for account info."""
stream_prefs = await hass.data[DATA_CAMERA_PREFS].get_dynamic_stream_settings(
msg["entity_id"]
)
stream_prefs = await get_dynamic_camera_stream_settings(hass, msg["entity_id"])
connection.send_result(msg["id"], asdict(stream_prefs))

View File

@@ -13,7 +13,7 @@ from homeassistant.helpers import entity_registry as er
from homeassistant.helpers.storage import Store
from homeassistant.helpers.typing import UNDEFINED, UndefinedType
from .const import DOMAIN, PREF_ORIENTATION, PREF_PRELOAD_STREAM
from .const import DATA_CAMERA_PREFS, DOMAIN, PREF_ORIENTATION, PREF_PRELOAD_STREAM
STORAGE_KEY: Final = DOMAIN
STORAGE_VERSION: Final = 1
@@ -106,3 +106,12 @@ class CameraPreferences:
)
self._dynamic_stream_settings_by_entity_id[entity_id] = settings
return settings
async def get_dynamic_camera_stream_settings(
hass: HomeAssistant, entity_id: str
) -> DynamicStreamSettings:
"""Get dynamic stream settings for a camera entity."""
if DATA_CAMERA_PREFS not in hass.data:
raise HomeAssistantError("Camera integration not set up")
return await hass.data[DATA_CAMERA_PREFS].get_dynamic_stream_settings(entity_id)

View File

@@ -3,7 +3,8 @@
import logging
import threading
import pychromecast
import pychromecast.discovery
import pychromecast.models
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EVENT_HOMEASSISTANT_STOP

View File

@@ -11,10 +11,13 @@ from uuid import UUID
import aiohttp
import attr
import pychromecast
from pychromecast import dial
from pychromecast.const import CAST_TYPE_GROUP
import pychromecast.controllers.media
import pychromecast.controllers.multizone
import pychromecast.controllers.receiver
from pychromecast.models import CastInfo
import pychromecast.socket_client
from homeassistant.core import HomeAssistant
from homeassistant.helpers import aiohttp_client

View File

@@ -10,8 +10,10 @@ import json
import logging
from typing import TYPE_CHECKING, Any, Concatenate
import pychromecast
import pychromecast.config
import pychromecast.const
from pychromecast.controllers.homeassistant import HomeAssistantController
import pychromecast.controllers.media
from pychromecast.controllers.media import (
MEDIA_PLAYER_ERROR_CODES,
MEDIA_PLAYER_STATE_BUFFERING,

View File

@@ -274,16 +274,16 @@
"message": "Provided temperature {check_temp} is not valid. Accepted range is {min_temp} to {max_temp}."
},
"low_temp_higher_than_high_temp": {
"message": "Target temperature low can not be higher than Target temperature high."
"message": "'Lower target temperature' can not be higher than 'Upper target temperature'."
},
"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."
"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."
"message": "Set temperature action was used with the 'Lower/Upper target temperature' parameter but the entity does not support it."
}
}
}

View File

@@ -35,7 +35,7 @@ from hassil.recognize import (
)
from hassil.string_matcher import UnmatchedRangeEntity, UnmatchedTextEntity
from hassil.trie import Trie
from hassil.util import merge_dict
from hassil.util import merge_dict, remove_punctuation
from home_assistant_intents import (
ErrorKey,
FuzzyConfig,
@@ -327,12 +327,10 @@ class DefaultAgent(ConversationEntity):
if self._exposed_names_trie is not None:
# Filter by input string
text_lower = user_input.text.strip().lower()
text = remove_punctuation(user_input.text).strip().lower()
slot_lists["name"] = TextSlotList(
name="name",
values=[
result[2] for result in self._exposed_names_trie.find(text_lower)
],
values=[result[2] for result in self._exposed_names_trie.find(text)],
)
start = time.monotonic()
@@ -1263,7 +1261,7 @@ class DefaultAgent(ConversationEntity):
name_list = TextSlotList.from_tuples(exposed_entity_names, allow_template=False)
for name_value in name_list.values:
assert isinstance(name_value.text_in, TextChunk)
name_text = name_value.text_in.text.strip().lower()
name_text = remove_punctuation(name_value.text_in.text).strip().lower()
self._exposed_names_trie.insert(name_text, name_value)
self._slot_lists = {

View File

@@ -1,7 +1,7 @@
{
"domain": "conversation",
"name": "Conversation",
"codeowners": ["@home-assistant/core", "@synesthesiam"],
"codeowners": ["@home-assistant/core", "@synesthesiam", "@arturpragacz"],
"dependencies": ["http", "intent"],
"documentation": "https://www.home-assistant.io/integrations/conversation",
"integration_type": "system",

View File

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

View File

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

View File

@@ -218,6 +218,12 @@ ECOWITT_SENSORS_MAPPING: Final = {
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
EcoWittSensorTypes.SOIL_MOISTURE: SensorEntityDescription(
key="SOIL_MOISTURE",
device_class=SensorDeviceClass.MOISTURE,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
),
}

View File

@@ -48,6 +48,7 @@ VALID_ENERGY_UNITS_GAS = {
UnitOfVolume.CUBIC_FEET,
UnitOfVolume.CUBIC_METERS,
UnitOfVolume.LITERS,
UnitOfVolume.MILLE_CUBIC_FEET,
*VALID_ENERGY_UNITS,
}
VALID_VOLUME_UNITS_WATER: set[str] = {
@@ -56,6 +57,7 @@ VALID_VOLUME_UNITS_WATER: set[str] = {
UnitOfVolume.CUBIC_METERS,
UnitOfVolume.GALLONS,
UnitOfVolume.LITERS,
UnitOfVolume.MILLE_CUBIC_FEET,
}
_LOGGER = logging.getLogger(__name__)

View File

@@ -42,6 +42,7 @@ GAS_USAGE_UNITS: dict[str, tuple[UnitOfEnergy | UnitOfVolume, ...]] = {
UnitOfVolume.CUBIC_FEET,
UnitOfVolume.CUBIC_METERS,
UnitOfVolume.LITERS,
UnitOfVolume.MILLE_CUBIC_FEET,
),
}
GAS_PRICE_UNITS = tuple(
@@ -57,6 +58,7 @@ WATER_USAGE_UNITS: dict[str, tuple[UnitOfVolume, ...]] = {
UnitOfVolume.CUBIC_METERS,
UnitOfVolume.GALLONS,
UnitOfVolume.LITERS,
UnitOfVolume.MILLE_CUBIC_FEET,
),
}
WATER_PRICE_UNITS = tuple(

View File

@@ -118,7 +118,6 @@ async def async_get_config_entry_diagnostics(
device_dict.pop("_cache", None)
# This can be removed when suggested_area is removed from DeviceEntry
device_dict.pop("_suggested_area")
device_dict.pop("is_new", None)
device_entities.append({"device": device_dict, "entities": entities})
# remove envoy serial

View File

@@ -17,7 +17,7 @@
"mqtt": ["esphome/discover/#"],
"quality_scale": "platinum",
"requirements": [
"aioesphomeapi==39.0.1",
"aioesphomeapi==40.0.1",
"esphome-dashboard-api==1.3.0",
"bleak-esphome==3.2.0"
],

View File

@@ -1,7 +1,7 @@
{
"domain": "evil_genius_labs",
"name": "Evil Genius Labs",
"codeowners": ["@balloob"],
"codeowners": [],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/evil_genius_labs",
"iot_class": "local_polling",

View File

@@ -14,6 +14,9 @@
"toggle": "[%key:common::device_automation::action_type::toggle%]",
"turn_on": "[%key:common::device_automation::action_type::turn_on%]",
"turn_off": "[%key:common::device_automation::action_type::turn_off%]"
},
"extra_fields": {
"for": "[%key:common::device_automation::extra_fields::for%]"
}
},
"entity_component": {

View File

@@ -14,5 +14,5 @@
"documentation": "https://www.home-assistant.io/integrations/fjaraskupan",
"iot_class": "local_polling",
"loggers": ["bleak", "fjaraskupan"],
"requirements": ["fjaraskupan==2.3.2"]
"requirements": ["fjaraskupan==2.3.3"]
}

View File

@@ -190,7 +190,7 @@ class FloDeviceDataUpdateCoordinator(DataUpdateCoordinator):
return bool(
self.pending_info_alerts_count
or self.pending_warning_alerts_count
or self.pending_warning_alerts_count
or self.pending_critical_alerts_count
)
@property

View File

@@ -1,7 +1,7 @@
{
"domain": "foscam",
"name": "Foscam",
"codeowners": ["@krmarien"],
"codeowners": ["@Foscam-wangzhengyu"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/foscam",
"iot_class": "local_polling",

View File

@@ -151,7 +151,7 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
configuration_url=f"http://{self.host}",
connections={(dr.CONNECTION_NETWORK_MAC, self.mac)},
identifiers={(DOMAIN, self.unique_id)},
manufacturer="AVM",
manufacturer="FRITZ!",
model=self.model,
name=self.config_entry.title,
sw_version=self.current_firmware,
@@ -471,7 +471,7 @@ class FritzBoxTools(DataUpdateCoordinator[UpdateCoordinatorDataType]):
dr.async_get(self.hass).async_get_or_create(
config_entry_id=self.config_entry.entry_id,
connections={(CONNECTION_NETWORK_MAC, dev_mac)},
default_manufacturer="AVM",
default_manufacturer="FRITZ!",
default_model="FRITZ!Box Tracked device",
default_name=device.hostname,
via_device=(DOMAIN, self.unique_id),

View File

@@ -125,7 +125,7 @@ class FritzBoxBaseCoordinatorEntity(CoordinatorEntity[AvmWrapper]):
configuration_url=f"http://{self.coordinator.host}",
connections={(dr.CONNECTION_NETWORK_MAC, self.coordinator.mac)},
identifiers={(DOMAIN, self.coordinator.unique_id)},
manufacturer="AVM",
manufacturer="FRITZ!",
model=self.coordinator.model,
name=self._device_name,
sw_version=self.coordinator.current_firmware,

View File

@@ -1,13 +1,13 @@
{
"domain": "fritz",
"name": "AVM FRITZ!Box Tools",
"name": "FRITZ!Box Tools",
"codeowners": ["@AaronDavidSchneider", "@chemelli74", "@mib1185"],
"config_flow": true,
"dependencies": ["network"],
"documentation": "https://www.home-assistant.io/integrations/fritz",
"iot_class": "local_polling",
"loggers": ["fritzconnection"],
"requirements": ["fritzconnection[qr]==1.14.0", "xmltodict==0.13.0"],
"requirements": ["fritzconnection[qr]==1.15.0", "xmltodict==0.13.0"],
"ssdp": [
{
"st": "urn:schemas-upnp-org:device:fritzbox:1"

View File

@@ -28,7 +28,7 @@
}
},
"reauth_confirm": {
"title": "Updating FRITZ!Box Tools - credentials",
"title": "FRITZ!Box Tools - Update credentials",
"description": "Update FRITZ!Box Tools credentials for: {host}.\n\nFRITZ!Box Tools is unable to log in to your FRITZ!Box.",
"data": {
"username": "[%key:common::config_flow::data::username%]",
@@ -40,7 +40,7 @@
}
},
"reconfigure": {
"title": "Updating FRITZ!Box Tools - configuration",
"title": "FRITZ!Box Tools - Update configuration",
"description": "Update FRITZ!Box Tools configuration for: {host}.",
"data": {
"host": "[%key:common::config_flow::data::host%]",
@@ -183,8 +183,8 @@
"description": "Sets a new password for the guest Wi-Fi. The password must be between 8 and 63 characters long. If no additional parameter is set, the password will be auto-generated with a length of 12 characters.",
"fields": {
"device_id": {
"name": "Fritz!Box Device",
"description": "Select the Fritz!Box to configure."
"name": "FRITZ!Box device",
"description": "Select the FRITZ!Box to configure."
},
"password": {
"name": "[%key:common::config_flow::data::password%]",
@@ -192,7 +192,7 @@
},
"length": {
"name": "Password length",
"description": "Length of the new password. The password will be auto-generated, if no password is set."
"description": "Length of the new password. It will be auto-generated if no password is set."
}
}
}

View File

@@ -276,7 +276,7 @@ class FritzBoxBaseCoordinatorSwitch(CoordinatorEntity[AvmWrapper], SwitchEntity)
configuration_url=f"http://{self.coordinator.host}",
connections={(CONNECTION_NETWORK_MAC, self.coordinator.mac)},
identifiers={(DOMAIN, self.coordinator.unique_id)},
manufacturer="AVM",
manufacturer="FRITZ!",
model=self.coordinator.model,
name=self._device_name,
sw_version=self.coordinator.current_firmware,

View File

@@ -49,7 +49,7 @@ class FritzBoxTemplate(FritzBoxEntity, ButtonEntity):
name=self.data.name,
identifiers={(DOMAIN, self.ain)},
configuration_url=self.coordinator.configuration_url,
manufacturer="AVM",
manufacturer="FRITZ!",
model="SmartHome Template",
)

View File

@@ -1,6 +1,6 @@
{
"domain": "fritzbox",
"name": "AVM FRITZ!SmartHome",
"name": "FRITZ!SmartHome",
"codeowners": ["@mib1185", "@flabbamann"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/fritzbox",

View File

@@ -3,7 +3,7 @@
"flow_title": "{name}",
"step": {
"user": {
"description": "Enter your AVM FRITZ!Box information.",
"description": "Enter your FRITZ!Box information.",
"data": {
"host": "[%key:common::config_flow::data::host%]",
"username": "[%key:common::config_flow::data::username%]",
@@ -42,7 +42,7 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"ignore_ip6_link_local": "IPv6 link local address is not supported.",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"not_supported": "Connected to AVM FRITZ!Box but it's unable to control Smart Home devices.",
"not_supported": "Connected to FRITZ!Box but it's unable to control Smart Home devices.",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]"
},

View File

@@ -35,7 +35,7 @@ async def async_setup_entry(
except FritzSecurityError as ex:
_LOGGER.error(
(
"User has insufficient permissions to access AVM FRITZ!Box settings and"
"User has insufficient permissions to access FRITZ!Box settings and"
" its phonebooks: %s"
),
ex,
@@ -44,7 +44,7 @@ async def async_setup_entry(
except FritzConnectionException as ex:
raise ConfigEntryAuthFailed from ex
except RequestsConnectionError as ex:
_LOGGER.error("Unable to connect to AVM FRITZ!Box call monitor: %s", ex)
_LOGGER.error("Unable to connect to FRITZ!Box call monitor: %s", ex)
raise ConfigEntryNotReady from ex
config_entry.runtime_data = fritzbox_phonebook

View File

@@ -35,6 +35,6 @@ DEFAULT_PHONEBOOK = 0
DEFAULT_NAME = "Phone"
DOMAIN: Final = "fritzbox_callmonitor"
MANUFACTURER: Final = "AVM"
MANUFACTURER: Final = "FRITZ!"
PLATFORMS = [Platform.SENSOR]

View File

@@ -1,11 +1,11 @@
{
"domain": "fritzbox_callmonitor",
"name": "AVM FRITZ!Box Call Monitor",
"name": "FRITZ!Box Call Monitor",
"codeowners": ["@cdce8p"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/fritzbox_callmonitor",
"integration_type": "device",
"iot_class": "local_polling",
"loggers": ["fritzconnection"],
"requirements": ["fritzconnection[qr]==1.14.0"]
"requirements": ["fritzconnection[qr]==1.15.0"]
}

View File

@@ -28,7 +28,7 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"insufficient_permissions": "User has insufficient permissions to access AVM FRITZ!Box settings and its phonebooks.",
"insufficient_permissions": "User has insufficient permissions to access FRITZ!Box settings and its phonebooks.",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
},
"error": {

View File

@@ -38,6 +38,8 @@ from homeassistant.util.hass_dict import HassKey
from .storage import async_setup_frontend_storage
_LOGGER = logging.getLogger(__name__)
DOMAIN = "frontend"
CONF_THEMES = "themes"
CONF_THEMES_MODES = "modes"
@@ -73,9 +75,11 @@ VALUE_NO_THEME = "none"
PRIMARY_COLOR = "primary-color"
_LOGGER = logging.getLogger(__name__)
EXTENDED_THEME_SCHEMA = vol.Schema(
LEGACY_THEME_SCHEMA = vol.Any(
# Legacy theme scheme
{cv.string: cv.string},
# New extended schema with mode support
{
# Theme variables that apply to all modes
cv.string: cv.string,
@@ -86,28 +90,46 @@ EXTENDED_THEME_SCHEMA = vol.Schema(
vol.Optional(CONF_THEMES_DARK): vol.Schema({cv.string: cv.string}),
}
),
}
},
)
THEME_SCHEMA = vol.Schema(
{
cv.string: (
vol.Any(
# Legacy theme scheme
{cv.string: cv.string},
# New extended schema with mode support
EXTENDED_THEME_SCHEMA,
)
)
# Theme variables that apply to all modes
cv.string: cv.string,
# Mode specific theme variables
vol.Optional(CONF_THEMES_MODES): vol.All(
{
vol.Optional(CONF_THEMES_LIGHT): vol.Schema({cv.string: cv.string}),
vol.Optional(CONF_THEMES_DARK): vol.Schema({cv.string: cv.string}),
},
cv.has_at_least_one_key(CONF_THEMES_LIGHT, CONF_THEMES_DARK),
),
}
)
def _validate_themes(themes: dict) -> dict[str, Any]:
"""Validate themes."""
validated_themes = {}
for theme_name, theme in themes.items():
theme_name = cv.string(theme_name)
LEGACY_THEME_SCHEMA(theme)
try:
validated_themes[theme_name] = THEME_SCHEMA(theme)
except vol.Invalid as err:
_LOGGER.error("Theme %s is invalid: %s", theme_name, err)
return validated_themes
CONFIG_SCHEMA = vol.Schema(
{
DOMAIN: vol.Schema(
{
vol.Optional(CONF_FRONTEND_REPO): cv.isdir,
vol.Optional(CONF_THEMES): THEME_SCHEMA,
vol.Optional(CONF_THEMES): vol.All(dict, _validate_themes),
vol.Optional(CONF_EXTRA_MODULE_URL): vol.All(
cv.ensure_list, [cv.string]
),
@@ -546,7 +568,7 @@ async def _async_setup_themes(
new_themes = config.get(DOMAIN, {}).get(CONF_THEMES, {})
try:
THEME_SCHEMA(new_themes)
new_themes = _validate_themes(new_themes)
except vol.Invalid as err:
raise HomeAssistantError(f"Failed to reload themes: {err}") from err

View File

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

View File

@@ -31,7 +31,9 @@ from homeassistant.components.camera import (
WebRTCSendMessage,
async_register_webrtc_provider,
)
from homeassistant.components.camera.prefs import get_dynamic_camera_stream_settings
from homeassistant.components.default_config import DOMAIN as DEFAULT_CONFIG_DOMAIN
from homeassistant.components.stream import Orientation
from homeassistant.config_entries import SOURCE_SYSTEM, ConfigEntry
from homeassistant.const import CONF_URL, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import Event, HomeAssistant, callback
@@ -57,12 +59,13 @@ from .server import Server
_LOGGER = logging.getLogger(__name__)
_FFMPEG = "ffmpeg"
_SUPPORTED_STREAMS = frozenset(
(
"bubble",
"dvrip",
"expr",
"ffmpeg",
_FFMPEG,
"gopro",
"homekit",
"http",
@@ -315,6 +318,32 @@ class WebRTCProvider(CameraWebRTCProvider):
await self.teardown()
raise HomeAssistantError("Stream source is not supported by go2rtc")
camera_prefs = await get_dynamic_camera_stream_settings(
self._hass, camera.entity_id
)
if camera_prefs.orientation is not Orientation.NO_TRANSFORM:
# Camera orientation manually set by user
if not stream_source.startswith(_FFMPEG):
stream_source = _FFMPEG + ":" + stream_source
stream_source += "#video=h264#audio=copy"
match camera_prefs.orientation:
case Orientation.MIRROR:
stream_source += "#raw=-vf hflip"
case Orientation.ROTATE_180:
stream_source += "#rotate=180"
case Orientation.FLIP:
stream_source += "#raw=-vf vflip"
case Orientation.ROTATE_LEFT_AND_FLIP:
# Cannot use any filter when using raw one
stream_source += "#raw=-vf transpose=2,vflip"
case Orientation.ROTATE_LEFT:
stream_source += "#rotate=-90"
case Orientation.ROTATE_RIGHT_AND_FLIP:
# Cannot use any filter when using raw one
stream_source += "#raw=-vf transpose=1,vflip"
case Orientation.ROTATE_RIGHT:
stream_source += "#rotate=90"
streams = await self._rest_client.streams.list()
if (stream := streams.get(camera.entity_id)) is None or not any(

View File

@@ -134,8 +134,6 @@ async def async_setup_entry(hass: HomeAssistant, entry: GoogleConfigEntry) -> bo
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
return True
@@ -151,12 +149,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: GoogleConfigEntry) -> b
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_reload_entry(hass: HomeAssistant, entry: GoogleConfigEntry) -> None:
"""Reload config entry if the access options change."""
if not async_entry_has_scopes(entry):
await hass.config_entries.async_reload(entry.entry_id)
async def async_remove_entry(hass: HomeAssistant, entry: GoogleConfigEntry) -> None:
"""Handle removal of a local storage."""
store = LocalCalendarStore(hass, entry.entry_id)

View File

@@ -11,7 +11,11 @@ from gcal_sync.api import GoogleCalendarService
from gcal_sync.exceptions import ApiException, ApiForbiddenException
import voluptuous as vol
from homeassistant.config_entries import SOURCE_REAUTH, ConfigFlowResult, OptionsFlow
from homeassistant.config_entries import (
SOURCE_REAUTH,
ConfigFlowResult,
OptionsFlowWithReload,
)
from homeassistant.core import callback
from homeassistant.helpers import config_entry_oauth2_flow
from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -237,12 +241,12 @@ class OAuth2FlowHandler(
@callback
def async_get_options_flow(
config_entry: GoogleConfigEntry,
) -> OptionsFlow:
) -> OptionsFlowHandler:
"""Create an options flow."""
return OptionsFlowHandler()
class OptionsFlowHandler(OptionsFlow):
class OptionsFlowHandler(OptionsFlowWithReload):
"""Google Calendar options flow."""
async def async_step_init(

View File

@@ -12,15 +12,9 @@ PLATFORMS = [Platform.STT, Platform.TTS]
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a config entry."""
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(async_update_options))
return True
async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
"""Handle options update."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -15,7 +15,7 @@ from homeassistant.config_entries import (
ConfigEntry,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
OptionsFlowWithReload,
)
from homeassistant.core import callback
from homeassistant.helpers.selector import (
@@ -138,7 +138,7 @@ class GoogleCloudConfigFlow(ConfigFlow, domain=DOMAIN):
return GoogleCloudOptionsFlowHandler()
class GoogleCloudOptionsFlowHandler(OptionsFlow):
class GoogleCloudOptionsFlowHandler(OptionsFlowWithReload):
"""Google Cloud options flow."""
async def async_step_init(

View File

@@ -9,7 +9,6 @@ from enum import StrEnum
from habiticalib import ContentData, UserData
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
BinarySensorEntity,
BinarySensorEntityDescription,
)
@@ -108,7 +107,6 @@ class HabiticaPartyBinarySensorEntity(HabiticaPartyBase, BinarySensorEntity):
entity_description = BinarySensorEntityDescription(
key=HabiticaBinarySensor.QUEST_RUNNING,
translation_key=HabiticaBinarySensor.QUEST_RUNNING,
device_class=BinarySensorDeviceClass.RUNNING,
)
def __init__(

View File

@@ -7,5 +7,5 @@
"iot_class": "cloud_polling",
"loggers": ["habiticalib"],
"quality_scale": "platinum",
"requirements": ["habiticalib==0.4.3"]
"requirements": ["habiticalib==0.4.5"]
}

View File

@@ -33,6 +33,7 @@ from .util import (
pending_quest_items,
quest_attributes,
quest_boss,
rage_attributes,
)
_LOGGER = logging.getLogger(__name__)
@@ -111,6 +112,8 @@ class HabiticaSensorEntity(StrEnum):
BOSS_HP = "boss_hp"
BOSS_HP_REMAINING = "boss_hp_remaining"
COLLECTED_ITEMS = "collected_items"
BOSS_RAGE = "boss_rage"
BOSS_RAGE_LIMIT = "boss_rage_limit"
SENSOR_DESCRIPTIONS: tuple[HabiticaSensorEntityDescription, ...] = (
@@ -342,6 +345,25 @@ SENSOR_DESCRIPTIONS_PARTY: tuple[HabiticaPartySensorEntityDescription, ...] = (
else None
),
),
HabiticaPartySensorEntityDescription(
key=HabiticaSensorEntity.BOSS_RAGE,
translation_key=HabiticaSensorEntity.BOSS_RAGE,
value_fn=lambda p, _: p.quest.progress.rage,
entity_picture=ha.RAGE,
suggested_display_precision=2,
),
HabiticaPartySensorEntityDescription(
key=HabiticaSensorEntity.BOSS_RAGE_LIMIT,
translation_key=HabiticaSensorEntity.BOSS_RAGE_LIMIT,
value_fn=(
lambda p, c: boss.rage.value
if (boss := quest_boss(p, c)) and boss.rage
else None
),
entity_picture=ha.RAGE,
suggested_display_precision=0,
attributes_fn=rage_attributes,
),
)

View File

@@ -8,6 +8,7 @@
"unit_mana_points": "MP",
"unit_experience_points": "XP",
"unit_items": "items",
"unit_rage": "rage",
"config_entry_description": "Select the Habitica account to update a task.",
"task_description": "The name (or task ID) of the task you want to update.",
"rename_name": "Rename",
@@ -459,6 +460,22 @@
"collected_items": {
"name": "Collected quest items",
"unit_of_measurement": "[%key:component::habitica::common::unit_items%]"
},
"boss_rage_limit": {
"name": "Boss rage limit break",
"unit_of_measurement": "[%key:component::habitica::common::unit_rage%]",
"state_attributes": {
"rage_skill": {
"name": "Rage skill"
},
"effect": {
"name": "Effect"
}
}
},
"boss_rage": {
"name": "Boss rage",
"unit_of_measurement": "[%key:component::habitica::common::unit_rage%]"
}
},
"switch": {

View File

@@ -196,6 +196,15 @@ def quest_attributes(party: GroupData, content: ContentData) -> dict[str, Any]:
}
def rage_attributes(party: GroupData, content: ContentData) -> dict[str, Any]:
"""Display name of rage skill and description of it's effect in attributes."""
boss = quest_boss(party, content)
return {
"rage_skill": boss.rage.title if boss and boss.rage else None,
"effect": boss.rage.effect if boss and boss.rage else None,
}
def quest_boss(party: GroupData, content: ContentData) -> QuestBoss | None:
"""Quest boss."""

View File

@@ -7,7 +7,7 @@
"documentation": "https://www.home-assistant.io/integrations/harmony",
"iot_class": "local_push",
"loggers": ["aioharmony", "slixmpp"],
"requirements": ["aioharmony==0.5.2"],
"requirements": ["aioharmony==0.5.3"],
"ssdp": [
{
"manufacturer": "Logitech",

View File

@@ -303,9 +303,9 @@ async def _websocket_forward(
elif msg.type is aiohttp.WSMsgType.BINARY:
await ws_to.send_bytes(msg.data)
elif msg.type is aiohttp.WSMsgType.PING:
await ws_to.ping()
await ws_to.ping(msg.data)
elif msg.type is aiohttp.WSMsgType.PONG:
await ws_to.pong()
await ws_to.pong(msg.data)
elif ws_to.closed:
await ws_to.close(code=ws_to.close_code, message=msg.extra) # type: ignore[arg-type]
except RuntimeError:

View File

@@ -0,0 +1,71 @@
"""The Home Assistant Connect ZBT-2 integration."""
from __future__ import annotations
import logging
import os.path
from homeassistant.components.usb import USBDevice, async_register_port_event_callback
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.typing import ConfigType
from .const import DEVICE, DOMAIN
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = cv.empty_config_schema(DOMAIN)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Home Assistant Connect ZBT-2 integration."""
@callback
def async_port_event_callback(
added: set[USBDevice], removed: set[USBDevice]
) -> None:
"""Handle USB port events."""
current_entries_by_path = {
entry.data[DEVICE]: entry
for entry in hass.config_entries.async_entries(DOMAIN)
}
for device in added | removed:
path = device.device
entry = current_entries_by_path.get(path)
if entry is not None:
_LOGGER.debug(
"Device %r has changed state, reloading config entry %s",
path,
entry,
)
hass.config_entries.async_schedule_reload(entry.entry_id)
async_register_port_event_callback(hass, async_port_event_callback)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a Home Assistant Connect ZBT-2 config entry."""
# Postpone loading the config entry if the device is missing
device_path = entry.data[DEVICE]
if not await hass.async_add_executor_job(os.path.exists, device_path):
raise ConfigEntryNotReady(
translation_domain=DOMAIN,
translation_key="device_disconnected",
)
await hass.config_entries.async_forward_entry_setups(entry, ["update"])
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
await hass.config_entries.async_unload_platforms(entry, ["update"])
return True

View File

@@ -0,0 +1,206 @@
"""Config flow for the Home Assistant Connect ZBT-2 integration."""
from __future__ import annotations
import logging
from typing import TYPE_CHECKING, Any, Protocol
from homeassistant.components import usb
from homeassistant.components.homeassistant_hardware import firmware_config_flow
from homeassistant.components.homeassistant_hardware.util import (
ApplicationType,
FirmwareInfo,
)
from homeassistant.config_entries import (
ConfigEntry,
ConfigEntryBaseFlow,
ConfigFlowContext,
ConfigFlowResult,
OptionsFlow,
)
from homeassistant.core import callback
from homeassistant.helpers.service_info.usb import UsbServiceInfo
from .const import (
DEVICE,
DOMAIN,
FIRMWARE,
FIRMWARE_VERSION,
HARDWARE_NAME,
MANUFACTURER,
NABU_CASA_FIRMWARE_RELEASES_URL,
PID,
PRODUCT,
SERIAL_NUMBER,
VID,
)
from .util import get_usb_service_info
_LOGGER = logging.getLogger(__name__)
if TYPE_CHECKING:
class FirmwareInstallFlowProtocol(Protocol):
"""Protocol describing `BaseFirmwareInstallFlow` for a mixin."""
def _get_translation_placeholders(self) -> dict[str, str]:
return {}
async def _install_firmware_step(
self,
fw_update_url: str,
fw_type: str,
firmware_name: str,
expected_installed_firmware_type: ApplicationType,
step_id: str,
next_step_id: str,
) -> ConfigFlowResult: ...
else:
# Multiple inheritance with `Protocol` seems to break
FirmwareInstallFlowProtocol = object
class ZBT2FirmwareMixin(ConfigEntryBaseFlow, FirmwareInstallFlowProtocol):
"""Mixin for Home Assistant Connect ZBT-2 firmware methods."""
context: ConfigFlowContext
async def async_step_install_zigbee_firmware(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Install Zigbee firmware."""
return await self._install_firmware_step(
fw_update_url=NABU_CASA_FIRMWARE_RELEASES_URL,
fw_type="zbt2_zigbee_ncp",
firmware_name="Zigbee",
expected_installed_firmware_type=ApplicationType.EZSP,
step_id="install_zigbee_firmware",
next_step_id="pre_confirm_zigbee",
)
async def async_step_install_thread_firmware(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Install Thread firmware."""
return await self._install_firmware_step(
fw_update_url=NABU_CASA_FIRMWARE_RELEASES_URL,
fw_type="zbt2_openthread_rcp",
firmware_name="OpenThread",
expected_installed_firmware_type=ApplicationType.SPINEL,
step_id="install_thread_firmware",
next_step_id="start_otbr_addon",
)
class HomeAssistantConnectZBT2ConfigFlow(
ZBT2FirmwareMixin,
firmware_config_flow.BaseFirmwareConfigFlow,
domain=DOMAIN,
):
"""Handle a config flow for Home Assistant Connect ZBT-2."""
VERSION = 1
MINOR_VERSION = 1
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Initialize the config flow."""
super().__init__(*args, **kwargs)
self._usb_info: UsbServiceInfo | None = None
@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
) -> OptionsFlow:
"""Return the options flow."""
return HomeAssistantConnectZBT2OptionsFlowHandler(config_entry)
async def async_step_usb(self, discovery_info: UsbServiceInfo) -> ConfigFlowResult:
"""Handle usb discovery."""
device = discovery_info.device
vid = discovery_info.vid
pid = discovery_info.pid
serial_number = discovery_info.serial_number
manufacturer = discovery_info.manufacturer
description = discovery_info.description
unique_id = f"{vid}:{pid}_{serial_number}_{manufacturer}_{description}"
device = discovery_info.device = await self.hass.async_add_executor_job(
usb.get_serial_by_id, discovery_info.device
)
try:
await self.async_set_unique_id(unique_id)
finally:
self._abort_if_unique_id_configured(updates={DEVICE: device})
self._usb_info = discovery_info
# Set parent class attributes
self._device = self._usb_info.device
self._hardware_name = HARDWARE_NAME
return await self.async_step_confirm()
def _async_flow_finished(self) -> ConfigFlowResult:
"""Create the config entry."""
assert self._usb_info is not None
assert self._probed_firmware_info is not None
return self.async_create_entry(
title=HARDWARE_NAME,
data={
VID: self._usb_info.vid,
PID: self._usb_info.pid,
SERIAL_NUMBER: self._usb_info.serial_number,
MANUFACTURER: self._usb_info.manufacturer,
PRODUCT: self._usb_info.description,
DEVICE: self._usb_info.device,
FIRMWARE: self._probed_firmware_info.firmware_type.value,
FIRMWARE_VERSION: self._probed_firmware_info.firmware_version,
},
)
class HomeAssistantConnectZBT2OptionsFlowHandler(
ZBT2FirmwareMixin, firmware_config_flow.BaseFirmwareOptionsFlow
):
"""Zigbee and Thread options flow handlers."""
def __init__(self, *args: Any, **kwargs: Any) -> None:
"""Instantiate options flow."""
super().__init__(*args, **kwargs)
self._usb_info = get_usb_service_info(self.config_entry)
self._hardware_name = HARDWARE_NAME
self._device = self._usb_info.device
self._probed_firmware_info = FirmwareInfo(
device=self._device,
firmware_type=ApplicationType(self.config_entry.data[FIRMWARE]),
firmware_version=self.config_entry.data[FIRMWARE_VERSION],
source="guess",
owners=[],
)
# Regenerate the translation placeholders
self._get_translation_placeholders()
def _async_flow_finished(self) -> ConfigFlowResult:
"""Create the config entry."""
assert self._probed_firmware_info is not None
self.hass.config_entries.async_update_entry(
entry=self.config_entry,
data={
**self.config_entry.data,
FIRMWARE: self._probed_firmware_info.firmware_type.value,
FIRMWARE_VERSION: self._probed_firmware_info.firmware_version,
},
options=self.config_entry.options,
)
return self.async_create_entry(title="", data={})

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