Compare commits

..

331 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
Franck Nijhof
220c233c0b 2025.9.0 (#151263) 2025-09-03 19:49:19 +02:00
Franck Nijhof
cdf7d8df16 Bump version to 2025.9.0 2025-09-03 17:12:06 +00:00
karwosts
3385151c26 Test for async_show_menu sort (#151630) 2025-09-03 18:46:57 +02:00
Franck Nijhof
5db4057781 Bump version to 2025.9.0b6 2025-09-03 16:17:57 +00:00
Bram Kragten
5d9277e4ab Update frontend to 20250903.2 (#151629) 2025-09-03 16:16:36 +00:00
Michael Hansen
67d3a9623d Bump intents (#151627) 2025-09-03 16:14:25 +00:00
Norbert Rittel
0ad44e423b Fix naming of "State of charge" sensor in growatt_server (#151619) 2025-09-03 16:12:59 +00:00
jan iversen
cb7097cdf1 Simplify Modbus update methods (#151494) 2025-09-03 16:12:57 +00:00
mattreim
22b8ad9d0b 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 16:12:56 +00: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
Franck Nijhof
26c9d283a4 Bump version to 2025.9.0b5 2025-09-03 11:38:27 +00:00
Bram Kragten
4dbccbc056 Update frontend to 20250903.1 (#151617) 2025-09-03 11:38:03 +00:00
Erik Montnemery
17466ce866 Bump device registry version to 1.12 (#151616) 2025-09-03 11:38:02 +00:00
Erik Montnemery
422862a699 Handle colliding aliases for floors (#151614) 2025-09-03 11:38:00 +00:00
Erik Montnemery
5be2e4e14b Handle colliding aliases for areas (#151613) 2025-09-03 11:37:59 +00:00
Bram Kragten
75ebbe60db Update frontend to 20250903.0 (#151612) 2025-09-03 11:37:58 +00:00
Robert Resch
f0e18cc63d Bump aioecowitt to 2025.9.0 (#151608) 2025-09-03 11:37:57 +00:00
Krisjanis Lejejs
c2b4e9b075 Bump hass-nabucasa from 1.0.0 to 1.1.0 (#151606) 2025-09-03 11:37:55 +00:00
jan iversen
baa1c51bcf Fix racing bug in slave entities in Modbus (#151522) 2025-09-03 11:37:54 +00: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
Franck Nijhof
a1d484fa73 Bump version to 2025.9.0b4 2025-09-03 09:24:19 +00:00
Paul Bottein
6013f50aa6 Update frontend to 20250902.1 (#151593) 2025-09-03 09:23:18 +00:00
Stefan Agner
b4ab63d9db Update Home Assistant base image to 2025.09.0 (#151582) 2025-09-03 09:23:16 +00:00
Thomas D
7229781aeb Bump volvocarsapi to v0.4.2 (#151579) 2025-09-03 09:23:15 +00:00
Erik Montnemery
465512b0ea Improve migration to device registry version 1.10 (#151571) 2025-09-03 09:23:14 +00:00
Erik Montnemery
75d6c0bb53 Improve migration to entity registry version 1.18 (#151570) 2025-09-03 09:23:12 +00:00
Erik Montnemery
7500406e36 Revert "Improve migration to device registry version 1.11" (#151563) 2025-09-03 09:23:11 +00:00
Erik Montnemery
2afbca9751 Revert "Improve migration to entity registry version 1.18" (#151561) 2025-09-03 09:23:09 +00:00
Simone Chemelli
6023a8e6b0 Remove config entry from device instead of deleting in Uptime robot (#151557) 2025-09-03 09:23:08 +00:00
Erik Montnemery
869801b643 Exclude non mowers from husqvarna_automower_ble discovery (#151507) 2025-09-03 09:23:07 +00: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
Franck Nijhof
ed9e46bbca Bump version to 2025.9.0b3 2025-09-02 08:42:14 +00:00
Abílio Costa
ac4eef0571 Add back missing controller cleanup to Govee Light Local (#151541) 2025-09-02 08:42:08 +00:00
Abílio Costa
1039936f39 Filter out IPv6 addresses in Govee Light Local (#151540) 2025-09-02 08:42:06 +00:00
Lukas
9910df2b21 Remove mac address from Pooldose device (#151536)
Co-authored-by: copilot-swe-agent[bot] <198982749+Copilot@users.noreply.github.com>
2025-09-02 08:42:05 +00:00
Bram Kragten
e57019a80b Update frontend to 20250901.0 (#151529) 2025-09-02 08:42:03 +00:00
Imeon-Energy
1f6853db28 Fix typo in const.py for Imeon inverter integration (#151515)
Co-authored-by: TheBushBoy <theodavid@icloud.com>
2025-09-02 08:42:02 +00:00
Andrew Jackson
dc4d6ddbef Bump aiomealie to 0.10.2 (#151514) 2025-09-02 08:42:00 +00:00
Antoni Czaplicki
399286deae Remove the vulcan integration (#151504) 2025-09-02 08:41:59 +00:00
Joost Lekkerkerker
1a2898cc89 Update Pooldose quality scale (#151499) 2025-09-02 08:41:57 +00:00
Iskra kranj
d9629affca Bump pyiskra to 0.1.26 (#151489) 2025-09-02 08:41:56 +00:00
Willem-Jan van Rootselaar
9581c705b9 Fix add checks for None values and check if DHW is available (#151376) 2025-09-02 08:41:55 +00:00
Andrea Turri
e2a4a9393e Miele refrigerators cause index out of range errors when offline (#151299) 2025-09-02 08:41:53 +00:00
Norbert Rittel
f32d12c519 Fix wrong description for numeric_state observation in bayesian (#151291) 2025-09-02 08:41:52 +00:00
Artur Pragacz
18ce6da4e6 Allow ignored Onkyo devices to be set up from the user flow (#150921) 2025-09-02 08:41:51 +00:00
Jozef Kruszynski
031ae3a921 Fix sort order in media browser for music assistant integration (#150910) 2025-09-02 08:41:49 +00:00
Artur Pragacz
bbe66f5cea Improve unpair schema in homekit (#150235) 2025-09-02 08:41:48 +00:00
Phil Male
9f4369dc8b Use average color for Hue light group state (#149499) 2025-09-02 08:41:46 +00: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
622 changed files with 28540 additions and 6698 deletions

View File

@@ -8,6 +8,8 @@
"PYTHONASYNCIODEBUG": "1" "PYTHONASYNCIODEBUG": "1"
}, },
"features": { "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/anthropics/devcontainer-features/claude-code:1.0": {},
"ghcr.io/devcontainers/features/github-cli:1": {} "ghcr.io/devcontainers/features/github-cli:1": {}
}, },

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

66
CODEOWNERS generated
View File

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

View File

@@ -1,10 +1,10 @@
image: ghcr.io/home-assistant/{arch}-homeassistant image: ghcr.io/home-assistant/{arch}-homeassistant
build_from: build_from:
aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2025.05.0 aarch64: ghcr.io/home-assistant/aarch64-homeassistant-base:2025.09.0
armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2025.05.0 armhf: ghcr.io/home-assistant/armhf-homeassistant-base:2025.09.0
armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2025.05.0 armv7: ghcr.io/home-assistant/armv7-homeassistant-base:2025.09.0
amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2025.05.0 amd64: ghcr.io/home-assistant/amd64-homeassistant-base:2025.09.0
i386: ghcr.io/home-assistant/i386-homeassistant-base:2025.05.0 i386: ghcr.io/home-assistant/i386-homeassistant-base:2025.09.0
codenotary: codenotary:
signer: notary@home-assistant.io signer: notary@home-assistant.io
base_image: notary@home-assistant.io base_image: notary@home-assistant.io

View File

@@ -187,6 +187,12 @@ def main() -> int:
from . import config, runner # noqa: PLC0415 from . import config, runner # noqa: PLC0415
# 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
safe_mode = config.safe_mode_enabled(config_dir) safe_mode = config.safe_mode_enabled(config_dir)
runtime_conf = runner.RuntimeConfig( runtime_conf = runner.RuntimeConfig(

View File

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

View File

@@ -20,7 +20,7 @@ from . import (
SetupFlow, 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) CONFIG_SCHEMA = MULTI_FACTOR_AUTH_MODULE_SCHEMA.extend({}, extra=vol.PREVENT_EXTRA)

View File

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

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
from __future__ import annotations from __future__ import annotations
from dataclasses import dataclass from dataclasses import dataclass
from datetime import datetime from datetime import datetime, timedelta
from functools import partial from functools import partial
import mimetypes import mimetypes
from pathlib import Path from pathlib import Path
@@ -13,6 +13,7 @@ from typing import Any
import voluptuous as vol import voluptuous as vol
from homeassistant.components import camera, conversation, media_source 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.core import HomeAssistant, ServiceResponse, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.chat_session import ChatSession, async_get_chat_session 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: if IMAGE_EXPIRY_TIME > 0:
async_call_later(hass, IMAGE_EXPIRY_TIME, partial(_purge_image, filename)) 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}" service_result["media_source_id"] = f"media-source://{DOMAIN}/images/{filename}"
return service_result return service_result

View File

@@ -2,7 +2,7 @@
from __future__ import annotations 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.const import CONF_HOST, CONF_PASSWORD, CONF_USERNAME, Platform
from homeassistant.core import HomeAssistant 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. # with no option in the web UI to change or upload a custom certificate.
session = async_get_clientsession(hass, verify_ssl=False) session = async_get_clientsession(hass, verify_ssl=False)
airos_device = AirOS( airos_device = AirOS8(
host=entry.data[CONF_HOST], host=entry.data[CONF_HOST],
username=entry.data[CONF_USERNAME], username=entry.data[CONF_USERNAME],
password=entry.data[CONF_PASSWORD], password=entry.data[CONF_PASSWORD],

View File

@@ -15,7 +15,7 @@ from homeassistant.const import EntityCategory
from homeassistant.core import HomeAssistant from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import AirOSConfigEntry, AirOSData, AirOSDataUpdateCoordinator from .coordinator import AirOS8Data, AirOSConfigEntry, AirOSDataUpdateCoordinator
from .entity import AirOSEntity from .entity import AirOSEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -27,7 +27,7 @@ PARALLEL_UPDATES = 0
class AirOSBinarySensorEntityDescription(BinarySensorEntityDescription): class AirOSBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Describe an AirOS binary sensor.""" """Describe an AirOS binary sensor."""
value_fn: Callable[[AirOSData], bool] value_fn: Callable[[AirOS8Data], bool]
BINARY_SENSORS: tuple[AirOSBinarySensorEntityDescription, ...] = ( 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 homeassistant.helpers.aiohttp_client import async_get_clientsession
from .const import DOMAIN from .const import DOMAIN
from .coordinator import AirOS from .coordinator import AirOS8
_LOGGER = logging.getLogger(__name__) _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. # with no option in the web UI to change or upload a custom certificate.
session = async_get_clientsession(self.hass, verify_ssl=False) session = async_get_clientsession(self.hass, verify_ssl=False)
airos_device = AirOS( airos_device = AirOS8(
host=user_input[CONF_HOST], host=user_input[CONF_HOST],
username=user_input[CONF_USERNAME], username=user_input[CONF_USERNAME],
password=user_input[CONF_PASSWORD], password=user_input[CONF_PASSWORD],

View File

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

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/airos", "documentation": "https://www.home-assistant.io/integrations/airos",
"iot_class": "local_polling", "iot_class": "local_polling",
"quality_scale": "bronze", "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.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType from homeassistant.helpers.typing import StateType
from .coordinator import AirOSConfigEntry, AirOSData, AirOSDataUpdateCoordinator from .coordinator import AirOS8Data, AirOSConfigEntry, AirOSDataUpdateCoordinator
from .entity import AirOSEntity from .entity import AirOSEntity
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
@@ -42,7 +42,7 @@ PARALLEL_UPDATES = 0
class AirOSSensorEntityDescription(SensorEntityDescription): class AirOSSensorEntityDescription(SensorEntityDescription):
"""Describe an AirOS sensor.""" """Describe an AirOS sensor."""
value_fn: Callable[[AirOSData], StateType] value_fn: Callable[[AirOS8Data], StateType]
SENSORS: tuple[AirOSSensorEntityDescription, ...] = ( 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 from __future__ import annotations
@@ -63,7 +66,10 @@ CONFIG_SCHEMA = vol.Schema(
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool: 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) component = EntityComponent[AlertEntity](LOGGER, DOMAIN, hass)
entities: list[AlertEntity] = [] 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 from __future__ import annotations
@@ -27,7 +30,10 @@ from .const import DOMAIN, LOGGER
class AlertEntity(Entity): class AlertEntity(Entity):
"""Representation of an alert.""" """Representation of an alert.
DEVELOPMENT OF THE ALERT INTEGRATION IS FROZEN.
"""
_attr_should_poll = False _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 from __future__ import annotations

View File

@@ -24,7 +24,12 @@ from homeassistant.components.recorder import (
get_instance as get_recorder_instance, get_instance as get_recorder_instance,
) )
from homeassistant.config_entries import SOURCE_IGNORE 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.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, entity_registry as er 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: async def async_devices_payload(hass: HomeAssistant) -> dict:
"""Return the devices payload.""" """Return detailed information about entities and devices."""
devices: list[dict[str, Any]] = [] integrations_info: dict[str, dict[str, Any]] = {}
dev_reg = dr.async_get(hass) 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(): for device_entry in dev_reg.devices.values():
if not device.primary_config_entry: if not device_entry.primary_config_entry:
continue 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: if config_entry is None:
continue 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_info = integration_info["devices"]
devices.append(
device_id_mapping[device_entry.id] = (integration_domain, len(devices_info))
devices_info.append(
{ {
"integration": config_entry.domain, "entities": [],
"manufacturer": device.manufacturer, "entry_type": device_entry.entry_type,
"model_id": device.model_id, "has_configuration_url": device_entry.configuration_url is not None,
"model": device.model, "hw_version": device_entry.hw_version,
"sw_version": device.sw_version, "manufacturer": device_entry.manufacturer,
"hw_version": device.hw_version, "model": device_entry.model,
"has_configuration_url": device.configuration_url is not None, "model_id": device_entry.model_id,
"via_device": None, "sw_version": device_entry.sw_version,
"entry_type": device.entry_type.value if device.entry_type else None, "via_device": device_entry.via_device_id,
} }
) )
if device.via_device_id: # Fill out via_device with new device ids
via_devices[device.id] = device.via_device_id for integration_info in integrations_info.values():
for device_info in integration_info["devices"]:
for from_device, via_device in via_devices.items(): if device_info["via_device"] is None:
if via_device not in new_indexes:
continue continue
devices[new_indexes[from_device]]["via_device"] = new_indexes[via_device] device_info["via_device"] = device_id_mapping.get(device_info["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 = { integrations = {
domain: integration domain: integration
for domain, integration in ( for domain, integration in (
await async_get_integrations(hass, seen_integrations) await async_get_integrations(hass, integrations_info.keys())
).items() ).items()
if isinstance(integration, Integration) if isinstance(integration, Integration)
} }
for device_info in devices: for domain, integration_info in integrations_info.items():
if integration := integrations.get(device_info["integration"]): if integration := integrations.get(domain):
device_info["is_custom_integration"] = not integration.is_built_in integration_info["is_custom_integration"] = not integration.is_built_in
# Include version for custom integrations # Include version for custom integrations
if not integration.is_built_in and integration.version: 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 { return {
"version": "home-assistant:1", "version": "home-assistant:1",
"home_assistant": HA_VERSION, "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: if user_input is not None:
self.host = user_input[CONF_HOST] self.host = user_input[CONF_HOST]
api = create_api(self.hass, self.host, enable_ime=False) api = create_api(self.hass, self.host, enable_ime=False)
try:
await api.async_generate_cert_if_missing() await api.async_generate_cert_if_missing()
try:
self.name, self.mac = await api.async_get_name_and_mac() 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)) await self.async_set_unique_id(format_mac(self.mac))
if self.source == SOURCE_RECONFIGURE: if self.source == SOURCE_RECONFIGURE:
self._abort_if_unique_id_mismatch() self._abort_if_unique_id_mismatch()
@@ -81,10 +86,9 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
}, },
) )
self._abort_if_unique_id_configured(updates={CONF_HOST: self.host}) self._abort_if_unique_id_configured(updates={CONF_HOST: self.host})
try:
return await self._async_start_pair() return await self._async_start_pair()
except (CannotConnect, ConnectionClosed): 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" errors["base"] = "cannot_connect"
else: else:
user_input = {} user_input = {}
@@ -112,22 +116,9 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle the pair step.""" """Handle the pair step."""
errors: dict[str, str] = {} errors: dict[str, str] = {}
if user_input is not None: if user_input is not None:
try:
pin = user_input["pin"] pin = user_input["pin"]
try:
await self.api.async_finish_pairing(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: except InvalidAuth:
# Invalid PIN. Stay in the pair step allowing the user to enter # Invalid PIN. Stay in the pair step allowing the user to enter
# a different PIN. # 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 # them to enter a new IP address but we cannot do that for the zeroconf
# flow. Simpler to abort for both flows. # flow. Simpler to abort for both flows.
return self.async_abort(reason="cannot_connect") 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( return self.async_show_form(
step_id="pair", step_id="pair",
data_schema=STEP_PAIR_DATA_SCHEMA, data_schema=STEP_PAIR_DATA_SCHEMA,

View File

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

View File

@@ -175,7 +175,11 @@ class AndroidTVRemoteMediaPlayerEntity(AndroidTVRemoteBaseEntity, MediaPlayerEnt
"""Play a piece of media.""" """Play a piece of media."""
if media_type == MediaType.CHANNEL: if media_type == MediaType.CHANNEL:
if not media_id.isnumeric(): 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: if self._channel_set_task:
self._channel_set_task.cancel() self._channel_set_task.cancel()
self._channel_set_task = asyncio.create_task( self._channel_set_task = asyncio.create_task(
@@ -188,7 +192,11 @@ class AndroidTVRemoteMediaPlayerEntity(AndroidTVRemoteBaseEntity, MediaPlayerEnt
self._send_launch_app_command(media_id) self._send_launch_app_command(media_id)
return 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( async def async_browse_media(
self, self,

View File

@@ -85,6 +85,12 @@
"exceptions": { "exceptions": {
"connection_closed": { "connection_closed": {
"message": "Connection to the Android TV device is 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, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/aosmith", "documentation": "https://www.home-assistant.io/integrations/aosmith",
"iot_class": "cloud_polling", "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 from .coordinator import APCUPSdConfigEntry, APCUPSdCoordinator
PLATFORMS: Final = (Platform.BINARY_SENSOR, Platform.SENSOR) PLATFORMS: Final = [Platform.BINARY_SENSOR, Platform.SENSOR]
async def async_setup_entry( async def async_setup_entry(

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -7,5 +7,5 @@
"integration_type": "hub", "integration_type": "hub",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["aioasuswrt", "asusrouter", "asyncssh"], "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")}, identifiers={(DOMAIN, self._entry.unique_id or "AsusWRT")},
name=self.host, name=self.host,
model=self._api.model or "Asus Router", model=self._api.model or "Asus Router",
model_id=self._api.model_id,
serial_number=self._api.serial_number,
manufacturer="Asus", manufacturer="Asus",
configuration_url=f"http://{self.host}", configuration_url=f"http://{self.host}",
) )

View File

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

View File

@@ -96,7 +96,7 @@
}, },
"numeric_state": { "numeric_state": {
"title": "[%key:component::bayesian::config_subentries::observation::step::state::title%]", "title": "[%key:component::bayesian::config_subentries::observation::step::state::title%]",
"description": "[%key:component::bayesian::config_subentries::observation::step::state::description%]", "description": "[%key:component::bayesian::config_subentries::observation::step::numeric_state::description%]",
"data": { "data": {
"name": "[%key:common::config_flow::data::name%]", "name": "[%key:common::config_flow::data::name%]",
"entity_id": "[%key:component::bayesian::config_subentries::observation::step::state::data::entity_id%]", "entity_id": "[%key:component::bayesian::config_subentries::observation::step::state::data::entity_id%]",

View File

@@ -6,7 +6,7 @@
"config_flow": true, "config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/bluesound", "documentation": "https://www.home-assistant.io/integrations/bluesound",
"iot_class": "local_polling", "iot_class": "local_polling",
"requirements": ["pyblu==2.0.4"], "requirements": ["pyblu==2.0.5"],
"zeroconf": [ "zeroconf": [
{ {
"type": "_musc._tcp.local." "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): if self.available is False or (self.is_grouped and not self.is_leader):
return None 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 return sources
@@ -340,7 +346,7 @@ class BluesoundPlayer(CoordinatorEntity[BluesoundCoordinator], MediaPlayerEntity
input_.id == self._status.input_id input_.id == self._status.input_id
or input_.url == self._status.stream_url 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: for preset in self._presets:
if preset.url == self._status.stream_url: 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 # presets and inputs might have the same name; presets have priority
for input_ in self._inputs: for input_ in self._inputs:
if input_.text == source: if source in (input_.text, input_.id):
await self._player.play_url(input_.url) await self._player.play_url(input_.url)
return return
for preset in self._presets: for preset in self._presets:

View File

@@ -21,6 +21,6 @@
"bluetooth-auto-recovery==1.5.2", "bluetooth-auto-recovery==1.5.2",
"bluetooth-data-tools==1.28.2", "bluetooth-data-tools==1.28.2",
"dbus-fast==2.44.3", "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 typing import Any
from habluetooth import ( from habluetooth import (
BaseHaScanner,
BluetoothScanningMode, BluetoothScanningMode,
HaBluetoothSlotAllocations, HaBluetoothSlotAllocations,
HaScannerModeChange,
HaScannerRegistration, HaScannerRegistration,
HaScannerRegistrationEvent, HaScannerRegistrationEvent,
) )
@@ -27,12 +29,54 @@ from .models import BluetoothChange
from .util import InvalidConfigEntryID, InvalidSource, config_entry_id_to_source 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 @callback
def async_setup(hass: HomeAssistant) -> None: def async_setup(hass: HomeAssistant) -> None:
"""Set up the bluetooth websocket API.""" """Set up the bluetooth websocket API."""
websocket_api.async_register_command(hass, ws_subscribe_advertisements) 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_connection_allocations)
websocket_api.async_register_command(hass, ws_subscribe_scanner_details) websocket_api.async_register_command(hass, ws_subscribe_scanner_details)
websocket_api.async_register_command(hass, ws_subscribe_scanner_state)
@lru_cache(maxsize=1024) @lru_cache(maxsize=1024)
@@ -180,16 +224,12 @@ async def ws_subscribe_connection_allocations(
) -> None: ) -> None:
"""Handle subscribe advertisements websocket command.""" """Handle subscribe advertisements websocket command."""
ws_msg_id = msg["id"] ws_msg_id = msg["id"]
source: str | None = None config_entry_id = msg.get("config_entry_id")
if config_entry_id := msg.get("config_entry_id"): source = _async_get_source_from_config_entry(
try: hass, connection, ws_msg_id, config_entry_id
source = config_entry_id_to_source(hass, config_entry_id) )
except InvalidConfigEntryID as err: if config_entry_id and source is None:
connection.send_error(ws_msg_id, "invalid_config_entry_id", str(err)) return # Error already sent by helper
return
except InvalidSource as err:
connection.send_error(ws_msg_id, "invalid_source", str(err))
return
def _async_allocations_changed(allocations: HaBluetoothSlotAllocations) -> None: def _async_allocations_changed(allocations: HaBluetoothSlotAllocations) -> None:
connection.send_message( connection.send_message(
@@ -220,20 +260,12 @@ async def ws_subscribe_scanner_details(
) -> None: ) -> None:
"""Handle subscribe scanner details websocket command.""" """Handle subscribe scanner details websocket command."""
ws_msg_id = msg["id"] ws_msg_id = msg["id"]
source: str | None = None config_entry_id = msg.get("config_entry_id")
if config_entry_id := msg.get("config_entry_id"): source = _async_get_source_from_config_entry(
if ( hass, connection, ws_msg_id, config_entry_id, validate_source=False
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 if config_entry_id and source is None:
source = entry.unique_id return # Error already sent by helper
assert source is not None
def _async_event_message(message: dict[str, Any]) -> None: def _async_event_message(message: dict[str, Any]) -> None:
connection.send_message( connection.send_message(
@@ -260,3 +292,70 @@ async def ws_subscribe_scanner_details(
] ]
): ):
_async_event_message({"add": matching_scanners}) _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", "documentation": "https://www.home-assistant.io/integrations/bmw_connected_drive",
"iot_class": "cloud_polling", "iot_class": "cloud_polling",
"loggers": ["bimmer_connected"], "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", "integration_type": "device",
"iot_class": "local_polling", "iot_class": "local_polling",
"loggers": ["brother", "pyasn1", "pysmi", "pysnmp"], "loggers": ["brother", "pyasn1", "pysmi", "pysnmp"],
"requirements": ["brother==5.0.1"], "requirements": ["brother==5.1.0"],
"zeroconf": [ "zeroconf": [
{ {
"type": "_printer._tcp.local.", "type": "_printer._tcp.local.",

View File

@@ -81,11 +81,15 @@ class BSBLANClimate(BSBLanEntity, ClimateEntity):
@property @property
def current_temperature(self) -> float | None: def current_temperature(self) -> float | None:
"""Return the current temperature.""" """Return the current temperature."""
if self.coordinator.data.state.current_temperature is None:
return None
return self.coordinator.data.state.current_temperature.value return self.coordinator.data.state.current_temperature.value
@property @property
def target_temperature(self) -> float | None: def target_temperature(self) -> float | None:
"""Return the temperature we try to reach.""" """Return the temperature we try to reach."""
if self.coordinator.data.state.target_temperature is None:
return None
return self.coordinator.data.state.target_temperature.value return self.coordinator.data.state.target_temperature.value
@property @property

View File

@@ -25,7 +25,7 @@ class BSBLANFlowHandler(ConfigFlow, domain=DOMAIN):
def __init__(self) -> None: def __init__(self) -> None:
"""Initialize BSBLan flow.""" """Initialize BSBLan flow."""
self.host: str | None = None self.host: str = ""
self.port: int = DEFAULT_PORT self.port: int = DEFAULT_PORT
self.mac: str | None = None self.mac: str | None = None
self.passkey: str | None = None self.passkey: str | None = None

View File

@@ -28,6 +28,7 @@ class BSBLanSensorEntityDescription(SensorEntityDescription):
"""Describes BSB-Lan sensor entity.""" """Describes BSB-Lan sensor entity."""
value_fn: Callable[[BSBLanCoordinatorData], StateType] value_fn: Callable[[BSBLanCoordinatorData], StateType]
exists_fn: Callable[[BSBLanCoordinatorData], bool] = lambda data: True
SENSOR_TYPES: tuple[BSBLanSensorEntityDescription, ...] = ( SENSOR_TYPES: tuple[BSBLanSensorEntityDescription, ...] = (
@@ -37,7 +38,12 @@ SENSOR_TYPES: tuple[BSBLanSensorEntityDescription, ...] = (
device_class=SensorDeviceClass.TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS, native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.sensor.current_temperature.value, value_fn=lambda data: (
data.sensor.current_temperature.value
if data.sensor.current_temperature is not None
else None
),
exists_fn=lambda data: data.sensor.current_temperature is not None,
), ),
BSBLanSensorEntityDescription( BSBLanSensorEntityDescription(
key="outside_temperature", key="outside_temperature",
@@ -45,7 +51,12 @@ SENSOR_TYPES: tuple[BSBLanSensorEntityDescription, ...] = (
device_class=SensorDeviceClass.TEMPERATURE, device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS, native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT, state_class=SensorStateClass.MEASUREMENT,
value_fn=lambda data: data.sensor.outside_temperature.value, value_fn=lambda data: (
data.sensor.outside_temperature.value
if data.sensor.outside_temperature is not None
else None
),
exists_fn=lambda data: data.sensor.outside_temperature is not None,
), ),
) )
@@ -57,7 +68,16 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up BSB-Lan sensor based on a config entry.""" """Set up BSB-Lan sensor based on a config entry."""
data = entry.runtime_data data = entry.runtime_data
async_add_entities(BSBLanSensor(data, description) for description in SENSOR_TYPES)
# Only create sensors for available data points
entities = [
BSBLanSensor(data, description)
for description in SENSOR_TYPES
if description.exists_fn(data.coordinator.data)
]
if entities:
async_add_entities(entities)
class BSBLanSensor(BSBLanEntity, SensorEntity): class BSBLanSensor(BSBLanEntity, SensorEntity):

View File

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

View File

@@ -41,6 +41,18 @@ async def async_setup_entry(
) -> None: ) -> None:
"""Set up BSBLAN water heater based on a config entry.""" """Set up BSBLAN water heater based on a config entry."""
data = entry.runtime_data data = entry.runtime_data
# Only create water heater entity if DHW (Domestic Hot Water) is available
# Check if we have any DHW-related data indicating water heater support
dhw_data = data.coordinator.data.dhw
if (
dhw_data.operating_mode is None
and dhw_data.nominal_setpoint is None
and dhw_data.dhw_actual_value_top_temperature is None
):
# No DHW functionality available, skip water heater setup
return
async_add_entities([BSBLANWaterHeater(data)]) async_add_entities([BSBLANWaterHeater(data)])
@@ -61,23 +73,31 @@ class BSBLANWaterHeater(BSBLanEntity, WaterHeaterEntity):
# Set temperature limits based on device capabilities # Set temperature limits based on device capabilities
self._attr_temperature_unit = data.coordinator.client.get_temperature_unit self._attr_temperature_unit = data.coordinator.client.get_temperature_unit
if data.coordinator.data.dhw.reduced_setpoint is not None:
self._attr_min_temp = data.coordinator.data.dhw.reduced_setpoint.value self._attr_min_temp = data.coordinator.data.dhw.reduced_setpoint.value
if data.coordinator.data.dhw.nominal_setpoint_max is not None:
self._attr_max_temp = data.coordinator.data.dhw.nominal_setpoint_max.value self._attr_max_temp = data.coordinator.data.dhw.nominal_setpoint_max.value
@property @property
def current_operation(self) -> str | None: def current_operation(self) -> str | None:
"""Return current operation.""" """Return current operation."""
if self.coordinator.data.dhw.operating_mode is None:
return None
current_mode = self.coordinator.data.dhw.operating_mode.desc current_mode = self.coordinator.data.dhw.operating_mode.desc
return OPERATION_MODES.get(current_mode) return OPERATION_MODES.get(current_mode)
@property @property
def current_temperature(self) -> float | None: def current_temperature(self) -> float | None:
"""Return the current temperature.""" """Return the current temperature."""
if self.coordinator.data.dhw.dhw_actual_value_top_temperature is None:
return None
return self.coordinator.data.dhw.dhw_actual_value_top_temperature.value return self.coordinator.data.dhw.dhw_actual_value_top_temperature.value
@property @property
def target_temperature(self) -> float | None: def target_temperature(self) -> float | None:
"""Return the temperature we try to reach.""" """Return the temperature we try to reach."""
if self.coordinator.data.dhw.nominal_setpoint is None:
return None
return self.coordinator.data.dhw.nominal_setpoint.value return self.coordinator.data.dhw.nominal_setpoint.value
async def async_set_temperature(self, **kwargs: Any) -> None: async def async_set_temperature(self, **kwargs: Any) -> None:

View File

@@ -81,7 +81,11 @@ from .const import (
) )
from .helper import get_camera_from_entity_id from .helper import get_camera_from_entity_id
from .img_util import scale_jpeg_camera_image 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 ( from .webrtc import (
DATA_ICE_SERVERS, DATA_ICE_SERVERS,
CameraWebRTCProvider, CameraWebRTCProvider,
@@ -550,9 +554,9 @@ class Camera(Entity, cached_properties=CACHED_PROPERTIES_WITH_ATTR_):
self.hass, self.hass,
source, source,
options=self.stream_options, options=self.stream_options,
dynamic_stream_settings=await self.hass.data[ dynamic_stream_settings=await get_dynamic_camera_stream_settings(
DATA_CAMERA_PREFS self.hass, self.entity_id
].get_dynamic_stream_settings(self.entity_id), ),
stream_label=self.entity_id, stream_label=self.entity_id,
) )
self.stream.set_update_callback(self.async_write_ha_state) 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] hass: HomeAssistant, connection: ActiveConnection, msg: dict[str, Any]
) -> None: ) -> None:
"""Handle request for account info.""" """Handle request for account info."""
stream_prefs = await hass.data[DATA_CAMERA_PREFS].get_dynamic_stream_settings( stream_prefs = await get_dynamic_camera_stream_settings(hass, msg["entity_id"])
msg["entity_id"]
)
connection.send_result(msg["id"], asdict(stream_prefs)) 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.storage import Store
from homeassistant.helpers.typing import UNDEFINED, UndefinedType 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_KEY: Final = DOMAIN
STORAGE_VERSION: Final = 1 STORAGE_VERSION: Final = 1
@@ -106,3 +106,12 @@ class CameraPreferences:
) )
self._dynamic_stream_settings_by_entity_id[entity_id] = settings self._dynamic_stream_settings_by_entity_id[entity_id] = settings
return 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 logging
import threading import threading
import pychromecast import pychromecast.discovery
import pychromecast.models
from homeassistant.config_entries import ConfigEntry from homeassistant.config_entries import ConfigEntry
from homeassistant.const import EVENT_HOMEASSISTANT_STOP from homeassistant.const import EVENT_HOMEASSISTANT_STOP

View File

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

View File

@@ -10,8 +10,10 @@ import json
import logging import logging
from typing import TYPE_CHECKING, Any, Concatenate from typing import TYPE_CHECKING, Any, Concatenate
import pychromecast import pychromecast.config
import pychromecast.const
from pychromecast.controllers.homeassistant import HomeAssistantController from pychromecast.controllers.homeassistant import HomeAssistantController
import pychromecast.controllers.media
from pychromecast.controllers.media import ( from pychromecast.controllers.media import (
MEDIA_PLAYER_ERROR_CODES, MEDIA_PLAYER_ERROR_CODES,
MEDIA_PLAYER_STATE_BUFFERING, 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}." "message": "Provided temperature {check_temp} is not valid. Accepted range is {min_temp} to {max_temp}."
}, },
"low_temp_higher_than_high_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": { "humidity_out_of_range": {
"message": "Provided humidity {humidity} is not valid. Accepted range is {min_humidity} to {max_humidity}." "message": "Provided humidity {humidity} is not valid. Accepted range is {min_humidity} to {max_humidity}."
}, },
"missing_target_temperature_entity_feature": { "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": { "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

@@ -13,6 +13,6 @@
"integration_type": "system", "integration_type": "system",
"iot_class": "cloud_push", "iot_class": "cloud_push",
"loggers": ["acme", "hass_nabucasa", "snitun"], "loggers": ["acme", "hass_nabucasa", "snitun"],
"requirements": ["hass-nabucasa==1.0.0"], "requirements": ["hass-nabucasa==1.1.0"],
"single_config_entry": true "single_config_entry": true
} }

View File

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

View File

@@ -1,10 +1,10 @@
{ {
"domain": "conversation", "domain": "conversation",
"name": "Conversation", "name": "Conversation",
"codeowners": ["@home-assistant/core", "@synesthesiam"], "codeowners": ["@home-assistant/core", "@synesthesiam", "@arturpragacz"],
"dependencies": ["http", "intent"], "dependencies": ["http", "intent"],
"documentation": "https://www.home-assistant.io/integrations/conversation", "documentation": "https://www.home-assistant.io/integrations/conversation",
"integration_type": "system", "integration_type": "system",
"quality_scale": "internal", "quality_scale": "internal",
"requirements": ["hassil==3.2.0", "home-assistant-intents==2025.8.29"] "requirements": ["hassil==3.2.0", "home-assistant-intents==2025.9.3"]
} }

View File

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

View File

@@ -177,7 +177,7 @@ class DeconzSceneMixin(DeconzDevice[PydeconzScene]):
"""Return a device description for device registry.""" """Return a device description for device registry."""
return DeviceInfo( return DeviceInfo(
identifiers={(DOMAIN, self._group_identifier)}, identifiers={(DOMAIN, self._group_identifier)},
manufacturer="Dresden Elektronik", manufacturer="dresden elektronik",
model="deCONZ group", model="deCONZ group",
name=self.group.name, name=self.group.name,
via_device=(DOMAIN, self.hub.api.config.bridge_id), via_device=(DOMAIN, self.hub.api.config.bridge_id),

View File

@@ -14,7 +14,6 @@ from pydeconz.models.event import EventType
from homeassistant.config_entries import SOURCE_HASSIO from homeassistant.config_entries import SOURCE_HASSIO
from homeassistant.core import Event, HomeAssistant, callback from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.helpers import device_registry as dr, entity_registry as er from homeassistant.helpers import device_registry as dr, entity_registry as er
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.helpers.dispatcher import async_dispatcher_send from homeassistant.helpers.dispatcher import async_dispatcher_send
from ..const import CONF_MASTER_GATEWAY, DOMAIN, HASSIO_CONFIGURATION_URL, PLATFORMS from ..const import CONF_MASTER_GATEWAY, DOMAIN, HASSIO_CONFIGURATION_URL, PLATFORMS
@@ -169,17 +168,8 @@ class DeconzHub:
async def async_update_device_registry(self) -> None: async def async_update_device_registry(self) -> None:
"""Update device registry.""" """Update device registry."""
if self.api.config.mac is None:
return
device_registry = dr.async_get(self.hass) device_registry = dr.async_get(self.hass)
# Host device
device_registry.async_get_or_create(
config_entry_id=self.config_entry.entry_id,
connections={(CONNECTION_NETWORK_MAC, self.api.config.mac)},
)
# Gateway service # Gateway service
configuration_url = f"http://{self.config.host}:{self.config.port}" configuration_url = f"http://{self.config.host}:{self.config.port}"
if self.config_entry.source == SOURCE_HASSIO: if self.config_entry.source == SOURCE_HASSIO:
@@ -189,11 +179,10 @@ class DeconzHub:
configuration_url=configuration_url, configuration_url=configuration_url,
entry_type=dr.DeviceEntryType.SERVICE, entry_type=dr.DeviceEntryType.SERVICE,
identifiers={(DOMAIN, self.api.config.bridge_id)}, identifiers={(DOMAIN, self.api.config.bridge_id)},
manufacturer="Dresden Elektronik", manufacturer="dresden elektronik",
model=self.api.config.model_id, model=self.api.config.model_id,
name=self.api.config.name, name=self.api.config.name,
sw_version=self.api.config.software_version, sw_version=self.api.config.software_version,
via_device=(CONNECTION_NETWORK_MAC, self.api.config.mac),
) )
@staticmethod @staticmethod

View File

@@ -396,7 +396,7 @@ class DeconzGroup(DeconzBaseLight[Group]):
"""Return a device description for device registry.""" """Return a device description for device registry."""
return DeviceInfo( return DeviceInfo(
identifiers={(DOMAIN, self.unique_id)}, identifiers={(DOMAIN, self.unique_id)},
manufacturer="Dresden Elektronik", manufacturer="dresden elektronik",
model="deCONZ group", model="deCONZ group",
name=self._device.name, name=self._device.name,
via_device=(DOMAIN, self.hub.api.config.bridge_id), via_device=(DOMAIN, self.hub.api.config.bridge_id),

View File

@@ -11,7 +11,6 @@ from homeassistant.helpers import (
device_registry as dr, device_registry as dr,
entity_registry as er, entity_registry as er,
) )
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC
from homeassistant.util.read_only_dict import ReadOnlyDict from homeassistant.util.read_only_dict import ReadOnlyDict
from .const import CONF_BRIDGE_ID, DOMAIN, LOGGER from .const import CONF_BRIDGE_ID, DOMAIN, LOGGER
@@ -120,8 +119,8 @@ async def async_configure_service(hub: DeconzHub, data: ReadOnlyDict) -> None:
"field": "/lights/1/state", "field": "/lights/1/state",
"data": {"on": true} "data": {"on": true}
} }
See Dresden Elektroniks REST API documentation for details: See deCONZ REST-API documentation for details:
http://dresden-elektronik.github.io/deconz-rest-doc/rest/ https://dresden-elektronik.github.io/deconz-rest-doc/
""" """
field = data.get(SERVICE_FIELD, "") field = data.get(SERVICE_FIELD, "")
entity_id = data.get(SERVICE_ENTITY) entity_id = data.get(SERVICE_ENTITY)
@@ -162,14 +161,6 @@ async def async_remove_orphaned_entries_service(hub: DeconzHub) -> None:
) )
] ]
# Don't remove the Gateway host entry
if hub.api.config.mac:
hub_host = device_registry.async_get_device(
connections={(CONNECTION_NETWORK_MAC, hub.api.config.mac)},
)
if hub_host and hub_host.id in devices_to_be_removed:
devices_to_be_removed.remove(hub_host.id)
# Don't remove the Gateway service entry # Don't remove the Gateway service entry
hub_service = device_registry.async_get_device( hub_service = device_registry.async_get_device(
identifiers={(DOMAIN, hub.api.config.bridge_id)} identifiers={(DOMAIN, hub.api.config.bridge_id)}

View File

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

View File

@@ -218,6 +218,12 @@ ECOWITT_SENSORS_MAPPING: Final = {
native_unit_of_measurement=PERCENTAGE, native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT, 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_FEET,
UnitOfVolume.CUBIC_METERS, UnitOfVolume.CUBIC_METERS,
UnitOfVolume.LITERS, UnitOfVolume.LITERS,
UnitOfVolume.MILLE_CUBIC_FEET,
*VALID_ENERGY_UNITS, *VALID_ENERGY_UNITS,
} }
VALID_VOLUME_UNITS_WATER: set[str] = { VALID_VOLUME_UNITS_WATER: set[str] = {
@@ -56,6 +57,7 @@ VALID_VOLUME_UNITS_WATER: set[str] = {
UnitOfVolume.CUBIC_METERS, UnitOfVolume.CUBIC_METERS,
UnitOfVolume.GALLONS, UnitOfVolume.GALLONS,
UnitOfVolume.LITERS, UnitOfVolume.LITERS,
UnitOfVolume.MILLE_CUBIC_FEET,
} }
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -28,7 +28,7 @@
} }
}, },
"reauth_confirm": { "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.", "description": "Update FRITZ!Box Tools credentials for: {host}.\n\nFRITZ!Box Tools is unable to log in to your FRITZ!Box.",
"data": { "data": {
"username": "[%key:common::config_flow::data::username%]", "username": "[%key:common::config_flow::data::username%]",
@@ -40,7 +40,7 @@
} }
}, },
"reconfigure": { "reconfigure": {
"title": "Updating FRITZ!Box Tools - configuration", "title": "FRITZ!Box Tools - Update configuration",
"description": "Update FRITZ!Box Tools configuration for: {host}.", "description": "Update FRITZ!Box Tools configuration for: {host}.",
"data": { "data": {
"host": "[%key:common::config_flow::data::host%]", "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.", "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": { "fields": {
"device_id": { "device_id": {
"name": "Fritz!Box Device", "name": "FRITZ!Box device",
"description": "Select the Fritz!Box to configure." "description": "Select the FRITZ!Box to configure."
}, },
"password": { "password": {
"name": "[%key:common::config_flow::data::password%]", "name": "[%key:common::config_flow::data::password%]",
@@ -192,7 +192,7 @@
}, },
"length": { "length": {
"name": "Password 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}", configuration_url=f"http://{self.coordinator.host}",
connections={(CONNECTION_NETWORK_MAC, self.coordinator.mac)}, connections={(CONNECTION_NETWORK_MAC, self.coordinator.mac)},
identifiers={(DOMAIN, self.coordinator.unique_id)}, identifiers={(DOMAIN, self.coordinator.unique_id)},
manufacturer="AVM", manufacturer="FRITZ!",
model=self.coordinator.model, model=self.coordinator.model,
name=self._device_name, name=self._device_name,
sw_version=self.coordinator.current_firmware, sw_version=self.coordinator.current_firmware,

View File

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

View File

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

View File

@@ -3,7 +3,7 @@
"flow_title": "{name}", "flow_title": "{name}",
"step": { "step": {
"user": { "user": {
"description": "Enter your AVM FRITZ!Box information.", "description": "Enter your FRITZ!Box information.",
"data": { "data": {
"host": "[%key:common::config_flow::data::host%]", "host": "[%key:common::config_flow::data::host%]",
"username": "[%key:common::config_flow::data::username%]", "username": "[%key:common::config_flow::data::username%]",
@@ -42,7 +42,7 @@
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"ignore_ip6_link_local": "IPv6 link local address is not supported.", "ignore_ip6_link_local": "IPv6 link local address is not supported.",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", "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%]", "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_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: except FritzSecurityError as ex:
_LOGGER.error( _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" " its phonebooks: %s"
), ),
ex, ex,
@@ -44,7 +44,7 @@ async def async_setup_entry(
except FritzConnectionException as ex: except FritzConnectionException as ex:
raise ConfigEntryAuthFailed from ex raise ConfigEntryAuthFailed from ex
except RequestsConnectionError as 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 raise ConfigEntryNotReady from ex
config_entry.runtime_data = fritzbox_phonebook config_entry.runtime_data = fritzbox_phonebook

View File

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

View File

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

View File

@@ -28,7 +28,7 @@
"abort": { "abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]", "already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]", "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%]" "reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]"
}, },
"error": { "error": {

View File

@@ -38,6 +38,8 @@ from homeassistant.util.hass_dict import HassKey
from .storage import async_setup_frontend_storage from .storage import async_setup_frontend_storage
_LOGGER = logging.getLogger(__name__)
DOMAIN = "frontend" DOMAIN = "frontend"
CONF_THEMES = "themes" CONF_THEMES = "themes"
CONF_THEMES_MODES = "modes" CONF_THEMES_MODES = "modes"
@@ -73,9 +75,11 @@ VALUE_NO_THEME = "none"
PRIMARY_COLOR = "primary-color" 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 # Theme variables that apply to all modes
cv.string: cv.string, cv.string: cv.string,
@@ -86,28 +90,46 @@ EXTENDED_THEME_SCHEMA = vol.Schema(
vol.Optional(CONF_THEMES_DARK): vol.Schema({cv.string: cv.string}), vol.Optional(CONF_THEMES_DARK): vol.Schema({cv.string: cv.string}),
} }
), ),
} },
) )
THEME_SCHEMA = vol.Schema( THEME_SCHEMA = vol.Schema(
{ {
cv.string: ( # Theme variables that apply to all modes
vol.Any( cv.string: cv.string,
# Legacy theme scheme # Mode specific theme variables
{cv.string: cv.string}, vol.Optional(CONF_THEMES_MODES): vol.All(
# New extended schema with mode support {
EXTENDED_THEME_SCHEMA, 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( CONFIG_SCHEMA = vol.Schema(
{ {
DOMAIN: vol.Schema( DOMAIN: vol.Schema(
{ {
vol.Optional(CONF_FRONTEND_REPO): cv.isdir, 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( vol.Optional(CONF_EXTRA_MODULE_URL): vol.All(
cv.ensure_list, [cv.string] cv.ensure_list, [cv.string]
), ),
@@ -546,7 +568,7 @@ async def _async_setup_themes(
new_themes = config.get(DOMAIN, {}).get(CONF_THEMES, {}) new_themes = config.get(DOMAIN, {}).get(CONF_THEMES, {})
try: try:
THEME_SCHEMA(new_themes) new_themes = _validate_themes(new_themes)
except vol.Invalid as err: except vol.Invalid as err:
raise HomeAssistantError(f"Failed to reload themes: {err}") from 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", "documentation": "https://www.home-assistant.io/integrations/frontend",
"integration_type": "system", "integration_type": "system",
"quality_scale": "internal", "quality_scale": "internal",
"requirements": ["home-assistant-frontend==20250829.0"] "requirements": ["home-assistant-frontend==20250903.3"]
} }

View File

@@ -31,7 +31,9 @@ from homeassistant.components.camera import (
WebRTCSendMessage, WebRTCSendMessage,
async_register_webrtc_provider, 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.default_config import DOMAIN as DEFAULT_CONFIG_DOMAIN
from homeassistant.components.stream import Orientation
from homeassistant.config_entries import SOURCE_SYSTEM, ConfigEntry from homeassistant.config_entries import SOURCE_SYSTEM, ConfigEntry
from homeassistant.const import CONF_URL, EVENT_HOMEASSISTANT_STOP from homeassistant.const import CONF_URL, EVENT_HOMEASSISTANT_STOP
from homeassistant.core import Event, HomeAssistant, callback from homeassistant.core import Event, HomeAssistant, callback
@@ -57,12 +59,13 @@ from .server import Server
_LOGGER = logging.getLogger(__name__) _LOGGER = logging.getLogger(__name__)
_FFMPEG = "ffmpeg"
_SUPPORTED_STREAMS = frozenset( _SUPPORTED_STREAMS = frozenset(
( (
"bubble", "bubble",
"dvrip", "dvrip",
"expr", "expr",
"ffmpeg", _FFMPEG,
"gopro", "gopro",
"homekit", "homekit",
"http", "http",
@@ -315,6 +318,32 @@ class WebRTCProvider(CameraWebRTCProvider):
await self.teardown() await self.teardown()
raise HomeAssistantError("Stream source is not supported by go2rtc") 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() streams = await self._rest_client.streams.list()
if (stream := streams.get(camera.entity_id)) is None or not any( 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) await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(async_reload_entry))
return True 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) 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: async def async_remove_entry(hass: HomeAssistant, entry: GoogleConfigEntry) -> None:
"""Handle removal of a local storage.""" """Handle removal of a local storage."""
store = LocalCalendarStore(hass, entry.entry_id) 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 from gcal_sync.exceptions import ApiException, ApiForbiddenException
import voluptuous as vol 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.core import callback
from homeassistant.helpers import config_entry_oauth2_flow from homeassistant.helpers import config_entry_oauth2_flow
from homeassistant.helpers.aiohttp_client import async_get_clientsession from homeassistant.helpers.aiohttp_client import async_get_clientsession
@@ -237,12 +241,12 @@ class OAuth2FlowHandler(
@callback @callback
def async_get_options_flow( def async_get_options_flow(
config_entry: GoogleConfigEntry, config_entry: GoogleConfigEntry,
) -> OptionsFlow: ) -> OptionsFlowHandler:
"""Create an options flow.""" """Create an options flow."""
return OptionsFlowHandler() return OptionsFlowHandler()
class OptionsFlowHandler(OptionsFlow): class OptionsFlowHandler(OptionsFlowWithReload):
"""Google Calendar options flow.""" """Google Calendar options flow."""
async def async_step_init( async def async_step_init(

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