Compare commits

...

1239 Commits

Author SHA1 Message Date
Franck Nijhof
35c8fefbd6 Fix AttributeError in radio_browser media source when runtime_data is missing
The media_player.play_media action was failing with AttributeError when
the radio_browser integration wasn't properly loaded. This happened when
accessing the media source before the config entry was fully initialized.

- Add proper error handling in RadioMediaSource.radios property
- Raise Unresolvable exception when runtime_data is missing or None
- Add comprehensive tests for the error handling scenario

Fixes #141755

🤖 Generated with [Claude Code](https://claude.ai/code)

Co-Authored-By: Claude <noreply@anthropic.com>
2025-07-04 20:38:42 +00:00
Joakim Plate
470baa782e Add zeroconf discovery to philips_js (#147913)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-07-04 22:24:40 +02:00
Sid
6e607ffa01 Add reconfigure flow to eheimdigital (#147930) 2025-07-04 22:18:13 +02:00
Wesley Vos
f5b51c6cf0 Add serial_numbers to device_info of inverters, encharge and enpower (#147964) 2025-07-04 22:04:48 +02:00
Hessel
bfccee17ef Wallbox, Improve test setup (#148036) 2025-07-04 21:56:44 +02:00
Erik Montnemery
b6b6de24ac Replace MediaPlayerState.STANDBY with MediaPlayerState.OFF in cambridge_audio (#148133) 2025-07-04 21:54:11 +02:00
Thomas55555
70624f72b6 Additional icon translation for Husqvarna Automower (#148167) 2025-07-04 21:51:47 +02:00
Thomas55555
c61cd422d1 Delete stale icon translation in Husqvarna Automower (#148168) 2025-07-04 20:47:32 +02:00
karwosts
0b2db2510f Support translating number selector UoM (#148162) 2025-07-04 21:06:33 +03:00
Erik Montnemery
bb1e263149 Remove cv.SUN_CONDITION_SCHEMA (#148158) 2025-07-04 18:34:55 +02:00
Michael Freeman
8e6b9c04f6 Bump venstarcolortouch to 0.21 (#148152) 2025-07-04 17:46:59 +02:00
Greg Dowling
cf931a75a7 Remove incorrect use of via_device in roon component (#146572) 2025-07-04 17:04:16 +02:00
Thomas55555
3250a2fb46 Bump aioautomower to 1.2.0 (#148078) 2025-07-04 16:43:36 +02:00
Franck Nijhof
6235adc69a Fix flaky emulated_roku/test_binding.py::test_events_fired_properly test (#148069) 2025-07-04 16:42:24 +02:00
Simone Chemelli
5d258c2f82 Bump aioamazondevices to 3.2.3 (#148082) 2025-07-04 16:33:16 +02:00
hanwg
cc2aca2c2c Fix Telegram bots using plain text parser failing to load on restart (#148050) 2025-07-04 16:32:46 +02:00
Erik Montnemery
04bd1967a7 Replace MediaPlayerState.STANDBY with MediaPlayerState.OFF in apple_tv (#148132) 2025-07-04 16:31:44 +02:00
Erik Montnemery
a046530eaf Replace MediaPlayerState.STANDBY with MediaPlayerState.IDLE in mediaroom (#148135) 2025-07-04 16:30:03 +02:00
Erik Montnemery
631523dfaf Replace MediaPlayerState.STANDBY with MediaPlayerState.OFF in lookin (#148134) 2025-07-04 16:27:54 +02:00
Erik Montnemery
dc20375506 Replace MediaPlayerState.STANDBY with MediaPlayerState.OFF in snapcast (#148138) 2025-07-04 16:27:33 +02:00
Erik Montnemery
811f085556 Replace MediaPlayerState.STANDBY with MediaPlayerState.IDLE in androidtv (#148130) 2025-07-04 16:27:01 +02:00
Erik Montnemery
fd86a43b28 Replace MediaPlayerState.STANDBY with MediaPlayerState.OFF in ps4 (#148136) 2025-07-04 16:25:59 +02:00
Bram Kragten
b7f830523e Update frontend to 20250702.1 (#148131) 2025-07-04 16:25:28 +02:00
Erik Montnemery
3f752e13ff Replace MediaPlayerState.STANDBY with MediaPlayerState.OFF in roku (#148137) 2025-07-04 16:23:18 +02:00
Marc Mueller
783102f2f6 [ci] Fix typing issue with aiohttp and aiosignal (#148141) 2025-07-04 16:22:38 +02:00
Erik Montnemery
8ce30d9559 Add tests of legacy entity without platform writing state (#148109) 2025-07-04 16:21:48 +02:00
Paulus Schoutsen
cde17fc0ca add extra tests for media source URI parsing (#148114) 2025-07-04 16:21:11 +02:00
David Knowles
83ae5f52da Bump pydrawise to 2025.7.0 (#148088) 2025-07-04 16:20:24 +02:00
tronikos
1cb9767bb8 Enable strict typing for Opower (#148096) 2025-07-04 16:19:04 +02:00
tronikos
e98fe7dc9c Add data_description to Opower forms (#148099) 2025-07-04 16:17:41 +02:00
tronikos
40ec51c0a3 Add redirect URL in Google Assistant SDK setup (#148076) 2025-07-04 16:17:10 +02:00
Harry Heymann
40fcc3b75b Rename Matter device conversion methods (#148090) 2025-07-04 16:13:40 +02:00
Erik Montnemery
510fd09163 Allow core integrations to describe their conditions (#147529)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-07-04 16:03:42 +02:00
epenet
e47bdc06a0 Set docstyle convention to google in ruff (#148142) 2025-07-04 16:00:37 +02:00
Allen Porter
b3d9908cd9 Add AI task structured output (#148083)
Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: Claude <noreply@anthropic.com>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2025-07-04 15:03:34 +02:00
Erik Montnemery
99d63c49bb Add comment about error assigning in frame.report_usage (#148105) 2025-07-04 14:47:01 +02:00
Robin Thoni
4be2e84ce6 Add backward compatibility with older versions of Traccar server (#146639)
Co-authored-by: Joakim Sørensen <joasoe@proton.me>
2025-07-04 14:36:25 +02:00
Allen Porter
1fc624c7a7 Update LLM selector serializer to support ObjectSelector fields and arrays (#148094) 2025-07-04 13:05:16 +02:00
tronikos
8641a2141c Fix has-entity-name and entity-translations in Opower (#148098) 2025-07-04 10:10:21 +02:00
Paulus Schoutsen
04cc451c76 Add AI Task platform to Google Gen AI (#146766) 2025-07-03 23:36:34 -07:00
Erik Montnemery
a3b03caead Deduce integration from module in loader.async_get_issue_tracker (#148017) 2025-07-04 07:55:20 +02:00
Franck Nijhof
49d1d781b8 Fix ezviz test timeout (#148066) 2025-07-03 23:11:54 +02:00
HeroOfCanton16
11c75d7ef2 Add sensor attributes restore to modem_callerid integration (#147753) 2025-07-03 22:10:26 +01:00
Arie Catsman
8ef6b62d9a Cancel enphase mac verification on unload. (#148072) 2025-07-03 22:06:38 +02:00
tronikos
b410b414ec Add reconfigure flow in Android TV Remote (#148044) 2025-07-03 22:00:07 +02:00
Arie Catsman
e5f7421703 Bump pyenphase to 2.2.0 (#148070) 2025-07-03 21:04:13 +02:00
Marc Mueller
8330ae2d3a Update license-expression to 30.4.3 (#147941) 2025-07-03 20:22:10 +02:00
tronikos
4b162f09bd Bump androidtvremote2 to 0.2.3 (#148042) 2025-07-03 20:15:47 +02:00
tronikos
9c558fabcd Use AndroidTVRemoteConfigEntry (#148046) 2025-07-03 20:15:36 +02:00
tronikos
5f9cc0a5f6 Add data_description to forms in Android TV Remote (#148045)
Co-authored-by: Franck Nijhof <frenck@frenck.nl>
Co-authored-by: Artem Draft <Drafteed@users.noreply.github.com>
2025-07-03 20:13:44 +02:00
Erik Montnemery
bc4a322e81 Improve helpers.frame.report_usage when called from outside the event loop (#148021) 2025-07-03 20:12:52 +02:00
Jeef
b999c5906e Bump weatherflow4py to 1.4.1 (#148054) 2025-07-03 20:11:33 +02:00
Erik Montnemery
d2825e1c80 Don't gather TRIGGER_PLATFORM_SUBSCRIPTIONS (#147954)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-07-03 19:33:28 +02:00
epenet
419e4f3b1d Remove unused module in tuya tests (#148058) 2025-07-03 19:14:27 +02:00
Thomas55555
4a937d2452 Set timeout for remote calendar (#147024) 2025-07-03 10:08:58 -07:00
Noah Husby
01b4a5ceed Bump aiorussound to 4.7.0 (#148057) 2025-07-03 19:04:18 +02:00
Abílio Costa
4e71745c62 Set assist_satellite preannounce default to True (#148060) 2025-07-03 18:41:08 +02:00
Franck Nijhof
6a88ee7a8f Add Task issue form (#148038) 2025-07-03 18:27:51 +02:00
J. Nick Koston
3c4ecffa1b Bump aioesphomeapi to 34.1.0 (#148048) 2025-07-03 17:33:44 +02:00
Joakim Sørensen
244e0f5ea8 Bump hass-nabucasa from 0.104.0 to 0.105.0 (#148040) 2025-07-03 14:24:51 +02:00
epenet
a656b6e26a Use HassKey in media_source (#148011) 2025-07-03 09:56:46 +02:00
epenet
691681a78a Move medcom_ble coordinator to separate module (#148009) 2025-07-03 09:32:57 +02:00
epenet
3bc00824e2 Use runtime_data in mystrom (#148020) 2025-07-03 09:27:38 +02:00
epenet
7d36a2e3a7 Move meteoclimatic coordinator to separate module (#148018) 2025-07-03 09:26:24 +02:00
Norbert Rittel
b1e3561ead Clarify description of autorelock setting in zwave_js (#148019) 2025-07-03 09:23:45 +02:00
epenet
bfc814c839 Use entry.async_on_unload in meteo_france (#148015) 2025-07-03 09:22:27 +02:00
epenet
5008151688 Use entry.async_on_unload in monoprice (#148016) 2025-07-03 09:20:50 +02:00
Franck Nijhof
d738c0d6b1 Merge branch 'master' into dev 2025-07-03 07:04:46 +00:00
epenet
e42235285d Use runtime_data in melcloud (#148012)
Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-03 08:57:22 +02:00
epenet
04e69479f4 Fix hass.data reference in lookin (#148008) 2025-07-03 08:54:20 +02:00
epenet
b973916032 Move met_eireann coordinator to separate module (#148014) 2025-07-03 08:53:22 +02:00
epenet
6f4757ef42 Use runtime_data in melnor (#148013) 2025-07-03 08:52:40 +02:00
epenet
a6962e9e1e Fix missing port in samsungtv (#147962)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-07-03 08:51:38 +02:00
Marcel van der Veldt
142c10cccc Fix state being incorrectly reported in some situations on Music Assistant players (#147997) 2025-07-03 08:50:41 +02:00
Matthias Alphart
c137c96cfd KNX: use async_load_json_object_fixture in tests (#147991) 2025-07-03 08:00:34 +02:00
Robert Svensson
f0e0c954e7 Bump aiounifi to v84 (#147987) 2025-07-02 23:10:21 +02:00
Norbert Rittel
681961d3a5 Use common config_flow strings in vegehub (#147984) 2025-07-02 22:14:55 +02:00
Matthias Alphart
53d2f6b0c6 KNX: Use a ConfigExtractor helper class for value retrieval (#147983) 2025-07-02 21:49:24 +02:00
G Johansson
78c39f8a06 Remove deprecated battery properties from demo vacuum (#147980) 2025-07-02 21:49:12 +02:00
Ludovic BOUÉ
a748525e03 Allow LevelControl Cluster for Matter Pump devices (#145004)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-07-02 21:48:15 +02:00
Manuel Rüger
8ca1fe83b7 Bump switchbot-api to v2.7.0 (#147978) 2025-07-02 21:36:06 +02:00
Matthias Alphart
8968cf704b Use send_json_auto_id in KNX tests (#147982) 2025-07-02 21:34:30 +02:00
puddly
ebe04466f4 Bump ZHA to 0.0.62 (#147966) 2025-07-02 21:19:32 +02:00
G Johansson
e31470ba5b Change breaking version for battery props in vacuum (#147956) 2025-07-02 19:06:56 +02:00
Franck Nijhof
4bc2951f44 2025.7.0 (#147533) 2025-07-02 18:01:06 +02:00
Franck Nijhof
8334a0398c Bump version to 2025.7.0 2025-07-02 15:12:16 +00:00
Ville Skyttä
80a1e0e4cd Improve huawei_lte config flow class naming (#147910) 2025-07-02 17:02:39 +02:00
Thomas55555
3778f537d5 Remove noisy debug logs in Husgvarna Automower (#147958) 2025-07-02 15:28:42 +01:00
Petro31
adec157d43 Allow trigger based numeric sensors to be set to unknown (#137047)
* Allow trigger based numeric sensors to be set to unknown

* resolve comments

* Do case insensitive check

* use _parse_result

---------

Co-authored-by: abmantis <amfcalt@gmail.com>
2025-07-02 15:35:47 +02:00
Franck Nijhof
8fc3fa51a8 Bump version to 2025.7.0b9 2025-07-02 13:30:51 +00:00
c0ffeeca7
4eb688b560 Z-Wave JS: rename controller to adapter according to term decision (#147955)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-02 13:30:31 +00:00
Simone Chemelli
9472ff5d36 Bump aioamazondevices to 3.2.2 (#147953) 2025-07-02 13:30:29 +00:00
Bram Kragten
12e8b81ec7 Update frontend to 20250702.0 (#147952) 2025-07-02 13:30:28 +00:00
Paulus Schoutsen
ec5e543c09 Ollama: Migrate pick model to subentry (#147944) 2025-07-02 13:30:27 +00:00
Paulus Schoutsen
116c745872 Split Ollama entity (#147769) 2025-07-02 13:30:26 +00:00
Robert Resch
1fdf152292 Bump deebot-client to 13.5.0 (#147938) 2025-07-02 13:27:47 +00:00
G Johansson
b816f1a408 Handle additional errors in Nord Pool (#147937) 2025-07-02 13:27:46 +00:00
John Hess
eb351e6505 Bump thermopro-ble to 0.13.1 (#147924) 2025-07-02 13:27:45 +00:00
Maciej Bieniek
2f27d55495 Open repair issue when outbound WebSocket is enabled for Shelly non-sleeping RPC device (#147901) 2025-07-02 13:26:03 +00:00
Space
fa1bed1849 Skip processing request body for HTTP HEAD requests (#147899)
* Skip processing request body for HTTP HEAD requests

* Use aiohttp's must_be_empty_body() to check whether ingress requests should be streamed

* Only call must_be_empty_body() once per request

* Fix incorrect use of walrus operator
2025-07-02 13:26:01 +00:00
Raphael Hehl
b8c19f23f3 UnifiProtect Change log level from debug to error for connection exceptions in ProtectFlowHandler (#147730) 2025-07-02 13:26:00 +00:00
Erwin Douna
b677ce6c90 SMA add DHCP strictness (#145753)
* Add DHCP strictness (needs beta check)

* Update to check on CONF_MAC

* Update to check on CONF_HOST

* Update hostname

* Polish it a bit

* Update to CONF_HOST, again

* Add split

* Add CONF_MAC add upon detection

* epenet feedback

* epenet round II
2025-07-02 13:25:59 +00:00
c0ffeeca7
d6da686ffe Z-Wave JS: rename controller to adapter according to term decision (#147955)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-07-02 15:23:08 +02:00
Paulus Schoutsen
f50ef79c72 Ollama: Migrate pick model to subentry (#147944) 2025-07-02 15:20:42 +02:00
Erik Montnemery
943fb9948b Adjust logic related to entity platform state (#147882)
* Adjust logic related to entity platform state

* Break up hard to read if-statement

* Add and improve tests
2025-07-02 14:57:53 +02:00
Raphael Hehl
7447cf329b UnifiProtect Change log level from debug to error for connection exceptions in ProtectFlowHandler (#147730) 2025-07-02 14:57:46 +02:00
Erwin Douna
3d27c0ce52 SMA add DHCP strictness (#145753)
* Add DHCP strictness (needs beta check)

* Update to check on CONF_MAC

* Update to check on CONF_HOST

* Update hostname

* Polish it a bit

* Update to CONF_HOST, again

* Add split

* Add CONF_MAC add upon detection

* epenet feedback

* epenet round II
2025-07-02 14:48:21 +02:00
Simone Chemelli
b7496be61f Bump aioamazondevices to 3.2.2 (#147953) 2025-07-02 14:27:51 +02:00
Bram Kragten
57a98240bd Update frontend to 20250702.0 (#147952) 2025-07-02 14:26:19 +02:00
Ville Skyttä
ff76017ba6 Simplify unnecessary re match.groups()[0] calls (#147909) 2025-07-02 14:12:26 +02:00
Maikel Punie
f10fcde6d8 Remove the deprecated interface paramater for velbus (#147868) 2025-07-02 14:07:47 +02:00
Marc Mueller
a7002e3a24 Update pytest to 8.4.1 (#147951) 2025-07-02 13:02:18 +01:00
tronikos
bbe03dcab7 Add missing Opower tests (#147934) 2025-07-02 13:46:40 +02:00
Andre Lengwenus
f77e6cc8fc Add missing exception translations to LCN (#147723) 2025-07-02 13:41:06 +02:00
Petro31
cb8e076703 Fix missing device_class and state_class on compensation entities (#146115)
Co-authored-by: Robert Resch <robert@resch.dev>
2025-07-02 13:39:19 +02:00
G Johansson
73251fbb1c Handle additional errors in Nord Pool (#147937) 2025-07-02 13:26:47 +02:00
Maciej Bieniek
7ff90ca49d Open repair issue when outbound WebSocket is enabled for Shelly non-sleeping RPC device (#147901) 2025-07-02 13:06:27 +02:00
Manu
bab9ec9976 Add sensor for online status to PlayStation Network (#147842) 2025-07-02 11:47:41 +01:00
Marc Mueller
1051f85ac0 Update coverage to 7.9.1 (#147940) 2025-07-02 12:20:50 +02:00
Marc Mueller
6c7da57af2 Update pytest-cov to 6.2.1 (#147942) 2025-07-02 12:14:27 +02:00
Marc Mueller
73e505d48d Update pytest-xdist to 3.8.0 (#147943) 2025-07-02 12:11:09 +02:00
Marc Mueller
ec65066f5e Update mypy-dev to 1.17.0a4 (#147939) 2025-07-02 12:09:39 +02:00
Robert Resch
9c4951261c Bump deebot-client to 13.5.0 (#147938) 2025-07-02 12:00:48 +02:00
Space
00dfc04b86 Skip processing request body for HTTP HEAD requests (#147899)
* Skip processing request body for HTTP HEAD requests

* Use aiohttp's must_be_empty_body() to check whether ingress requests should be streamed

* Only call must_be_empty_body() once per request

* Fix incorrect use of walrus operator
2025-07-02 11:45:45 +02:00
Manu
bee07ad284 Fix Online ID string in PlayStation Network integration (#147915) 2025-07-02 10:45:07 +02:00
Paulus Schoutsen
b2108fdd40 Update Dockerfile.dev to only use uv for Python (#147926) 2025-07-02 10:40:16 +02:00
John Hess
3730a1a379 Bump thermopro-ble to 0.13.1 (#147924) 2025-07-02 10:11:49 +02:00
Sid
088c02d38a Complete tests for eheimdigital (#143337)
* Complete tests for eheimdigital

* Review

* Review

* Review

* Review

* Fix tests
2025-07-02 10:09:30 +02:00
Harry Heymann
afb247c907 Bump Python Matter server to 8.0.0 (#147783) 2025-07-02 08:12:47 +02:00
Franck Nijhof
0e6bbb30c1 Bump version to 2025.7.0b8 2025-07-02 06:04:14 +00:00
J. Nick Koston
fdba791f18 Bump bluetooth-data-tools to 1.28.2 (#147920) 2025-07-02 06:03:56 +00:00
Ivan Lopez Hernandez
d4dec6c7a9 Swap the Models label for the model name not it's display name, (#147918)
Swap display name for name.
2025-07-02 06:03:55 +00:00
Simone Chemelli
f838e85a79 Manager wrong country selection in Alexa Devices (#147914)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-07-02 06:03:54 +00:00
Simone Chemelli
04ae966544 Bump aioamazondevices to 3.2.1 (#147912) 2025-07-02 06:03:53 +00:00
Simone Chemelli
77dcba0984 Manager wrong country selection in Alexa Devices (#147914)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-07-02 08:02:53 +02:00
Simone Chemelli
48f9a12cca Bump aioamazondevices to 3.2.1 (#147912) 2025-07-02 07:36:41 +02:00
J. Nick Koston
bdd2ac9ae4 Bump bluetooth-data-tools to 1.28.2 (#147920) 2025-07-02 07:34:40 +02:00
Ivan Lopez Hernandez
2e7113d881 Swap the Models label for the model name not it's display name, (#147918)
Swap display name for name.
2025-07-01 21:12:58 -07:00
Sid
6842bfae4c Bump eheimdigital to 1.3.0 (#147908) 2025-07-01 23:00:25 +01:00
nadimz
392cde20d9 Add support for opening state in template lock (#147813)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-07-01 22:03:20 +01:00
cristianburrini
a6146fb5a9 Increase the number of irrigation zones up to 8 for Tuya enabled controllers. (#147793) 2025-07-01 22:40:36 +02:00
Franck Nijhof
b2c393db72 Bump version to 2025.7.0b7 2025-07-01 20:11:01 +00:00
Jesse Hills
6104731d53 Remove codeowner from ESPHome (#147850) 2025-07-01 22:09:23 +02:00
Marcel van der Veldt
3ed440a3af Bump Music Assistant Client to 1.2.3 (#147885) 2025-07-01 20:08:45 +00:00
Jamin
01e7efc7b4 Bump VoIP utils to 0.3.3 (#147880) 2025-07-01 20:08:44 +00:00
avee87
60a930554a Fix station name sensor for metoffice (#145500) 2025-07-01 20:08:43 +00:00
Erik Montnemery
66308a848a Set Entity._platform_state in google_assistant tests (#147892) 2025-07-01 21:46:36 +02:00
Erik Montnemery
c71dbd9d4d Set Entity._platform_state in universal tests (#147894) 2025-07-01 21:46:01 +02:00
Erik Montnemery
1195c2ec10 Set Entity._platform_state in core customize test (#147895) 2025-07-01 21:45:08 +02:00
Norbert Rittel
78a9cd9201 Use (new) common state "Empty" for water level in switchbot (#147836) 2025-07-01 21:43:21 +02:00
Erik Montnemery
639a749a0f Mock recorder in ista_ecotrend tests (#147893) 2025-07-01 20:09:48 +01:00
Simone Chemelli
058f3b8b6e Add reauth to Alexa Devices config flow (#147773) 2025-07-01 20:57:24 +02:00
Manu
926e9261ab Add switch to enable/disable boost in IronOS integration (#147831) 2025-07-01 20:53:13 +02:00
Erik Montnemery
d6fb860889 Use entity_registry_enabled_by_default fixture in dsmr_reader tests (#147891) 2025-07-01 20:50:38 +02:00
Marcel van der Veldt
5e03900e0a Bump Music Assistant Client to 1.2.3 (#147885) 2025-07-01 20:26:26 +02:00
Erik Montnemery
1e6e5ca1b6 Fix broadlink tests (#147890) 2025-07-01 18:32:58 +01:00
Erik Montnemery
60e3b38de1 Set Entity._platform_state in arcam_fmj tests (#147889) 2025-07-01 17:58:15 +02:00
epenet
852522219c Use correctly formatted MAC in bond tests (#147887) 2025-07-01 17:56:10 +02:00
epenet
23f1e8d1a3 Use correctly formatted MAC in elkm1 tests (#147888) 2025-07-01 17:55:46 +02:00
Franck Nijhof
c707bf6264 Bump version to 2025.7.0b6 2025-07-01 14:26:59 +00:00
avee87
655f009f07 Fix station name sensor for metoffice (#145500) 2025-07-01 16:18:13 +02:00
Paul Bottein
3548ab70fd Update frontend to 20250701.0 (#147879) 2025-07-01 14:10:30 +00:00
Erik Montnemery
e272ab1885 Initialize EsphomeEntity._has_state (#147877) 2025-07-01 14:10:29 +00:00
Erik Montnemery
d5d1b620d0 Correct openai conversation config entry migration (#147859) 2025-07-01 14:10:28 +00:00
Erik Montnemery
8b2f4f0f86 Correct ollama config entry migration (#147858) 2025-07-01 14:10:26 +00:00
Erik Montnemery
725269ecda Correct anthropic config entry migration (#147857) 2025-07-01 14:10:25 +00:00
Erik Montnemery
c42fc818bf Correct Google generative AI config entry migration (#147856) 2025-07-01 14:10:23 +00:00
Jesse Hills
5554e38171 Implement suggested_display_precision for ESPHome (#147849) 2025-07-01 14:10:22 +00:00
Jan Bouwhuis
b25acfe823 Fix invalid configuration of MQTT device QoS option in subentry flow (#147837) 2025-07-01 14:10:21 +00:00
micha91
ff25948e37 fix: Create new aiohttp session with DummyCookieJar (#147827) 2025-07-01 14:10:19 +00:00
Maciej Bieniek
f85fc7173f Bump Nettigo Air Monitor backend library to version 5.0.0 (#147812) 2025-07-01 14:10:18 +00:00
Bob Laz
748cc6386d fix state_class for water used today sensor (#147787) 2025-07-01 14:10:17 +00:00
Manu
47b232db49 Add more mac address prefixes for discovery to PlayStation Network (#147739) 2025-07-01 14:10:15 +00:00
hanwg
c61935fc41 Include chat ID in Telegram bot subentry title (#147643) 2025-07-01 14:10:14 +00:00
Jan-Philipp Benecke
414318f3fb Catch access denied errors in webdav and display proper message (#147093) 2025-07-01 14:10:12 +00:00
Paul Bottein
08985d783f Fix Meteo france Ciel clair condition mapping (#146965)
Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2025-07-01 14:10:11 +00:00
Thomas55555
e4bcde7d20 Fix wrong state in Husqvarna Automower (#146075) 2025-07-01 14:10:10 +00:00
Jamin
59bf39f4ed Bump VoIP utils to 0.3.3 (#147880) 2025-07-01 16:09:51 +02:00
Fredrik Mårtensson
510e3977df Add water_level sensor to Tuya pet fountain cwysj (#146602)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2025-07-01 14:57:17 +01:00
micha91
922720576a fix: Create new aiohttp session with DummyCookieJar (#147827) 2025-07-01 15:50:04 +02:00
Paul Bottein
e10b581d4b Fix Meteo france Ciel clair condition mapping (#146965)
Co-authored-by: Simon Lamon <32477463+silamon@users.noreply.github.com>
2025-07-01 15:43:34 +02:00
hanwg
e38eac9415 Include chat ID in Telegram bot subentry title (#147643) 2025-07-01 15:42:32 +02:00
Maciej Bieniek
11c9aa9280 Bump Nettigo Air Monitor backend library to version 5.0.0 (#147812) 2025-07-01 15:39:29 +02:00
Paul Bottein
52c86f8a6a Update frontend to 20250701.0 (#147879) 2025-07-01 15:38:04 +02:00
Marc Mueller
6364a9ad98 Update pillow to 11.3.0 (#147869) 2025-07-01 14:31:06 +01:00
Manu
651162b8e7 Fix error in last online sensor of PlayStation integration (#147844)
* Fix Last online sensor

* set unavailable

* available_fn
2025-07-01 15:17:10 +02:00
Denis Shulyaka
7deca35172 Add multiple LLM API support for MCP Server (#147785)
* Add multiple LLM API support for MCP Server

* Update homeassistant/components/mcp_server/config_flow.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* ruff

* Update tests/components/mcp_server/conftest.py

Co-authored-by: Allen Porter <allen.porter@gmail.com>

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Allen Porter <allen.porter@gmail.com>
2025-07-01 06:14:03 -07:00
epenet
073a467fb2 Use correctly formatted MAC in bond tests (#147870) 2025-07-01 14:41:31 +02:00
epenet
3f9590b03b Use correctly formatted MAC in gogogate2 tests (#147872) 2025-07-01 14:41:20 +02:00
epenet
b47f989c77 Use correctly formatted MAC in wmspro tests (#147876) 2025-07-01 14:40:41 +02:00
epenet
4ebffa8d23 Use correctly formatted MAC in palazzetti tests (#147875) 2025-07-01 14:40:27 +02:00
epenet
c5873c6dd0 Use correctly formatted MAC in dlink tests (#147871) 2025-07-01 14:40:12 +02:00
Erik Montnemery
2cb80e083e Initialize EsphomeEntity._has_state (#147877) 2025-07-01 07:33:33 -05:00
epenet
871296dff6 Use correctly formatted MAC in lamarzocco tests (#147874) 2025-07-01 14:13:21 +02:00
Claudio Ruggeri - CR-Tech
c92873bbff Change default slave id from 0 to 1 in modbus actions (#142865)
* set default slave id in service calls

* add test

* revert out of scope change
2025-07-01 13:15:32 +02:00
Norbert Rittel
5fea4915ef Use (new) common state "Empty" in litterrobot (#147835) 2025-07-01 13:13:12 +02:00
dependabot[bot]
8fa016059d Bump github/codeql-action from 3.29.1 to 3.29.2 (#147867)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-07-01 12:30:01 +02:00
Bob Laz
61a29db72c fix state_class for water used today sensor (#147787) 2025-07-01 12:28:13 +02:00
epenet
5a3aa7874d Use correctly formatted MAC in airthings tests (#147817) 2025-07-01 12:26:10 +02:00
Parker Brown
12e2493c42 Capitalize "version" in Tesla fleet strings (#146501) 2025-07-01 12:18:55 +02:00
Paulus Schoutsen
659cd42739 Move async_reload on updates in async_setup_entry in Anthropic (#147862)
Co-authored-by: Claude <noreply@anthropic.com>
2025-07-01 12:16:00 +02:00
Paulus Schoutsen
7fcea17e83 Move async_reload on updates in async_setup_entry in OpenAI Conversation (#147863)
Co-authored-by: Claude <noreply@anthropic.com>
2025-07-01 12:15:28 +02:00
Paulus Schoutsen
30a85c40da Move async_reload on updates in async_setup_entry in Ollama (#147861)
Co-authored-by: Claude <noreply@anthropic.com>
2025-07-01 12:14:46 +02:00
epenet
57a8f1e0cc Use correctly formatted MAC in rehlko tests (#147864) 2025-07-01 12:09:00 +02:00
epenet
78aeae577d Use correctly formatted MAC in roomba tests (#147865) 2025-07-01 11:24:08 +02:00
epenet
3f95cb37e6 Use correctly formatted MAC in sma tests (#147866) 2025-07-01 11:23:31 +02:00
epenet
12aef4aae5 Use correctly formatted MAC in knocki tests (#147821) 2025-07-01 11:22:48 +02:00
Thomas55555
2e12db001d Fix wrong state in Husqvarna Automower (#146075) 2025-07-01 10:53:55 +02:00
epenet
573325be97 Use correctly formatted MAC in home_connect tests (#147818) 2025-07-01 10:51:49 +02:00
Erik Montnemery
7021fe7495 Correct openai conversation config entry migration (#147859) 2025-07-01 10:49:07 +02:00
Erik Montnemery
b7999755bd Correct anthropic config entry migration (#147857) 2025-07-01 10:47:06 +02:00
Erik Montnemery
99f7a031d6 Correct Google generative AI config entry migration (#147856) 2025-07-01 10:46:13 +02:00
Erik Montnemery
8fc31283b7 Correct ollama config entry migration (#147858) 2025-07-01 10:45:17 +02:00
Jan-Philipp Benecke
5ff698c78d Catch access denied errors in webdav and display proper message (#147093) 2025-07-01 10:15:45 +02:00
Jesse Hills
9469c6ad1c Implement suggested_display_precision for ESPHome (#147849) 2025-07-01 09:16:23 +02:00
Norbert Rittel
35f0505c7b Use (new) common state "Empty" in whirlpool (#147847)
Use (new) common state "Empty"
2025-07-01 08:59:55 +02:00
Norbert Rittel
a180cabea9 Use (new) common state "Full" in overkiz (#147848)
Use (new) common state "Full"
2025-07-01 08:58:31 +02:00
Jan Bouwhuis
4f7348b8bc Fix invalid configuration of MQTT device QoS option in subentry flow (#147837) 2025-07-01 08:46:58 +02:00
On Freund
ddf56f053b Support device removal in CoolMasterNet integration (#147851) 2025-07-01 08:26:04 +02:00
G Johansson
9719d2ef2b Start deprecation of battery properties in vacuum (#146401)
* Start deprecation of battery properties in vacuum

* Small fixes

* Fixes

* Deprecate battery supported feature
2025-07-01 08:23:47 +02:00
Manu
2afe475234 Add more mac address prefixes for discovery to PlayStation Network (#147739) 2025-07-01 07:12:00 +02:00
Norbert Rittel
23c304fc75 Use (new) common state "Full" in enphase_envoy (#147834)
Use (new) common state "Full"
2025-06-30 20:13:05 -04:00
Norbert Rittel
84645d0ca6 Use (new) common states for "Full" and "Empty" in lg_thinq (#147833)
Use (new) common states for "Full" and "Empty"
2025-07-01 01:59:33 +02:00
Norbert Rittel
2bdfc8cf5e Add common states "Empty" and "Full" (#146646) 2025-06-30 22:08:55 +02:00
epenet
603e277a5b Add docstring to DhcpServiceInfo MAC address (#147823)
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-06-30 21:54:05 +02:00
Paulus Schoutsen
38a7b21052 Split Anthropic entity (#147770) 2025-06-30 21:47:44 +02:00
Franck Nijhof
db04c77e62 Bump version to 2025.7.0b5 2025-06-30 19:39:34 +00:00
puddly
e8204e5f8e Await firmware installation task when flashing ZBT-1/Yellow firmware (#147824) 2025-06-30 19:39:03 +00:00
starkillerOG
66cf9c4ed5 Bump reolink_aio to 0.14.2 (#147797) 2025-06-30 19:39:02 +00:00
mkmer
1f6d28dcbf Honeywell: Don't use shared session (#147772) 2025-06-30 19:39:02 +00:00
Paulus Schoutsen
328e838351 Use media selector for Assist Satellite actions (#147767)
Co-authored-by: Michael Hansen <mike@rhasspy.org>
2025-06-30 19:39:01 +00:00
cdnninja
62a1c8af11 Fix Vesync set_percentage error (#147751) 2025-06-30 19:39:00 +00:00
tronikos
b50e599517 Move the async_reload on updates in async_setup_entry in Google Generative AI (#147748)
Move the async_reload on updates in async_setup_entry
2025-06-30 19:38:59 +00:00
Manu
3c7c9176d2 Fix sensor displaying unknown when getting readings from heat meters in ista EcoTrend (#147741) 2025-06-30 19:37:54 +00:00
J. Nick Koston
c771f5fe1e Preserve httpx boolean behavior in REST integration after aiohttp conversion (#147738) 2025-06-30 19:35:31 +00:00
hanwg
6dc464ad73 Fix Telegram bot proxy URL not initialized when creating a new bot (#147707) 2025-06-30 19:35:30 +00:00
Marc Hörsken
ae48e3716e Update pywmspro to 0.3.0 to wait for short-lived actions (#147679)
Replace action delays with detailed action responses.
2025-06-30 19:35:29 +00:00
Hessel
1543726095 Wallbox Integration, Reduce API impact by limiting the amount of API calls made (#147618) 2025-06-30 19:35:27 +00:00
Evan Severson
adbace95c3 Fixed pushbullet handling of fields longer than 255 characters (#146993) 2025-06-30 19:35:26 +00:00
Shay Levy
578b43cf61 Bump aioshelly to 13.7.1 (#146221)
* Bump aioshelly to 13.8.0

* Change version to 13.7.1
2025-06-30 19:35:25 +00:00
mvn23
a8b5d1511d Populate hvac_modes list in opentherm_gw (#142074) 2025-06-30 19:35:24 +00:00
Pete Sage
5a0a1bbbf4 Person ble_trackers for non-home zones not processed correctly (#138475)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-06-30 19:35:23 +00:00
Paulus Schoutsen
bf74ba990a Split Ollama entity (#147769) 2025-06-30 21:31:54 +02:00
Paulus Schoutsen
70856bd92a Split OpenAI entity (#147771) 2025-06-30 21:11:51 +02:00
Paulus Schoutsen
be6b624081 Improve validation for media selector (#147768) 2025-06-30 20:26:52 +02:00
mvn23
217fbb2849 Populate hvac_modes list in opentherm_gw (#142074) 2025-06-30 20:24:13 +02:00
epenet
22a14da19c Rename service registration method (#146615) 2025-06-30 20:21:38 +02:00
puddly
20f5d85800 Await firmware installation task when flashing ZBT-1/Yellow firmware (#147824) 2025-06-30 20:18:22 +02:00
hanwg
88feb5139b Fix Telegram bot proxy URL not initialized when creating a new bot (#147707) 2025-06-30 20:16:45 +02:00
Hessel
90cbe272a0 Wallbox Integration, Reduce API impact by limiting the amount of API calls made (#147618) 2025-06-30 20:15:48 +02:00
Paulus Schoutsen
511b739bf6 Use media selector for Assist Satellite actions (#147767)
Co-authored-by: Michael Hansen <mike@rhasspy.org>
2025-06-30 20:12:03 +02:00
Manu
9961a499ee Fix sensor displaying unknown when getting readings from heat meters in ista EcoTrend (#147741) 2025-06-30 20:11:46 +02:00
rubenbe
d8c7ed473b Bump xiaomi-ble to 1.1.0 (#147828)
Bump xiaomi-ble to 1.1.0
2025-06-30 20:11:03 +02:00
Manu
2c30a5a14c Improve exception handling of PlayStation Network (#147792) 2025-06-30 19:53:46 +02:00
Manu
5e3fc858d8 Add sensor last online to PlayStation Network integration (#147796) 2025-06-30 19:52:11 +02:00
epenet
f03af213d4 Use correctly formatted MAC in lg_thinq tests (#147822) 2025-06-30 19:50:50 +02:00
epenet
1e3ebd5650 Use correctly formatted MAC in incomfort tests (#147819) 2025-06-30 18:02:42 +02:00
epenet
53936ab062 Use async_load_fixture in weatherflow_cloud (#147816) 2025-06-30 18:01:14 +02:00
Bouwe Westerdijk
b52a248def Bump plugwise to v1.7.7 and adapt (#147809) 2025-06-30 14:40:10 +01:00
Jeef
ea70229426 Add Weatherflow Cloud wind support via websocket (#125611)
* rebase off of dev

* update tests

* update tests

* addressing PR finally

* API to back

* adding a return type

* need to test

* removed teh extra check on available

* some changes

* ready for re-review

* change assertions

* remove icon function

* update ambr

* ruff

* update snapshot and push

* Update homeassistant/components/weatherflow_cloud/coordinator.py

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

* Update homeassistant/components/weatherflow_cloud/coordinator.py

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

* enhnaced tests

* better coverage

* Update homeassistant/components/weatherflow_cloud/coordinator.py

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

* Update homeassistant/components/weatherflow_cloud/coordinator.py

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

* Update homeassistant/components/weatherflow_cloud/coordinator.py

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

* Update homeassistant/components/weatherflow_cloud/coordinator.py

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

* Update homeassistant/components/weatherflow_cloud/coordinator.py

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

* remove comments

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-06-30 15:26:17 +02:00
Erik Montnemery
741a3d5009 Remove backup helper (#143558)
* Remove backup helper

* Update aws_s3 tests
2025-06-30 14:11:10 +02:00
Pete Sage
ee8830cc77 Person ble_trackers for non-home zones not processed correctly (#138475)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-06-30 13:35:19 +02:00
Bouwe Westerdijk
7fbf25e862 Plugwise: remove outdated fixtures (#147806) 2025-06-30 12:15:52 +02:00
epenet
e642cd45ae Enforce async_load_fixture in async test functions (#145709) 2025-06-30 11:56:26 +02:00
dependabot[bot]
179e1c2b00 Bump github/codeql-action from 3.29.0 to 3.29.1 (#147799)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-30 11:53:30 +02:00
Phill (pssc)
52a99aea0c Squeezebox: Fix Allow server device details to merge with players with the same MAC (#133517)
* Disambiguate bewtween servers and player to stop them being merged

* ruff format

* make SqueezeLite players not a service

* ruff

* Tidy redunant code

* config url

* revert config url

* change to domain server

* use default to see how they are mereged with server device

* refactor to use defaults so where a player is part of a bigger ie server service device in the same intergration it doesnt replace its information

* ruff

* make test match the new data

* Fix merge

* Fix tests

* Fix meregd test data

* Fix all tests add new test for merged device in reg

* Remove info from device_info so its only a lookup

* manual merge of server player shared devices

* Fix format of merged entires

* fixes for testing

* Fix test with input from @peteS-UK device knowlonger exits for this test

* Fix test now device doesnt exits for tests

* Update homeassistant/components/squeezebox/media_player.py

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>

* Fix Copilots formatting

* Apply suggestions from code review

---------

Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-06-30 11:41:22 +02:00
Paulus Schoutsen
c7b2f236be Type Z-Wave JS config entry (#147456)
* Type Z-Wave JS config entry

* Migrate to data class
2025-06-30 11:15:12 +02:00
Evan Severson
a6e3da43ca Fixed pushbullet handling of fields longer than 255 characters (#146993) 2025-06-30 11:08:50 +02:00
Steffen Rusitschka
4d58024d5d Add publish_string_states config to zabbix (#134773)
* Add include_strings config to zabbix

* Remove commented code

* Fix ruff formatting

* Update homeassistant/components/zabbix/__init__.py

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>

* Update homeassistant/components/zabbix/__init__.py

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>

* Don't use dict.get, CONF_INCLUDE_STRINGS has a default value and will always be set.

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

* Convert to string only when include_strings is true

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

* change to guard

* Fix review comments

* ruff, mypy, pylint fixes

* more ruff, mypy fixes

* and another ruff format fix

---------

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-06-30 10:52:33 +02:00
Manu
c7603b39ec Fix inputs to correctly handle Fahrenheit in IronOS (#135421)
* Fix inputs to correctly handle Fahrenheit in IronOS

* some refactoring

* add boost switch entity

* Revert switch entity

* refactor

* remove commented code

* some changes
2025-06-30 10:44:39 +02:00
epenet
c17ee0d123 Allow binary sensor template to return state unknown (#128861)
* Allow binary sensor template to return state unknown

* Add tests

* Adjust TriggerBinarySensorEntity

* Add restore tests for BinarySensorTemplate

* Add tests for TriggerBinarySensorEntity

* Tweak

* Tweak

* Adjust tests

* Adjust
2025-06-30 10:06:05 +02:00
Alexandre CUER
97c1e21a69 Add possibility to synchronize automatically all available feeds in emoncms (#128122)
* Add checkbox in options to sync all feeds once

* Add sync mode selector in async_step_user
Remove checkbox in options

* Correct use of SYNC_MODE & SYNC_MODE_AUTO in tests

* Use dropdown for mode selection

* rmv_unused_const

* Add separate tests + use SelectSelector
2025-06-30 10:05:07 +02:00
starkillerOG
c9a6b1fd45 Bump reolink_aio to 0.14.2 (#147797) 2025-06-30 09:39:02 +02:00
mkmer
05ceee568e Honeywell: Don't use shared session (#147772) 2025-06-29 21:22:59 +02:00
Shay Levy
08a6b38699 Bump aioshelly to 13.7.1 (#146221)
* Bump aioshelly to 13.8.0

* Change version to 13.7.1
2025-06-29 21:41:50 +03:00
Norbert Rittel
4add346272 Deduplicate strings and fix sentence-casing in proximity (#147777)
* Deduplicate strings and fix sentence-casing in `proximity`

* Update test_init.py
2025-06-29 21:00:16 +03:00
Andre Lengwenus
369c8d1e0d Bump pypck to 0.8.10 (#147774) 2025-06-29 20:58:41 +03:00
tronikos
25ab47a587 Move the async_reload on updates in async_setup_entry in Google Generative AI (#147748)
Move the async_reload on updates in async_setup_entry
2025-06-29 07:56:37 +02:00
Marc Hörsken
617ea1925c Update pywmspro to 0.3.0 to wait for short-lived actions (#147679)
Replace action delays with detailed action responses.
2025-06-29 07:33:44 +02:00
cdnninja
8bacab4f9c Fix Vesync set_percentage error (#147751) 2025-06-29 07:22:04 +02:00
J. Nick Koston
6d28b99344 Preserve httpx boolean behavior in REST integration after aiohttp conversion (#147738) 2025-06-28 17:24:09 -05:00
cnico
bbd1cbf5c9 Correct Chlorine unit definition in flipr integration (#147537)
* Correction of bug 145683

* constant for chlorine unit correction

* constant name correction

* Review correction
2025-06-28 22:29:24 +01:00
Paulus Schoutsen
cf2e69ed74 Bump version to 2025.7.0b4 2025-06-28 20:27:42 +00:00
J. Nick Koston
c32b44b774 Improve rest error logging (#147736)
* Improve rest error logging

* Improve rest error logging

* Improve rest error logging

* Improve rest error logging

* Improve rest error logging

* top level
2025-06-28 20:27:20 +00:00
Florian von Garrel
2f69ed4a8a bump pypaperless to 4.1.1 (#147735) 2025-06-28 20:27:19 +00:00
Marc Hörsken
4b3449fe0c Fix error if cover position is not available or unknown (#147732) 2025-06-28 20:27:18 +00:00
starkillerOG
33e1c6de68 Reduce idle timeout of HLS stream to conserve camera battery life (#147728)
* Reduce IDLE timeout of HLS stream to conserve camera battery life

* adjust tests
2025-06-28 20:27:17 +00:00
Daniel Hjelseth Høyer
81e712ea49 Bump pytibber to 0.31.6 (#147703) 2025-06-28 20:27:16 +00:00
Shay Levy
d3c5684cd0 Fix Shelly Block entity removal (#147694) 2025-06-28 20:27:16 +00:00
Jan Bouwhuis
862b7460b5 Move MQTT device sw and hw version to collapsed section in subentry flow (#147685)
Move MQTT device sw and hw version to collapsed section
2025-06-28 20:27:15 +00:00
Samuel Xiao
a65eb57539 Add lock models to switchbot cloud (#147569) 2025-06-28 20:27:14 +00:00
Antoni Czaplicki
b537850f52 Bump vulcan-api to 2.4.2 (#146857) 2025-06-28 20:27:13 +00:00
starkillerOG
43450d4489 Reduce idle timeout of HLS stream to conserve camera battery life (#147728)
* Reduce IDLE timeout of HLS stream to conserve camera battery life

* adjust tests
2025-06-28 22:20:47 +02:00
J. Nick Koston
f8c052e0ce Improve rest error logging (#147736)
* Improve rest error logging

* Improve rest error logging

* Improve rest error logging

* Improve rest error logging

* Improve rest error logging

* top level
2025-06-28 22:18:46 +02:00
Florian von Garrel
1f3bdfc7b7 bump pypaperless to 4.1.1 (#147735) 2025-06-28 22:13:51 +02:00
Antoni Czaplicki
0652bffd68 Bump vulcan-api to 2.4.2 (#146857) 2025-06-28 22:11:59 +02:00
Manu
8322611099 Use test parametrization in ista EcoTrend integration (#147729) 2025-06-28 21:57:51 +02:00
Marc Hörsken
134967b817 Fix error if cover position is not available or unknown (#147732) 2025-06-28 21:57:26 +02:00
Shay Levy
39abae36f0 Fix Shelly Block entity removal (#147694) 2025-06-28 22:40:58 +03:00
Marc Mueller
227760f203 Fix RuntimeWarnings in homeassistant_yellow tests (#147724) 2025-06-28 20:31:01 +02:00
Jan Bouwhuis
969809456e Move MQTT device sw and hw version to collapsed section in subentry flow (#147685)
Move MQTT device sw and hw version to collapsed section
2025-06-28 11:25:59 +02:00
Daniel Hjelseth Høyer
d2e8a48b2c Bump pytibber to 0.31.6 (#147703) 2025-06-28 10:11:17 +02:00
epenet
ea6332ee42 Move backup services to separate module (#146427) 2025-06-27 20:54:56 +02:00
Erik Montnemery
91c3b43d7f Improve comment for helpers.entity.entity_sources (#146529) 2025-06-27 20:54:19 +02:00
Thomas55555
1d82d44794 Add device prefix to summary in Husqvarna Automower (#147405) 2025-06-27 20:34:50 +02:00
Thomas55555
571376badc Bump aioautomower to 1.0.1 (#147683) 2025-06-27 20:28:45 +02:00
Manu
32236b2f4d Add reconfiguration flow to PlayStation Network (#147552) 2025-06-27 20:17:06 +02:00
Samuel Xiao
18c1953bc5 Add lock models to switchbot cloud (#147569) 2025-06-27 20:16:21 +02:00
Franck Nijhof
16c6bd08f8 Bump version to 2025.7.0b3 2025-06-27 17:55:31 +00:00
Simone Chemelli
18834849c2 Bump aioamazondevices to 3.1.22 (#147681) 2025-06-27 17:54:40 +00:00
hanwg
e4d820799f Add codeowner for Telegram bot (#147680) 2025-06-27 17:54:38 +00:00
mkmer
013a35176a Bump aiosomecomfort to 0.0.33 (#147673) 2025-06-27 17:54:37 +00:00
Norbert Rittel
8230557aef Fix sentence-casing and spacing of button in thermopro (#147671) 2025-06-27 17:54:36 +00:00
Paul Bottein
5451063714 Update frontend to 20250627.0 (#147668) 2025-06-27 17:54:35 +00:00
Shay Levy
8cdc7523a4 Fix Shelly entity removal (#147665) 2025-06-27 17:54:33 +00:00
Josef Zweck
77ccfbd3a9 Fix: Unhandled NoneType sessions in jellyfin (#147659) 2025-06-27 17:54:32 +00:00
Josef Zweck
4977ee4998 Bump jellyfin-apiclient-python to 1.11.0 (#147658) 2025-06-27 17:54:31 +00:00
Josef Zweck
5c0f2d37f0 Make jellyfin not single config entry (#147656) 2025-06-27 17:54:29 +00:00
Thomas55555
0b5d2ab8e4 Respect availability of parent class in Husqvarna Automower (#147649) 2025-06-27 17:54:28 +00:00
Brett Adams
47f3bf29dd Fix energy history in Teslemetry (#147646) 2025-06-27 17:54:26 +00:00
Manu
62f7cbb51e Remove dweet.io integration (#147645) 2025-06-27 17:54:25 +00:00
Bernardus Jansen
b9e2c5d34c Add previously missing state classes to dsmr sensors (#147633) 2025-06-27 17:54:24 +00:00
Petar Petrov
1829acd0e1 Z-WaveJS config flow: Change keys question (#147518)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2025-06-27 17:54:22 +00:00
Bernardus Jansen
d874c28dc9 Add previously missing state classes to dsmr sensors (#147633) 2025-06-27 19:45:36 +02:00
Brett Adams
19d89c8952 Fix energy history in Teslemetry (#147646) 2025-06-27 19:43:03 +02:00
Ludovic BOUÉ
e3ba1f34ca Matter TemperatureControl (#145706)
* TemperatureControl

* Add tests

* Commands.SetTemperature

* Update homeassistant/components/matter/number.py

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

* Apply suggestions from code review

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

* Update number.py

* Update number.py

* Update number.py

* Update homeassistant/components/matter/number.py

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

* Refactor MatterRangeNumber to streamline command handling in async_set_native_value

* testing requested changes

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-06-27 19:41:39 +02:00
Thomas55555
b630fb0520 Respect availability of parent class in Husqvarna Automower (#147649) 2025-06-27 19:38:42 +02:00
Ville Skyttä
5129f89086 Finish config flow in huawei_lte SSDP test (#147542) 2025-06-27 19:00:01 +02:00
Ville Skyttä
0be0e22e76 Simplify rflink dimmable set_level parsing (#147636) 2025-06-27 18:59:10 +02:00
epenet
b8500b338a Improve tests for binary sensor template (#147657) 2025-06-27 18:58:16 +02:00
Simone Chemelli
4cab3a0465 Bump aioamazondevices to 3.1.22 (#147681) 2025-06-27 18:44:01 +02:00
hanwg
ff711324d5 Add codeowner for Telegram bot (#147680) 2025-06-27 18:18:01 +02:00
Michael
113e7dc003 Add data descriptions to PEGELONLINE integration (#147594) 2025-06-27 18:16:38 +02:00
Shay Levy
2120ff6a0a Fix Shelly entity removal (#147665) 2025-06-27 18:50:35 +03:00
Marc Mueller
8ee5c30754 Update ruff to 0.12.1 (#147677) 2025-06-27 17:40:08 +02:00
Paul Bottein
a1518b96c4 Update frontend to 20250627.0 (#147668) 2025-06-27 17:28:14 +02:00
Petar Petrov
bba7f5c3f0 Z-WaveJS config flow: Change keys question (#147518)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2025-06-27 17:27:43 +02:00
Manu
8a5671af76 Remove dweet.io integration (#147645) 2025-06-27 17:23:42 +02:00
Raphael Hehl
8a18dea8c7 UniFi Protect removing early access checks and issue creation (#147432)
Co-authored-by: Copilot <175728472+Copilot@users.noreply.github.com>
2025-06-27 17:15:34 +02:00
Thomas55555
4b02f22724 Bump aioautomower to 1.0.0 (#147676) 2025-06-27 17:02:52 +02:00
mkmer
7229c2ca2c Bump aiosomecomfort to 0.0.33 (#147673) 2025-06-27 16:32:25 +02:00
Norbert Rittel
d83eddf13b Fix sentence-casing and spacing of button in thermopro (#147671) 2025-06-27 15:53:18 +02:00
Josef Zweck
4a192a7b09 Bump jellyfin-apiclient-python to 1.11.0 (#147658) 2025-06-27 11:07:14 +02:00
Josef Zweck
58c434887e Fix: Unhandled NoneType sessions in jellyfin (#147659) 2025-06-27 11:00:23 +02:00
Abílio Costa
78c2405e61 Bump whirlpool to 0.21.1 (#147611) 2025-06-27 10:33:49 +02:00
Josef Zweck
8cc4105984 Make jellyfin not single config entry (#147656) 2025-06-27 10:31:13 +02:00
Franck Nijhof
41b9a7a9a3 Bump version to 2025.7.0b2 2025-06-27 08:08:02 +00:00
Norbert Rittel
9782637ec8 Clarify descriptions of subaru.unlock_specific_door action (#147655) 2025-06-27 08:05:06 +00:00
Manu
6bd6fa65d2 Bump pynecil to v4.1.1 (#147648) 2025-06-27 08:05:05 +00:00
Joost Lekkerkerker
85343a9f53 Make sure Ollama integration migration is clean (#147630) 2025-06-27 08:05:04 +00:00
Joost Lekkerkerker
bc607dd013 Make sure Anthropic integration migration is clean (#147629) 2025-06-27 08:05:02 +00:00
Joost Lekkerkerker
c2c388e0cc Make sure OpenAI integration migration is clean (#147627) 2025-06-27 08:05:01 +00:00
Joost Lekkerkerker
3fc154e1d7 Make sure Google Generative AI integration migration is clean (#147625) 2025-06-27 08:05:00 +00:00
Jack Powell
efb29d024e Add Diagnostics to PlayStation Network (#147607)
* Add Diagnostics support to PlayStation_Network

* Remove unused constant

* minor cleanup

* Redact additional data

* Redact additional data
2025-06-27 08:04:58 +00:00
Michael
263823c92c Fix config schema to make credentials optional in NUT flows (#147593) 2025-06-27 08:04:57 +00:00
hanwg
e5e6ed601b Fix Telegram bot yaml import for webhooks containing None value for URL (#147586) 2025-06-27 08:04:56 +00:00
Petar Petrov
28dfc997f3 Do not factory reset old Z-Wave controller during migration (#147576)
* Do not factory reset old Z-Wave controller during migration

* PR comments

* remove obsolete test
2025-06-27 08:04:55 +00:00
puddly
f93ab8d519 Allow setup of Zigbee/Thread for ZBT-1 and Yellow without internet access (#147549)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2025-06-27 08:04:54 +00:00
Josef Zweck
cb359da79e Make entities unavailable when machine is physically off in lamarzocco (#147426) 2025-06-27 08:04:52 +00:00
Josef Zweck
917f1e4c6f Make entities unavailable when machine is physically off in lamarzocco (#147426) 2025-06-27 10:03:14 +02:00
hanwg
3879f6d2ef Fix Telegram bot yaml import for webhooks containing None value for URL (#147586) 2025-06-27 10:03:03 +02:00
Norbert Rittel
78060e4833 Clarify descriptions of subaru.unlock_specific_door action (#147655) 2025-06-27 10:01:44 +02:00
Guido Schmitz
fda66c4be4 Handle deleted devices dynamically in devolo Home Control (#147585) 2025-06-27 09:52:00 +02:00
Michael
21131d00b3 Fix config schema to make credentials optional in NUT flows (#147593) 2025-06-27 09:51:28 +02:00
puddly
a84313de33 Allow setup of Zigbee/Thread for ZBT-1 and Yellow without internet access (#147549)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2025-06-27 09:50:45 +02:00
Manu
c73346e6b3 Bump pynecil to v4.1.1 (#147648) 2025-06-27 09:31:35 +02:00
Franck Nijhof
55a37a2936 Extend GitHub Copilot instructions with new learnings from reviews (#147652) 2025-06-27 09:01:09 +02:00
Abílio Costa
e481f14335 Simplify reolink light tests (#147637) 2025-06-27 08:58:09 +02:00
Petar Petrov
1ca03c8ae9 Do not factory reset old Z-Wave controller during migration (#147576)
* Do not factory reset old Z-Wave controller during migration

* PR comments

* remove obsolete test
2025-06-27 08:02:12 +02:00
Ville Skyttä
61b43ca1fc Remove unnecessary wilight trigger regex use (#147638) 2025-06-26 23:16:21 +01:00
Joost Lekkerkerker
1b2be083c2 Make sure Google Generative AI integration migration is clean (#147625) 2025-06-26 23:03:36 +02:00
Joost Lekkerkerker
4bdf3d6f30 Make sure OpenAI integration migration is clean (#147627) 2025-06-26 23:03:11 +02:00
Joost Lekkerkerker
43535ede8b Make sure Anthropic integration migration is clean (#147629) 2025-06-26 23:02:59 +02:00
Joost Lekkerkerker
9bd0762799 Make sure Ollama integration migration is clean (#147630) 2025-06-26 23:02:35 +02:00
Ville Skyttä
1bb653b4f7 Remove unused config regexps (#147631) 2025-06-26 23:02:14 +02:00
Franck Nijhof
2655edcfc8 Extend GitHub Copilot instructions and make it suitable for Claude Code (#147632) 2025-06-26 23:00:02 +02:00
Franck Nijhof
7a08edc3dd Add Claude to gitignore (#147622) 2025-06-26 21:06:34 +02:00
Abílio Costa
b3131355b0 Use non-autospec mock for Reolink's light tests (#147621) 2025-06-26 21:05:23 +02:00
Abílio Costa
06d04c001d Use non-autospec mock for Reolink's host tests (#147619) 2025-06-26 20:55:46 +02:00
Jack Powell
babecdf32c Add Diagnostics to PlayStation Network (#147607)
* Add Diagnostics support to PlayStation_Network

* Remove unused constant

* minor cleanup

* Redact additional data

* Redact additional data
2025-06-26 20:52:07 +02:00
Franck Nijhof
6a7385590a Bump version to 2025.7.0b1 2025-06-26 18:03:11 +00:00
Joost Lekkerkerker
c0ec987b07 Fix meaters not being added after a reload (#147614) 2025-06-26 18:02:49 +00:00
Joost Lekkerkerker
26521f8cc0 Hide Telegram bot proxy URL behind section (#147613)
Co-authored-by: Manu <4445816+tr4nt0r@users.noreply.github.com>
2025-06-26 18:02:48 +00:00
Manu
4df1f702bf Fix asset url in Habitica integration (#147612) 2025-06-26 18:02:46 +00:00
Joost Lekkerkerker
c8422c9fb8 Improve explanation on how to get API token in Telegram (#147605) 2025-06-26 18:02:45 +00:00
Luca Angemi
f8207a2e0e Remove default icon for wind direction sensor for Buienradar (#147603)
* Fix wind direction state class sensor

* Remove default icon for wind direction sensor
2025-06-26 18:02:44 +00:00
Bram Kragten
9cc75f3458 Update frontend to 20250626.0 (#147601) 2025-06-26 18:02:43 +00:00
Joost Lekkerkerker
a233b6b1e3 Add default title to migrated Ollama entry (#147599) 2025-06-26 18:02:42 +00:00
Joost Lekkerkerker
c7677b91da Add default title to migrated Claude entry (#147598) 2025-06-26 18:02:40 +00:00
Joost Lekkerkerker
1f57bba9cd Add default conversation name for OpenAI integration (#147597) 2025-06-26 18:02:39 +00:00
Joost Lekkerkerker
4cc10ca2e2 Set Google AI model as device model (#147582)
* Set Google AI model as device model

* fix
2025-06-26 18:02:38 +00:00
Marcel van der Veldt
153e1e43e8 Do not make the favorite button unavailable when no content playing on a Music Assistant player (#147579) 2025-06-26 18:02:36 +00:00
Joost Lekkerkerker
398dd3ae46 Set right model in OpenAI conversation (#147575) 2025-06-26 18:02:35 +00:00
Petar Petrov
17fd850fa6 Hide unnamed paths when selecting a USB Z-Wave adapter (#147571)
* Hide unnamed paths when selecting a USB Z-Wave adapter

* remove pointless sorting
2025-06-26 18:02:34 +00:00
Petar Petrov
ae062b230c Remove obsolete routing info when migrating a Z-Wave network (#147568) 2025-06-26 18:02:33 +00:00
Marcel van der Veldt
d523f85404 Fix sending commands to Matter vacuum (#147567) 2025-06-26 18:02:31 +00:00
tronikos
f28d6582c6 Refactor in Google AI TTS in preparation for STT (#147562) 2025-06-26 18:02:30 +00:00
Petar Petrov
1e81e5990e Bump zwave-js-server-python to 0.65.0 (#147561)
* Bump zwave-js-server-python to 0.65.0

* update tests
2025-06-26 18:02:29 +00:00
tronikos
5fe2e4b6ed Include subentries in Google Generative AI diagnostics (#147558) 2025-06-26 18:02:28 +00:00
tronikos
914bb3aa76 Use default title for migrated Google Generative AI entries (#147551) 2025-06-26 18:02:26 +00:00
Simone Chemelli
cfa6746115 Fix unload for Alexa Devices (#147548) 2025-06-26 18:02:25 +00:00
Simone Chemelli
03f9caf3eb Add action exceptions to Alexa Devices (#147546) 2025-06-26 18:02:24 +00:00
Joost Lekkerkerker
6b2aaf3fdb Show current Lametric version if there is no newer version (#147538) 2025-06-26 18:02:23 +00:00
Luca Angemi
2c4ea0d584 Fix wind direction state class sensor for AEMET (#147535) 2025-06-26 18:02:21 +00:00
Anders Peter Fugmann
e627811f7a Bump dependency on pyW215 for DLink integration to 0.8.0 (#147534) 2025-06-26 18:02:20 +00:00
Simone Chemelli
150f41641b Improve config flow strings for Alexa Devices (#147523) 2025-06-26 18:02:19 +00:00
Erik Montnemery
b9a7371996 Set end date for when allowing unique id collisions in config entries (#147516)
* Set end date for when allowing unique id collisions in config entries

* Update test
2025-06-26 18:02:17 +00:00
tronikos
7d0e99da43 Fixes in Google AI TTS (#147501)
* Fix Google AI not using correct config options after subentries migration

* Fixes in Google AI TTS

* Fix tests by @IvanLH

* Change type name.

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2025-06-26 18:02:16 +00:00
hanwg
71f281cc14 Fix Telegram bot default target when sending messages (#147470)
* handle targets

* updated error message

* validate chat id for single target

* add validation for chat id

* handle empty target

* handle empty target
2025-06-26 18:02:15 +00:00
Renat Sibgatulin
aec812a475 Create a new client session for air-Q to fix cookie polution (#147027) 2025-06-26 18:00:50 +00:00
Robin Lintermann
d4b548b169 Fixed issue when tests (should) fail in Smarla (#146102)
* Fixed issue when tests (should) fail

* Use usefixture decorator

* Throw ConfigEntryError instead of AuthFailed
2025-06-26 18:00:48 +00:00
Fabio Natanael Kepler
a296324c30 Fix playing TTS and local media source over DLNA (#134903)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-06-26 18:00:47 +00:00
Renat Sibgatulin
17cd39748b Create a new client session for air-Q to fix cookie polution (#147027) 2025-06-26 19:59:49 +02:00
Simone Chemelli
c2f1e86a4e Add action exceptions to Alexa Devices (#147546) 2025-06-26 19:59:02 +02:00
Joost Lekkerkerker
61a32466b6 Hide Telegram bot proxy URL behind section (#147613)
Co-authored-by: Manu <4445816+tr4nt0r@users.noreply.github.com>
2025-06-26 19:55:38 +02:00
Manu
aef08091f8 Fix asset url in Habitica integration (#147612) 2025-06-26 19:52:58 +02:00
Joost Lekkerkerker
1416f0f1e0 Fix meaters not being added after a reload (#147614) 2025-06-26 19:52:29 +02:00
HarvsG
af7b1a76bc Add description placeholders to SchemaFlowFormStep (#147544)
* test description placeholders

* Update test_schema_config_entry_flow.py

* fix copy and paste indentation

* Apply suggestions from code review

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-06-26 19:51:31 +02:00
Maximilian Arzberger
bf88fcd5bf Add Manual Charge Switch for Installers for Kostal Plenticore (#146932)
* Add Manual Charge Switch for Installers

* Update stale docstring

* Installer config fixture

* fix ruff
2025-06-26 19:50:27 +02:00
Joost Lekkerkerker
35478e3162 Set Google AI model as device model (#147582)
* Set Google AI model as device model

* fix
2025-06-26 19:44:15 +02:00
Joost Lekkerkerker
69af74a593 Improve explanation on how to get API token in Telegram (#147605) 2025-06-26 18:21:56 +02:00
tronikos
b4dd912bee Refactor in Google AI TTS in preparation for STT (#147562) 2025-06-26 11:53:16 -04:00
Bram Kragten
b5821ef499 Update frontend to 20250626.0 (#147601) 2025-06-26 17:46:45 +02:00
Fabio Natanael Kepler
1a92d4530e Fix playing TTS and local media source over DLNA (#134903)
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-06-26 17:12:15 +02:00
Joost Lekkerkerker
7b80c1c693 Add default conversation name for OpenAI integration (#147597) 2025-06-26 17:11:48 +02:00
Joost Lekkerkerker
e7cc03c1d9 Add default title to migrated Claude entry (#147598) 2025-06-26 17:11:13 +02:00
Luca Angemi
69f0b6244a Remove default icon for wind direction sensor for Buienradar (#147603)
* Fix wind direction state class sensor

* Remove default icon for wind direction sensor
2025-06-26 17:05:59 +02:00
Joost Lekkerkerker
01205f8a14 Add default title to migrated Ollama entry (#147599) 2025-06-26 17:05:26 +02:00
hanwg
68924d23ab Fix Telegram bot default target when sending messages (#147470)
* handle targets

* updated error message

* validate chat id for single target

* add validation for chat id

* handle empty target

* handle empty target
2025-06-26 16:43:09 +02:00
Artur Pragacz
40f553a007 Migrate device connections to a normalized form (#140383)
* Normalize device connections migration

* Update version

* Slightly improve tests

* Update homeassistant/helpers/device_registry.py

* Add validators

* Fix validator

* Move format mac function too

* Add validator test

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-06-26 15:33:34 +02:00
Robin Lintermann
bc46894b74 Fixed issue when tests (should) fail in Smarla (#146102)
* Fixed issue when tests (should) fail

* Use usefixture decorator

* Throw ConfigEntryError instead of AuthFailed
2025-06-26 15:30:03 +02:00
Anders Peter Fugmann
6f4615f012 Bump dependency on pyW215 for DLink integration to 0.8.0 (#147534) 2025-06-26 12:56:46 +02:00
Joost Lekkerkerker
4244d2f66f Set right model in OpenAI conversation (#147575) 2025-06-26 12:49:33 +02:00
Petar Petrov
a73dafe097 Hide unnamed paths when selecting a USB Z-Wave adapter (#147571)
* Hide unnamed paths when selecting a USB Z-Wave adapter

* remove pointless sorting
2025-06-26 12:15:02 +02:00
Stefan Agner
be49296547 Deduplicate shared logic in Matter vacuum commands (#147578)
Get the run mode by tag in a single place to avoid code duplication.
Also raise an error if the run mode (unexpectedly) is not found.
2025-06-26 11:54:52 +02:00
Marcel van der Veldt
d55ecd885e Do not make the favorite button unavailable when no content playing on a Music Assistant player (#147579) 2025-06-26 11:49:06 +02:00
Luca Angemi
076248c455 Fix wind direction state class sensor for AEMET (#147535) 2025-06-26 11:07:07 +02:00
Petar Petrov
13ce27c94c Remove obsolete routing info when migrating a Z-Wave network (#147568) 2025-06-26 11:06:36 +02:00
Joost Lekkerkerker
4b9b08ece5 Show current Lametric version if there is no newer version (#147538) 2025-06-26 10:55:31 +02:00
Simone Chemelli
79df38eff2 Improve config flow strings for Alexa Devices (#147523) 2025-06-26 10:52:14 +02:00
tronikos
fb133664e4 Include subentries in Google Generative AI diagnostics (#147558) 2025-06-26 10:50:47 +02:00
Marcel van der Veldt
38669ce96c Fix sending commands to Matter vacuum (#147567) 2025-06-26 10:47:24 +02:00
Petar Petrov
651b33d49b Bump zwave-js-server-python to 0.65.0 (#147561)
* Bump zwave-js-server-python to 0.65.0

* update tests
2025-06-26 10:11:25 +03:00
Erik Montnemery
3b64db5f76 Set end date for when allowing unique id collisions in config entries (#147516)
* Set end date for when allowing unique id collisions in config entries

* Update test
2025-06-26 08:20:26 +02:00
tronikos
0f95fe566c Use default title for migrated Google Generative AI entries (#147551) 2025-06-25 22:30:41 -04:00
Simone Chemelli
6290facffb Fix unload for Alexa Devices (#147548) 2025-06-26 01:55:58 +02:00
tronikos
f0a78aadbe Fixes in Google AI TTS (#147501)
* Fix Google AI not using correct config options after subentries migration

* Fixes in Google AI TTS

* Fix tests by @IvanLH

* Change type name.

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
Co-authored-by: Paulus Schoutsen <paulus@home-assistant.io>
2025-06-25 18:12:23 -04:00
Pete Sage
345ec97dd5 Add enum sensor for Sonos Power Source (#147449)
* feat: add power source sensor

* fix: translations

* fix:cleanup

* fix: simpify

* fix: improve coverage

* fix: improve coverage

* fix: add missing test

* fix: call it charging_base

* fix: disable entity by default

* update snapshots

* Update homeassistant/components/sonos/strings.json

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

* fix: update test

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-06-25 23:49:06 +02:00
Franck Nijhof
1286b5d9d8 Bump version to 2025.8.0dev0 (#147531) 2025-06-25 21:38:35 +02:00
Franck Nijhof
cff3d3d6ac Bump version to 2025.7.0b0 2025-06-25 18:51:19 +00:00
Erik Montnemery
26e3caea9a Add support for condition platforms to provide multiple conditions (#147376) 2025-06-25 18:10:30 +01:00
Bouwe Westerdijk
2b5f5f641d Bump plugwise to v1.7.6 (#147508) 2025-06-25 18:48:38 +02:00
Simone Chemelli
99079d2980 Bump aioamazondevices to 3.1.19 (#147462) 2025-06-25 18:47:09 +02:00
Retha Runolfsson
2800921a5d Remove force latch mode for locklite in switchbot integration (#147474) 2025-06-25 18:45:37 +02:00
Jan Bouwhuis
3268b9ee18 Fix typo's in MQTT translation strings (#147489) 2025-06-25 18:45:09 +02:00
Bram Kragten
02c3cdd5d4 Update frontend to 20250625.0 (#147521) 2025-06-25 18:44:46 +02:00
Manu
f34f17bc24 Update codeowners of PlayStation Network integration (#147510)
Add myself as codeowner
2025-06-25 18:35:48 +02:00
Erik Montnemery
1fb587bf03 Allow core integrations to describe their triggers (#147075)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-06-25 17:35:15 +01:00
Pete Sage
d8258924f7 Remove mapping of entity_ids to speakers in Sonos (#147506)
* fix

* fix: change entity_id mappings

* fix: translate errors

* fix:merge issues

* fix: translate error messages

* fix: improve test coverage

* fix: remove unneeded strings
2025-06-25 18:29:23 +02:00
Retha Runolfsson
c05d8aab1c Add floor lamp and strip light 3 for switchbot integration (#147517) 2025-06-25 18:01:10 +02:00
Nathan Larsen
e210681751 Fix API POST endpoints json parsing error-handling (#134326)
* Fix API POST endpoints json parsing error-handling

* Add tests

* Fix mypy and ruff errors

* Fix coverage by removing non-needed error handling

* Correct error handling and improve tests

---------

Co-authored-by: Robert Resch <robert@resch.dev>
Co-authored-by: Erik <erik@montnemery.com>
2025-06-25 16:58:21 +02:00
Thomas D
809aced9cc Add cover platform to Qbus integration (#147420)
* Add scene platform

* Add cover platform

* Refactor receiving state

* Fix wrong auto-merged code
2025-06-25 15:38:43 +02:00
ocrease
977e8adbfb Fix operational state and vacuum state for matter vacuum (#147466) 2025-06-25 15:23:38 +02:00
Michael
c54ce7eabd Split models and helpers from coordinator module in AVM Fritz!Box tools (#147412)
* split models from coordinator

* split helpers from coordinator
2025-06-25 14:50:07 +02:00
Retha Runolfsson
c5f8acfe93 Add effect mode support for switchbot light (#147326)
* add support for strip light3 and floor lamp

* clear the color mode

* add led unit test

* use property for effect

* fix color mode issue

* remove new products

* fix adv data

* adjust log level

* add translation and icon
2025-06-25 14:45:07 +02:00
Pavel Skuratovich
8393f17bb3 Fix sensor state class for fuel sensor in StarLine integration (#146769) 2025-06-25 14:34:11 +02:00
Guido Schmitz
8918b0d7a9 Add missing reauth_confirm strings to devolo Home Control (#147496) 2025-06-25 14:33:37 +02:00
Manu
c447729ce4 Add sensor platform to PlayStation Network (#147469) 2025-06-25 14:33:02 +02:00
Guido Schmitz
12812049ea Split setup tests in devolo Home Network (#147498) 2025-06-25 14:14:33 +02:00
J. Nick Koston
47811e13a6 Bump PySwitchbot to 0.67.0 (#147503)
changelog: https://github.com/sblibs/pySwitchbot/compare/0.66.0...0.67.0
2025-06-25 13:58:39 +02:00
Erik Montnemery
7587fc985f Bump py-dormakaba-dkey to 1.0.6 (#147499) 2025-06-25 13:31:43 +02:00
puddly
716ec1eef2 Bump ZHA to 0.0.61 (#147472)
Co-authored-by: TheJulianJES <TheJulianJES@users.noreply.github.com>
2025-06-25 13:27:57 +02:00
Gábor Kiss
b95af2d86b Fix ESPHome entity_id generation if name contains unicode characters (#146796)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-06-25 13:19:55 +02:00
Andre Lengwenus
bca7502611 Add quality scale for LCN (#147367)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-06-25 11:50:00 +01:00
J. Diego Rodríguez Royo
1e4fbebf49 Improve Home Connect diagnostics exposing more data (#147492) 2025-06-25 11:49:54 +02:00
Pete Sage
c9e9575a3d Add tests for join and unjoin service calls in Sonos (#145602)
* fix: add tests for join and unjoin

* fix: update comments

* fix: update comments

* fix: refactor to common functions

* fix: refactor to common functions

* fix: add type def

* fix: add return types

* fix: add return types

* fix: correct type annontation for uui_ds

* fix: update comments

* fix: merge issues

* fix: merge issue

* fix: raise homeassistanterror on timeout

* fix: add comments

* fix: simplify test

* fix: simplify test

* fix: simplify test
2025-06-25 11:38:51 +02:00
tronikos
f897a728f1 Fix Google AI not using correct config options after subentries migration (#147493) 2025-06-25 11:25:01 +02:00
J. Diego Rodríguez Royo
0bbb168862 Add Home Connect DHCP information (#147494)
* Add Home Connect DHCP information

* Add tests
2025-06-25 11:24:38 +02:00
J. Nick Koston
0a884c7253 Add subdevices support to ESPHome (#147343) 2025-06-25 21:24:30 +12:00
Joakim Sørensen
58e60fdfac Bump hass-nabucasa from 0.103.0 to 0.104.0 (#147488) 2025-06-25 11:15:09 +02:00
Joost Lekkerkerker
33bd35bff4 Migrate Meater to use HassKey (#147485)
* Migrate Meater to use HassKey

* Update homeassistant/components/meater/sensor.py

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>

* Migrate Meater to use HassKey

* Migrate Meater to use HassKey

---------

Co-authored-by: epenet <6771947+epenet@users.noreply.github.com>
2025-06-25 10:36:58 +02:00
Simone Rescio
f4b95ff5f1 Ezviz battery camera work mode (#130478)
* Add support for EzViz Battery Camera work mode

* feat: address review comment, add 'battery' to work mode string

* feat: optimize entity addition for Ezviz select component

* refactor: streamline error handling in Ezviz select actions

* Update library

* update library

* Bump api to pin mqtt to compatable version

* fix after rebase

* Update code owners

* codeowners

* Add support for EzViz Battery Camera work mode

* feat: address review comment, add 'battery' to work mode string

* feat: optimize entity addition for Ezviz select component

* refactor: streamline error handling in Ezviz select actions

* feat: address review item simplify Ezviz select actions by removing base class and moving methods

* chore: fix ruff lint

* feat: check for SupportExt before adding battery select

* chore: cleanup logging

* feat: restored battery work mode, separated defnitions for sound and battery selects, check SupportExt with type casting

* Apply suggestions from code review

---------

Co-authored-by: Pierre-Jean Buffard <pierre-jean.buffard@dataiku.com>
Co-authored-by: Renier Moorcroft <66512715+RenierM26@users.noreply.github.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-06-25 09:41:18 +02:00
Simone Chemelli
f800248c10 Add more binary sensors to Alexa Devices (#146402)
* Add more binary sensors to Amazon Devices

* apply review comment

* Add sensor platform to Amazon Devices

* Revert "Add sensor platform to Amazon Devices"

This reverts commit 25a9ca673e450634a17bdb79462b14aa855aca10.

* clean

* fix logic after latest changes

* apply review comments
2025-06-25 09:33:13 +02:00
Retha Runolfsson
d0b2d1dc92 Add evaporative humidifier for switchbot integration (#146235)
* add support for evaporative humidifier

* add evaporative humidifier unit test

* clear the humidifier action in pyswitchbot

* fix ruff

* fix Sentence-casing issue

* add icon translation

* remove last run success

* use icon translations for water level

* remove the translation for last run success
2025-06-25 09:32:33 +02:00
Jan Bouwhuis
85e9919bbd Add entity category option to entities set up via an MQTT subentry (#146776)
* Add entity category option to entities set up via an MQTT subentry

* Rephrase

* typo

* Move entity category to entity details - remove service to action

* Move entity category to entity platform config flow step
2025-06-25 09:28:37 +02:00
Joost Lekkerkerker
51fb1ab8b6 Refactor Meater availability (#146956)
* Refactor Meater availability

* Fix

* Fix
2025-06-25 09:23:27 +02:00
epenet
066e840e06 Migrate lookin to use runtime_data (#147479) 2025-06-25 09:17:43 +02:00
Joost Lekkerkerker
7031167895 Set has entity name to True in Meater (#146954)
* Set has entity name to True in Meater

* Fix

* Fix
2025-06-25 08:59:28 +02:00
epenet
69bf79d3bd Migrate local_calendar to use runtime_data (#147481) 2025-06-25 08:47:29 +02:00
epenet
909d950b50 Migrate luftdaten to use runtime_data (#147480) 2025-06-25 08:07:34 +02:00
epenet
51da1bc25a Migrate loqed to use runtime_data (#147478)
* Migrate loqed to use runtime_data

* Fix tests
2025-06-25 08:07:17 +02:00
epenet
f22b623968 Move luftdaten coordinator to separate module (#147477) 2025-06-25 07:48:56 +02:00
epenet
2bcdc03661 Migrate lupusec to use runtime_data (#147476) 2025-06-25 07:48:30 +02:00
epenet
10d1affd81 Migrate lyric to use runtime_data (#147475) 2025-06-25 07:48:20 +02:00
Manu
91e7b75a44 Fix errors in legacy platform in PlayStation Network integration (#147471)
fix legacy platform presence
2025-06-25 06:48:45 +02:00
natepugh
42aaa888a1 Bump pyairnow to 1.3.1 (#147388)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-06-24 23:47:56 +01:00
Guido Schmitz
7b8ebb0803 Move DevoloMultiLevelSwitchDeviceEntity in devolo Home Control (#147450) 2025-06-24 22:42:42 +02:00
Paulus Schoutsen
c270ea4e0c Fix media accept config type (#147445) 2025-06-24 16:41:43 -04:00
Paulus Schoutsen
c93e45c0f2 Add missing config entry type for Husqvarna (#147455)
Add missing type for husqvarna
2025-06-24 22:37:35 +02:00
Michael Hansen
19b773df85 Only send ESPHome intent progress when necessary (#147458)
* Only send intent progress when necessary

* cover

* Fix logic

---------

Co-authored-by: J. Nick Koston <nick@koston.org>
2025-06-24 16:35:38 -04:00
puddly
9e7c7ec97e Flash ZBT-1 and Yellow firmwares from Core instead of using addons (#145019)
* Make `async_flash_firmware` a public helper

* [ZBT-1] Implement flashing for Zigbee and Thread within the config flow

* WIP: Begin fixing unit tests

* WIP: Unit tests, pass 2

* WIP: pass 3

* Fix hardware unit tests

* Have the individual hardware integrations depend on the firmware flasher

* Break out firmware filter into its own helper

* Mirror to Yellow

* Simplify

* Simplify

* Revert "Have the individual hardware integrations depend on the firmware flasher"

This reverts commit 096f4297dc.

* Move `async_flash_silabs_firmware` into `util`

* Fix existing unit tests

* Unconditionally upgrade Zigbee firmware during installation

* Fix failing error case unit tests

* Fix remaining failing unit tests

* Increase test coverage

* 100% test coverage

* Remove old translation strings

* Add new translation strings

* Do not probe OTBR firmware when completing the flow

* More translation strings

* Probe OTBR firmware info before starting the addon
2025-06-24 16:21:02 -04:00
Paulus Schoutsen
f735331699 Convert Ollama to subentries (#147286)
* Convert Ollama to subentries

* Add latest changes from Google subentries

* Move config entry type to init
2025-06-24 16:13:34 -04:00
Maciej Bieniek
5a20ef3f3f Bump aioshelly to version 13.7.0 (#147453) 2025-06-24 23:03:22 +03:00
Simone Chemelli
5ef054f2e0 Add quality scale bronze to SamsungTV (#142288) 2025-06-24 21:41:39 +02:00
Manu
b9fc198a7e Set quality scale to 🥇 gold for ista EcoTrend integration (#143462) 2025-06-24 21:25:53 +02:00
HarvsG
ad4fae7f59 Custom sentence triggers should be marked as processed locally (#145704)
* Mark custom sentence triggers a local agent

* Don't change agent ID

* adds tests to confirm processed_locally is True

* move asserts to after null check
2025-06-24 14:25:40 -05:00
Paulus Schoutsen
265de91fba Add type for wiz (#147454) 2025-06-24 15:13:51 -04:00
Paul Bottein
7322fe40da Define fields for assist ask_question action (#147219)
* Define fields for assist ask_question action

* Update hassfest

---------

Co-authored-by: Paulus Schoutsen <balloob@gmail.com>
2025-06-24 15:00:14 -04:00
Paulus Schoutsen
8eb906fad9 Migrate OpenAI to config subentries (#147282)
* Migrate OpenAI to config subentries

* Add latest changes from Google subentries

* Update homeassistant/components/openai_conversation/__init__.py

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

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-06-24 15:00:05 -04:00
Sven Naumann
4d9843172b Fix nfandroidtv service notify disappears when restarting home assistant (#128958)
* move connect to android tv host from init to short before sending a message

* Don't swallow exceptions

* use string literals for exception

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-06-24 20:09:45 +02:00
Abílio Costa
e8a534be9c Add missing method mock to Reolink chime test (#147447) 2025-06-24 20:06:54 +02:00
Abílio Costa
3148719864 Use newer mock in recent Reolink test (#147448) 2025-06-24 19:06:42 +01:00
Nathan Spencer
abfb7afcb7 Bump pylitterbot to 2024.2.1 (#147443) 2025-06-24 19:26:35 +02:00
Abílio Costa
fe4ff4f835 Use non-autospec mock for Reolink switch tests (#147441) 2025-06-24 19:19:41 +02:00
Michael Hansen
cefc8822b6 Support streaming TTS in wyoming (#147392)
* Support streaming TTS in wyoming

* Add test

* Refactor to avoid repeated task creation

* Manually manage client lifecycle
2025-06-24 13:04:40 -04:00
Michael Hansen
3dc8676b99 Add TTS streaming to Wyoming satellites (#147438)
* Add TTS streaming using intent-progress

* Handle incomplete header
2025-06-24 12:00:02 -05:00
Abílio Costa
0f112bb9c4 Use non-autospec mock for Reolink service tests (#147440) 2025-06-24 18:37:05 +02:00
Nathan Spencer
54e5107c34 Add total cycles sensor for Litter-Robot (#147435)
* Add total cycles sensor for Litter-Robot

* Add translatable unit of measurement cycles
2025-06-24 18:24:15 +02:00
karwosts
657a068087 Cleanup some duplicated code (#147439) 2025-06-24 17:22:13 +01:00
Luca Angemi
af6c2b5c8a Add device class to wind direction sensors for AEMET (#147430) 2025-06-24 16:25:16 +01:00
Manu
d5a8fa9c5c Add DHCP discovery to PlayStation Network integration (#147422)
Add DHCP discovery for PSN
2025-06-24 17:17:02 +02:00
Abílio Costa
cefde21140 Update Shelly test snapshots (#147429) 2025-06-24 18:08:27 +03:00
hanwg
160163b0cc Remove deprecated proxy params from Telegram bot integration (#147288) 2025-06-24 16:46:31 +02:00
Michael Hansen
6ce594539f Bump wyoming to 1.7.1 (#147385)
* Bump wyoming to 1.7.0

* Bump to 1.7.1 for Python version fix

* Address mypy errors
2025-06-24 09:28:09 -05:00
Robert Resch
4ca39ec7c3 Add range icons for wind_direction sensor device class (#147090)
Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-06-24 15:00:03 +01:00
Paul Bottein
cfdd7fbbce Add fields and multiple support to object selector (#147215)
* Add schema supports to object selector

* Update format

* Update homeassistant/helpers/selector.py

Co-authored-by: G Johansson <goran.johansson@shiftit.se>

---------

Co-authored-by: G Johansson <goran.johansson@shiftit.se>
2025-06-24 09:54:06 -04:00
Paulus Schoutsen
1cb36f4c18 Convert Claude to use subentries (#147285)
* Convert Claude to use subentries

* Add latest changes from Google subentries

* Revert accidental change to Google
2025-06-24 15:36:09 +02:00
Petar Petrov
602c1c64b3 Update ZwaveJS config flow strings (#147421)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-06-24 14:30:12 +01:00
Andre Lengwenus
3b8d6eb851 Log LCN connection established with log level info (#147424) 2025-06-24 15:24:25 +02:00
Franck Nijhof
9bb98eb514 Merge branch 'master' into dev 2025-06-24 13:19:28 +00:00
karwosts
39c431c55c Add 'max_sub_interval' option to derivative sensor (#125870)
* Add 'max_sub_interval' option to derivative sensor

* add strings

* little coverage

* improve test accuracy

* reimplement at dev head

* string

* handle unavailable

* simplify

* Add self to codeowner

* fix on remove

* Update homeassistant/components/derivative/sensor.py

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

* Fix parenthesis

* sort strings

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-06-24 15:05:28 +02:00
Franck Nijhof
0171b527d8 2025.6.3 (#147419)
Co-authored-by: Bram Kragten <mail@bramkragten.nl>
2025-06-24 14:58:48 +02:00
Paulus Schoutsen
7cccdf2205 Add accept keyword to Media selector (#145527)
* Add accept keyword to Media selector

* Adjust test
2025-06-24 07:36:48 -05:00
Petro31
97f3bb3da5 Add default to from_json (#146211) 2025-06-24 14:27:14 +02:00
Parker Wahle
fc62a6cd89 Add streaming support w/ audio to Android IP Webcam integration (#126009)
* Add streaming support w/ audio to Android IP Webcam integration

* ruff reformat

* Fix ruff

* Break long comments and strings

* Add camera test

* Fix docstring

* Remove dead code

* Call library function to get URL

* Simplify

---------

Co-authored-by: Shay Levy <levyshay1@gmail.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-06-24 13:54:34 +02:00
Josef Zweck
23b90f5984 Add door state sensors to tedee (#147386) 2025-06-24 12:30:13 +01:00
Paulus Schoutsen
63ac14a19b AI task generate_text -> generate_data (#147370) 2025-06-24 12:12:29 +01:00
Franck Nijhof
94fd9d1657 Bump version to 2025.6.3 2025-06-24 11:09:26 +00:00
Bram Kragten
2f89317fed Update frontend to 20250531.4 (#147414) 2025-06-24 11:09:03 +00:00
Manu
38c7eaf70a Add reauth flow to PlayStation Network integration (#147397)
* Add reauth flow to psn integration

* changes

* catch auth error in coordinator
2025-06-24 12:20:08 +02:00
dependabot[bot]
02e33c3551 Bump sigstore/cosign-installer from 3.8.2 to 3.9.0 (#147072)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-24 10:50:32 +02:00
Bram Kragten
d5187a6a40 Update frontend to 20250531.4 (#147414) 2025-06-24 10:49:51 +02:00
CubeZ2mDeveloper
703032ab27 Added auto-discovery configuration for SONOFF Dongle Max in zha. (#140574)
Co-authored-by: zetao.zheng <1050713479@qq.com>
2025-06-24 10:19:08 +02:00
Maciej Bieniek
438aa3486d Add full device snapshot tests for Shelly (#145620) 2025-06-24 10:16:46 +02:00
Duco Sebel
f2944f4d8e Add support for v2 API for HomeWizard kWh Meter (#147214) 2025-06-24 10:14:06 +02:00
Erik Montnemery
b8044f60fc Fix trigger config validation (#147408) 2025-06-24 10:13:44 +02:00
Michael Hansen
c67b497f30 Bump intents to 2025.6.23 (#147391) 2025-06-24 10:13:04 +02:00
puddly
aefd9c9b41 Bump universal-silabs-flasher to 0.0.31 (#147393) 2025-06-24 10:11:46 +02:00
Stefan Agner
e5d19baf3e Add container arch to system info (#147372) 2025-06-24 09:52:21 +02:00
Manu
121239bcf7 Fix unbound var and tests in PlayStation Network integration (#147398)
fix unbound var and test mocks
2025-06-24 08:53:45 +02:00
Geoff
eff35e93bd New core integration for VegeHub (#129598)
* Initial commit for VegeHub integration

* Moved several pieces to library, continuing.

* All device contact moved to library

* Updated documentation link

* Fixed an error in strings.json

* Removed commented out code and unused file

* Removed unneeded info logging, and a few missed lines of commented code

* Added/removed comments for clarity

* Converted integration to use webhooks.

* Update __init__.py to remove unnecessary code.

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Remove unnecessary code from config_flow.py

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>

* Simplify unique_id assertion.

* Switch to CONF_ constant for user input

* Added explanation for passing exception.

* Got rid of try-except, since I don't really handle the exceptions her anyway.

* Moved data transform to vegehub library

* Changed references to use HA constants.

* Fixed assigning and returning _attr properties.

* Moved temperature sensor transform to the library.

* Moved sensor names to strings.json

* Made webhook names unique to avoid collisions when multiple devices are added.

* Converted to using entry.runtime_data

* Removed options flow for first PR

* Removed switch support to limit PR to one platform

* Removed/updated outdated tests

* Update homeassistant/components/vegehub/__init__.py

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

* Got rid of strings in favor of constants.

* Got rid of unnecessary check

* Imported constant directly.

* Added custom type for entry

* Expanded CONF_ constants into sensor.py

* Get rid of extra `str` and `get`

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

* Added type to errors

* Added try/except to MAC address retrieval

* Moved functionality out of ConfigFlow that shouldn't have been there

* Removed IP:MAC tracking from ConfigFlow

* Added retries to VegeHub PyPI package, and implemented them in integration

* Removed different sensor types for now

* Fixed typo

* Changed abort to error

* Fixed error reporting in config flow

* Further simplify sensor.py to handle all sensors the same

* Added comment to clarify

* Got rid of unused constants

* Removed unused strings in strings.json

* Added quality_scale.yaml

* Fixed problems in sensor init

* Moved config url and sw version storage into vegehub package

* Get rid of extra declaration

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

* Removed unnecessary task

* Fix type for entry

* Added a test before setup

* Fixed tests and got test coverage of config flow to 100%

* Fixed test descriptions

* Implemented a coordinator

* Removed unused property

* Fixed a few minor issues with the coordinator implementation

* Removed unused function

* Fixed some tests

* Trying to fix a problem with re-initialization when server reboots. Mostly working.

* Moved hub.setup from async_setup_entry to config flow to avoid running it on system reboot

* Delete tests/testing_config/.storage/http.auth

* Fixed errors in coordinator.py

* Added IP validation for manual input IP addresses

* Moved data into self._discovered to simplify

* Removed redundant typing

* Shortened sensor unique ID and added coordinator handler

* Added call to super()._handle_coordinator_update() so state gets handled correctly

* Fixed == and is

* Got rid of "slot" and moved functionality to lib

* Got rid of mocked aiohttp calls in favor of just mocking the vegehub library

* Rewrote config flow to make more sense.

* Changed order of data and data_description

* Changes to sensor.py

* Got rid of async_update_data in coordinator and moved async_set_updated_data into webhook callback

* Changed sensor updates so that they keep using last known values if update doesn't contain data for them

* Changed config flow to use homeassistant.helpers.service_info zeroconf instead of homeassistant.components zeroconf

* Added types to test parameters

* Changes and notes in config_flow.py

* Minor fix to get existing tests working before making changes to tests

* Removed unused data and simplified data passing

* Fixed tests, removed unused data, moved sensor tests to snapshots

* Mocked async_setup_entry and async_unload_entry

* Eliminated retry step so that retries just happen in the user flow or zeroconf_confirm

* Bumped the library version

* Bumped library version again

* Changed test-before-setup test

* Improved use of coordinator

* Almost done reworking tests. A few more changes still needed.

* Added via device to sensor.py and key reference to strings.json

* Webhook tests are almost, but not quite, working

* Fully functional again

* Change error to assert

* made identifiers and via_device the same

* made the via_device just be the mac

* Fixed strings.json and updated translations

* Fixed test_sensor.py

* Cleaned up tests and added autouse to several fixtures to simplify

* Switched from error to assert, and added exemption to quality scale.

* Cleaned up some tests and added update of IP if unique ID of discovered device is the same.

* Improved zeroconfig to update IP and hostname, and added a test to make sure those work.

* Fixed a comment.

* Improved ip/hostname update test.

* Changed Hub to VegeHub in strings.json for clarity.

* Switched to using a base entity to simplify and make adding platforms in the future easier.

* Moved the vegehub object into the coordinator to simplify.

* Removed actuators from sensors, and added unique name for battery sensor

* Changed coordinator to manage its own data, changed sensors to use descriptions and return their value as a property

* Updated data retrieval keys

* Minor updates to several files

* Fixed a few things for pytest

* Reverted to explicit check for None for pytest

* Fixed a comment and a variable name

* Fixed a comment

* Fix

* Bumped depenency version to eliminate pytest from dependencies.

---------

Co-authored-by: Josef Zweck <24647999+zweckj@users.noreply.github.com>
Co-authored-by: Josef Zweck <josef@zweck.dev>
Co-authored-by: Joostlek <joostlek@outlook.com>
2025-06-24 06:55:34 +02:00
Paulus Schoutsen
0cf7952964 Remove duplicated subentry device update in Google Gen AI + add merge test (#147396)
* late comments on Google subentries

* Add test that merges 2 config entries
2025-06-23 22:34:06 -04:00
Paulus Schoutsen
56f4039ac2 Migrate Google Gen AI to use subentries (#147281)
* Migrate Google Gen AI to use subentries

* Add reconfig successful msg

* Address comments

* Do not allow addin subentry when not loaded

* Let HA do the migration

* Use config_entries.async_setup

* Remove fallback name on base entity

* Fix

* Fix

* Fix device name assignment in entity and tts modules

* Fix tests

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-06-23 20:59:32 -04:00
Andrey Kupreychik
6641cb3799 Handle router initialization, connection errors, and missing interfaces in options flow (#143475)
* Handle router initialization and connection errors in options flow

Added checks in the Keenetic NDMS2 options flow to handle cases where the integration is not initialized or there are connection errors. Relevant user feedback and abort reasons are now provided to ensure a better user experience.

* Add filtering saved/default options for interfaces before preparing an options form
2025-06-23 23:52:23 +02:00
Jack Powell
c671ff3cf1 Add PlayStation Network Integration (#133901)
* clean pull request

* Create one device per console

* Requested changes

* Pr/tr4nt0r/1 (#2)

* clean pull request

* Create one device per console

* device setup

* Merge PR1 - Dynamic Device Support

* Merge PR1 - Dynamic Device Support

---------

Co-authored-by: tr4nt0r <4445816+tr4nt0r@users.noreply.github.com>

* nitpicks

* Update config_flow test

* Update quality_scale.yaml

* repair integrations.json

* minor updates

* Add translation string for invalid account

* misc changes post review

* Minor strings updates

* strengthen config_flow test

* Requested changes

* Applied patch to commit a358725

* migrate PlayStationNetwork helper classes to HA

* Revert to standard psn library

* Updates to media_player logic

* add default_factory, change registered_platforms to set

* Improve test coverage

* Add snapshot test for media_player platform

* fix token parse error

* Parametrize media player test

* Add PS3 support

* Add PS3 support

* Add concurrent console support

* Adjust psnawp rate limit

* Convert to package PlatformType

* Update dependency to PSNAWP==3.0.0

* small improvements

* Add PlayStation PC Support

* Refactor active sessions list

* shift async logic to helper

* Implemented suggested changes

* Suggested changes

* Updated tests

* Suggested changes

* Fix test

* Suggested changes

* Suggested changes

* Update config_flow tests

* Group remaining api call in single executor

---------

Co-authored-by: tr4nt0r <4445816+tr4nt0r@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-06-23 23:46:06 +02:00
Manu
646ddf9c2d Add sensors to ntfy integration (#145262)
* Add sensors

* small changes

* test coverage

* changes

* update snapshot
2025-06-23 23:17:43 +02:00
Paulus Schoutsen
95abd69cc6 Add media class to media player search and play intent (#147097)
Co-authored-by: Michael Hansen <mike@rhasspy.org>
2025-06-23 16:12:32 -05:00
Arie Catsman
ab0ea753e9 Optimize Enphase envoy translation strings. (#147389)
optimize Enphase envoy translation strings.
2025-06-23 22:59:50 +02:00
Åke Strandberg
9b915e996b Refactor states and strings for Miele plate power steps (#144992)
* WIP

* Fix type check

* Empty commit
2025-06-23 22:40:46 +02:00
hanwg
dc948e3b6c Add strict typing for Telegram bot integration (#147262)
add strict typing
2025-06-23 22:22:00 +02:00
epenet
8b6205be25 Remove JuiceNet integration (#147206) 2025-06-23 21:46:51 +02:00
Paulus Schoutsen
7f99cd2d2b Clean up start_subentry_reconfigure_flow API for tests (#147381) 2025-06-23 21:45:33 +02:00
Petro31
b4fe6f3843 Add trigger based fan entities to template integration (#145497)
* Add trigger based fan entities to template integration

* more changes

* add tests

* update doc strings

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-06-23 21:20:55 +02:00
Erik Montnemery
c29879274a Refactor DeviceAutomationConditionProtocol (#147377) 2025-06-23 21:18:56 +02:00
Joost Lekkerkerker
512449a76d Add Bluetooth connection to LaMetric (#147342) 2025-06-23 21:01:01 +02:00
Alex Biddulph
fc91047d8d Add sensors for detailed Enphase inverter readings (#146916)
* Add extra details to Enphase inverters

* Bump pyenphase version to 2.1.0

* Add new inverter sensors and translations

* Add new endpoint

* Start updating tests

* Remove duplicate class

* Add `max_reported` sensor

* Move translation strings to correct location

* Update fixtures and snapshots

* Update unit tests

* Fix linting

* Apply suggestions from code review

Co-authored-by: Arie Catsman <120491684+catsmanac@users.noreply.github.com>

* Fix Telegram bot parsing of inline keyboard (#146376)

* bug fix for inline keyboard

* update inline keyboard test

* Update tests/components/telegram_bot/test_telegram_bot.py

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

* revert last_message_id and updated tests

* removed TypeError test

---------

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

* Handle the new JSON payload from traccar clients (#147254)

* Set `entity_id`

* Update unit tests

* Bump aioamazondevices to 3.1.14 (#147257)

* Bump pyseventeentrack to 1.1.1 (#147253)

Update pyseventeentrack requirement to version 1.1.1

* Bump uiprotect to version 7.14.1 (#147280)

* Fix `state_class`es for energy production

* Make `max_reported` `name` more descriptive

* Update snapshots

* Reuse some translations

* Remove unnecessary translation keys

* Update unit tests

* Update homeassistant/components/enphase_envoy/strings.json

* Update homeassistant/components/enphase_envoy/strings.json

* Fix

---------

Co-authored-by: Arie Catsman <120491684+catsmanac@users.noreply.github.com>
Co-authored-by: hanwg <han.wuguang@gmail.com>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
Co-authored-by: Joakim Sørensen <joasoe@proton.me>
Co-authored-by: Simone Chemelli <simone.chemelli@gmail.com>
Co-authored-by: Shai Ungar <shai.ungar@riskified.com>
Co-authored-by: Raphael Hehl <7577984+RaHehl@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-06-23 20:59:18 +02:00
starkillerOG
6af290eb74 Add Reolink Telephoto main stream (#146975) 2025-06-23 20:53:09 +02:00
starkillerOG
dd3d6f116e Rename second Reolink lens from "autotrack" to "telephoto" (#146898)
* Rename second Reolink lens from "autotrack" to "telephoto"

* Adjust tests
2025-06-23 20:45:24 +02:00
starkillerOG
b4af9a31cb Add multiple cmd_id pushes for Reolink floodlight (#146685)
Allow for multiple cmd_id pushes
2025-06-23 20:44:35 +02:00
starkillerOG
2862f76fca Add support for Reolink Floodlight PoE/WiFi (#146778)
* Add support for Floodlight PoE/WiFi

* Adjust test

* Add test
2025-06-23 20:43:01 +02:00
Matthias Alphart
3806e5b65c Set KNX to quality scale "silver" (#144879)
Update KNX integration quality scale
2025-06-23 20:41:00 +02:00
Marcel van der Veldt
673a2e35ad Add button entity to Music Assistant to add currently playing item to favorites (#145626)
* Add action to Music Assistant to add currently playing item to favorites

* add test

* Convert to button entity

* review comments

* Update test_button.ambr

* Fix

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Robert Resch <robert@resch.dev>
2025-06-23 20:39:46 +02:00
Petro31
e494f66c02 Add label_description to template engine (#147138)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-06-23 19:21:29 +01:00
tronikos
2833e97625 Default to gemini-2.5-flash (#147334) 2025-06-23 20:11:16 +02:00
Joost Lekkerkerker
442fb88011 Add update platform to LaMetric (#147354) 2025-06-23 20:08:13 +02:00
epenet
6b242fd277 Migrate lifx to use runtime_data and HassKey (#147348) 2025-06-23 20:01:21 +02:00
G Johansson
a7de947f00 Add vacuum activity to pylint type hints check (#147162) 2025-06-23 18:12:18 +01:00
epenet
dfa3fddd35 Migrate livisi to use runtime_data (#147352) 2025-06-23 19:09:38 +02:00
Josef Zweck
ce115cbfe1 Bump aiotedee to 0.2.25 (#147349)
* Bump aiotedee to 0.2.24

* bump to 25

* fix snapshot
2025-06-23 19:08:48 +02:00
epenet
e1d5d312b8 Migrate linear_garage_door to use runtime_data (#147351)
Migrate linear_garage_door to use runtime_data/HassKey
2025-06-23 19:08:32 +02:00
Andre Lengwenus
27565df86f Add PARALLEL_UPDATES constant to binary_sensor and sensor for LCN (#147369)
Add PARALLEL_UPDATES to binary_sensor and sensor
2025-06-23 19:08:18 +02:00
Petro31
7eaa60b17c Add trigger vacuum entities to template integration (#145534)
* Add trigger vacuum entities to template integration

* remove comment

---------

Co-authored-by: Erik Montnemery <erik@montnemery.com>
2025-06-23 18:10:44 +02:00
Foscam-wangzhengyu
7bb9936e81 Replace foscam dependency (#145766)
* Update Public Library

* Update conftest.py
2025-06-23 18:10:31 +02:00
J. Nick Koston
ccbc5ed65b Bump aioesphomeapi to 3.1.1 (#147345) 2025-06-23 17:50:56 +02:00
Ludovic BOUÉ
e98ec38ad8 Matter energy optimization opt-out attribute (#147096)
* ESAStateEnum

* Update snapshot

* Add test

* Update homeassistant/components/matter/strings.json

Co-authored-by: Norbert Rittel <norbert@rittel.de>

* Update homeassistant/components/matter/strings.json

Co-authored-by: Norbert Rittel <norbert@rittel.de>

* Update homeassistant/components/matter/icons.json

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

* Update homeassistant/components/matter/sensor.py

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

* Update homeassistant/components/matter/strings.json

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

* Update sensor.py

* Update snapshot

---------

Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-06-23 17:26:27 +02:00
Paulus Schoutsen
a11e274434 Address AI Task late comments (#147313) 2025-06-23 16:58:42 +02:00
Alena Bugrova
f8267b13d7 Add Altruist integration to Core (#146158)
* add altruist integration and tests

* requested fixes + remove some deprecated sensors

* add tests for unknown sensor and device attribute in config_flow

* use CONF_ in data_schema

* suggested fixes

* remove test_setup_entry_success

* create ZeroconfServiceInfo in tests

* use CONF_IP_ADDRESS in tests

* add unique id assert

* add integration to strict-typing, set unavailable if no sensor key in data, change device name

* use add_suggested_values_to_schema, mmHg for pressure

* update snapshots and config entry name in tests

* remove changes in devcontainer config

* fixture for create client error, typing in tests, remove "Altruist" from device name

* change native_value_fn return type

* change sensor.py docstring

* remove device id from entry data, fix docstrings

* remove checks for client and device attributes

* use less variables in tests

* change creating AltruistSensor, remove device from arguments

* Update homeassistant/components/altruist/sensor.py

* Update homeassistant/components/altruist/quality_scale.yaml

* Update homeassistant/components/altruist/quality_scale.yaml

* Update quality_scale.yaml

* hassfest run

* suggested fixes

* set suggested_unit_of_measurement for pressure

* use mock_config_entry, update snapshots

* abort if cant create client on zeroconf step

* move sensor names in translatin placeholders

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-06-23 16:57:51 +02:00
Sanjay Govind
3798e99ac8 Update bosch_alarm to platinum quality scale (#145027)
* update quality scale for bosch_alarm

* update quality scale

* update quality scale
2025-06-23 16:42:14 +02:00
LG-ThinQ-Integration
fa71c40ff5 Bump thinqconnect to 1.0.7 (#147073)
Co-authored-by: yunseon.park <yunseon.park@lge.com>
Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-06-23 16:12:28 +02:00
Petro31
8e6edf5e34 Add trigger based locks to template integration (#145528)
* Add trigger based locks to template integration

* fix comments
2025-06-23 16:11:15 +02:00
Petro31
c1e32aa9b7 Add trigger template alarm control panels (#145461)
* Add trigger template alarm control panels

* updates

* fix jumbled imports

* fix comments
2025-06-23 16:10:50 +02:00
Michael Heyman
b48ebeaa8a Tilt Pi integration (#139726)
* Create component via script.scaffold

* Create sensor definition

* Define coordinator

* Define config flow

* Refine sensor definition and add tests

* Refine coordinator after testing end to end

* Redefine sensor in a more idiomatic way

* Use entity (common-module)

* Follow config-flow conventions more closely

* Use custom ConfigEntry to conform to strict-typing

* Define API object instead of using aio directly

* Test before setup in init

* Add diagnostics

* Make some more quality changes

* Move scan interval to const

* Commit generated files

* Add quality scale

* feedback: Apply consistent language to Tilt Pi refs

* feedback: Remove empty manifest fields

* feedback: Use translations instead of hardcoded name

* feedback: Remove diagnostics

* feedback: Idiomatic and general improvements

* Use tilt-pi library

* feedback: Coordinator data returns dict

* feedback: Move client creation to coordinator

* feedback: Request only Tilt Pi URL from user

* Update homeassistant/components/tilt_pi/entity.py

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

* Update homeassistant/components/tilt_pi/sensor.py

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

* Update homeassistant/components/tilt_pi/entity.py

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

* feedback: Avoid redundant keyword arguments in function calls

* feedback: Remove unused models and variables

* feedback: Use icons.json

* feedback: Style best practices

* Update homeassistant/components/tilt_pi/entity.py

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

* Update tests/components/tilt_pi/test_config_flow.py

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

* feedback: Improve config flow unit tests

* feedback: Patch TiltPi client mock

* feedback: Mark entity-device-class as done

* feedback: Align quaity scale with current state

* feeback: Create brands file for Tilt brand

* feedback: Demonstrate recovery in config flow

* feedback: Test coordinator behavior via sensors

* Update homeassistant/components/tilt_pi/config_flow.py

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

* Update homeassistant/components/tilt_pi/coordinator.py

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

* Update homeassistant/components/tilt_pi/quality_scale.yaml

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

* Update homeassistant/components/tilt_pi/quality_scale.yaml

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

* Update homeassistant/components/tilt_pi/quality_scale.yaml

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

* Update homeassistant/components/tilt_pi/config_flow.py

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

* feedback: Update tilt_pi quality scale

* feedback: Move const to coordinator

* feedback: Correct strings.json for incorrect and missing fields

* feedback: Use tiltpi package version published via CI

* Run ruff format manually

* Add missing string for invalid host

* Fix

* Fix

---------

Co-authored-by: Michael Heyman <michaelheyman@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-06-23 16:09:41 +02:00
Joost Lekkerkerker
0c08b4fc8b Add Matter protocol to Switchbot (#147356) 2025-06-23 16:06:19 +02:00
Ludovic BOUÉ
9ae3129f16 Matter battery storage (#147235)
* BatCapacity

* BatCapacity

* PowerSourceBatTimeRemaining

* BatChargeState

* Update strings.json

Co-authored-by: Norbert Rittel <norbert@rittel.de>

* Review fixes

* Remove uneeded BatCapacity

* Update strings.json

* Update strings.json

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

* Update snapshots

* Update strings.json

* Update snapshot

---------

Co-authored-by: Norbert Rittel <norbert@rittel.de>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-06-23 16:04:40 +02:00
Joost Lekkerkerker
d38c880c45 Bump demetriek to 1.3.0 (#147350)
* Bump demetriek to 1.3.0

* Fix
2025-06-23 15:32:57 +02:00
epenet
7ec2e0c524 Move lyric coordinator to separate module (#147357) 2025-06-23 15:10:12 +02:00
Franck Nijhof
87ecf552dc Add unique ID support to Trend integration YAML configuration (#147346) 2025-06-23 14:44:58 +02:00
Simone Chemelli
2e155831e6 Fix reload for Shelly devices with no script support (#147344) 2025-06-23 14:38:16 +02:00
Hessel
756b858840 Wallbox fix too many requests by API (#147197) 2025-06-23 14:10:50 +02:00
rrooggiieerr
b2520394f4 Lametric add configuration url (#147118)
* Set cofiguration URL to LaMetric device web interface

* Update LaMetric unit tests to accomodate fro configuration url
2025-06-23 13:47:52 +02:00
Maciej Bieniek
2a97b128c3 Bump IMGW-PIB backend library to version 1.1.0 (#147341) 2025-06-23 13:42:33 +02:00
epenet
d06da8c2da Migrate lcn to use runtime_data (#147333) 2025-06-23 13:41:53 +02:00
Erik Montnemery
2bfb09cb11 Improve test of WS command get_services cache handling (#147134) 2025-06-23 13:29:29 +02:00
Guido Schmitz
bf733fdec5 Remove config flow unique_id migration from devolo Home Control (#147327)
Remove config flow unique_id conversion from devolo Home Control
2025-06-23 13:16:57 +02:00
epenet
3b4eb7c749 Migrate lametric to use runtime_data (#147328)
* Migrate lametric to use runtime_data

* One more

* Drop unused hass_config
2025-06-23 13:15:08 +02:00
Artur Pragacz
1119716c32 Clean superfluous cloud deps from pyproject (#147223) 2025-06-23 13:15:01 +02:00
epenet
0ab23ccb51 Migrate landisgyr_heat_meter to use runtime_data (#147329) 2025-06-23 13:14:38 +02:00
Matrix
436fcb7e85 Fixed YoLink incorrect valve status (#147021)
* Fix valve status

* Fix as suggested
2025-06-23 13:14:18 +02:00
epenet
4d2f0f2de6 Migrate laundrify to use runtime_data (#147331)
* Migrate laundrify to use runtime_data

* Adjust test
2025-06-23 13:14:11 +02:00
Ludovic BOUÉ
82c1751f85 Matter dishwasher alarm (#146842)
* Update binary_sensor.py

* Update silabs_dishwasher.json

DishwasherAlarm

* DishwasherAlarm

* Update snapshot

* DishwasherAlarm

* test_dishwasher_alarm

* DishwasherAlarm

* Update silabs_dishwasher.json

* Update snapshot
2025-06-23 12:40:37 +02:00
epenet
a2785a86dc Migrate ld2410_ble to use runtime_data (#147335) 2025-06-23 12:13:10 +02:00
epenet
741e89383b Migrate leaone to use runtime_data (#147336) 2025-06-23 12:12:32 +02:00
epenet
f64533e9e0 Migrate led_ble to use runtime_data (#147337) 2025-06-23 12:11:03 +02:00
epenet
b13dd4e6ca Migrate lg_netcast to use runtime_data (#147338) 2025-06-23 12:09:51 +02:00
Noah Husby
35f310748e Add switch entity to Russound RIO (#147323)
* Add switch entity to Russound RIO

* Add switch snapshot
2025-06-23 10:42:36 +02:00
epenet
69d2cd0ac0 Migrate lastfm to use runtime_data (#147330) 2025-06-23 10:40:48 +02:00
Marc Mueller
10c573bbc3 Use PEP 695 TypeVar syntax for unifi (#147157) 2025-06-23 10:34:35 +02:00
Brian Rogers
93030ad48d Remove address info from Rachio calendar events (#145896)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-06-23 10:31:35 +02:00
Noah Husby
a7290f92cf Add number entity to Russound RIO (#147228)
* Add number entity to Russound RIO

* Fixes

* Fix tests

* Change entity name
2025-06-23 00:02:16 +02:00
Simone Chemelli
b47706f360 Add sensor platform to Alexa Devices (#146469)
* Add sensor platform to Amazon Devices

* fix merge after rename

* fix requirements

* cleanup

* Revert "cleanup"

This reverts commit f34892da8a.

* tests

* move logic in sensor entity description

* update tests

* apply review comment

* apply review comments
2025-06-23 00:01:15 +02:00
DeerMaximum
25968925e7 Use has_entity_name in NINA (#146755)
* Comply with has-entity-name rule.

* Fix tests
2025-06-22 23:57:33 +02:00
msw
fcba1183f8 Add water filter replacement and usage sensors to SmartThings (#147279)
* Add "Filter status" binary sensor for Samsung refrigerators

* Add "Water filter usage" sensor for Samsung refrigerators
2025-06-22 23:57:02 +02:00
Guido Schmitz
75946065f2 Combine executor calls in devolo Home Control (#147216) 2025-06-22 23:55:51 +02:00
Markus Adrario
3734c4e91d fix reconfig in case of no connection. (#147275) 2025-06-22 19:05:56 +02:00
Michael
7d421bf223 Fix regex patterns in foobot sensor tests (#147306) 2025-06-22 19:02:43 +02:00
Michael
41e53297c2 Add update entity to immich integration (#147273)
* add update entity

* remove unneccessary entity description

* rename update entity to version

* simplify test

* define static attribute outside of the constructor

* move min version check into coordinator
2025-06-22 16:54:48 +02:00
J. Nick Koston
d4e7667ea0 Bump aioesphomeapi to 33.0.0 (#147296)
fixes compat warning with protobuf 6.x

changelog: https://github.com/esphome/aioesphomeapi/compare/v32.2.4...v33.0.0

Not a breaking change for HA since we are already on protobuf 6
2025-06-22 10:24:16 -04:00
Ravaka Razafimanantsoa
daa4ddabfe Switchbot Cloud: Fix device type filtering in sensor (#146945)
* Add Smart Lock Ultra support and fix device type filtering in sensor integration

* Adding fix in binary sensor

* Fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-06-22 14:12:09 +02:00
Michael
8cead00bc7 Bump aioimmich to 0.10.1 (#147293)
bump aioimmich to 0.10.1
2025-06-22 12:19:03 +02:00
G Johansson
db3090078b Remove deprecated support feature values in camera (#146988) 2025-06-22 09:31:16 +02:00
Ludovic BOUÉ
66e2fd997b Battery voltage translation key (#147238)
* Add translation_key

* Update strings.json

* Update snapshots

* Switch icon to DC

* Update snapshots
2025-06-22 09:27:44 +02:00
Raphael Hehl
a102eaf0cd Bump uiprotect to version 7.14.1 (#147280) 2025-06-22 02:14:26 +02:00
Shai Ungar
f3533dff44 Bump pyseventeentrack to 1.1.1 (#147253)
Update pyseventeentrack requirement to version 1.1.1
2025-06-21 22:50:53 +01:00
Simone Chemelli
c453eed32d Bump aioamazondevices to 3.1.14 (#147257) 2025-06-21 15:44:22 +02:00
Joakim Sørensen
79a9f34150 Handle the new JSON payload from traccar clients (#147254) 2025-06-21 11:53:17 +02:00
hanwg
7442f7af28 Fix Telegram bot parsing of inline keyboard (#146376)
* bug fix for inline keyboard

* update inline keyboard test

* Update tests/components/telegram_bot/test_telegram_bot.py

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

* revert last_message_id and updated tests

* removed TypeError test

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-06-21 03:21:10 +02:00
Markus Adrario
2e5de732a7 Bump pyHomee to version 1.2.10 (#147248)
bump pyHomee to version 1.2.10
2025-06-21 00:32:14 +01:00
Michael Hansen
9bcd74c449 Change async_supports_streaming_input to an instance method (#147245) 2025-06-20 15:39:22 -05:00
Noah Husby
ace18e540b Bump aiorussound to 4.6.1 (#147233) 2025-06-20 21:59:59 +02:00
Michael Hansen
65f897793d Use string instead of boolean for voice event (#147244)
Use string instead of bool
2025-06-20 15:18:03 -04:00
Robert Resch
435c08685d Bump deebot-client to 13.4.0 (#147221) 2025-06-20 20:22:33 +02:00
J. Diego Rodríguez Royo
95f292c43d Bump aiohomeconnect to 0.18.1 (#147236) 2025-06-20 19:27:29 +02:00
Manu
9346c584c3 Add reconfigure flow to ntfy integration (#143743) 2025-06-20 18:42:47 +02:00
Michael Hansen
6738085391 Minor clean up missed in previous PR (#147229) 2025-06-20 10:54:11 -05:00
Markus Adrario
d9e5bad55e Use entity name in homee (#147142)
* add name to HomeeEntity

* review change
2025-06-20 16:55:48 +02:00
Maciej Bieniek
f7429f3431 Fix Shelly entity names for gen1 sleeping devices (#147019) 2025-06-20 15:19:39 +02:00
Petar Petrov
46aea5d9dc Bump zwave-js-server-python to 0.64.0 (#147176) 2025-06-20 14:59:54 +02:00
Paulus Schoutsen
33bde48c9c AI Task integration (#145128)
* Add AI Task integration

* Remove GenTextTaskType

* Add AI Task prefs

* Add action to LLM task

* Remove WS command

* Rename result to text for GenTextTaskResult

* Apply suggestions from code review

Co-authored-by: Allen Porter <allen.porter@gmail.com>

* Add supported feature for generate text

* Update const.py

Co-authored-by: HarvsG <11440490+HarvsG@users.noreply.github.com>

* Update homeassistant/components/ai_task/services.yaml

Co-authored-by: HarvsG <11440490+HarvsG@users.noreply.github.com>

* Use WS API to set preferences

* Simplify pref storage

* Simplify pref test

* Update homeassistant/components/ai_task/services.yaml

Co-authored-by: Allen Porter <allen.porter@gmail.com>

---------

Co-authored-by: Allen Porter <allen.porter@gmail.com>
Co-authored-by: HarvsG <11440490+HarvsG@users.noreply.github.com>
2025-06-20 08:56:08 -04:00
Noah Husby
1b73acc025 Add sub-device support to Russound RIO (#146763) 2025-06-20 14:52:34 +02:00
Guido Schmitz
e28965770e Add translations for devolo Home Control exceptions (#147099)
* Add translations for devolo Home Control exceptions

* Adapt invalid_auth message

* Adapt connection_failed message
2025-06-20 14:31:16 +02:00
Kevin Stillhammer
f9d4bde0f6 Bump here-routing to 1.2.0 (#147204)
* Bump here-routing to 1.2.0

* Fix mypy typing errors

* Correct types for call assertion
2025-06-20 13:44:14 +02:00
Duco Sebel
a493bdc208 Implement battery group mode in HomeWizard (#146770)
* Implement battery group mode for HomeWizard P1

* Clean up test

* Disable 'entity_registry_enabled_default'

* Fix failing tests because of 'entity_registry_enabled_default'

* Proof entities are disabled by default

* Undo dev change

* Update homeassistant/components/homewizard/select.py

* Update homeassistant/components/homewizard/select.py

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

* Update homeassistant/components/homewizard/strings.json

* Apply suggestions from code review

* Update tests due to updated translations

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-06-20 13:19:45 +02:00
Markus Adrario
9ae9ad1e43 Improve test-coverage for homee locks (#147160)
test for unknown user
2025-06-20 12:28:49 +02:00
epenet
1b60ea8951 Migrate lutron to use runtime_data (#147198) 2025-06-20 12:26:07 +02:00
epenet
313eaff14e Migrate kaleidescape to use runtime_data (#147171)
* Migrate kaleidescape to use runtime_data

* Adjust tests
2025-06-20 12:25:57 +02:00
epenet
7dfd68f8c0 Migrate keenetic_ndms2 to use runtime_data (#147194)
* Migrate keenetic_ndms2 to use runtime_data

* Adjust tests
2025-06-20 12:23:59 +02:00
epenet
544fd2a4a6 Migrate lacrosse_view to use runtime_data (#147202) 2025-06-20 12:23:29 +02:00
epenet
cd51070219 Migrate kmtronic to use runtime_data (#147193) 2025-06-20 11:39:13 +02:00
Marc Mueller
3c91c78383 Use PEP 695 TypeVar syntax for ecovacs (#147153) 2025-06-20 10:41:25 +02:00
Brett Adams
96e0d1f5c6 Fix Charge Cable binary sensor in Teslemetry (#147136) 2025-06-20 10:39:43 +02:00
epenet
2859e7de9b Migrate kodi to use runtime_data (#147191) 2025-06-20 10:38:01 +02:00
Robert Resch
88683a318d Add support of taking a camera snapshot via go2rtc (#145205) 2025-06-20 10:34:43 +02:00
epenet
84e9422254 Move juicenet coordinator to separate module (#147168) 2025-06-20 10:33:17 +02:00
epenet
fde36d5034 Simplify update_listener in konnected (#147172) 2025-06-20 10:31:28 +02:00
Andre Lengwenus
8c1e43c07c Bump pypck to 0.8.9 (#147174) 2025-06-20 10:28:35 +02:00
epenet
05343392a7 Simplify update_listener in keenetic_ndms2 (#147173) 2025-06-20 10:27:47 +02:00
epenet
32314dbb13 Simplify update_listener in kmtronic (#147184) 2025-06-20 10:27:07 +02:00
epenet
8f661fc5cf Migrate kegtron to use runtime_data (#147177) 2025-06-20 10:26:53 +02:00
epenet
e315cb9859 Migrate kostal_plenticore to use runtime_data (#147188) 2025-06-20 10:25:08 +02:00
epenet
d0e77eb1e2 Migrate keymitt_ble to use runtime_data (#147179) 2025-06-20 10:24:56 +02:00
epenet
e23cac8bef Simplify remove listener in kodi (#147183) 2025-06-20 10:23:41 +02:00
epenet
973700542b Move kmtronic coordinator to separate module (#147182) 2025-06-20 10:19:19 +02:00
Krisjanis Lejejs
2e21493c19 Bump hass-nabucasa from 0.102.0 to 0.103.0 (#147186) 2025-06-20 10:18:03 +02:00
Markus Adrario
73bed96a0f remove unwanted attribute in homee sensor tests (#147158) 2025-06-20 08:11:20 +02:00
Markus Adrario
0a5d13f104 fix and improve cover tests for homee (#147164) 2025-06-20 08:10:44 +02:00
epenet
d16ec81727 Migrate justnimbus to use runtime_data (#147170) 2025-06-20 08:10:06 +02:00
Martin Hjelmare
11564e3df5 Fix Z-Wave device class endpoint discovery (#142171)
* Add test fixture and test for Glass 9 shutter

* Fix zwave_js device class discovery matcher

* Fall back to node device class

* Fix test_special_meters modifying node state

* Handle value added after node ready
2025-06-20 08:56:20 +03:00
Michael Hansen
341d9f15f0 Add ask_question action to Assist satellite (#145233)
* Add get_response to Assist satellite and ESPHome

* Rename get_response to ask_question

* Add possible answers to questions

* Add wildcard support and entity test

* Add ESPHome test

* Refactor to remove async_ask_question

* Use single entity_id instead of target

* Fix error message

* Remove ESPHome test

* Clean up

* Revert fix
2025-06-19 16:50:14 -05:00
Marc Mueller
2c13c70e12 Update ruff to 0.12.0 (#147106) 2025-06-19 20:39:09 +02:00
Marc Mueller
73d0d87705 Use PEP 695 TypeVar syntax for nextdns (#147155) 2025-06-19 20:26:07 +02:00
Marc Mueller
b8dfb2c850 Use PEP 695 TypeVar syntax for eheimdigital (#147154) 2025-06-19 20:25:45 +02:00
Marc Mueller
cf67a68454 Use PEP 695 TypeVar syntax for paperless_ngx (#147156) 2025-06-19 20:24:51 +02:00
karwosts
b003429912 Expose statistics selector, use for recorder.get_statistics (#147056)
* Expose statistics selector, use for `recorder.get_statistics`

* code review

* syntax formatting

* rerun ci
2025-06-19 20:04:28 +02:00
hahn-th
4aff032442 Bump homematicip to 2.0.6 (#147151) 2025-06-19 18:55:14 +02:00
Martin Hjelmare
da3d8a6332 Improve advanced Z-Wave battery discovery (#147127) 2025-06-19 18:56:47 +03:00
Marc Mueller
7a5c088149 [ci] Bump cache key version (#147148) 2025-06-19 17:42:30 +02:00
Norbert Rittel
31eec6f471 Add missing hyphen to "mains-powered" and "battery-powered" in zha (#147128)
Add missing hyphen to "mains-powered" and "battery-powered"
2025-06-19 14:36:40 +03:00
G Johansson
c602a0e279 Deprecated hass.http.register_static_path now raises error (#147039) 2025-06-19 13:14:42 +02:00
Marc Mueller
513045e489 Update pytest warnings filter (#147132) 2025-06-19 13:07:42 +02:00
Erik Montnemery
0db6520802 Add comment in helpers.llm.ActionTool explaining limitations (#147116) 2025-06-19 12:59:35 +02:00
Erik Montnemery
5bc2e271d2 Re-raise annotated_yaml.YAMLException as HomeAssistantError (#147129)
* Re-raise annotated_yaml.YAMLException as HomeAssistantError

* Fix comment
2025-06-19 12:52:01 +02:00
G Johansson
77dca49c75 Fix pylint plugin for vacuum entity (#146467)
* Clean out legacy VacuumEntity from pylint plugins

* Fix

* Fix pylint for vacuum

* More fixes

* Revert partial

* Add back state
2025-06-19 12:49:10 +02:00
Franck Nijhof
1baba8b880 Adjust feature request links in issue reporting (#147130) 2025-06-19 12:36:43 +02:00
Markus Adrario
875d81cab2 update pyHomee to v1.2.9 (#147094) 2025-06-19 12:04:59 +02:00
Raphael Hehl
956f726ef3 Bump uiprotect to version 7.14.0 (#147102) 2025-06-19 11:20:29 +02:00
epenet
fada81e1ce Bump ovoenergy to 2.0.1 (#147112) 2025-06-19 08:46:03 +02:00
Simon Lamon
6a16424bb4 Fix nightly build (#147110)
Update builder.yml
2025-06-19 08:20:19 +02:00
Abílio Costa
f90a740429 Use non-autospec mock for Reolink's binary_sensor, camera and diag tests (#147095) 2025-06-19 08:03:48 +02:00
Michael Hansen
3dba7e5bd2 Send intent progress events to ESPHome (#146966) 2025-06-18 22:12:37 -04:00
Erik Montnemery
8d8ff011fc Minor improvements of service helper (#147079) 2025-06-19 00:17:12 +01:00
Michael Hansen
6befd065a1 Bump aioesphomeapi to 32.2.4 (#147100)
Bump aioesphomeapi
2025-06-18 15:49:44 -05:00
Abílio Costa
9adf493acd Use non-autospec mock for Reolink's init tests (#146991) 2025-06-18 17:58:50 +01:00
Michael Hansen
a29d5fb56c tts_output is optional in run-start (#147092) 2025-06-18 12:08:53 -04:00
Petro31
bcb87cf812 Support variables, icon, and picture for all compatible template platforms (#145893)
* Fix template entity variables in blueprints

* add picture and icon tests

* add variable test for all platforms

* apply comments

* Update all test names
2025-06-18 16:49:46 +02:00
Jan Bouwhuis
d01758cea8 Ensure mqtt sensor has a valid native unit of measurement (#146722) 2025-06-18 15:48:38 +02:00
Joakim Sørensen
5487bfe1d9 Bump hass-nabucasa from 0.101.0 to 0.102.0 (#147087) 2025-06-18 15:47:01 +02:00
Simone Chemelli
fec65f40fc Bump aioamazondevices to 3.1.12 (#147055)
* Bump aioamazondevices to 3.1.10

* bump to 3.1.12
2025-06-18 10:20:51 +02:00
Guido Schmitz
596951ea9f Cleanup devolo Home Control tests (#147051) 2025-06-18 09:24:09 +02:00
Norbert Rittel
75d6b885cf Fix typo in state name references of homee (#146905)
Fix typo in state references

Replace wrong semicolons with colon.
2025-06-18 09:23:37 +02:00
Guido Schmitz
3fad76dfa1 Use missed typed ConfigEntry in devolo Home Control (#147049) 2025-06-18 09:22:37 +02:00
Pete Sage
43d8a151ab Remove internals from Sonos test_init.py (#147063)
* fix: test init

* fix: revert

* fix: revert

* fix: revert

* fix: revert

* fix: simplify
2025-06-18 09:21:21 +02:00
starkillerOG
07110e288d If no Reolink HTTP api available, do not set configuration_url (#146684)
* If no http api available, do not set configuration_url

* Add tests
2025-06-18 09:16:08 +02:00
Jan-Philipp Benecke
ba2aac4614 Bump aiowebdav2 to 0.4.6 (#147054) 2025-06-18 09:15:27 +02:00
msw
3449dae7a2 Capitalize "Ice Bites" and switch to "Cubed ice" (#147060) (#147061) 2025-06-18 09:14:45 +02:00
G Johansson
b8cd3f3635 Bump holidays lib to 0.75 (#147043) 2025-06-18 10:11:01 +03:00
Martin Hjelmare
be53ad5449 Disable Z-Wave idle notification button (#147026)
* Update test

* Disable Z-Wave idle notification button

* Update tests
2025-06-18 08:29:04 +03:00
J. Diego Rodríguez Royo
ffd940e07c Set quality scale at Home Connect manifest (#147050) 2025-06-17 21:42:40 +01:00
Josef Zweck
5e31b5ac4f Handle missing widget in lamarzocco (#147047) 2025-06-17 21:25:27 +02:00
puddly
81257f9d57 Bump ZHA to 0.0.60 (#147045) 2025-06-17 22:06:53 +03:00
Josef Zweck
ce1678719a Bump pylamarzocco to 2.0.9 (#147046) 2025-06-17 20:59:41 +02:00
Guido Schmitz
fc6844b3c9 Add _attr_has_entity_name to devolo Home Network device tracker platform (#146978)
* Add _attr_has_entity_name to devolo Home Network device tracker platform

* Set name

* Fix tests
2025-06-17 20:49:52 +02:00
J. Diego Rodríguez Royo
8e82e3aa3a Bump aiohomeconnect to 0.18.0 (#147044) 2025-06-17 20:48:09 +02:00
G Johansson
3bc68941e6 Remove not used constant in climate (#147041) 2025-06-17 20:43:16 +02:00
Josef Zweck
e69b38ab2c Fix log in onedrive (#147029) 2025-06-17 19:57:52 +02:00
Abílio Costa
ed9503324d Fix flaky Reolink webhook test (#147036) 2025-06-17 17:18:48 +01:00
Allen Porter
22a06a6c2e Bump ical to 10.0.4 (#147005)
* Bump ical to 10.0.4

* Bump ical to 10.0.4 in google
2025-06-17 07:06:51 -07:00
Michael Hansen
3b611b9b03 Add TTS response timeout for idle state (#146984)
* Add TTS response timeout for idle state

* Consider time spent sending TTS audio in timeout
2025-06-17 09:39:18 -04:00
Noah Husby
79cc3bffc6 Bump aiorussound to 4.6.0 (#147023) 2025-06-17 14:40:56 +02:00
Martin Hjelmare
5c455304a5 Disable Z-Wave indidator CC entities by default (#147018)
* Update discovery tests

* Disable Z-Wave indidator CC entities by default
2025-06-17 15:39:22 +03:00
Erik Montnemery
058f860be7 Fix incorrect use of zip in service.async_get_all_descriptions (#147013)
* Fix incorrect use of zip in service.async_get_all_descriptions

* Fix lint errors in test
2025-06-17 14:24:31 +02:00
Joost Lekkerkerker
ef319c966d Bump nextcord to 3.1.0 (#147020) 2025-06-17 14:11:55 +02:00
Robin Lintermann
adc4e9fdc1 Bump pysmarlaapi version to 0.9.0 (#146629)
Bump pysmarlaapi version
Fix default values of entities
2025-06-17 11:23:50 +02:00
Maciej Bieniek
40a00fb790 Address late review for NextDNS integration (#146980)
key instead of Key
2025-06-17 11:23:03 +02:00
G Johansson
0926b16095 Remove deprecated support feature values in cover (#146987) 2025-06-17 10:46:08 +02:00
G Johansson
308c89af4a Remove deprecated support feature values in media_player (#146986) 2025-06-17 10:33:41 +02:00
G Johansson
b0c2a47288 Remove deprecated support feature values in vacuum (#146982) 2025-06-17 10:32:58 +02:00
Joost Lekkerkerker
c446cce2cc Bump pySmartThings to 3.2.5 (#146983) 2025-06-16 22:44:14 +01:00
Abílio Costa
e02267ad89 Improve bootstrap file logging test (#146670) 2025-06-16 21:55:16 +01:00
Thomas55555
36381e6753 Bump aioautomower to 2025.6.0 (#146979) 2025-06-16 22:52:23 +02:00
Manu
6533562f4e Rename Xiaomi Miio integration to Xiaomi Home (#146555)
Co-authored-by: Norbert Rittel <norbert@rittel.de>
2025-06-16 21:51:54 +01:00
Ludovic BOUÉ
1bc6ea98ce Set Matter SolarPower tagList in fixture (#146837)
Update solar_power.json

Set tagList to [{"0":null,"1":15,"2":2,"3":"Solar"}]
2025-06-16 22:46:27 +02:00
elmurato
bab34b844b Fix blocking open in Minecraft Server (#146820)
Fix blocking open by dnspython
2025-06-16 22:46:11 +02:00
Etienne C.
ad3dac0373 Removed rounding of durations in Here Travel Time sensors (#146838)
* Removed rounding of durations

* Set duration sensors unit to seconds

* Updated Here Travel Time tests

* Update homeassistant/components/here_travel_time/sensor.py

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

* Update homeassistant/components/here_travel_time/sensor.py

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

* Updated Here Travel Time tests

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-06-16 22:20:01 +02:00
Maciej Bieniek
c5d93e5456 Fix translation key in NextDNS integration (#146976)
* Fix translation key

* Better wording
2025-06-16 21:37:19 +02:00
J. Diego Rodríguez Royo
ef9b46dce5 Record current IQS state for Home Connect (#131703)
* Home Connect quality scale

* Update current iqs

* Docs rules done

* parallel-updates rule

* Complete appropriate-polling's comment

* Apply suggestions

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

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-06-16 21:30:06 +02:00
Abílio Costa
6f3ceb83c2 Use non-autospec mock for Reolink's button tests (#146969) 2025-06-16 21:14:02 +02:00
Joost Lekkerkerker
589577a04c Add diagnostics support to Meater (#146967) 2025-06-16 20:17:30 +02:00
Joost Lekkerkerker
cb21bb6542 Make Meater cook state an enum (#146958) 2025-06-16 19:13:34 +01:00
mswilson
ad64139b8e Add switch for Samsung ice bites (and rename ice maker) (#146925)
* Add switch for ice bites (and rename ice maker)

Fixes: home-assistant/home-assistant.io#37826

* Fix tests

* Fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-06-16 19:31:49 +02:00
Joost Lekkerkerker
9ae0cfc7e5 Create entities directly on setup in Meater (#146953)
* Don't wait an update when adding devices in Meater

* Fix
2025-06-16 18:23:20 +02:00
Joost Lekkerkerker
dffaf49eca Use runtime data in Meater (#146961) 2025-06-16 17:18:21 +02:00
Maciej Bieniek
4add783108 Use entity base class for NextDNS entities (#146934)
* Add entity module

* Add NextDnsEntityDescription class

* Remove NextDnsEntityDescription

* Create DeviceInfo in entity module

* Use property
2025-06-16 16:58:47 +02:00
Joost Lekkerkerker
421251308f Add Meater sensor tests (#146952) 2025-06-16 16:19:35 +02:00
Aviad Levy
cce878213f Add Telegram Bot message reactions (#146354) 2025-06-16 14:48:59 +01:00
Joost Lekkerkerker
664441eaec Improve Meater config flow tests (#146951) 2025-06-16 15:40:43 +02:00
Maciej Bieniek
d4686a3cce Add config flow data description for NextDNS (#146938)
* Add config flow data description

* Better wording
2025-06-16 15:28:25 +02:00
Hessel
6e92247799 Fix missing key for ecosmart in older Wallbox models (#146847)
* fix 146839, missing key

* added tests for this issue

* added tests for this issue

* added tests for this issue, formatting

* Prevent loading select on missing key

* Prevent loading select on missing key - formatting fixed

* Update homeassistant/components/wallbox/coordinator.py

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

---------

Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-06-16 15:15:17 +02:00
Etienne C.
f5355c833e Add duration device class in Here Travel Time sensors (#146804) 2025-06-16 15:14:43 +02:00
Joost Lekkerkerker
add9f4c5ab Move Meater coordinator to module (#146946)
* Move Meater coordinator to module

* Fix tests
2025-06-16 14:48:44 +02:00
starkillerOG
38973fe64a Add Reolink privacy mask switch (#146906) 2025-06-16 14:40:19 +02:00
epenet
d657964729 Simplify habitica service actions (#146746) 2025-06-16 14:37:38 +02:00
Nathan Spencer
25c408484c Set goalzero total run time sensor device class to duration (#146897) 2025-06-16 14:35:56 +02:00
Florian von Garrel
c335b5b37c Add verify ssl option to paperless-ngx integration (#146802)
* add verify ssl config option

* Refactoring

* Use .get() with default value instead of migration

* Reconfigure fix

* minor changes
2025-06-16 14:31:22 +02:00
Josef Zweck
61b00892c3 Add debug log for update in onedrive (#146907) 2025-06-16 14:17:36 +02:00
Maciej Bieniek
e47e2c92fe Change PARALLEL_UPDATES to 0 for read-only NextDNS platforms (#146939)
Change PARALLEL_UPDATES to 0 for read-only platforms
2025-06-16 14:11:48 +02:00
Duco Sebel
3283965b45 Re-enable v2 API support for HomeWizard P1 Meter (#146927) 2025-06-16 14:11:35 +02:00
epenet
4a9cbc79f2 Bump pysml to 0.1.5 (#146935) 2025-06-16 12:56:03 +01:00
epenet
33978ce59e Bump pyosoenergyapi to 1.1.5 (#146942) 2025-06-16 12:46:38 +01:00
epenet
d5262231a1 Bump pymysensors to 0.25.0 (#146941) 2025-06-16 13:37:39 +02:00
Brett Adams
b563f9078a Significantly improve Tesla Fleet config flow (#146794)
* Improved config flow

* Tests

* Improvements

* Dashboard url & tests

* Apply suggestions from code review

Co-authored-by: Norbert Rittel <norbert@rittel.de>

* revert oauth change

* fully restore oauth file

* remove CONF_DOMAIN

* Add pick_implementation back in

* Use try else

* Improve translation

* use CONF_DOMAIN

---------

Co-authored-by: Norbert Rittel <norbert@rittel.de>
2025-06-16 13:29:17 +02:00
epenet
e8667dfbe0 Bump nessclient to 1.2.0 (#146937) 2025-06-16 12:11:57 +01:00
dependabot[bot]
8d4f5d78ff Bump dawidd6/action-download-artifact from 10 to 11 (#146928)
Bumps [dawidd6/action-download-artifact](https://github.com/dawidd6/action-download-artifact) from 10 to 11.
- [Release notes](https://github.com/dawidd6/action-download-artifact/releases)
- [Commits](https://github.com/dawidd6/action-download-artifact/compare/v10...v11)

---
updated-dependencies:
- dependency-name: dawidd6/action-download-artifact
  dependency-version: '11'
  dependency-type: direct:production
  update-type: version-update:semver-major
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-16 10:42:10 +02:00
mbo18
e354a850c9 Bump python-rflink to 0.0.67 (#146908)
* update python-rflink

* remove from FORBIDDEN_PACKAGE_EXCEPTIONS
2025-06-16 10:36:20 +02:00
Ernst Klamer
5ea026d369 Bump bthome-ble to 3.13.1 (#146871) 2025-06-16 11:29:00 +03:00
Brett Adams
ddfe17d0a4 Bump tesla-fleet-api to match Protobuf compatibility (#146918)
Bump for v1.2.0
2025-06-16 10:12:34 +02:00
Yuxin Wang
85aa7bef1e Add sensor categorizations for APCUPSD (#146863)
* Add sensor categorizations

* Fix snapshot problem

* Fix snapshot problem
2025-06-16 08:43:31 +02:00
Paulus Schoutsen
8498928e47 Move Google Gen AI fixture to allow reuse (#146921) 2025-06-15 23:00:27 -04:00
Paulus Schoutsen
fa21269f0d Simplify ChatLog dependencies (#146351) 2025-06-15 17:41:15 -04:00
starkillerOG
5f5869ffc6 Bump reolink-aio to 0.14.1 (#146903) 2025-06-15 20:53:32 +02:00
Nathan Spencer
7a2d99a450 Bump pylitterbot to 2024.2.0 (#146901) 2025-06-15 20:41:07 +02:00
Andre Lengwenus
6b669ce40c Bump pypck to 0.8.8 (#146841) 2025-06-15 19:32:13 +02:00
Markus Adrario
fdf4ed2aa5 Homee add button_state to event entities (#146860)
* use entityDescription

* Add new event and adapt tests

* change translation

* use references in strings
2025-06-15 18:17:52 +02:00
Simone Chemelli
1361d10cd7 Bump aioamazondevices to 3.1.4 (#146883) 2025-06-15 08:30:19 -07:00
Marc Mueller
8c7ba11493 Fix telegram_bot RuntimeWarning in tests (#146781) 2025-06-15 11:23:17 +03:00
Marc Mueller
29ce17abf4 Update eq3btsmart to 2.1.0 (#146335)
* Update eq3btsmart to 2.1.0

* Update import names

* Update register callbacks

* Updated data model

* Update Thermostat set value methods

* Update Thermostat init

* Thermostat status and device_data are always given

* Minor compatibility fixes

---------

Co-authored-by: Lennard Beers <l.beers@outlook.de>
2025-06-15 10:17:01 +02:00
Markus Lanthaler
c988d1ce36 Add support for Gemini's new TTS capabilities (#145872)
* Add support for Gemini TTS

* Add tests

* Use wave library and update a few comments
2025-06-14 22:21:04 -07:00
Paulus Schoutsen
ec02f6d010 Extract Google LLM base entity class (#146817) 2025-06-14 22:17:52 -07:00
Simone Chemelli
9f19c4250a Bump aioamazondevices to 3.1.3 (#146828) 2025-06-15 01:45:28 +03:00
Marc Mueller
d7b583ae51 Update pydantic to 2.11.7 (#146835) 2025-06-14 23:31:09 +02:00
Maciej Bieniek
152e5254e2 Use Shelly main device area as suggested area for sub-devices (#146810) 2025-06-14 13:53:51 -04:00
starkillerOG
3f8f7cd578 Bump motion blinds to 0.6.28 (#146831) 2025-06-14 19:01:41 +02:00
Chris Talkington
ed3fb62ffc Update rokuecp to 0.19.5 (#146788) 2025-06-14 18:49:16 +02:00
J. Nick Koston
1d14e1f018 Bump aiohttp to 3.12.13 (#146830)
changelog: https://github.com/aio-libs/aiohttp/compare/v3.12.12...v3.12.13

Likely does not affect us at all but just in case, tagging
2025-06-14 17:13:20 +01:00
hahn-th
2ac8901a0d Improve code quality in async_setup_entry of switches in homematicip_cloud (#146816)
improve setup of switches
2025-06-14 17:26:08 +02:00
Joris Pelgröm
6204fd5363 Add polling to LetPot coordinator (#146823)
- Adds polling (update_interval) to the coordinator for the LetPot integration. Push remains the primary update mechanism for all entities, but:
   - Polling makes entities go unavailable when the device can't be reached, which otherwise won't happen.
   - Pump changes do not always trigger a status push by the device (not sure why), polling makes the integration catch up to reality.
2025-06-14 16:24:48 +02:00
Brett Adams
ce52ef64db Bump tesla-fleet-api to 1.1.3 (#146793) 2025-06-14 08:39:27 -05:00
Paulus Schoutsen
059c12798d Drop user prompt from LLMContext (#146787) 2025-06-13 22:01:39 -04:00
epenet
56aa809074 Simplify google_photos service actions (#146744) 2025-06-13 18:57:11 -07:00
Marc Mueller
3d2dca5f0c Adjust scripts for compatibility with Python 3.14 (#146774) 2025-06-13 21:54:25 -04:00
starkillerOG
cdb2b407be Add Reolink baby cry sensitivity (#146773)
* Add baby cry sensitivity

* Adjust tests
2025-06-14 00:11:13 +01:00
Ian
186ed451a9 Bump nextbus client to 2.3.0 (#146780) 2025-06-14 00:09:29 +01:00
hahn-th
761a0877e6 Fix throttling issue in HomematicIP Cloud (#146683)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-06-13 19:57:03 +02:00
J. Nick Koston
91bc56b15c Bump aiodns to 3.5.0 (#146758) 2025-06-13 19:12:52 +02:00
Paulus Schoutsen
d1e2c62433 Remove unnecessary string formatting. (#146762) 2025-06-13 10:10:47 -07:00
Duco Sebel
524c16fbe1 Bumb python-homewizard-energy to 9.1.1 (#146723)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-06-13 18:59:28 +02:00
Marc Mueller
2fdd3d66bc Update pydantic to 2.11.6 (#146745) 2025-06-13 18:53:05 +02:00
Simone Chemelli
6a1e3b60ee Filter speak notify entity for WHA devices in Alexa Devices (#146688) 2025-06-13 18:49:18 +02:00
DeerMaximum
434cd95a66 Use ConfigEntry.runtime_data to store runtime data in NINA (#146754)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-06-13 18:47:21 +02:00
Vasilis Valatsos
1a5bc2c7e0 Drop HostKeyAlgorithms in aruba (#146619) 2025-06-13 18:47:07 +02:00
epenet
a66e9a1a2c Simplify reolink service actions (#146751) 2025-06-13 18:08:59 +02:00
Paulus Schoutsen
d880ce6bb4 Clean up Google conversation entity (#146736) 2025-06-13 10:30:14 -04:00
Paulus Schoutsen
c96023dcae Clean up Anthropic conversation entity (#146737) 2025-06-13 10:29:26 -04:00
Paulus Schoutsen
2f8ad4d5bf Clean up Ollama conversation entity (#146738) 2025-06-13 10:29:19 -04:00
Marc Mueller
038a848d53 Fix androidtv isfile patcher in tests (#146696) 2025-06-13 16:25:09 +02:00
epenet
ff17d79e73 Bump wakeonlan to 3.1.0 (#146655)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-06-13 08:58:44 -05:00
tronikos
a8201009f3 Fix opower to work with aiohttp>=3.12.7 by disabling cookie quoting (#146697)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-06-13 08:58:27 -05:00
Simone Chemelli
a349653282 Bump aioamazondevices to 3.1.2 (#146690) 2025-06-13 16:53:18 +03:00
epenet
355ee1178e Add callback decorator to async_setup_services (#146729) 2025-06-13 15:16:55 +02:00
Marc Mueller
30c5df3eaa Adjust core create_task tests with event_loop patch (#146699) 2025-06-13 15:16:28 +02:00
Marc Mueller
10874af19a Ignore lingering pycares shutdown thread (#146733) 2025-06-13 15:09:37 +02:00
Marc Mueller
704118b3d0 Remove unnecessary patch from toon tests (#146691) 2025-06-13 12:53:33 +02:00
Marc Mueller
7c575d0316 Fix asuswrt test patch (#146692) 2025-06-13 12:52:56 +02:00
starkillerOG
ab3f11bfe7 Add Reolink IR brightness entity (#146717) 2025-06-13 12:50:12 +02:00
Allen Porter
f0357539ad Add myself as a remote calendar code owner (#146703) 2025-06-13 12:48:24 +02:00
Allen Porter
e70a2dd257 Partial revert of update to remote calendar to fix issue where calendar does not update (#146702)
Partial revert
2025-06-13 12:47:56 +02:00
Allen Porter
5ef99a15a5 Revert scan interval change in local calendar (#146700) 2025-06-13 12:46:01 +02:00
Marc Mueller
6421973cd6 Remove unnecessary patch from panel_custom tests (#146695) 2025-06-13 10:46:26 +02:00
Marc Mueller
7201171eb5 Replace unnecessary pydantic import in matrix tests (#146693) 2025-06-13 10:45:54 +02:00
Abílio Costa
1fb438fa6c Add missing mock value to Reolink test (#146689) 2025-06-13 07:43:21 +02:00
starkillerOG
89ae68c5af Reolink check if camera and motion supported (#146666) 2025-06-12 22:19:46 +01:00
Paul Bottein
c78b66d5d5 Update frontend to 20250531.3 (#146638) 2025-06-12 16:52:09 -04:00
starkillerOG
d756cf91ce Add model_id to Reolink IPC camera (#146664) 2025-06-12 20:41:13 +01:00
Simon Lamon
8d13bf93ab Bump linkplay to v0.2.12 (#146669) 2025-06-12 20:38:42 +01:00
Franck Nijhof
e86e793842 Tweak non-English issue detection (#146636) 2025-06-12 13:38:20 -04:00
Tsvi Mostovicz
7e6bb021ce Bump hdate to 1.1.2 (#146659) 2025-06-12 18:29:47 +01:00
starkillerOG
680b70aa29 Reolink add diagnostics for baichuan (#146667)
* Add baichuan diagnostics

* adjust tests
2025-06-12 19:26:37 +02:00
Andre Lengwenus
8eebebc586 Bump pypck to 0.8.7 (#146657) 2025-06-12 17:36:50 +01:00
epenet
48e4624ba0 Add basic xiaomi_miio fan tests (#146593) 2025-06-12 17:33:45 +01:00
epenet
b0cf974b34 Simplify swiss public transport service actions (#146611) 2025-06-12 16:27:20 +02:00
Simone Chemelli
171f7c5f81 Fix cookies with aiohttp >= 3.12.7 for Vodafone Station (#146647) 2025-06-12 16:24:10 +02:00
Avi Miller
8807c530a9 Fix palette handling for LIFX Ceiling SKY effect (#146582)
Signed-off-by: Avi Miller <me@dje.li>
2025-06-12 14:32:04 +02:00
dependabot[bot]
28bd90aeb0 Bump actions/attest-build-provenance from 2.3.0 to 2.4.0 (#146594)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-12 14:18:04 +02:00
dependabot[bot]
af1eccabce Bump github/codeql-action from 3.28.19 to 3.29.0 (#146595)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-12 14:17:36 +02:00
Pete Sage
afc0a2789d Update Sonos to use SonosConfigEntry and runtime data (#145512)
* fix: initial

* fix: cleanup

* fix: cleanup

* fix: cleanup

* fix: SonosConfigEntry

* add config_entry.py

* fix: sonos_data to runtime_data

* fix: move to helpers.py
2025-06-12 14:05:51 +02:00
epenet
78ed1097c4 Simplify netgear_lte service actions (#146606) 2025-06-12 14:02:17 +02:00
epenet
2991726d35 Simplify screenlogic service actions (#146609) 2025-06-12 14:02:06 +02:00
epenet
c34596e54d Simplify seventeentrack service actions (#146610)
* Simplify seventeentrack service actions

* callback
2025-06-12 14:01:53 +02:00
epenet
74a92e2cd8 Simplify tado service actions (#146614) 2025-06-12 14:01:45 +02:00
Franck Nijhof
e19f178864 Make duplicate issue detection more strict (#146633) 2025-06-12 13:55:26 +02:00
epenet
9dfbccf0cb Improve type hints in xiaomi_miio fan (#146596) 2025-06-12 12:18:46 +02:00
epenet
64e503bc27 Fix fan is_on status in xiaomi_miio (#146592) 2025-06-12 12:18:23 +02:00
epenet
9d1e60cf7e Simplify mealie service actions (#146601) 2025-06-12 12:17:27 +02:00
epenet
4160521349 Simplify overseerr service actions (#146607) 2025-06-12 12:17:00 +02:00
epenet
14c30ef2df Mark async_setup_services as callback (#146617) 2025-06-12 11:34:56 +02:00
epenet
e14cf8a5b9 Remove deprecated service in plex (#146608)
* Remove deprecated service in plex

* Update json/yaml
2025-06-12 10:43:03 +02:00
epenet
30dbd5a900 Simplify synology_dsm service actions (#146612) 2025-06-12 10:42:40 +02:00
G Johansson
25e6eab008 Not valid hvac modes now fails in Climate (#145242)
* Not valid hvac modes now fails

* Fix some tests

* Some more

* More

* fix ruff

* HVAC

* Fritzbox

* Clean up

* Use dict[key]

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-06-12 07:15:07 +02:00
Thomas55555
8bf562b7b6 Add strings for pick implementation (#146557)
* Add string for pick implementation

* add missing
2025-06-12 06:02:26 +02:00
rappenze
7cb3c397b2 Support more dimmer devices in fibaro (#145864) 2025-06-11 23:55:38 +02:00
Christopher Boyd
f44f2522ef Add 'AdvancedToggle' to list of supported Lutron button types (#145676) 2025-06-11 23:54:22 +02:00
Denis Shulyaka
8c9acf5a4d Separate steps for openai_conversation options flow (#141533) 2025-06-11 23:54:01 +02:00
starkillerOG
e46e7f5a81 Bump reolink-aio to 0.14.0 (#146566) 2025-06-11 23:52:31 +02:00
Calvin C
c01f521199 Bump hyperion-py to 0.7.6 and add switch for Audio Capture to Hyperion Integration (#145952)
Co-authored-by: ToniCipriani <ToniCipriani@users.noreply.github.com>
Co-authored-by: Robert Resch <robert@resch.dev>
2025-06-11 21:20:22 +02:00
Robert Resch
4a15f12a0b Add aiofiles to pyproject.toml (#146561) 2025-06-11 20:32:38 +02:00
Ståle Storø Hauknes
8d24d775f1 Set suggested precision for Airthings sensors (#145966) 2025-06-11 20:04:03 +02:00
epenet
aca0e69081 Simplify service registration in recorder (#146237) 2025-06-11 20:01:13 +02:00
G Johansson
f4e5036275 New helper for templating args in command_line (#145899) 2025-06-11 19:58:28 +02:00
rappenze
59aba339d8 Add support for more cover devices in Fibaro (#146486) 2025-06-11 19:56:38 +02:00
Joost Lekkerkerker
864e440685 Make issue creation check architecture instead of uname (#146537) 2025-06-11 18:39:46 +02:00
tronikos
2f6fcb5801 Rename Amazon Devices to Alexa Devices (#146362)
Co-authored-by: Simone Chemelli <simone.chemelli@gmail.com>
Co-authored-by: Joostlek <joostlek@outlook.com>
2025-06-11 18:35:26 +02:00
G Johansson
bdb6124aa3 Remove previously deprecated cached_property (#146478)
Co-authored-by: Robert Resch <robert@resch.dev>
2025-06-11 18:22:11 +02:00
epenet
613e2fd4b3 Simplify google_mail service actions (#146511) 2025-06-11 18:19:57 +02:00
Kevin Stillhammer
0e71ef3861 Fix stale options in here_travel_time (#145911) 2025-06-11 18:17:11 +02:00
andreimoraru
5076c10959 Bump yt-dlp to 2025.06.09 (#146553)
* Bumped yt-dlp to 2025.06.09

* fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-06-11 17:53:25 +02:00
Shay Levy
ab2fc4e9a6 Remove stale Shelly BLU TRV devices (#145994)
* Remove stale Shelly BLU TRV devices

* Add test

* Remove config entry from device
2025-06-11 17:39:49 +02:00
Erik Montnemery
e39edcc234 Remove unused attribute EntityInfo.custom_component (#146550) 2025-06-11 17:27:17 +02:00
Paul Bottein
54c8e59bcd Update frontend to 20250531.2 (#146551) 2025-06-11 17:12:34 +02:00
Franck Nijhof
c806555879 Add non-English issue detection using GitHub AI models (#146547) 2025-06-11 16:52:35 +02:00
G Johansson
4836930cb1 Remove previously deprecated StrEnum backport (#146477) 2025-06-11 16:41:40 +02:00
epenet
4a8faad62e Simplify fully_kiosk service actions (#146509) 2025-06-11 16:34:48 +02:00
peteS-UK
ba69301dda Move available property to entity.py for Squeezebox (#146531) 2025-06-11 16:34:08 +02:00
Aidan Timson
724c349194 Add guide for Honeywell Lyric application credentials setup (#146281)
* Add guide for Honeywell Lyric application credentials setup

* Fix

---------

Co-authored-by: Joostlek <joostlek@outlook.com>
2025-06-11 16:24:37 +02:00
epenet
9346f8d658 Simplify blink service actions (#146508) 2025-06-11 16:21:42 +02:00
hanwg
0af41d9cb1 Bug fix for Telegram bot integration: Handle plain text parse_mode (#146535) 2025-06-11 16:19:22 +02:00
Marc Mueller
b02c0419b4 Update types packages (#146546) 2025-06-11 16:15:54 +02:00
Marc Mueller
0bc6408137 Update pytest-unordered to 0.7.0 (#146545) 2025-06-11 15:01:27 +01:00
Tsvi Mostovicz
3f1d2b1b71 Bump hdate to 1.1.1 (#146536) 2025-06-11 15:46:52 +02:00
Paul Bottein
bcfdee23e3 Update frontend to 20250531.1 (#146542) 2025-06-11 15:46:19 +02:00
Franck Nijhof
4a50f4ffc1 Add duplicate issue detection using GitHub AI models (#146487) 2025-06-11 15:42:37 +02:00
Petar Petrov
9ee45518e9 Remove the Delete button on the ZwaveJS device page (#146544) 2025-06-11 15:39:02 +02:00
Erik Montnemery
09a5ac5979 Handle changes to source entities in generic_thermostat helper (#146541) 2025-06-11 15:26:52 +02:00
Erik Montnemery
296b5c627a Handle changes to source entities in generic_hygrostat helper (#146538) 2025-06-11 15:18:04 +02:00
Erik Montnemery
120338d510 Handle changes to source entity in utility_meter (#146526) 2025-06-11 15:17:52 +02:00
Erik Montnemery
9b4ab60adb Handle changes to source entity in trend helper (#146525) 2025-06-11 15:17:42 +02:00
Erik Montnemery
51b0642789 Handle changes to source entity in threshold helper (#146524) 2025-06-11 15:17:34 +02:00
Erik Montnemery
cb9c213496 Handle changes to source entity in statistics helper (#146523) 2025-06-11 15:17:19 +02:00
Erik Montnemery
cb42d99c28 Handle changes to source entity in integration helper (#146522) 2025-06-11 15:17:08 +02:00
Erik Montnemery
cf5cdf3cdb Handle changes to source entity in history_stats helper (#146521) 2025-06-11 15:16:51 +02:00
epenet
acf31f609a Adjust urllib3 constraint (#145485)
* Remove urllib3 upper bound constraint

* Disable neato

* Disable neato tests

* Simplify test ignore

* Add to PACKAGE_CHECK_VERSION_RANGE

* Adjust

* Adjust

* Force 2.0
2025-06-11 15:11:58 +02:00
Simon Lamon
42377ff7ac Bump linkplay to v0.2.11 (#146530) 2025-06-11 15:10:00 +02:00
Petro31
3e0aab55a8 Fix delay_on and delay_off restarting when a new trigger occurs during the delay (#145050) 2025-06-11 14:08:10 +01:00
Erik Montnemery
0362012bb3 Correct misleading comment for const.ATTR_RESTORED (#146528) 2025-06-11 13:29:16 +02:00
Jesse Hills
ba5d0f2723 Fix solax state class of Today's Generated Energy (#146492) 2025-06-11 12:46:40 +02:00
Erik Montnemery
167e688139 Allow removing entity registry items twice (#146519) 2025-06-11 12:42:09 +02:00
Martin Hjelmare
c49d95b230 Remove Z-Wave useless reconfigure options (#146520)
* Remove emulate hardware option

* Remove log level option
2025-06-11 13:31:07 +03:00
Erik Montnemery
c4c8f88765 Simplify helper_integration.async_handle_source_entity_changes (#146516) 2025-06-11 12:27:51 +02:00
epenet
f908e0cf4d Bump pybotvac to 0.0.28 (#146513) 2025-06-11 12:19:54 +02:00
epenet
29c720a66d Bump weheat to 2025.6.10 (#146515) 2025-06-11 12:19:06 +02:00
epenet
4e628dbd9f Bump sensorpush-api to 2.1.3 (#146514) 2025-06-11 12:18:55 +02:00
Petro31
37d904dfdc Add color_temp_kelvin to set_temperature action variables (#146448) 2025-06-11 11:58:07 +02:00
Åke Strandberg
a53997dfc7 Graceful handling of missing datapoint in myuplink (#146517) 2025-06-11 11:55:28 +02:00
Joost Lekkerkerker
dd216ac15b Split deprecated system issue in 2 places (#146453) 2025-06-11 11:35:14 +02:00
Erik Montnemery
2afdec4711 Do not remove derivative config entry when input sensor is removed (#146506)
* Do not remove derivative config entry when input sensor is removed

* Add comments

* Update homeassistant/helpers/helper_integration.py

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

---------

Co-authored-by: Franck Nijhof <git@frenck.dev>
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-06-11 11:19:44 +02:00
karwosts
5b4c309170 Create a deprecation/repair for sensor.sun_solar_rising (#146462)
* Create a deprecation/repair for `sensor.sun_solar_rising`

* test

* Update homeassistant/components/sun/strings.json
2025-06-11 11:02:14 +02:00
hanwg
8deec55204 Add service validation for send file for Telegram bot integration (#146192)
* added service validation for send file

* update strings

* Apply suggestions from code review

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

* updated exception in tests

* removed TypeError since it is not thrown

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-06-11 10:59:08 +02:00
Robert Resch
f0a2c4e30a Bump deebot-client to 13.3.0 (#146507) 2025-06-11 10:49:38 +02:00
Joost Lekkerkerker
e9a71a8d7f Explain Nest setup (#146217) 2025-06-11 10:31:08 +02:00
Felix Schneider
1462366764 Bump apsystems to 2.7.0 (#146485) 2025-06-11 10:26:01 +02:00
Artur Pragacz
33528eb6bd Update pywizlight to 0.6.3 (#146490) 2025-06-11 08:26:55 +02:00
epenet
776a014ab0 Drop deprecated add_event service in google (#146432) 2025-06-10 20:35:17 -07:00
Michael Hansen
ea202eff66 Bump intents to 2025.6.10 (#146491) 2025-06-10 18:16:18 -05:00
Tsvi Mostovicz
b7404f5a05 Fix Jewish calendar not updating (#146465) 2025-06-10 21:25:47 +02:00
Joost Lekkerkerker
d015dff855 Remove DHCP discovery from Amazon Devices (#146476) 2025-06-10 20:55:00 +02:00
Joost Lekkerkerker
2f1977fa0c Fix typo in hassio (#146474) 2025-06-10 20:52:43 +02:00
Erik Montnemery
26fe23eb5c Improve support for trigger platforms with multiple triggers (#144827)
* Improve support for trigger platforms with multiple triggers

* Adjust zwave_js

* Refactor the Trigger class

* Silence mypy

* Adjust

* Revert "Adjust"

This reverts commit 17b3d16a267d54c082b12f07550faa8ac4ac3a49.

* Revert "Silence mypy"

This reverts commit c2a011b16f9b02880fc3dc673b5b12501f7995fc.

* Reapply "Adjust"

This reverts commit c64ba202dd.

* Apply suggestions from code review

* Revert "Apply suggestions from code review"

This reverts commit 0314955c5a.
2025-06-10 20:48:51 +02:00
hahn-th
dbfecf99dc Bump homematicip to 2.0.4 (#144096)
* Bump to 2.0.2 with all necessary changes

* bump to prerelease

* add addiional tests

* Bump to homematicip 2.0.3

* do not delete device

* Setup BRAND_SWITCH_MEASURING as light

* bump to 2.0.4

* refactor test_remove_obsolete_entities

* move test

* use const from homematicip lib
2025-06-10 20:44:06 +02:00
hanwg
4d28992f2b Add Telegram bot webhooks tests (#146436)
* add tests for webhooks

* added asserts
2025-06-10 19:58:15 +02:00
Markus Adrario
7a428a66bd Add support for HeatIt Thermostat TF056 to homee (#145515)
* adapt climate for Heatit TF 056

* add sensors & numbers for Heatit TF056

* Add select for Heatit TF056

* Adapt climat tests for changes

* Fix sentence case

* fix review comments

* Update homeassistant/components/homee/climate.py

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

* fix tests

* update diagnostics snapshot for this change

---------

Co-authored-by: Franck Nijhof <frenck@frenck.nl>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: Franck Nijhof <git@frenck.dev>
2025-06-10 19:41:13 +02:00
G Johansson
481bf2694b Fix incorrect categories handling in holiday (#146470) 2025-06-10 19:28:48 +02:00
Simone Chemelli
5cc9cc3c99 Fix EntityCategory for binary_sensor platform in Amazon Devices (#146472)
* Fix EntityCategory for  binary_sensor platform in Amazon Devices

* update snapshots
2025-06-10 19:28:37 +02:00
Whitney Young
87ce683b39 Add tests for initial state of OpenUV sensors (#146464)
This is a followup to #146408 to add test coverage.
2025-06-10 19:28:29 +02:00
Simone Chemelli
936d56f9af Avoid closing shared aiohttp session in Vodafone Station (#146471) 2025-06-10 19:18:19 +02:00
starkillerOG
d71ddcf69e Reolink conserve battery (#145452) 2025-06-10 18:05:55 +02:00
Robert Resch
3af2746fea Update wording deprecated system package integration repair (#146450)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-06-10 18:04:22 +02:00
Joost Lekkerkerker
5b6d7142fb Bump pySmartThings to 3.2.4 (#146459) 2025-06-10 17:37:21 +02:00
Whitney Young
7aa9301038 Fix initial state of UV protection window (#146408)
The `binary_sensor` is created when the config entry is loaded after the
`async_config_entry_first_refresh` has completed (during the forward of
setup to platforms). Therefore, the update coordinator will already have
data and will not trigger the invocation of
`_handle_coordinator_update`.

Fixing this just means performing the same update at initialization.
2025-06-10 17:35:40 +02:00
hanwg
627831dfaf Fix Telegram bot leave_chat service action (#146139)
* bug fix for leave chat

* update strings
2025-06-10 17:33:54 +02:00
Joost Lekkerkerker
db8a6f8583 Catch exception before retrying in AirGradient (#146460) 2025-06-10 17:31:30 +02:00
Paulus Schoutsen
014010acbd Assist Pipeline: Intent progress event when we start streaming (#146388)
Intent progress event when we start streaming
2025-06-10 09:55:43 -05:00
Arie Catsman
9b90ed04e5 fix possible mac collision in enphase_envoy (#145549)
* fix possible mac collision in enphase_envoy

* remove redundant device registry async_get
2025-06-10 16:25:26 +02:00
hanwg
0f27d0bf4a Bug fix for Telegram bot integration: fix async_unload_entry error for polling bot (#146277)
* removed reload from update_listener

* removed reload from update_listener
2025-06-10 16:24:51 +02:00
Andrea Turri
1fa55f96f8 Add evaporate water program id for Miele oven (#145996) 2025-06-10 16:23:55 +02:00
Jamin
2d60115ec6 Check hangup error in voip (#146423)
Check hangup error

Prevent an error where the call end future may have already been set
when a hangup is detected.
2025-06-10 16:22:53 +02:00
Luca Schröder
3b81480091 Update caldav to 1.6.0 (#146456)
Fixes #140798
2025-06-10 16:20:35 +02:00
Will Schlitzer
255acfa8c0 Fix typo in overseerr component docstring (#146457)
Change 'airgradient' to 'overseerr' in sensor.py
2025-06-10 16:15:40 +02:00
Marc Mueller
4617cc4e0a Update awesomeversion to 25.5.0 (#146032) 2025-06-10 15:44:53 +02:00
tronikos
b9e8cfb291 Handle grpc errors in Google Assistant SDK (#146438) 2025-06-10 15:31:32 +02:00
J. Nick Koston
7da1671b06 Shift ESPHome log parsing to the library (#146349) 2025-06-10 15:30:19 +02:00
Marc Mueller
6c5f7eabff Fix RuntimeWarning in rest tests (#146452) 2025-06-10 15:26:07 +02:00
Ian
f448f488ba Throttle Nextbus if we are reaching the rate limit (#146064)
Co-authored-by: Josef Zweck <josef@zweck.dev>
Co-authored-by: Robert Resch <robert@resch.dev>
2025-06-10 15:03:20 +02:00
Marc Mueller
20b5d5a755 Add requests to hassfest requirements check (#146446) 2025-06-10 15:01:05 +02:00
Marc Mueller
bb38a3a8ac Update requests to 2.32.4 (#146445) 2025-06-10 15:00:41 +02:00
Brett Adams
d0d1fb2da7 Prevent energy history returning zero in Teslemetry (#146202) 2025-06-10 15:00:02 +02:00
Marc Mueller
d82be09ed4 Update aiomealie to 0.9.6 (#146447) 2025-06-10 14:53:56 +02:00
Joost Lekkerkerker
110627e16e Return expected state in SmartThings water heater (#146449) 2025-06-10 14:52:24 +02:00
Klaas Schoute
b77ef7304a Change interval for Powerfox integration (#146348) 2025-06-10 14:38:52 +02:00
Erik Montnemery
16a0b7f44e Handle changes to source entity in derivative helper (#146407)
* Handle changes to source entity in derivative helper

* Rename helper function, improve docstring

* Add tests

* Improve derivative tests

* Deduplicate tests

* Rename helpers/helper_entity.py to helpers/helper_integration.py

* Rename tests
2025-06-10 14:31:18 +02:00
Joost Lekkerkerker
4fdbb9c0e2 Remove __all__ from switch_as_x (#146331)
* Remove `__all__` from switch_as_x

* Update homeassistant/components/switch_as_x/__init__.py
2025-06-10 14:21:01 +02:00
J. Diego Rodríguez Royo
c32a988838 Improvements for Home Connect application credentials string (#146443) 2025-06-10 14:11:07 +02:00
Jan-Philipp Benecke
927c9d3480 Improve error logging in trend binary sensor (#146358) 2025-06-10 14:10:49 +02:00
Joost Lekkerkerker
bf776d33b2 Explain Withings setup (#146216) 2025-06-10 14:10:35 +02:00
epenet
279539265b Use async_load_fixture in modern_forms tests (#146011) 2025-06-10 12:38:25 +02:00
J. Diego Rodríguez Royo
4acad77437 Fix typo at application credentials string at Home Connect integration (#146442)
Fix typos
2025-06-10 11:56:24 +02:00
J. Nick Koston
0c5b7401b9 Use entity unique id for ESPHome media player formats (#146318) 2025-06-10 11:48:11 +02:00
Erik Montnemery
ce739fd9b6 Restore entity ID and user customizations of deleted entities (#145278)
* Restore entity ID and user customizations of deleted entities

* Clear removed areas, categories and labels from deleted entities

* Correct test

* Fix logic for disabled_by and hidden_by

* Improve test coverage

* Fix sorting

* Always restore disabled_by and hidden_by

* Update mqtt test

* Update pglab tests
2025-06-10 11:47:54 +02:00
Erik Montnemery
11d9014be0 Restore user customizations of deleted devices (#145191)
* Restore user customizations of deleted devices

* Apply suggestions from code review

* Improve test coverage

* Always restore disabled_by
2025-06-10 11:47:39 +02:00
J. Nick Koston
c9dcb1c11b Bump propcache to 0.3.2 (#146418) 2025-06-10 11:44:34 +02:00
J. Diego Rodríguez Royo
ef7f32a28d Explain Home Connect setup (#146356)
* Explain Home Connect setup

* Avoid using "we"

* Fix login spelling

* Fix signup spelling
2025-06-10 11:41:36 +02:00
J. Nick Koston
4f5cf5797f Bump yarl to 1.20.1 (#146424) 2025-06-10 11:26:29 +02:00
Retha Runolfsson
4c5485ad04 Bump pyswitchbot to 0.66.0 (#146430)
bump pyswitchbot to 0.66.0
2025-06-10 11:16:08 +02:00
Franck Nijhof
5ad96dedfa Reformat Dockerfile to reduce merge conflicts (#146435) 2025-06-10 11:14:31 +02:00
epenet
0c18fe35e5 Migrate cloudflare to use runtime data (#146429) 2025-06-10 09:50:31 +02:00
epenet
6a23ad96ca Move google assistant sdk services to separate module (#146434) 2025-06-10 00:49:56 -07:00
J. Nick Koston
def0384608 Bump aiohttp to 3.12.12 (#146426) 2025-06-10 09:39:53 +02:00
Raphael Hehl
a4d12694da Bump uiprotect to 7.13.0 (#146410) 2025-06-09 19:26:54 -05:00
J. Nick Koston
2278e3f06f Bump aioesphomeapi to 32.2.1 (#146375) 2025-06-09 19:25:29 -05:00
Will Schlitzer
0144a0bb1f Fix minor docstring typos in jellyfin component media_source.py (#146398) 2025-06-09 20:12:32 +02:00
Imeon-Energy
7cc8f91bf9 Basic entity class for Imeon inverter integration (#145778)
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
Co-authored-by: TheBushBoy <theodavid@icloud.com>
2025-06-09 20:04:25 +02:00
hanwg
d58157ca9e Bug fix for Telegram bot integration: handle last message id (#146378) 2025-06-09 20:01:16 +02:00
David Knowles
f401ffb08c Bump pydrawise to 2025.6.0 (#146369) 2025-06-09 20:00:37 +02:00
Simone Chemelli
8f7b831b94 Bump aioamazondevices to 3.0.6 (#146385) 2025-06-09 19:59:02 +02:00
wittypluck
9ed6b591a5 Fix CO concentration unit in OpenWeatherMap (#146403) 2025-06-09 19:55:09 +02:00
Michael Davie
98ea067285 Bump env-canada to v0.11.2 (#146371) 2025-06-09 12:53:44 -05:00
G Johansson
7e507dd378 Bump pynordpool to 0.3.0 (#146396) 2025-06-09 19:51:46 +02:00
Erik Montnemery
8e87223c40 Update switch_as_x to handle wrapped switch moved to another device (#146387)
* Update switch_as_x to handle wrapped switch moved to another device

* Reload switch_as_x config entry after updating device

* Make sure the switch_as_x entity is not removed
2025-06-09 17:04:55 +02:00
Abílio Costa
0cce4d1b81 Test all device classes in Sensor device condition/trigger tests (#146366) 2025-06-09 14:22:58 +01:00
Erik Montnemery
46dcc91510 Fix switch_as_x entity_id tracking (#146386) 2025-06-09 13:24:40 +02:00
Markus Adrario
b1a2af9fd3 Add Homee diagnostics platform (#146340)
* Initial dignostics implementation

* Add diagnostics tests

* change data-set for device diagnostics

* adapt for upcoming pyHomee release

* other solution

* fix review and more
2025-06-09 13:24:07 +02:00
Michael Arthur
5d58cdd98e DNSIP: Add literal to querytype (#146367) 2025-06-09 09:36:17 +02:00
Simon Lamon
a8aebbce9a Bump python-linkplay to v0.2.10 (#146359) 2025-06-08 16:43:20 -05:00
tronikos
f1244c182a Allow different manufacturer than Amazon in Amazon Devices (#146333) 2025-06-08 11:47:46 -07:00
Simon Lamon
560eeac457 Do not probe linkplay device if another config entry already contains the host (#146305)
* Do not probe if config entry already contains the host

* Add unit test

* Use common fixture
2025-06-08 19:47:00 +02:00
J. Nick Koston
d33080d79e Bump aioesphomeapi to 32.2.0 (#146344) 2025-06-08 11:15:00 -05:00
Michael
25f02c5b38 Bump py-synologydsm-api to 2.7.3 (#146338)
bump py-synologydsm-api to 2.7.3
2025-06-08 17:02:06 +01:00
Raphael Hehl
cb01af9f92 Bump uiprotect to 7.12.0 (#146337) 2025-06-08 10:57:50 -05:00
Sanjay Govind
9a6ebb0848 Fix bosch alarm areas not correctly subscribing to alarms (#146322)
* Fix bosch alarm areas not correctly subscribing to alarms

* add test
2025-06-08 14:35:54 +02:00
Pete Sage
fd30dd0aee Add tests for sonos switch alarms on and off (#146314)
* fix: add tests for switch on/off

* fix: simplify

* fix: simplify

* fix: comment

* fix: comment
2025-06-08 11:45:20 +02:00
tronikos
4a5e261709 Fix typo in Utility Meter always_available (#146320) 2025-06-08 10:53:48 +03:00
Marc Mueller
2842f55460 Add additional package version range checks (#146299)
* Add additional package version range checks

* Add exception for scipy
2025-06-08 00:06:20 +02:00
J. Nick Koston
7573a74cb0 Migrate rest to use aiohttp (#146306) 2025-06-07 13:44:25 -05:00
J. Nick Koston
636b484d9d Migrate onvif to use onvif-zeep-async 4.0.1 with aiohttp (#146297) 2025-06-07 13:39:59 -05:00
G Johansson
a979f884f9 Bump holidays to 0.74 (#146290) 2025-06-07 20:18:24 +03:00
J. Nick Koston
990ea78dec Bump aiohttp to 3.12.11 (#146298) 2025-06-07 12:08:32 -05:00
Marc Mueller
ee6db3bd23 Update numpy to 2.3.0 (#146296) 2025-06-07 18:43:18 +02:00
Arie Catsman
ae5606aa2f Migrate Enphase envoy from httpx to aiohttp (#146283)
Co-authored-by: J. Nick Koston <nick@koston.org>
2025-06-07 10:52:54 -05:00
Marc Mueller
7f9f106729 Update airtouch5py to 0.3.0 (#146278) 2025-06-07 16:58:53 +02:00
J. Nick Koston
44c63ce6f1 Bump aiohttp-fast-zlib to 0.3.0 (#146285)
changelog: https://github.com/Bluetooth-Devices/aiohttp-fast-zlib/compare/v0.2.3...v0.3.0

proper aiohttp 3.12 support
2025-06-07 17:30:43 +03:00
hanwg
cbf7ca6a9a Add bronze quality scale for Telegram bot integration (#146148)
* added quality scale

* updated appropriate-polling comment

* Remove entities comment

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-06-07 14:47:48 +02:00
Brett Adams
eb892df65a Change default range sensors in Teslemetry (#146268) 2025-06-07 10:51:57 +02:00
Brett Adams
24b5886d88 Add missing write state to Teslemetry (#146267) 2025-06-07 04:43:16 +02:00
Willem-Jan van Rootselaar
d5e902a170 Update python-bsblan requirement to version 2.1.0 (#146253) 2025-06-06 22:47:44 +03:00
hanwg
d907e4c10b Handle error in setup_entry for Telegram Bot (#146242)
* handle error in setup_entry

* Update homeassistant/components/telegram_bot/__init__.py

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>

---------

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-06-06 15:00:48 +01:00
Robin Lintermann
c4be3c4de2 Smarla integration number platform (#145747)
Add number platform to smarla integration
2025-06-06 12:13:06 +02:00
Retha Runolfsson
626591f832 Fix unit test for switchbot integration (#146247)
fix unit test
2025-06-06 12:06:01 +02:00
epenet
2bd3196183 Move abode services to separate module (#146142)
* Move abode services to separate module

* Rename

* Adjust test imports
2025-06-06 10:20:57 +02:00
epenet
fd93cf375d Tweak zwave_js service registration (#146244) 2025-06-06 09:41:51 +02:00
epenet
6bf8b84d26 Rename service registration method (#146236) 2025-06-06 08:08:06 +02:00
Michael
c72fea57a1 Bump aioimmich to 0.9.1 (#146222)
bump aioimmich to 0.9.1
2025-06-05 21:50:19 +02:00
Renat Sibgatulin
17dad7d8ae Bump aioairq to v0.4.6 (#146169)
This version exposes an API to control LED brightness.
2025-06-05 18:27:20 +02:00
Joost Lekkerkerker
14664719d9 Remove zeroconf discovery from Spotify (#146213) 2025-06-05 18:02:11 +02:00
epenet
b14cd1e14b Move elkm1 services to separate module (#146147)
* Move elkm1 services to separate module

* Rename
2025-06-05 16:51:01 +02:00
Retha Runolfsson
fd38d9788d Bump pyswitchbot to 0.65.0 (#146133)
* update pyswitchbot to 0.65.0

* fix relay switch 1pm test

* fix ma to a
2025-06-05 16:42:24 +02:00
epenet
0b3b641328 Move services to separate module in opentherm_gw (#146098)
* Move services to separate module in opentherm_gw

* Rename
2025-06-05 16:40:18 +02:00
Brett Adams
6ef77f8243 Fix Export Rule Select Entity in Tessie (#146203)
Fix TessieExportRuleSelectEntity
2025-06-05 16:39:55 +02:00
Ludovic BOUÉ
3a27143012 Matter add Service Area Cluster to vacuum_cleaner fixture (#145743)
Update vacuum_cleaner.json

Service Area Cluster
2025-06-05 16:39:08 +02:00
Samuel Xiao
9a6c642bdf Bump switchbot-api to 2.5.0 (#146205)
* update switchbot-api to 2.5.0

* update switchbot-api to 2.5.0
2025-06-05 16:16:45 +02:00
epenet
38b8d0b018 Move google_sheets services to separate module (#146160)
* Move google_sheets services to separate module

* Move to async_setup

* Do not remove the services

* hassfest

* Rename
2025-06-05 15:07:15 +02:00
epenet
4d3443dbf5 Move amcrest services to separate module (#146144)
* Move amcrest services to separate module

* Rename
2025-06-05 14:43:22 +02:00
Marc Mueller
4f99e54402 Update pandas to 2.3.0 (#146206) 2025-06-05 14:42:21 +02:00
epenet
d6615e3d44 Move ffmpeg services to separate module (#146149)
* Move ffmpeg services to separate module

* Fix tests

* Rename
2025-06-05 14:39:44 +02:00
Willem-Jan van Rootselaar
9c23331ead Bump python-bsblan to version 2.0.1 (#146198)
* Bump python-bsblan to version 2.0.1

* Remove 'bsblan' exception for 'python-bsblan' from forbidden package exceptions
2025-06-05 13:07:16 +02:00
epenet
5fb2802bf4 Move zoneminder services to separate module (#146151) 2025-06-05 06:35:32 +02:00
epenet
b4864e6a8a Move matrix services to separate module (#146161) 2025-06-05 06:35:10 +02:00
Raphael Hehl
04c34877f4 Bump uiprotect to 7.11.0 (#146171)
Bump uiprotect to version 7.11.0
2025-06-04 23:32:44 +03:00
Ludovic BOUÉ
bdeb61fafc Matter Extractor hood fixture (#146174)
* Create extractor_hood.json

* Matter Extractor hood fixture

* Format document
2025-06-04 21:17:51 +02:00
J. Nick Koston
76d4257f51 Bump aiohttp to 3.12.9 (#146178) 2025-06-04 20:12:19 +02:00
Markus Adrario
c6c7e7eae1 Add homee reconfiguration flow (#146065)
* Add a reconfigure flow to homee

* Add tests for reconfiguration flow

* string refinement

* fix review comments

* more review fixes
2025-06-04 15:27:07 +02:00
Iskra kranj
07557e27b0 Bump pyiskra to 0.1.21 (#146156) 2025-06-04 14:51:40 +02:00
J. Nick Koston
f211da60e0 Bump aiohttp to 3.12.8 (#146153) 2025-06-04 12:57:40 +01:00
Michael
64b74d00f7 Bump aioimmich to 0.9.0 (#146154)
bump aioimmich to 0.9.0
2025-06-04 13:35:16 +02:00
J. Nick Koston
96cb645644 Bump aioesphomeapi to 32.0.0 (#146135) 2025-06-04 09:34:04 +01:00
Claudio Ruggeri - CR-Tech
9b0db3bd51 Bump pymodbus to 3.9.2 (#145948) 2025-06-04 10:28:34 +02:00
Robert Resch
ffdefd1e0f Deprecate eddystone temperature integration (#145833) 2025-06-04 10:00:50 +02:00
Max Velitchko
59ad0268a9 Bump pyvera to 0.3.16 (#146089)
* Update vera integration with the latest pyvera package

* python3 -m script.gen_requirements_all

* Fix license
2025-06-04 07:47:41 +01:00
dependabot[bot]
f28851e76f Bump github/codeql-action from 3.28.18 to 3.28.19 (#146131)
Bumps [github/codeql-action](https://github.com/github/codeql-action) from 3.28.18 to 3.28.19.
- [Release notes](https://github.com/github/codeql-action/releases)
- [Changelog](https://github.com/github/codeql-action/blob/main/CHANGELOG.md)
- [Commits](https://github.com/github/codeql-action/compare/v3.28.18...v3.28.19)

---
updated-dependencies:
- dependency-name: github/codeql-action
  dependency-version: 3.28.19
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-06-04 07:41:34 +01:00
J. Nick Koston
4f5c1d544b Bump protobuf to 6.31.1 (#146128)
changelog: https://github.com/protocolbuffers/protobuf/compare/v30.2...v31.1
2025-06-04 07:40:10 +01:00
Marc Mueller
a8ccf1c6fc Update pytest to 8.4.0 (#146114) 2025-06-04 08:09:19 +02:00
Ian
e3f7e5706b Add config option for controlling Ollama think parameter (#146000)
* Add config option for controlling Ollama think parameter

Allows enabling or disable thinking for supported models. Neither option
will dislay thinking content in the chat. Future support for displaying
think content will require frontend changes for formatting.

* Add thinking strings
2025-06-03 20:42:16 -07:00
Erwin Douna
7ad1e756e7 SMA fix strings (#146112)
* Fix

* Feedback
2025-06-03 21:54:44 +02:00
Norbert Rittel
8868f214f3 Replace "numbers" with "digits" in invalid_backbone_key message of knx (#146124)
The KNX Backbone Key has a length of 128 bits, so written as a hexadecimal number that yields 32 digits.

This fix thus replaces "numbers" with "digits" in the `invalid_backbone_key` message.
2025-06-03 20:47:54 +02:00
J. Nick Koston
3ecff19a45 Bump habluetooth to 3.49.0 (#146111)
* Bump habluetooth to 3.49.0

changelog: https://github.com/Bluetooth-Devices/habluetooth/compare/v3.48.2...v3.49.0

* update diag

* diag
2025-06-03 16:56:20 +02:00
Ian
74421db747 NextBus: Bump py_nextbusnext to 2.2.0 (#145904) 2025-06-03 13:20:14 +02:00
J. Nick Koston
1cccfac3dc Bump bleak-esphome to 2.16.0 (#146110) 2025-06-03 11:57:58 +01:00
David Bonnes
c254548a64 Add required_features to WaterHeater entity service registrations (#141873) 2025-06-03 12:51:46 +02:00
epenet
7f8b782e95 Adjust SamsungTV on/off logging (#146045)
* Adjust SamsungTV on/off logging

* Update coordinator.py
2025-06-03 12:30:18 +02:00
Erwin Douna
cd518d4a46 SMA add missing strings for DHCP (#145782) 2025-06-03 12:12:56 +02:00
Retha Runolfsson
c5db07e84d Fix nightlatch option for all switchbot locks (#146090) 2025-06-03 12:11:02 +02:00
epenet
d1e0225520 Adjust ConnectionFailure logging in SamsungTV (#146044) 2025-06-03 12:05:33 +02:00
Robin Lintermann
d439bb68eb Smarla integration improve tests (#145803)
* Improve smarla integration tests

* Do not import descriptions instead use seperate list
2025-06-03 11:49:24 +02:00
Matthias Alphart
980dbf364d Add exception translations for KNX services (#146104) 2025-06-03 11:31:32 +02:00
SNoof85
842e7ce171 Add state class measurement to Freebox temperature sensors (#146074) 2025-06-03 11:23:52 +02:00
epenet
8afec8ada9 Use async_load_fixture in youtube tests (#146018) 2025-06-03 11:07:56 +02:00
Simone Chemelli
7b699f7733 Avoid services unload for Homematicip Cloud (#146050)
* Avoid services unload

* fix tests

* apply review comments

* cleanup

* apply review comment
2025-06-03 11:01:23 +02:00
Noah Groß
d448ef9f16 Bump python-picnic-api2 to 1.3.1 (#145962) 2025-06-03 10:57:59 +02:00
epenet
03912a1704 Use async_load_fixture in tplink_omada tests (#146014) 2025-06-03 10:54:22 +02:00
epenet
54c20d5d5a Use async_load_fixture in remaining tests (#146021) 2025-06-03 10:52:51 +02:00
epenet
2dbf24e798 Use async_load_fixture in skybell tests (#146017) 2025-06-03 10:47:03 +02:00
epenet
791654a420 Move services to separate module in nzbget (#146093) 2025-06-03 10:41:40 +02:00
epenet
5fe07e49e4 Move services to separate module in insteon (#146094) 2025-06-03 10:41:13 +02:00
epenet
0bd287788c Move service registration to async_setup in icloud (#146095) 2025-06-03 10:40:48 +02:00
Brett Adams
40e0c0f98d Fix BMS and Charge states in Teslemetry (#146091)
Fix BMS and Charge states
2025-06-03 10:40:20 +02:00
Pär Holmdahl
85b608912b Add energy sensor to adax (#145995)
* 2nd attempt to add energysensors to Adax component

* Ruff format changes

* I did not reuse the first call for information.. Now i do..

* Fixed some tests after the last change

* Remove extra attributes

* Dont use info logger

* aggregate if not rooms

* Raise error if no rooms are discovered

* Move code out of try catch

* Catch more specific errors

* removed platforms from manifest.json

* remove attribute translation key

* Getting rid of the summation of energy used..

* Fixed errorness in test

* set roomproperty in Init

* concatenated the two functions

* use raw Wh values and suggest a konversion for HomeAssistant

* Use snapshot testing

* Update homeassistant/components/adax/coordinator.py

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

* Update homeassistant/components/adax/strings.json

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

* Update homeassistant/components/adax/sensor.py

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

* Update homeassistant/components/adax/sensor.py

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

* Update homeassistant/components/adax/sensor.py

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

* Update homeassistant/components/adax/sensor.py

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

* Removing un needed logg

* Removing initial value

* Changing tests to snapshot_platform

* Removing available property from sensor.py and doing a ruff formating..

* Fix a broken indent

* Add fix for coordinator updates in Adax energisensor and namesetting

* Update homeassistant/components/adax/sensor.py

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

* Update homeassistant/components/adax/coordinator.py

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

* Update homeassistant/components/adax/coordinator.py

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

* Update homeassistant/components/adax/sensor.py

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

* generated snapshots

* Ruff changes

* Even more ruff changes, that did not appear on ruff command locally

* Trying to fix CI updates

* Update homeassistant/components/adax/sensor.py

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

* Improve AdaxEnergySensor by simplifying code and ensuring correct handling of energy values. Adjust how room and device information is retrieved to avoid duplication and improve readability.

* Removed a test för device_id as per request..

* Make supersure that value is int and not "Any"

* removing executable status

* Update tests/components/adax/test_sensor.py

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

---------

Co-authored-by: Josef Zweck <josef@zweck.dev>
2025-06-03 10:36:43 +02:00
Pete Sage
987753dd1c Bump aiokem to 1.0.1 (#146085) 2025-06-03 10:16:08 +02:00
epenet
5df05fb6dd Move async_register_services to async_setup (#146092) 2025-06-03 08:38:02 +02:00
Simone Chemelli
f295ca27af Bump aioamazondevices to 3.0.5 (#146073) 2025-06-03 01:18:49 +03:00
Marc Mueller
8f75cc6a33 Update pyatmo to 9.2.1 (#146077) 2025-06-02 23:47:50 +02:00
Marc Mueller
19c71f0f49 Update python-homewizard-energy to 8.3.3 (#146076) 2025-06-02 23:34:50 +02:00
Marc Mueller
22c2028c00 Update typing-extensions to 4.14.0 (#146054) 2025-06-02 23:15:53 +02:00
Ian
39f687e3a3 Bump ollama to 0.5.1 (#146063)
* Bump ollama to 0.5.1
* Add ollama to license exceptions
2025-06-02 22:43:00 +02:00
Shay Levy
6692b9b71f Fix Shelly BLU TRV calibrate button (#146066) 2025-06-02 22:38:17 +03:00
J. Nick Koston
2f5787e7be Bump aiohttp to 3.12.7 (#146028) 2025-06-02 21:27:08 +02:00
Simone Chemelli
bbda1761bf Avoid services unload for Isy994 (#146069)
* Avoid services unload for Isy994

* cleanup
2025-06-02 21:19:10 +02:00
Robert Resch
ecc10e9793 Bump go2rtc-client to 0.2.1 (#146019)
* Bump go2rtc-client to 0.2.0

* Bump go2rtc-client to 0.2.1

* Clean up hassfest exception

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-06-02 20:48:40 +02:00
Simone Chemelli
9e1e889fd7 Rename mispelled services python files (#146049) 2025-06-02 20:41:31 +02:00
Michael
eefe1e6f0f Don't use multi-line conditionals in immich (#146062) 2025-06-02 19:58:54 +02:00
Marc Mueller
397ed87f2d Update aiohomekit to 3.2.15 (#146059)
* Update aiohomekit to 3.2.15

* Remove Python version exception for homekit_controller
2025-06-02 18:23:04 +01:00
Marc Mueller
15830f383e Update pyoverkiz to 1.17.2 (#146056) 2025-06-02 18:21:26 +01:00
epenet
87395efc6e Add awesomeversion to dependency version checks (#146047) 2025-06-02 17:28:13 +02:00
Marc Mueller
27d79bb10a Update yamllint to 1.37.1 (#146038) 2025-06-02 16:35:31 +02:00
Simone Chemelli
7427db70aa Move async_setup_services to async_setup (#146048)
* Moved async_setup_services to async_setup

* fix schema missing
2025-06-02 16:23:20 +02:00
Marc Mueller
77d5bffa85 Update pytest warnings filter (#146024) 2025-06-02 16:01:23 +02:00
Marc Mueller
ab7c7b8d89 Update ruff to 0.11.12 (#146037)
* Update ruff to 0.11.12
* Replace ruff legacy alias with ruff-check
2025-06-02 16:01:10 +02:00
Simon Lamon
93b8cc38d8 Small nmbs sensor attributes refactoring (#145956)
Attributes refactoring
2025-06-02 15:13:23 +02:00
Pete Sage
e5f95b3aff Add diagnostics tests for Sonos (#146040)
* fix: add tests for diagnostics

* fix: add new files

* fix: add new files
2025-06-02 15:12:34 +02:00
starkillerOG
613728ad3b Improve debug logging Reolink (#146033)
Add debug logging
2025-06-02 15:12:13 +02:00
starkillerOG
cb1bfe6ebe Bump reolink-aio to 0.13.5 (#145974)
* Add debug logging

* Bump reolink-aio to 0.13.5

* Revert "Add debug logging"

This reverts commit f96030a6c8.
2025-06-02 15:11:56 +02:00
Joost Lekkerkerker
434179ab3f Remove NMBS YAML import (#145733)
* Remove NMBS YAML import

* Remove NMBS YAML import
2025-06-02 15:10:46 +02:00
TimL
eb53277fcc Bump pysmlight to 0.2.6 (#146039)
Co-authored-by: Tim Lunn <tim@feathertop.org>
2025-06-02 15:04:34 +02:00
J. Nick Koston
850ddb3667 Bump grpcio to 1.72.1 (#146029) 2025-06-02 15:04:02 +02:00
epenet
5a727a4fa3 Avoid constant alias for integration DOMAIN (#145788)
* Avoid constant alias for integration DOMAIN

* Tweak

* Improve

* Three more

---------

Co-authored-by: Shay Levy <levyshay1@gmail.com>
2025-06-02 10:37:29 +02:00
karwosts
33fc700952 Make sun solar_rising a binary_sensor (#140956)
* Make sun solar_rising a binary_sensor.

* Add a state translation

* code review

* fix test

* move PLATFORMS

* Update strings.json
2025-06-02 10:32:48 +02:00
Joakim Sørensen
ad493e077e Submit legacy integrations for analytics (#145787)
* Submit legacy integrations for analytics

* adjustments
2025-06-02 10:29:17 +02:00
Marc Mueller
a2b2f6f20a Update pre-commit to 4.2.0 (#145986) 2025-06-02 09:56:20 +02:00
Marc Mueller
ee57fd413a Update freezegun to 1.5.2 (#145982) 2025-06-02 09:53:12 +02:00
Martin Hjelmare
f5d585e0f0 Fix removal of devices during Z-Wave migration (#145867) 2025-06-02 09:52:02 +02:00
Simone Chemelli
1899388f35 Add diagnostics to Amazon devices (#145964) 2025-06-02 09:48:42 +02:00
Allen Porter
4d833e9b1c Bump ical to 10.0.0 (#145954) 2025-06-02 09:47:05 +02:00
Robert Resch
6d827cd412 Deprecate hddtemp (#145850) 2025-06-02 09:45:14 +02:00
epenet
ebfbea39ff Use async_load_fixture in twitch tests (#146016) 2025-06-02 09:27:53 +02:00
dependabot[bot]
89a40f1c48 Bump dawidd6/action-download-artifact from 9 to 10 (#146015) 2025-06-02 09:21:26 +02:00
epenet
664eb7af10 Use async_load_fixture in moehlenhoff_alpha2 tests (#146012) 2025-06-02 08:59:19 +02:00
epenet
33b99b6627 Use async_load_fixture in netatmo tests (#146013) 2025-06-02 08:59:11 +02:00
epenet
0cf2ee0bcb Remove unnecessary DOMAIN alias in tests (l-r) (#146009)
* Remove unnecessary DOMAIN alias in tests (l-r)

* Keep late import in lirc
2025-06-02 08:54:55 +02:00
hanwg
85a86c3f11 Add config flow for telegram bot integration (#144617)
* added config flow for telegram integration

* added chat id in config entry title and added config flow tests

* fix import issue when there are no notifiers in configuration.yaml

* Revert "fix import issue when there are no notifiers in configuration.yaml"

This reverts commit b5b83e2a9a.

* Revert "added chat id in config entry title and added config flow tests"

This reverts commit 30c2bb4ae4.

* Revert "added config flow for telegram integration"

This reverts commit 1f44afcd45.

* added config and subentry flows

* added options flow to configure webhooks

* refactor module setup so it only load once

* moved service registration from async_setup_entry to async_setup

* Apply suggestions from code review

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

* import only last yaml config

* import only last yaml config

* reduced scope of try-block

* create issue when importing from yaml

* Apply suggestions from code review

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

* handle options update by reloading telegram bot

* handle import errors for create issue

* include bot's platform when creating issues

* handle options reload without needing HA restart

* moved url and trusted_networks inputs from options to new config flow step

* Apply suggestions from code review

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

* minor fixes

* refactor config flow

* moved constants to const.py

* Apply suggestions from code review

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

* Update homeassistant/components/telegram_bot/config_flow.py

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

* Update homeassistant/components/telegram_bot/config_flow.py

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

* Update homeassistant/components/telegram_bot/config_flow.py

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

* added options flow tests

* Update homeassistant/components/telegram_bot/__init__.py

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

* Update homeassistant/components/telegram_bot/__init__.py

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

* Update homeassistant/components/telegram_bot/__init__.py

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

* Update homeassistant/components/telegram_bot/config_flow.py

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

* Update homeassistant/components/telegram_bot/config_flow.py

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

* added reconfigure flow

* added reauth flow

* added tests for reconfigure flow

* added tests for reauth

* added tests for subentry flow

* added tests for user and webhooks flow with error scenarios

* added import flow tests

* handle webhook deregister exception

* added config entry id to all services

* fix leave chat bug

* Update homeassistant/components/telegram_bot/__init__.py

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

* removed leave chat bug fixes

* Update homeassistant/components/telegram_bot/strings.json

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

* handle other error types for import

* reuse translations

* added test for duplicated config entry for user step

* added tests

---------

Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-06-02 08:52:31 +02:00
epenet
de4a5fa30b Remove unnecessary DOMAIN alias in tests (s-z) (#146010) 2025-06-02 08:48:37 +02:00
Marc Mueller
43ac550ca0 Update pydantic to 2.11.5 (#145985)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-06-02 08:48:22 +02:00
Marc Mueller
c3c4d224b2 Update PyTurboJPEG to 1.8.0 (#145984)
Co-authored-by: Allen Porter <allen.porter@gmail.com>
2025-06-02 08:40:10 +02:00
Marc Mueller
6f865beacd Update attrs to 25.3.0 (#145977) 2025-06-02 07:58:35 +02:00
Marc Mueller
de25195383 Update bcrypt to 4.3.0 (#145978) 2025-06-02 07:56:51 +02:00
Marc Mueller
0139d2cabf Update cryptography to 45.0.3 (#145979) 2025-06-02 07:53:58 +02:00
Marc Mueller
17542614b5 Update aiohttp-cors to 0.8.1 (#145976)
* Update aiohttp-cors to 0.8.1

* Fix mypy
2025-06-02 07:52:23 +02:00
Marc Mueller
885367e690 Update coverage to 7.8.2 (#145983) 2025-06-02 07:47:56 +02:00
Marc Mueller
f8c44aad25 Update pytest-cov to 6.1.1 (#145989) 2025-06-02 07:34:11 +02:00
Marc Mueller
2323cc2869 Update numpy to 2.2.6 (#145981) 2025-06-01 21:23:30 -07:00
Marc Mueller
7f0249bbf7 Update pytest-timeout to 2.4.0 (#145990) 2025-06-02 06:17:39 +02:00
Marc Mueller
7a23b778a4 Update pytest-xdist to 3.7.0 (#145991) 2025-06-02 06:16:17 +02:00
Marc Mueller
d910924032 Update syrupy to 4.9.1 (#145992) 2025-06-02 06:14:52 +02:00
Marc Mueller
0b93a8c2f2 Update types packages (#145993) 2025-06-02 06:13:08 +02:00
Marc Mueller
5e377b89fc Update pytest-asyncio to 1.0.0 (#145988)
* Update pytest-asyncio to 1.0.0

* Remove event_loop fixture uses
2025-06-02 06:12:22 +02:00
Marc Mueller
dd85a1e5f0 Update mypy-dev to 1.17.0a2 (#146002)
* Update mypy-dev to 1.17.0a2

* Fix
2025-06-02 06:06:38 +02:00
Simone Chemelli
b96a7aebcd Bump aioamazondevices to 3.0.4 (#145971) 2025-06-01 21:15:18 +02:00
Michael
3cfcf382da Bump aioimmich to 0.8.0 (#145908) 2025-06-01 21:14:19 +02:00
epenet
ed9fd2c643 Use async_load_fixture in async test functions (b-i) (#145714)
* Use async_load_fixture in async test functions (b-i)

* Adjust
2025-06-01 06:31:37 -07:00
epenet
a007e8dc26 Use async_load_fixture in async test functions (l-z) (#145717)
* Use async_load_fixture in async test functions (l-z)

* Adjust
2025-06-01 06:29:17 -07:00
TimL
b318644998 Bump pysmlight to v0.2.5 (#145949) 2025-06-01 03:14:08 +02:00
Ståle Storø Hauknes
0434eea3ab Add sound pressure to Airthings (#145946)
Add sound pressure
2025-06-01 02:05:19 +02:00
Josef Zweck
c19b984660 Increase update intervals in lamarzocco (#145939) 2025-05-31 20:25:57 +02:00
Josef Zweck
0d6bb8a325 Bump pylamarzocco to 2.0.8 (#145938) 2025-05-31 20:25:47 +02:00
Joost Lekkerkerker
094b969301 Add more Amazon Devices DHCP matches (#145776) 2025-05-31 20:25:24 +02:00
Brett Adams
ddef6fdb98 Add streaming to charge cable connected in Teslemetry (#145880) 2025-05-31 20:01:10 +02:00
Robert Resch
cabf7860b3 Deprecate snips integration (#145784) 2025-05-31 20:00:34 +02:00
Bram Kragten
0c0a2403e5 Update frontend to 20250531.0 (#145933) 2025-05-31 17:54:36 +02:00
tronikos
be6c3d8bbd Bump opower to 0.12.3 (#145918) 2025-05-31 11:22:49 +02:00
Josef Zweck
c01536ee58 Move server device creation to init in jellyfin (#145910)
* Move server device creation to init in jellyfin

* move device creation to after coordinator refresh
2025-05-31 11:19:32 +02:00
J. Nick Koston
a9f36a50e4 Bump aiohttp to 3.12.6 (#145919)
* Bump aiohttp to 3.12.5

changelog: https://github.com/aio-libs/aiohttp/compare/v3.12.4...v3.12.5

* .6

* fix mock
2025-05-31 11:12:00 +02:00
Samuel Xiao
6d11c0395f Bump switchbot-api to 2.4.0 (#145786)
* update switchbot-api version to 2.4.0

* debug for test code
2025-05-30 20:22:40 +02:00
Brett Adams
66bb638dd0 Bump tesla-fleet-api to 1.1.1. (#145869)
bump
2025-05-30 20:21:51 +02:00
Iskra kranj
0d72bfef70 Bump pyiskra to 0.1.19 (#145889) 2025-05-30 20:21:14 +02:00
markhannon
6e44552d41 Minor cleanup of Zimi Integration (#144293) 2025-05-30 19:53:33 +02:00
Simon Lamon
9ec02633b3 Bump python-linkplay to v0.2.9 (#145892) 2025-05-30 19:35:08 +02:00
Jordan Harvey
5d340332bf Bump pyprobeplus to 1.0.1 (#145897) 2025-05-30 19:33:03 +02:00
J. Diego Rodríguez Royo
1e973c1d74 Bump aiohomeconnect to 0.17.1 (#145873) 2025-05-30 01:40:11 +02:00
starkillerOG
618ada64f8 Ensure Reolink host device is setup first (#145843) 2025-05-29 19:32:21 +02:00
Robert Resch
2d6802e06a Deprecate tensorflow (#145806)
Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-05-29 14:35:35 +01:00
starkillerOG
9687a34a70 Reolink fallback to download command for playback (#145842) 2025-05-29 15:31:50 +02:00
Michael
5ba0ceb6c2 Bump aioimmich to 0.7.0 (#145845) 2025-05-29 15:30:02 +02:00
G Johansson
d8e3e88c63 Fix language selections in workday (#145813) 2025-05-29 15:28:54 +02:00
Robert Resch
d1d1bca29d Deprecate sms integration (#145847) 2025-05-29 14:12:51 +02:00
Michael
80189495c5 Use mime type provided by Immich (#145830)
use mime type from immich instead of guessing it
2025-05-29 10:28:02 +02:00
Josef Zweck
cad6c72cfa Bump aiotedee to 0.2.23 (#145822)
* Bump aiotedee to 0.2.23

* update snapshot
2025-05-29 10:35:05 +03:00
J. Nick Koston
23ac22e213 Remove default args to ESPHome test fixture calls (#145840) 2025-05-29 01:45:37 -05:00
J. Nick Koston
55e664fc0d Bump aiohttp to 3.12.4 (#145838) 2025-05-28 21:08:01 -05:00
Brett Adams
881ce45afa Fix Tessie volume max and step (#145835)
* Use fixed volume max and step

* Update snapshot
2025-05-29 03:58:29 +02:00
André Lersveen
b80195df81 Set correct nobo_hub max temperature (#145751)
Max temperature 30°C is implemented upstream in pynobo and the Nobø Energy Hub app also stops at 30°C.
2025-05-29 03:52:05 +02:00
Matthew FitzGerald-Chamberlain
e57ce0a9df Bump pyaprilaire to 0.9.1 (#145836) 2025-05-29 03:43:28 +02:00
J. Nick Koston
ff66ad7705 Bump aiohttp to 3.12.3 (#145837) 2025-05-28 19:38:06 -05:00
Robert Resch
33e98ebffa Remove decora-wifi from excluded requirements (#145832) 2025-05-29 00:14:38 +02:00
Robert Resch
8fd9e2046e Deprecate decora integration (#145807) 2025-05-28 23:54:48 +02:00
Bram Kragten
32c2f47ab5 Update frontend to 20250528.0 (#145828)
Co-authored-by: Robert Resch <robert@resch.dev>
2025-05-28 23:17:14 +02:00
Ståle Storø Hauknes
e2fc2dce84 Move Airthings coordinator to separate module (#145827)
* Create coordinator

* Fix sensor.py
2025-05-28 22:38:33 +02:00
Michael
afa97f8ec1 Add level of collections in Immich media source tree (#145734)
* add layer for collections in media source tree

* re-arange tests, add test for collection layer

* fix
2025-05-28 20:51:27 +02:00
Michael
2708c1c94c Fix Immich media source browsing with multiple config entries (#145823)
fix media source browsing with multiple config entries
2025-05-28 20:49:20 +02:00
Michael Hansen
d76ed6a3c2 Bump intents to 2025.5.28 (#145816) 2025-05-28 21:14:13 +03:00
epenet
695f69bd90 Remove unnecessary DOMAIN alias in tests (e-k) (#145818) 2025-05-28 21:06:25 +03:00
epenet
7da8e24e21 Remove unnecessary DOMAIN alias in tests (a-d) (#145817) 2025-05-28 21:00:38 +03:00
David Bonnes
9d0fc0d513 Fix HOMEASSISTANT_STOP unsubscribe in data update coordinator (#145809)
* initial commit

* a better approach

* Add comment
2025-05-28 17:52:51 +01:00
Robert Resch
ca567aa7fc Deprecate lirc integration (#145797) 2025-05-28 17:28:37 +01:00
Robert Resch
27af2d8ec6 Deprecate keyboard integration (#145805) 2025-05-28 17:22:18 +02:00
Lennart Nederstigt
59ea6f375a Add hardwired chime toggle to Reolink Battery Doorbell (#145779)
Co-authored-by: starkillerOG <starkiller.og@gmail.com>
2025-05-28 17:10:38 +02:00
Marc Mueller
6c365c94ed Update sqlalchemy to 2.0.41 (#145790) 2025-05-28 16:39:10 +02:00
Marc Mueller
6693fc764f Update httpcore to 1.0.9 and h11 to 0.16.0 (#145789) 2025-05-28 16:35:11 +02:00
starkillerOG
e855b6c2bc Bump reolink-aio to 0.13.4 (#145799) 2025-05-28 16:33:20 +02:00
Abílio Costa
23a1dddc23 Add Shelly zwave virtual integration (#145749) 2025-05-28 14:56:47 +01:00
epenet
bd5fef1ddb Use async_load_fixture in async test functions (a) (#145718) 2025-05-28 15:51:49 +02:00
epenet
c3ade400fb Use Platform constant in tests (#145801)
* Use Platform constant in tests

* spelling

* Fix platform
2025-05-28 15:51:37 +02:00
epenet
1889f0ef66 Use Platform constant in hue tests (#145798) 2025-05-28 14:43:48 +02:00
epenet
6b28af8282 Remove unnecessary DOMAIN alias in components (#145791) 2025-05-28 14:04:35 +02:00
Robert Resch
f59001d45f Deprecate pandora integration (#145785) 2025-05-28 13:12:55 +02:00
Erik Montnemery
a857461059 Handle late abort when creating subentry (#145765)
* Handle late abort when creating subentry

* Move error handling to the base class

* Narrow down expected error in test
2025-05-28 12:26:28 +02:00
epenet
e4cc842584 Use async_load_json_(array/object)_fixture in async test functions (#145773) 2025-05-28 12:09:05 +02:00
Robert Resch
bb52058920 Deprecate GStreamer integration (#145768) 2025-05-28 11:16:08 +02:00
J. Diego Rodríguez Royo
c1676570da Add more information about possible hostnames at Home Connect (#145770) 2025-05-28 10:57:01 +02:00
G Johansson
4858b2171e Modernize tests for smhi (#139334)
* Modernize tests for smhi

* Fixes

* Mods

* Fix weather

* Coverage 100%

* Fix init test

* Fixes

* Fixes

* Remove waits
2025-05-28 10:56:07 +02:00
Jan Bouwhuis
192aa76cd7 Ensure mqtt sensor unit of measurement validation for state class measurement_angle (#145648) 2025-05-28 10:16:40 +02:00
Josef Zweck
ddf611bfdf Fix uom for prebrew numbers in lamarzocco (#145772) 2025-05-28 10:15:24 +02:00
Robert Resch
3164394982 Deprecate dlib image processing integrations (#145767) 2025-05-28 09:58:44 +02:00
Josef Zweck
b250a03ff5 Bump pylamarzocco to 2.0.7 (#145763) 2025-05-28 09:39:33 +02:00
dependabot[bot]
2dd7f035f6 Bump docker/build-push-action from 6.17.0 to 6.18.0 (#145764)
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2025-05-28 09:10:37 +02:00
Joost Lekkerkerker
2c08b3f30c Add more Amazon Devices DHCP matches (#145754) 2025-05-28 08:43:59 +02:00
Josef Zweck
c3ec30ce3b Update otp description for amazon_devices (#145701)
* Update otp description from amazon_devices

* separate

* Update strings.json
2025-05-28 08:13:28 +02:00
Erik Montnemery
9d4375ca76 Make async_remove_stale_devices_links_keep_entity_device move entities (#145719)
Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
Co-authored-by: Joost Lekkerkerker <joostlek@outlook.com>
2025-05-27 23:00:52 +02:00
Raphael Hehl
3870b87db9 Bump uiprotect to version 7.10.1 (#145737)
Co-authored-by: Jan Bouwhuis <jbouwh@users.noreply.github.com>
2025-05-27 22:58:46 +02:00
Joost Lekkerkerker
ff2fd7e9ef Add DHCP discovery to LG ThinQ (#145746) 2025-05-27 16:45:30 -04:00
G Johansson
719dd09eb3 Fix dns resolver error in dnsip config flow validation (#145735)
Fix dns resolver error in dnsip
2025-05-27 22:17:34 +02:00
Bram Kragten
2cf2613dbd Update frontend to 20250527.0 (#145741) 2025-05-27 22:12:07 +02:00
Jan Bouwhuis
181a3d142e Revert "squeezebox Better result for testing (#144622)" (#145739)
This reverts commit 987af8f7df.
2025-05-27 21:36:51 +02:00
Elias Wernicke
c20ad5fde1 Add complete intent function for shopping list component (#128565)
* add intent

* add tests

* raise IntentHandleError

* add check for non completed

* Prefer completing non complete items

* cleanup

* cleanup tests

* rename test

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>

* remove duplicated test

* update test

* complete all items

* fix event

* remove type def

* return speech slots

---------

Co-authored-by: Abílio Costa <abmantis@users.noreply.github.com>
2025-05-27 14:35:14 -05:00
Erwin Douna
4fcebf18dc Tado update mobile devices interval (#145738)
Update the mobile devices interval to five minutes
2025-05-27 21:27:52 +02:00
Joost Lekkerkerker
a6e04be076 Remove niko_home_control YAML import (#145732) 2025-05-27 19:58:05 +02:00
Erwin Douna
330a8e197d MELCloud remove deprecated YAML import strings (#145731)
Remove deprecated YAML import strings
2025-05-27 19:50:31 +02:00
Joost Lekkerkerker
4300e846e6 Fix unbound local variable in Acmeda config flow (#145729) 2025-05-27 19:29:04 +02:00
Kevin Stillhammer
07fd1f99df Support addresses with comma in google_travel_time (#145663)
Support addresses with comma
2025-05-27 18:53:45 +02:00
Kevin Stillhammer
481639bcf9 Catch PermissionDenied(Route API disabled) in google_travel_time (#145722)
Catch PermissionDenied(Route API disabled)
2025-05-27 18:45:49 +02:00
Martin Hjelmare
376008940b Disable advanced window cover position Matter sensor by default (#145713)
* Disable advanced window cover position Matter sensor by default

* Enanble disabled sensors in snapshot test
2025-05-27 17:46:21 +02:00
epenet
b2c2db3394 Add check for transient packages restricting Python version (#145695) 2025-05-27 17:45:51 +02:00
Kevin Stillhammer
a636e38d24 Debug log the update response in google_travel_time (#145725)
Debug log the update response
2025-05-27 17:44:48 +02:00
Martin Hjelmare
ae1294830c Remove static pin code length Matter sensors (#145711)
* Remove static Matter sensors

* Clean up translation strings
2025-05-27 17:35:11 +02:00
Robin Lintermann
d87fdf028b Improve smarla base entity (#145710) 2025-05-27 15:58:19 +02:00
Petar Petrov
6f5d5d4cdb Change text of installing and starting Z-WaveJs add-on steps (#145702) 2025-05-27 14:51:22 +02:00
epenet
12fdd7034a Simplify boolean check in onewire (#145700) 2025-05-27 13:30:44 +02:00
Martin Hjelmare
f295d72cd9 Fix error stack trace for HomeAssistantError in websocket service call (#145699)
* Add test

* Fix error stack trace for HomeAssistantError in websocket service call
2025-05-27 12:54:57 +02:00
Petar Petrov
2605fda185 Remove confirm screen after Z-Wave usb discovery (#145682)
* Remove confirm screen after Z-Wave usb discovery

* Simplify async_step_usb
2025-05-27 12:53:30 +02:00
Joost Lekkerkerker
2189dc3e2a Use string type for amazon devices OTP code (#145698) 2025-05-27 12:33:02 +02:00
Franck Nijhof
8364d8a2e3 Bump version to 2025.7.0dev0 (#145647)
Co-authored-by: Martin Hjelmare <marhje52@gmail.com>
2025-05-27 10:59:34 +02:00
epenet
96c9636086 Add check for packages restricting Python version (#145690)
* Add check for packages restricting Python version

* Apply suggestions from code review

* until

* until
2025-05-27 10:44:00 +02:00
Petar Petrov
7b1dfc35d1 Change description on recommended/custom Z-Wave install step (#145688)
Change description on recommended/custom Z-WaveJS step
2025-05-27 10:04:29 +02:00
Norbert Rittel
2e94730491 Replace "Invalid API key" with common string in overseerr (#145689)
Replace "Invalid API key" with common string
2025-05-27 09:56:16 +02:00
Markus Adrario
11c6998bf2 Add homee siren platform (#145675)
* port siren.py from custom component

* Add Siren Tests

* last small nits
2025-05-27 09:48:59 +02:00
epenet
055a024d10 Add async-timeout to forbidden packages (#145679) 2025-05-27 08:57:35 +02:00
Joost Lekkerkerker
f73afd71fd Fix Amazon devices offline handling (#145656) 2025-05-27 08:49:25 +02:00
Jan Bouwhuis
ec64194ab9 Fix justnimbus CI test (#145681) 2025-05-27 08:48:06 +02:00
karwosts
d49a613c62 Add read_only entity_id to Trend options flow (#145657) 2025-05-27 08:42:08 +02:00
Artur Pragacz
6fc064fa6a Test that recorder is not promoted to earlier stage in bootstrap (#142695)
Test that recorder is not promoted to earlier stage
2025-05-27 08:23:39 +02:00
Artur Pragacz
b36b591ccf Improve error message for global timeout (#141563)
* Improve error message for global timeout

* Add test

* Message works with zone too
2025-05-27 07:49:18 +02:00
J. Nick Koston
d25ba79427 Bump aiohttp to 3.12.2 (#145671) 2025-05-26 21:58:46 -05:00
Joost Lekkerkerker
df35f30321 Handle Google Nest DHCP flows (#145658)
* Handle Google Nest DHCP flows

* Handle Google Nest DHCP flows
2025-05-26 15:01:35 -07:00
Jan Bouwhuis
1e3d06a993 Fix translation for sensor measurement angle state class (#145649) 2025-05-26 22:47:53 +01:00
Florian von Garrel
2ee6bf7340 Add update platform to paperless integration (#145638)
* Add uüdate platform to paperless integration

* Add tests to paperless

* Add translation

* Fixed update unavailable

* Fetch remote version in update platform

* changed diagnostics

* changed diagnostic data

* Code quality

* revert changes

* code quality
2025-05-26 23:24:53 +02:00
Joost Lekkerkerker
13a8e5e021 Fix Aquacell snapshot (#145651) 2025-05-26 23:08:07 +02:00
2242 changed files with 95100 additions and 27794 deletions

View File

@@ -1,15 +1,14 @@
name: Report an issue with Home Assistant Core
description: Report an issue with Home Assistant Core.
type: Bug
body:
- type: markdown
attributes:
value: |
This issue form is for reporting bugs only!
If you have a feature or enhancement request, please use the [feature request][fr] section of our [Community Forum][fr].
If you have a feature or enhancement request, please [request them here instead][fr].
[fr]: https://community.home-assistant.io/c/feature-requests
[fr]: https://github.com/orgs/home-assistant/discussions
- type: textarea
validations:
required: true

View File

@@ -10,8 +10,8 @@ contact_links:
url: https://www.home-assistant.io/help
about: We use GitHub for tracking bugs, check our website for resources on getting help.
- name: Feature Request
url: https://community.home-assistant.io/c/feature-requests
about: Please use our Community Forum for making feature requests.
url: https://github.com/orgs/home-assistant/discussions
about: Please use this link to request new features or enhancements to existing features.
- name: I'm unsure where to go
url: https://www.home-assistant.io/join-chat
about: If you are unsure where to go, then joining our chat is recommended; Just ask!

51
.github/ISSUE_TEMPLATE/task.yml vendored Normal file
View File

@@ -0,0 +1,51 @@
name: Task
description: For staff only - Create a task
type: Task
body:
- type: markdown
attributes:
value: |
## ⚠️ RESTRICTED ACCESS
**This form is restricted to Open Home Foundation staff, authorized contributors, and integration code owners only.**
If you are a community member wanting to contribute, please:
- For bug reports: Use the [bug report form](https://github.com/home-assistant/core/issues/new?template=bug_report.yml)
- For feature requests: Submit to [Feature Requests](https://github.com/orgs/home-assistant/discussions)
---
### For authorized contributors
Use this form to create tasks for development work, improvements, or other actionable items that need to be tracked.
- type: textarea
id: description
attributes:
label: Task description
description: |
Provide a clear and detailed description of the task that needs to be accomplished.
Be specific about what needs to be done, why it's important, and any constraints or requirements.
placeholder: |
Describe the task, including:
- What needs to be done
- Why this task is needed
- Expected outcome
- Any constraints or requirements
validations:
required: true
- type: textarea
id: additional_context
attributes:
label: Additional context
description: |
Any additional information, links, research, or context that would be helpful.
Include links to related issues, research, prototypes, roadmap opportunities etc.
placeholder: |
- Roadmap opportunity: [links]
- Feature request: [link]
- Technical design documents: [link]
- Prototype/mockup: [link]
validations:
required: false

File diff suppressed because it is too large Load Diff

View File

@@ -94,7 +94,7 @@ jobs:
- name: Download nightly wheels of frontend
if: needs.init.outputs.channel == 'dev'
uses: dawidd6/action-download-artifact@v9
uses: dawidd6/action-download-artifact@v11
with:
github_token: ${{secrets.GITHUB_TOKEN}}
repo: home-assistant/frontend
@@ -105,10 +105,10 @@ jobs:
- name: Download nightly wheels of intents
if: needs.init.outputs.channel == 'dev'
uses: dawidd6/action-download-artifact@v9
uses: dawidd6/action-download-artifact@v11
with:
github_token: ${{secrets.GITHUB_TOKEN}}
repo: home-assistant/intents-package
repo: OHF-Voice/intents-package
branch: main
workflow: nightly.yaml
workflow_conclusion: success
@@ -324,7 +324,7 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Install Cosign
uses: sigstore/cosign-installer@v3.8.2
uses: sigstore/cosign-installer@v3.9.1
with:
cosign-release: "v2.2.3"
@@ -509,7 +509,7 @@ jobs:
password: ${{ secrets.GITHUB_TOKEN }}
- name: Build Docker image
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile
@@ -522,7 +522,7 @@ jobs:
- name: Push Docker image
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
id: push
uses: docker/build-push-action@1dc73863535b631f98b2378be8619f83b136f4a0 # v6.17.0
uses: docker/build-push-action@263435318d21b8e681c14492fe198d362a7d2c83 # v6.18.0
with:
context: . # So action will not pull the repository again
file: ./script/hassfest/docker/Dockerfile
@@ -531,7 +531,7 @@ jobs:
- name: Generate artifact attestation
if: needs.init.outputs.channel != 'dev' && needs.init.outputs.publish == 'true'
uses: actions/attest-build-provenance@db473fddc028af60658334401dc6fa3ffd8669fd # v2.3.0
uses: actions/attest-build-provenance@e8998f949152b193b063cb0ec769d69d929409be # v2.4.0
with:
subject-name: ${{ env.HASSFEST_IMAGE_NAME }}
subject-digest: ${{ steps.push.outputs.digest }}

View File

@@ -37,10 +37,10 @@ on:
type: boolean
env:
CACHE_VERSION: 3
CACHE_VERSION: 4
UV_CACHE_VERSION: 1
MYPY_CACHE_VERSION: 1
HA_SHORT_VERSION: "2025.6"
HA_SHORT_VERSION: "2025.8"
DEFAULT_PYTHON: "3.13"
ALL_PYTHON_VERSIONS: "['3.13']"
# 10.3 is the oldest supported version
@@ -360,7 +360,7 @@ jobs:
- name: Run ruff
run: |
. venv/bin/activate
pre-commit run --hook-stage manual ruff --all-files --show-diff-on-failure
pre-commit run --hook-stage manual ruff-check --all-files --show-diff-on-failure
env:
RUFF_OUTPUT_FORMAT: github

View File

@@ -24,11 +24,11 @@ jobs:
uses: actions/checkout@v4.2.2
- name: Initialize CodeQL
uses: github/codeql-action/init@v3.28.18
uses: github/codeql-action/init@v3.29.2
with:
languages: python
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v3.28.18
uses: github/codeql-action/analyze@v3.29.2
with:
category: "/language:python"

View File

@@ -0,0 +1,385 @@
name: Auto-detect duplicate issues
# yamllint disable-line rule:truthy
on:
issues:
types: [labeled]
permissions:
issues: write
models: read
jobs:
detect-duplicates:
runs-on: ubuntu-latest
steps:
- name: Check if integration label was added and extract details
id: extract
uses: actions/github-script@v7.0.1
with:
script: |
// Debug: Log the event payload
console.log('Event name:', context.eventName);
console.log('Event action:', context.payload.action);
console.log('Event payload keys:', Object.keys(context.payload));
// Check the specific label that was added
const addedLabel = context.payload.label;
if (!addedLabel) {
console.log('No label found in labeled event payload');
core.setOutput('should_continue', 'false');
return;
}
console.log(`Label added: ${addedLabel.name}`);
if (!addedLabel.name.startsWith('integration:')) {
console.log('Added label is not an integration label, skipping duplicate detection');
core.setOutput('should_continue', 'false');
return;
}
console.log(`Integration label added: ${addedLabel.name}`);
let currentIssue;
let integrationLabels = [];
try {
const issue = await github.rest.issues.get({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number
});
currentIssue = issue.data;
// Check if potential-duplicate label already exists
const hasPotentialDuplicateLabel = currentIssue.labels
.some(label => label.name === 'potential-duplicate');
if (hasPotentialDuplicateLabel) {
console.log('Issue already has potential-duplicate label, skipping duplicate detection');
core.setOutput('should_continue', 'false');
return;
}
integrationLabels = currentIssue.labels
.filter(label => label.name.startsWith('integration:'))
.map(label => label.name);
} catch (error) {
core.error(`Failed to fetch issue #${context.payload.issue.number}:`, error.message);
core.setOutput('should_continue', 'false');
return;
}
// Check if we've already posted a duplicate detection comment recently
let comments;
try {
comments = await github.rest.issues.listComments({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
per_page: 10
});
} catch (error) {
core.error('Failed to fetch comments:', error.message);
// Continue anyway, worst case we might post a duplicate comment
comments = { data: [] };
}
// Check if we've already posted a duplicate detection comment
const recentDuplicateComment = comments.data.find(comment =>
comment.user && comment.user.login === 'github-actions[bot]' &&
comment.body.includes('<!-- workflow: detect-duplicate-issues -->')
);
if (recentDuplicateComment) {
console.log('Already posted duplicate detection comment, skipping');
core.setOutput('should_continue', 'false');
return;
}
core.setOutput('should_continue', 'true');
core.setOutput('current_number', currentIssue.number);
core.setOutput('current_title', currentIssue.title);
core.setOutput('current_body', currentIssue.body);
core.setOutput('current_url', currentIssue.html_url);
core.setOutput('integration_labels', JSON.stringify(integrationLabels));
console.log(`Current issue: #${currentIssue.number}`);
console.log(`Integration labels: ${integrationLabels.join(', ')}`);
- name: Fetch similar issues
id: fetch_similar
if: steps.extract.outputs.should_continue == 'true'
uses: actions/github-script@v7.0.1
env:
INTEGRATION_LABELS: ${{ steps.extract.outputs.integration_labels }}
CURRENT_NUMBER: ${{ steps.extract.outputs.current_number }}
with:
script: |
const integrationLabels = JSON.parse(process.env.INTEGRATION_LABELS);
const currentNumber = parseInt(process.env.CURRENT_NUMBER);
if (integrationLabels.length === 0) {
console.log('No integration labels found, skipping duplicate detection');
core.setOutput('has_similar', 'false');
return;
}
// Use GitHub search API to find issues with matching integration labels
console.log(`Searching for issues with integration labels: ${integrationLabels.join(', ')}`);
// Build search query for issues with any of the current integration labels
const labelQueries = integrationLabels.map(label => `label:"${label}"`);
// Calculate date 6 months ago
const sixMonthsAgo = new Date();
sixMonthsAgo.setMonth(sixMonthsAgo.getMonth() - 6);
const dateFilter = `created:>=${sixMonthsAgo.toISOString().split('T')[0]}`;
let searchQuery;
if (labelQueries.length === 1) {
searchQuery = `repo:${context.repo.owner}/${context.repo.repo} is:issue ${labelQueries[0]} ${dateFilter}`;
} else {
searchQuery = `repo:${context.repo.owner}/${context.repo.repo} is:issue (${labelQueries.join(' OR ')}) ${dateFilter}`;
}
console.log(`Search query: ${searchQuery}`);
let result;
try {
result = await github.rest.search.issuesAndPullRequests({
q: searchQuery,
per_page: 15,
sort: 'updated',
order: 'desc'
});
} catch (error) {
core.error('Failed to search for similar issues:', error.message);
if (error.status === 403 && error.message.includes('rate limit')) {
core.error('GitHub API rate limit exceeded');
}
core.setOutput('has_similar', 'false');
return;
}
// Filter out the current issue, pull requests, and newer issues (higher numbers)
const similarIssues = result.data.items
.filter(item =>
item.number !== currentNumber &&
!item.pull_request &&
item.number < currentNumber // Only include older issues (lower numbers)
)
.map(item => ({
number: item.number,
title: item.title,
body: item.body,
url: item.html_url,
state: item.state,
createdAt: item.created_at,
updatedAt: item.updated_at,
comments: item.comments,
labels: item.labels.map(l => l.name)
}));
console.log(`Found ${similarIssues.length} issues with matching integration labels`);
console.log('Raw similar issues:', JSON.stringify(similarIssues.slice(0, 3), null, 2));
if (similarIssues.length === 0) {
console.log('No similar issues found, setting has_similar to false');
core.setOutput('has_similar', 'false');
return;
}
console.log('Similar issues found, setting has_similar to true');
core.setOutput('has_similar', 'true');
// Clean the issue data to prevent JSON parsing issues
const cleanedIssues = similarIssues.slice(0, 15).map(item => {
// Handle body with improved truncation and null handling
let cleanBody = '';
if (item.body && typeof item.body === 'string') {
// Remove control characters
const cleaned = item.body.replace(/[\u0000-\u001F\u007F-\u009F]/g, '');
// Truncate to 1000 characters and add ellipsis if needed
cleanBody = cleaned.length > 1000
? cleaned.substring(0, 1000) + '...'
: cleaned;
}
return {
number: item.number,
title: item.title.replace(/[\u0000-\u001F\u007F-\u009F]/g, ''), // Remove control characters
body: cleanBody,
url: item.url,
state: item.state,
createdAt: item.createdAt,
updatedAt: item.updatedAt,
comments: item.comments,
labels: item.labels
};
});
console.log(`Cleaned issues count: ${cleanedIssues.length}`);
console.log('First cleaned issue:', JSON.stringify(cleanedIssues[0], null, 2));
core.setOutput('similar_issues', JSON.stringify(cleanedIssues));
- name: Detect duplicates using AI
id: ai_detection
if: steps.extract.outputs.should_continue == 'true' && steps.fetch_similar.outputs.has_similar == 'true'
uses: actions/ai-inference@v1.1.0
with:
model: openai/gpt-4o
system-prompt: |
You are a Home Assistant issue duplicate detector. Your task is to identify TRUE DUPLICATES - issues that report the EXACT SAME problem, not just similar or related issues.
CRITICAL: An issue is ONLY a duplicate if:
- It describes the SAME problem with the SAME root cause
- Issues about the same integration but different problems are NOT duplicates
- Issues with similar symptoms but different causes are NOT duplicates
Important considerations:
- Open issues are more relevant than closed ones for duplicate detection
- Recently updated issues may indicate ongoing work or discussion
- Issues with more comments are generally more relevant and active
- Older closed issues might be resolved differently than newer approaches
- Consider the time between issues - very old issues may have different contexts
Rules:
1. ONLY mark as duplicate if the issues describe IDENTICAL problems
2. Look for issues that report the same problem or request the same functionality
3. Different error messages = NOT a duplicate (even if same integration)
4. For CLOSED issues, only mark as duplicate if they describe the EXACT same problem
5. For OPEN issues, use a lower threshold (90%+ similarity)
6. Prioritize issues with higher comment counts as they indicate more activity/relevance
7. When in doubt, do NOT mark as duplicate
8. Return ONLY a JSON array of issue numbers that are duplicates
9. If no duplicates are found, return an empty array: []
10. Maximum 5 potential duplicates, prioritize open issues with comments
11. Consider the age of issues - prefer recent duplicates over very old ones
Example response format:
[1234, 5678, 9012]
prompt: |
Current issue (just created):
Title: ${{ steps.extract.outputs.current_title }}
Body: ${{ steps.extract.outputs.current_body }}
Other issues to compare against (each includes state, creation date, last update, and comment count):
${{ steps.fetch_similar.outputs.similar_issues }}
Analyze these issues and identify which ones describe IDENTICAL problems and thus are duplicates of the current issue. When sorting them, consider their state (open/closed), how recently they were updated, and their comment count (higher = more relevant).
max-tokens: 100
- name: Post duplicate detection results
id: post_results
if: steps.extract.outputs.should_continue == 'true' && steps.fetch_similar.outputs.has_similar == 'true'
uses: actions/github-script@v7.0.1
env:
AI_RESPONSE: ${{ steps.ai_detection.outputs.response }}
SIMILAR_ISSUES: ${{ steps.fetch_similar.outputs.similar_issues }}
with:
script: |
const aiResponse = process.env.AI_RESPONSE;
console.log('Raw AI response:', JSON.stringify(aiResponse));
let duplicateNumbers = [];
try {
// Clean the response of any potential control characters
const cleanResponse = aiResponse.trim().replace(/[\u0000-\u001F\u007F-\u009F]/g, '');
console.log('Cleaned AI response:', cleanResponse);
duplicateNumbers = JSON.parse(cleanResponse);
// Ensure it's an array and contains only numbers
if (!Array.isArray(duplicateNumbers)) {
console.log('AI response is not an array, trying to extract numbers');
const numberMatches = cleanResponse.match(/\d+/g);
duplicateNumbers = numberMatches ? numberMatches.map(n => parseInt(n)) : [];
}
// Filter to only valid numbers
duplicateNumbers = duplicateNumbers.filter(n => typeof n === 'number' && !isNaN(n));
} catch (error) {
console.log('Failed to parse AI response as JSON:', error.message);
console.log('Raw response:', aiResponse);
// Fallback: try to extract numbers from the response
const numberMatches = aiResponse.match(/\d+/g);
duplicateNumbers = numberMatches ? numberMatches.map(n => parseInt(n)) : [];
console.log('Extracted numbers as fallback:', duplicateNumbers);
}
if (!Array.isArray(duplicateNumbers) || duplicateNumbers.length === 0) {
console.log('No duplicates detected by AI');
return;
}
console.log(`AI detected ${duplicateNumbers.length} potential duplicates: ${duplicateNumbers.join(', ')}`);
// Get details of detected duplicates
const similarIssues = JSON.parse(process.env.SIMILAR_ISSUES);
const duplicates = similarIssues.filter(issue => duplicateNumbers.includes(issue.number));
if (duplicates.length === 0) {
console.log('No matching issues found for detected numbers');
return;
}
// Create comment with duplicate detection results
const duplicateLinks = duplicates.map(issue => `- [#${issue.number}: ${issue.title}](${issue.url})`).join('\n');
const commentBody = [
'<!-- workflow: detect-duplicate-issues -->',
'### 🔍 **Potential duplicate detection**',
'',
'I\'ve analyzed similar issues and found the following potential duplicates:',
'',
duplicateLinks,
'',
'**What to do next:**',
'1. Please review these issues to see if they match your issue',
'2. If you find an existing issue that covers your problem:',
' - Consider closing this issue',
' - Add your findings or 👍 on the existing issue instead',
'3. If your issue is different or adds new aspects, please clarify how it differs',
'',
'This helps keep our issues organized and ensures similar issues are consolidated for better visibility.',
'',
'*This message was generated automatically by our duplicate detection system.*'
].join('\n');
try {
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
body: commentBody
});
console.log(`Posted duplicate detection comment with ${duplicates.length} potential duplicates`);
// Add the potential-duplicate label
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.payload.issue.number,
labels: ['potential-duplicate']
});
console.log('Added potential-duplicate label to the issue');
} catch (error) {
core.error('Failed to post duplicate detection comment or add label:', error.message);
if (error.status === 403) {
core.error('Permission denied or rate limit exceeded');
}
// Don't throw - we've done the analysis, just couldn't post the result
}

View File

@@ -0,0 +1,193 @@
name: Auto-detect non-English issues
# yamllint disable-line rule:truthy
on:
issues:
types: [opened]
permissions:
issues: write
models: read
jobs:
detect-language:
runs-on: ubuntu-latest
steps:
- name: Check issue language
id: detect_language
uses: actions/github-script@v7.0.1
env:
ISSUE_NUMBER: ${{ github.event.issue.number }}
ISSUE_TITLE: ${{ github.event.issue.title }}
ISSUE_BODY: ${{ github.event.issue.body }}
ISSUE_USER_TYPE: ${{ github.event.issue.user.type }}
with:
script: |
// Get the issue details from environment variables
const issueNumber = process.env.ISSUE_NUMBER;
const issueTitle = process.env.ISSUE_TITLE || '';
const issueBody = process.env.ISSUE_BODY || '';
const userType = process.env.ISSUE_USER_TYPE;
// Skip language detection for bot users
if (userType === 'Bot') {
console.log('Skipping language detection for bot user');
core.setOutput('should_continue', 'false');
return;
}
console.log(`Checking language for issue #${issueNumber}`);
console.log(`Title: ${issueTitle}`);
// Combine title and body for language detection
const fullText = `${issueTitle}\n\n${issueBody}`;
// Check if the text is too short to reliably detect language
if (fullText.trim().length < 20) {
console.log('Text too short for reliable language detection');
core.setOutput('should_continue', 'false'); // Skip processing for very short text
return;
}
core.setOutput('issue_number', issueNumber);
core.setOutput('issue_text', fullText);
core.setOutput('should_continue', 'true');
- name: Detect language using AI
id: ai_language_detection
if: steps.detect_language.outputs.should_continue == 'true'
uses: actions/ai-inference@v1.1.0
with:
model: openai/gpt-4o-mini
system-prompt: |
You are a language detection system. Your task is to determine if the provided text is written in English or another language.
Rules:
1. Analyze the text and determine the primary language of the USER'S DESCRIPTION only
2. IGNORE markdown headers (lines starting with #, ##, ###, etc.) as these are from issue templates, not user input
3. IGNORE all code blocks (text between ``` or ` markers) as they may contain system-generated error messages in other languages
4. IGNORE error messages, logs, and system output even if not in code blocks - these often appear in the user's system language
5. Consider technical terms, code snippets, URLs, and file paths as neutral (they don't indicate non-English)
6. Focus ONLY on the actual sentences and descriptions written by the user explaining their issue
7. If the user's explanation/description is in English but includes non-English error messages or logs, consider it ENGLISH
8. Return ONLY a JSON object with two fields:
- "is_english": boolean (true if the user's description is primarily in English, false otherwise)
- "detected_language": string (the name of the detected language, e.g., "English", "Spanish", "Chinese", etc.)
9. Be lenient - if the user's explanation is in English with non-English system output, it's still English
10. Common programming terms, error messages, and technical jargon should not be considered as non-English
11. If you cannot reliably determine the language, set detected_language to "undefined"
Example response:
{"is_english": false, "detected_language": "Spanish"}
prompt: |
Please analyze the following issue text and determine if it is written in English:
${{ steps.detect_language.outputs.issue_text }}
max-tokens: 50
- name: Process non-English issues
if: steps.detect_language.outputs.should_continue == 'true'
uses: actions/github-script@v7.0.1
env:
AI_RESPONSE: ${{ steps.ai_language_detection.outputs.response }}
ISSUE_NUMBER: ${{ steps.detect_language.outputs.issue_number }}
with:
script: |
const issueNumber = parseInt(process.env.ISSUE_NUMBER);
const aiResponse = process.env.AI_RESPONSE;
console.log('AI language detection response:', aiResponse);
let languageResult;
try {
languageResult = JSON.parse(aiResponse.trim());
// Validate the response structure
if (!languageResult || typeof languageResult.is_english !== 'boolean') {
throw new Error('Invalid response structure');
}
} catch (error) {
core.error(`Failed to parse AI response: ${error.message}`);
console.log('Raw AI response:', aiResponse);
// Log more details for debugging
core.warning('Defaulting to English due to parsing error');
// Default to English if we can't parse the response
return;
}
if (languageResult.is_english) {
console.log('Issue is in English, no action needed');
return;
}
// If language is undefined or not detected, skip processing
if (!languageResult.detected_language || languageResult.detected_language === 'undefined') {
console.log('Language could not be determined, skipping processing');
return;
}
console.log(`Issue detected as non-English: ${languageResult.detected_language}`);
// Post comment explaining the language requirement
const commentBody = [
'<!-- workflow: detect-non-english-issues -->',
'### 🌐 Non-English issue detected',
'',
`This issue appears to be written in **${languageResult.detected_language}** rather than English.`,
'',
'The Home Assistant project uses English as the primary language for issues to ensure that everyone in our international community can participate and help resolve issues. This allows any of our thousands of contributors to jump in and provide assistance.',
'',
'**What to do:**',
'1. Re-create the issue using the English language',
'2. If you need help with translation, consider using:',
' - Translation tools like Google Translate',
' - AI assistants like ChatGPT or Claude',
'',
'This helps our community provide the best possible support and ensures your issue gets the attention it deserves from our global contributor base.',
'',
'Thank you for your understanding! 🙏'
].join('\n');
try {
// Add comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
body: commentBody
});
console.log('Posted language requirement comment');
// Add non-english label
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
labels: ['non-english']
});
console.log('Added non-english label');
// Close the issue
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: issueNumber,
state: 'closed',
state_reason: 'not_planned'
});
console.log('Closed the issue');
} catch (error) {
core.error('Failed to process non-English issue:', error.message);
if (error.status === 403) {
core.error('Permission denied or rate limit exceeded');
}
}

View File

@@ -0,0 +1,84 @@
name: Restrict task creation
# yamllint disable-line rule:truthy
on:
issues:
types: [opened]
jobs:
check-authorization:
runs-on: ubuntu-latest
# Only run if this is a Task issue type (from the issue form)
if: github.event.issue.issue_type == 'Task'
steps:
- name: Check if user is authorized
uses: actions/github-script@v7
with:
script: |
const issueAuthor = context.payload.issue.user.login;
// First check if user is an organization member
try {
await github.rest.orgs.checkMembershipForUser({
org: 'home-assistant',
username: issueAuthor
});
console.log(`✅ ${issueAuthor} is an organization member`);
return; // Authorized, no need to check further
} catch (error) {
console.log(` ${issueAuthor} is not an organization member, checking codeowners...`);
}
// If not an org member, check if they're a codeowner
try {
// Fetch CODEOWNERS file from the repository
const { data: codeownersFile } = await github.rest.repos.getContent({
owner: context.repo.owner,
repo: context.repo.repo,
path: 'CODEOWNERS',
ref: 'dev'
});
// Decode the content (it's base64 encoded)
const codeownersContent = Buffer.from(codeownersFile.content, 'base64').toString('utf-8');
// Check if the issue author is mentioned in CODEOWNERS
// GitHub usernames in CODEOWNERS are prefixed with @
if (codeownersContent.includes(`@${issueAuthor}`)) {
console.log(`✅ ${issueAuthor} is a integration code owner`);
return; // Authorized
}
} catch (error) {
console.error('Error checking CODEOWNERS:', error);
}
// If we reach here, user is not authorized
console.log(`❌ ${issueAuthor} is not authorized to create Task issues`);
// Close the issue with a comment
await github.rest.issues.createComment({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
body: `Hi @${issueAuthor}, thank you for your contribution!\n\n` +
`Task issues are restricted to Open Home Foundation staff, authorized contributors, and integration code owners.\n\n` +
`If you would like to:\n` +
`- Report a bug: Please use the [bug report form](https://github.com/home-assistant/core/issues/new?template=bug_report.yml)\n` +
`- Request a feature: Please submit to [Feature Requests](https://github.com/orgs/home-assistant/discussions)\n\n` +
`If you believe you should have access to create Task issues, please contact the maintainers.`
});
await github.rest.issues.update({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
state: 'closed'
});
// Add a label to indicate this was auto-closed
await github.rest.issues.addLabels({
owner: context.repo.owner,
repo: context.repo.repo,
issue_number: context.issue.number,
labels: ['auto-closed']
});

6
.gitignore vendored
View File

@@ -137,4 +137,8 @@ tmp_cache
.ropeproject
# Will be created from script/split_tests.py
pytest_buckets.txt
pytest_buckets.txt
# AI tooling
.claude

View File

@@ -1,8 +1,8 @@
repos:
- repo: https://github.com/astral-sh/ruff-pre-commit
rev: v0.11.0
rev: v0.12.1
hooks:
- id: ruff
- id: ruff-check
args:
- --fix
- id: ruff-format
@@ -30,7 +30,7 @@ repos:
- --branch=master
- --branch=rc
- repo: https://github.com/adrienverge/yamllint.git
rev: v1.35.1
rev: v1.37.1
hooks:
- id: yamllint
- repo: https://github.com/pre-commit/mirrors-prettier

View File

@@ -67,6 +67,7 @@ homeassistant.components.alert.*
homeassistant.components.alexa.*
homeassistant.components.alexa_devices.*
homeassistant.components.alpha_vantage.*
homeassistant.components.altruist.*
homeassistant.components.amazon_polly.*
homeassistant.components.amberelectric.*
homeassistant.components.ambient_network.*
@@ -380,6 +381,7 @@ homeassistant.components.openai_conversation.*
homeassistant.components.openexchangerates.*
homeassistant.components.opensky.*
homeassistant.components.openuv.*
homeassistant.components.opower.*
homeassistant.components.oralb.*
homeassistant.components.otbr.*
homeassistant.components.overkiz.*
@@ -502,6 +504,7 @@ homeassistant.components.tautulli.*
homeassistant.components.tcp.*
homeassistant.components.technove.*
homeassistant.components.tedee.*
homeassistant.components.telegram_bot.*
homeassistant.components.text.*
homeassistant.components.thethingsnetwork.*
homeassistant.components.threshold.*

2
.vscode/tasks.json vendored
View File

@@ -45,7 +45,7 @@
{
"label": "Ruff",
"type": "shell",
"command": "pre-commit run ruff --all-files",
"command": "pre-commit run ruff-check --all-files",
"group": {
"kind": "test",
"isDefault": true

1
CLAUDE.md Symbolic link
View File

@@ -0,0 +1 @@
.github/copilot-instructions.md

26
CODEOWNERS generated
View File

@@ -57,6 +57,8 @@ build.json @home-assistant/supervisor
/tests/components/aemet/ @Noltari
/homeassistant/components/agent_dvr/ @ispysoftware
/tests/components/agent_dvr/ @ispysoftware
/homeassistant/components/ai_task/ @home-assistant/core
/tests/components/ai_task/ @home-assistant/core
/homeassistant/components/air_quality/ @home-assistant/core
/tests/components/air_quality/ @home-assistant/core
/homeassistant/components/airgradient/ @airgradienthq @joostlek
@@ -91,6 +93,8 @@ build.json @home-assistant/supervisor
/tests/components/alexa/ @home-assistant/cloud @ochlocracy @jbouwh
/homeassistant/components/alexa_devices/ @chemelli74
/tests/components/alexa_devices/ @chemelli74
/homeassistant/components/altruist/ @airalab @LoSk-p
/tests/components/altruist/ @airalab @LoSk-p
/homeassistant/components/amazon_polly/ @jschlyter
/homeassistant/components/amberelectric/ @madpilot
/tests/components/amberelectric/ @madpilot
@@ -327,8 +331,8 @@ build.json @home-assistant/supervisor
/tests/components/demo/ @home-assistant/core
/homeassistant/components/denonavr/ @ol-iver @starkillerOG
/tests/components/denonavr/ @ol-iver @starkillerOG
/homeassistant/components/derivative/ @afaucogney
/tests/components/derivative/ @afaucogney
/homeassistant/components/derivative/ @afaucogney @karwosts
/tests/components/derivative/ @afaucogney @karwosts
/homeassistant/components/devialet/ @fwestenberg
/tests/components/devialet/ @fwestenberg
/homeassistant/components/device_automation/ @home-assistant/core
@@ -448,8 +452,8 @@ build.json @home-assistant/supervisor
/tests/components/eq3btsmart/ @eulemitkeule @dbuezas
/homeassistant/components/escea/ @lazdavila
/tests/components/escea/ @lazdavila
/homeassistant/components/esphome/ @OttoWinter @jesserockz @kbx81 @bdraco
/tests/components/esphome/ @OttoWinter @jesserockz @kbx81 @bdraco
/homeassistant/components/esphome/ @jesserockz @kbx81 @bdraco
/tests/components/esphome/ @jesserockz @kbx81 @bdraco
/homeassistant/components/eufylife_ble/ @bdr99
/tests/components/eufylife_ble/ @bdr99
/homeassistant/components/event/ @home-assistant/core
@@ -784,8 +788,6 @@ build.json @home-assistant/supervisor
/tests/components/jellyfin/ @RunC0deRun @ctalkington
/homeassistant/components/jewish_calendar/ @tsvi
/tests/components/jewish_calendar/ @tsvi
/homeassistant/components/juicenet/ @jesserockz
/tests/components/juicenet/ @jesserockz
/homeassistant/components/justnimbus/ @kvanzuijlen
/tests/components/justnimbus/ @kvanzuijlen
/homeassistant/components/jvc_projector/ @SteveEasley @msavazzi
@@ -1167,6 +1169,8 @@ build.json @home-assistant/supervisor
/tests/components/ping/ @jpbede
/homeassistant/components/plaato/ @JohNan
/tests/components/plaato/ @JohNan
/homeassistant/components/playstation_network/ @jackjpowell @tr4nt0r
/tests/components/playstation_network/ @jackjpowell @tr4nt0r
/homeassistant/components/plex/ @jjlawren
/tests/components/plex/ @jjlawren
/homeassistant/components/plugwise/ @CoMPaTech @bouwew
@@ -1274,8 +1278,8 @@ build.json @home-assistant/supervisor
/tests/components/rehlko/ @bdraco @peterager
/homeassistant/components/remote/ @home-assistant/core
/tests/components/remote/ @home-assistant/core
/homeassistant/components/remote_calendar/ @Thomas55555
/tests/components/remote_calendar/ @Thomas55555
/homeassistant/components/remote_calendar/ @Thomas55555 @allenporter
/tests/components/remote_calendar/ @Thomas55555 @allenporter
/homeassistant/components/renault/ @epenet
/tests/components/renault/ @epenet
/homeassistant/components/renson/ @jimmyd-be
@@ -1549,6 +1553,8 @@ build.json @home-assistant/supervisor
/tests/components/technove/ @Moustachauve
/homeassistant/components/tedee/ @patrickhilker @zweckj
/tests/components/tedee/ @patrickhilker @zweckj
/homeassistant/components/telegram_bot/ @hanwg
/tests/components/telegram_bot/ @hanwg
/homeassistant/components/tellduslive/ @fredrike
/tests/components/tellduslive/ @fredrike
/homeassistant/components/template/ @Petro31 @home-assistant/core
@@ -1578,6 +1584,8 @@ build.json @home-assistant/supervisor
/tests/components/tile/ @bachya
/homeassistant/components/tilt_ble/ @apt-itude
/tests/components/tilt_ble/ @apt-itude
/homeassistant/components/tilt_pi/ @michaelheyman
/tests/components/tilt_pi/ @michaelheyman
/homeassistant/components/time/ @home-assistant/core
/tests/components/time/ @home-assistant/core
/homeassistant/components/time_date/ @fabaff
@@ -1666,6 +1674,8 @@ build.json @home-assistant/supervisor
/tests/components/vallox/ @andre-richter @slovdahl @viiru- @yozik04
/homeassistant/components/valve/ @home-assistant/core
/tests/components/valve/ @home-assistant/core
/homeassistant/components/vegehub/ @ghowevege
/tests/components/vegehub/ @ghowevege
/homeassistant/components/velbus/ @Cereal2nd @brefra
/tests/components/velbus/ @Cereal2nd @brefra
/homeassistant/components/velux/ @Julius2342 @DeerMaximum @pawlizio

View File

@@ -1,15 +1,7 @@
FROM mcr.microsoft.com/devcontainers/python:1-3.13
FROM mcr.microsoft.com/vscode/devcontainers/base:debian
SHELL ["/bin/bash", "-o", "pipefail", "-c"]
# Uninstall pre-installed formatting and linting tools
# They would conflict with our pinned versions
RUN \
pipx uninstall pydocstyle \
&& pipx uninstall pycodestyle \
&& pipx uninstall mypy \
&& pipx uninstall pylint
RUN \
curl -sS https://dl.yarnpkg.com/debian/pubkey.gpg | apt-key add - \
&& apt-get update \
@@ -32,21 +24,18 @@ RUN \
libxml2 \
git \
cmake \
autoconf \
&& apt-get clean \
&& rm -rf /var/lib/apt/lists/*
# Add go2rtc binary
COPY --from=ghcr.io/alexxit/go2rtc:latest /usr/local/bin/go2rtc /bin/go2rtc
# Install uv
RUN pip3 install uv
WORKDIR /usr/src
# Setup hass-release
RUN git clone --depth 1 https://github.com/home-assistant/hass-release \
&& uv pip install --system -e hass-release/ \
&& chown -R vscode /usr/src/hass-release/data
COPY --from=ghcr.io/astral-sh/uv:latest /uv /usr/local/bin/uv
RUN uv python install 3.13.2
USER vscode
ENV VIRTUAL_ENV="/home/vscode/.local/ha-venv"
@@ -55,6 +44,10 @@ ENV PATH="$VIRTUAL_ENV/bin:$PATH"
WORKDIR /tmp
# Setup hass-release
RUN git clone --depth 1 https://github.com/home-assistant/hass-release ~/hass-release \
&& uv pip install -e ~/hass-release/
# Install Python dependencies from requirements
COPY requirements.txt ./
COPY homeassistant/package_constraints.txt homeassistant/package_constraints.txt
@@ -65,4 +58,4 @@ RUN uv pip install -r requirements_test.txt
WORKDIR /workspaces
# Set the default shell to bash instead of sh
ENV SHELL /bin/bash
ENV SHELL=/bin/bash

View File

@@ -38,8 +38,7 @@ def validate_python() -> None:
def ensure_config_path(config_dir: str) -> None:
"""Validate the configuration directory."""
# pylint: disable-next=import-outside-toplevel
from . import config as config_util
from . import config as config_util # noqa: PLC0415
lib_dir = os.path.join(config_dir, "deps")
@@ -80,8 +79,7 @@ def ensure_config_path(config_dir: str) -> None:
def get_arguments() -> argparse.Namespace:
"""Get parsed passed in arguments."""
# pylint: disable-next=import-outside-toplevel
from . import config as config_util
from . import config as config_util # noqa: PLC0415
parser = argparse.ArgumentParser(
description="Home Assistant: Observe, Control, Automate.",
@@ -177,8 +175,7 @@ def main() -> int:
validate_os()
if args.script is not None:
# pylint: disable-next=import-outside-toplevel
from . import scripts
from . import scripts # noqa: PLC0415
return scripts.run(args.script)
@@ -188,8 +185,7 @@ def main() -> int:
ensure_config_path(config_dir)
# pylint: disable-next=import-outside-toplevel
from . import config, runner
from . import config, runner # noqa: PLC0415
safe_mode = config.safe_mode_enabled(config_dir)

View File

@@ -52,28 +52,28 @@ _LOGGER = logging.getLogger(__name__)
def _generate_secret() -> str:
"""Generate a secret."""
import pyotp # pylint: disable=import-outside-toplevel
import pyotp # noqa: PLC0415
return str(pyotp.random_base32())
def _generate_random() -> int:
"""Generate a 32 digit number."""
import pyotp # pylint: disable=import-outside-toplevel
import pyotp # noqa: PLC0415
return int(pyotp.random_base32(length=32, chars=list("1234567890")))
def _generate_otp(secret: str, count: int) -> str:
"""Generate one time password."""
import pyotp # pylint: disable=import-outside-toplevel
import pyotp # noqa: PLC0415
return str(pyotp.HOTP(secret).at(count))
def _verify_otp(secret: str, otp: str, count: int) -> bool:
"""Verify one time password."""
import pyotp # pylint: disable=import-outside-toplevel
import pyotp # noqa: PLC0415
return bool(pyotp.HOTP(secret).verify(otp, count))

View File

@@ -37,7 +37,7 @@ DUMMY_SECRET = "FPPTH34D4E3MI2HG"
def _generate_qr_code(data: str) -> str:
"""Generate a base64 PNG string represent QR Code image of data."""
import pyqrcode # pylint: disable=import-outside-toplevel
import pyqrcode # noqa: PLC0415
qr_code = pyqrcode.create(data)
@@ -59,7 +59,7 @@ def _generate_qr_code(data: str) -> str:
def _generate_secret_and_qr_code(username: str) -> tuple[str, str, str]:
"""Generate a secret, url, and QR code."""
import pyotp # pylint: disable=import-outside-toplevel
import pyotp # noqa: PLC0415
ota_secret = pyotp.random_base32()
url = pyotp.totp.TOTP(ota_secret).provisioning_uri(
@@ -107,7 +107,7 @@ class TotpAuthModule(MultiFactorAuthModule):
def _add_ota_secret(self, user_id: str, secret: str | None = None) -> str:
"""Create a ota_secret for user."""
import pyotp # pylint: disable=import-outside-toplevel
import pyotp # noqa: PLC0415
ota_secret: str = secret or pyotp.random_base32()
@@ -163,7 +163,7 @@ class TotpAuthModule(MultiFactorAuthModule):
def _validate_2fa(self, user_id: str, code: str) -> bool:
"""Validate two factor authentication code."""
import pyotp # pylint: disable=import-outside-toplevel
import pyotp # noqa: PLC0415
if (ota_secret := self._users.get(user_id)) is None: # type: ignore[union-attr]
# even we cannot find user, we still do verify
@@ -196,7 +196,7 @@ class TotpSetupFlow(SetupFlow[TotpAuthModule]):
Return self.async_show_form(step_id='init') if user_input is None.
Return self.async_create_entry(data={'result': result}) if finish.
"""
import pyotp # pylint: disable=import-outside-toplevel
import pyotp # noqa: PLC0415
errors: dict[str, str] = {}

View File

@@ -1,29 +0,0 @@
"""Enum backports from standard lib.
This file contained the backport of the StrEnum of Python 3.11.
Since we have dropped support for Python 3.10, we can remove this backport.
This file is kept for now to avoid breaking custom components that might
import it.
"""
from __future__ import annotations
from enum import StrEnum as _StrEnum
from functools import partial
from homeassistant.helpers.deprecation import (
DeprecatedAlias,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
# StrEnum deprecated as of 2024.5 use enum.StrEnum instead.
_DEPRECATED_StrEnum = DeprecatedAlias(_StrEnum, "enum.StrEnum", "2025.5")
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View File

@@ -1,31 +0,0 @@
"""Functools backports from standard lib.
This file contained the backport of the cached_property implementation of Python 3.12.
Since we have dropped support for Python 3.11, we can remove this backport.
This file is kept for now to avoid breaking custom components that might
import it.
"""
from __future__ import annotations
# pylint: disable-next=hass-deprecated-import
from functools import cached_property as _cached_property, partial
from homeassistant.helpers.deprecation import (
DeprecatedAlias,
all_with_deprecated_constants,
check_if_deprecated_constant,
dir_with_deprecated_constants,
)
# cached_property deprecated as of 2024.5 use functools.cached_property instead.
_DEPRECATED_cached_property = DeprecatedAlias(
_cached_property, "functools.cached_property", "2025.5"
)
__getattr__ = partial(check_if_deprecated_constant, module_globals=globals())
__dir__ = partial(
dir_with_deprecated_constants, module_globals_keys=[*globals().keys()]
)
__all__ = all_with_deprecated_constants(globals())

View File

@@ -75,8 +75,8 @@ from .core_config import async_process_ha_core_config
from .exceptions import HomeAssistantError
from .helpers import (
area_registry,
backup,
category_registry,
condition,
config_validation as cv,
device_registry,
entity,
@@ -89,6 +89,7 @@ from .helpers import (
restore_state,
template,
translation,
trigger,
)
from .helpers.dispatcher import async_dispatcher_send_internal
from .helpers.storage import get_internal_store_manager
@@ -171,8 +172,6 @@ FRONTEND_INTEGRATIONS = {
# Stage 0 is divided into substages. Each substage has a name, a set of integrations and a timeout.
# The substage containing recorder should have no timeout, as it could cancel a database migration.
# Recorder freezes "recorder" timeout during a migration, but it does not freeze other timeouts.
# The substages preceding it should also have no timeout, until we ensure that the recorder
# is not accidentally promoted as a dependency of any of the integrations in them.
# If we add timeouts to the frontend substages, we should make sure they don't apply in recovery mode.
STAGE_0_INTEGRATIONS = (
# Load logging and http deps as soon as possible
@@ -396,7 +395,7 @@ async def async_setup_hass(
def open_hass_ui(hass: core.HomeAssistant) -> None:
"""Open the UI."""
import webbrowser # pylint: disable=import-outside-toplevel
import webbrowser # noqa: PLC0415
if hass.config.api is None or "frontend" not in hass.config.components:
_LOGGER.warning("Cannot launch the UI because frontend not loaded")
@@ -454,6 +453,8 @@ async def async_load_base_functionality(hass: core.HomeAssistant) -> None:
create_eager_task(restore_state.async_load(hass)),
create_eager_task(hass.config_entries.async_initialize()),
create_eager_task(async_get_system_info(hass)),
create_eager_task(condition.async_setup(hass)),
create_eager_task(trigger.async_setup(hass)),
)
@@ -563,8 +564,7 @@ async def async_enable_logging(
if not log_no_color:
try:
# pylint: disable-next=import-outside-toplevel
from colorlog import ColoredFormatter
from colorlog import ColoredFormatter # noqa: PLC0415
# basicConfig must be called after importing colorlog in order to
# ensure that the handlers it sets up wraps the correct streams.
@@ -881,10 +881,6 @@ async def _async_set_up_integrations(
if "recorder" in all_domains:
recorder.async_initialize_recorder(hass)
# Initialize backup
if "backup" in all_domains:
backup.async_initialize_backup(hass)
stages: list[tuple[str, set[str], int | None]] = [
*(
(name, domain_group, timeout)
@@ -929,7 +925,11 @@ async def _async_set_up_integrations(
await _async_setup_multi_components(hass, stage_all_domains, config)
continue
try:
async with hass.timeout.async_timeout(timeout, cool_down=COOLDOWN_TIME):
async with hass.timeout.async_timeout(
timeout,
cool_down=COOLDOWN_TIME,
cancel_message=f"Bootstrap stage {name} timeout",
):
await _async_setup_multi_components(hass, stage_all_domains, config)
except TimeoutError:
_LOGGER.warning(
@@ -941,7 +941,11 @@ async def _async_set_up_integrations(
# Wrap up startup
_LOGGER.debug("Waiting for startup to wrap up")
try:
async with hass.timeout.async_timeout(WRAP_UP_TIMEOUT, cool_down=COOLDOWN_TIME):
async with hass.timeout.async_timeout(
WRAP_UP_TIMEOUT,
cool_down=COOLDOWN_TIME,
cancel_message="Bootstrap startup wrap up timeout",
):
await hass.async_block_till_done()
except TimeoutError:
_LOGGER.warning(

View File

@@ -1,5 +1,11 @@
{
"domain": "sony",
"name": "Sony",
"integrations": ["braviatv", "ps4", "sony_projector", "songpal"]
"integrations": [
"braviatv",
"ps4",
"sony_projector",
"songpal",
"playstation_network"
]
}

View File

@@ -0,0 +1,5 @@
{
"domain": "tilt",
"name": "Tilt",
"integrations": ["tilt_ble", "tilt_pi"]
}

View File

@@ -14,30 +14,24 @@ from jaraco.abode.exceptions import (
)
from jaraco.abode.helpers.timeline import Groups as GROUPS
from requests.exceptions import ConnectTimeout, HTTPError
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import (
ATTR_DATE,
ATTR_DEVICE_ID,
ATTR_ENTITY_ID,
ATTR_TIME,
CONF_PASSWORD,
CONF_USERNAME,
EVENT_HOMEASSISTANT_STOP,
Platform,
)
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant, ServiceCall
from homeassistant.core import CALLBACK_TYPE, Event, HomeAssistant
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import dispatcher_send
from homeassistant.helpers.typing import ConfigType
from .const import CONF_POLLING, DOMAIN, LOGGER
SERVICE_SETTINGS = "change_setting"
SERVICE_CAPTURE_IMAGE = "capture_image"
SERVICE_TRIGGER_AUTOMATION = "trigger_automation"
from .services import async_setup_services
ATTR_DEVICE_NAME = "device_name"
ATTR_DEVICE_TYPE = "device_type"
@@ -45,22 +39,12 @@ ATTR_EVENT_CODE = "event_code"
ATTR_EVENT_NAME = "event_name"
ATTR_EVENT_TYPE = "event_type"
ATTR_EVENT_UTC = "event_utc"
ATTR_SETTING = "setting"
ATTR_USER_NAME = "user_name"
ATTR_APP_TYPE = "app_type"
ATTR_EVENT_BY = "event_by"
ATTR_VALUE = "value"
CONFIG_SCHEMA = cv.removed(DOMAIN, raise_if_present=False)
CHANGE_SETTING_SCHEMA = vol.Schema(
{vol.Required(ATTR_SETTING): cv.string, vol.Required(ATTR_VALUE): cv.string}
)
CAPTURE_IMAGE_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
AUTOMATION_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
PLATFORMS = [
Platform.ALARM_CONTROL_PANEL,
Platform.BINARY_SENSOR,
@@ -85,7 +69,7 @@ class AbodeSystem:
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up the Abode component."""
setup_hass_services(hass)
async_setup_services(hass)
return True
@@ -138,60 +122,6 @@ async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
return unload_ok
def setup_hass_services(hass: HomeAssistant) -> None:
"""Home Assistant services."""
def change_setting(call: ServiceCall) -> None:
"""Change an Abode system setting."""
setting = call.data[ATTR_SETTING]
value = call.data[ATTR_VALUE]
try:
hass.data[DOMAIN].abode.set_setting(setting, value)
except AbodeException as ex:
LOGGER.warning(ex)
def capture_image(call: ServiceCall) -> None:
"""Capture a new image."""
entity_ids = call.data[ATTR_ENTITY_ID]
target_entities = [
entity_id
for entity_id in hass.data[DOMAIN].entity_ids
if entity_id in entity_ids
]
for entity_id in target_entities:
signal = f"abode_camera_capture_{entity_id}"
dispatcher_send(hass, signal)
def trigger_automation(call: ServiceCall) -> None:
"""Trigger an Abode automation."""
entity_ids = call.data[ATTR_ENTITY_ID]
target_entities = [
entity_id
for entity_id in hass.data[DOMAIN].entity_ids
if entity_id in entity_ids
]
for entity_id in target_entities:
signal = f"abode_trigger_automation_{entity_id}"
dispatcher_send(hass, signal)
hass.services.async_register(
DOMAIN, SERVICE_SETTINGS, change_setting, schema=CHANGE_SETTING_SCHEMA
)
hass.services.async_register(
DOMAIN, SERVICE_CAPTURE_IMAGE, capture_image, schema=CAPTURE_IMAGE_SCHEMA
)
hass.services.async_register(
DOMAIN, SERVICE_TRIGGER_AUTOMATION, trigger_automation, schema=AUTOMATION_SCHEMA
)
async def setup_hass_events(hass: HomeAssistant) -> None:
"""Home Assistant start and stop callbacks."""

View File

@@ -0,0 +1,90 @@
"""Support for the Abode Security System."""
from __future__ import annotations
from jaraco.abode.exceptions import Exception as AbodeException
import voluptuous as vol
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.dispatcher import dispatcher_send
from .const import DOMAIN, LOGGER
SERVICE_SETTINGS = "change_setting"
SERVICE_CAPTURE_IMAGE = "capture_image"
SERVICE_TRIGGER_AUTOMATION = "trigger_automation"
ATTR_SETTING = "setting"
ATTR_VALUE = "value"
CHANGE_SETTING_SCHEMA = vol.Schema(
{vol.Required(ATTR_SETTING): cv.string, vol.Required(ATTR_VALUE): cv.string}
)
CAPTURE_IMAGE_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
AUTOMATION_SCHEMA = vol.Schema({ATTR_ENTITY_ID: cv.entity_ids})
def _change_setting(call: ServiceCall) -> None:
"""Change an Abode system setting."""
setting = call.data[ATTR_SETTING]
value = call.data[ATTR_VALUE]
try:
call.hass.data[DOMAIN].abode.set_setting(setting, value)
except AbodeException as ex:
LOGGER.warning(ex)
def _capture_image(call: ServiceCall) -> None:
"""Capture a new image."""
entity_ids = call.data[ATTR_ENTITY_ID]
target_entities = [
entity_id
for entity_id in call.hass.data[DOMAIN].entity_ids
if entity_id in entity_ids
]
for entity_id in target_entities:
signal = f"abode_camera_capture_{entity_id}"
dispatcher_send(call.hass, signal)
def _trigger_automation(call: ServiceCall) -> None:
"""Trigger an Abode automation."""
entity_ids = call.data[ATTR_ENTITY_ID]
target_entities = [
entity_id
for entity_id in call.hass.data[DOMAIN].entity_ids
if entity_id in entity_ids
]
for entity_id in target_entities:
signal = f"abode_trigger_automation_{entity_id}"
dispatcher_send(call.hass, signal)
@callback
def async_setup_services(hass: HomeAssistant) -> None:
"""Home Assistant services."""
hass.services.async_register(
DOMAIN, SERVICE_SETTINGS, _change_setting, schema=CHANGE_SETTING_SCHEMA
)
hass.services.async_register(
DOMAIN, SERVICE_CAPTURE_IMAGE, _capture_image, schema=CAPTURE_IMAGE_SCHEMA
)
hass.services.async_register(
DOMAIN,
SERVICE_TRIGGER_AUTOMATION,
_trigger_automation,
schema=AUTOMATION_SCHEMA,
)

View File

@@ -8,7 +8,7 @@ from homeassistant.core import HomeAssistant
from .const import CONNECTION_TYPE, LOCAL
from .coordinator import AdaxCloudCoordinator, AdaxConfigEntry, AdaxLocalCoordinator
PLATFORMS = [Platform.CLIMATE]
PLATFORMS = [Platform.CLIMATE, Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: AdaxConfigEntry) -> bool:

View File

@@ -41,7 +41,30 @@ class AdaxCloudCoordinator(DataUpdateCoordinator[dict[str, dict[str, Any]]]):
async def _async_update_data(self) -> dict[str, dict[str, Any]]:
"""Fetch data from the Adax."""
rooms = await self.adax_data_handler.get_rooms() or []
try:
if hasattr(self.adax_data_handler, "fetch_rooms_info"):
rooms = await self.adax_data_handler.fetch_rooms_info() or []
_LOGGER.debug("fetch_rooms_info returned: %s", rooms)
else:
_LOGGER.debug("fetch_rooms_info method not available, using get_rooms")
rooms = []
if not rooms:
_LOGGER.debug(
"No rooms from fetch_rooms_info, trying get_rooms as fallback"
)
rooms = await self.adax_data_handler.get_rooms() or []
_LOGGER.debug("get_rooms fallback returned: %s", rooms)
if not rooms:
raise UpdateFailed("No rooms available from Adax API")
except OSError as e:
raise UpdateFailed(f"Error communicating with API: {e}") from e
for room in rooms:
room["energyWh"] = int(room.get("energyWh", 0))
return {r["id"]: r for r in rooms}

View File

@@ -0,0 +1,77 @@
"""Support for Adax energy sensors."""
from __future__ import annotations
from typing import cast
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorStateClass,
)
from homeassistant.const import UnitOfEnergy
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import AdaxConfigEntry
from .const import CONNECTION_TYPE, DOMAIN, LOCAL
from .coordinator import AdaxCloudCoordinator
async def async_setup_entry(
hass: HomeAssistant,
entry: AdaxConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up the Adax energy sensors with config flow."""
if entry.data.get(CONNECTION_TYPE) != LOCAL:
cloud_coordinator = cast(AdaxCloudCoordinator, entry.runtime_data)
# Create individual energy sensors for each device
async_add_entities(
AdaxEnergySensor(cloud_coordinator, device_id)
for device_id in cloud_coordinator.data
)
class AdaxEnergySensor(CoordinatorEntity[AdaxCloudCoordinator], SensorEntity):
"""Representation of an Adax energy sensor."""
_attr_has_entity_name = True
_attr_translation_key = "energy"
_attr_device_class = SensorDeviceClass.ENERGY
_attr_native_unit_of_measurement = UnitOfEnergy.WATT_HOUR
_attr_suggested_unit_of_measurement = UnitOfEnergy.KILO_WATT_HOUR
_attr_state_class = SensorStateClass.TOTAL_INCREASING
_attr_suggested_display_precision = 3
def __init__(
self,
coordinator: AdaxCloudCoordinator,
device_id: str,
) -> None:
"""Initialize the energy sensor."""
super().__init__(coordinator)
self._device_id = device_id
room = coordinator.data[device_id]
self._attr_unique_id = f"{room['homeId']}_{device_id}_energy"
self._attr_device_info = DeviceInfo(
identifiers={(DOMAIN, device_id)},
name=room["name"],
manufacturer="Adax",
)
@property
def available(self) -> bool:
"""Return True if entity is available."""
return (
super().available and "energyWh" in self.coordinator.data[self._device_id]
)
@property
def native_value(self) -> int:
"""Return the native value of the sensor."""
return int(self.coordinator.data[self._device_id]["energyWh"])

View File

@@ -185,6 +185,7 @@ FORECAST_SENSORS: Final[tuple[AemetSensorEntityDescription, ...]] = (
keys=[AOD_TOWN, AOD_FORECAST_DAILY, AOD_FORECAST_CURRENT, AOD_WIND_DIRECTION],
name="Daily forecast wind bearing",
native_unit_of_measurement=DEGREE,
device_class=SensorDeviceClass.WIND_DIRECTION,
),
AemetSensorEntityDescription(
entity_registry_enabled_default=False,
@@ -192,6 +193,7 @@ FORECAST_SENSORS: Final[tuple[AemetSensorEntityDescription, ...]] = (
keys=[AOD_TOWN, AOD_FORECAST_HOURLY, AOD_FORECAST_CURRENT, AOD_WIND_DIRECTION],
name="Hourly forecast wind bearing",
native_unit_of_measurement=DEGREE,
device_class=SensorDeviceClass.WIND_DIRECTION,
),
AemetSensorEntityDescription(
entity_registry_enabled_default=False,
@@ -334,7 +336,8 @@ WEATHER_SENSORS: Final[tuple[AemetSensorEntityDescription, ...]] = (
keys=[AOD_WEATHER, AOD_WIND_DIRECTION],
name="Wind bearing",
native_unit_of_measurement=DEGREE,
state_class=SensorStateClass.MEASUREMENT,
state_class=SensorStateClass.MEASUREMENT_ANGLE,
device_class=SensorDeviceClass.WIND_DIRECTION,
),
AemetSensorEntityDescription(
key=ATTR_API_WIND_MAX_SPEED,

View File

@@ -15,7 +15,7 @@ from homeassistant.helpers.entity_platform import (
)
from . import AgentDVRConfigEntry
from .const import ATTRIBUTION, CAMERA_SCAN_INTERVAL_SECS, DOMAIN as AGENT_DOMAIN
from .const import ATTRIBUTION, CAMERA_SCAN_INTERVAL_SECS, DOMAIN
SCAN_INTERVAL = timedelta(seconds=CAMERA_SCAN_INTERVAL_SECS)
@@ -82,7 +82,7 @@ class AgentCamera(MjpegCamera):
still_image_url=f"{device.client._server_url}{device.still_image_url}&size={device.mjpegStreamWidth}x{device.mjpegStreamHeight}", # noqa: SLF001
)
self._attr_device_info = DeviceInfo(
identifiers={(AGENT_DOMAIN, self.unique_id)},
identifiers={(DOMAIN, self.unique_id)},
manufacturer="Agent",
model="Camera",
name=f"{device.client.name} {device.name}",

View File

@@ -0,0 +1,162 @@
"""Integration to offer AI tasks to Home Assistant."""
import logging
from typing import Any
import voluptuous as vol
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import ATTR_ENTITY_ID, CONF_DESCRIPTION, CONF_SELECTOR
from homeassistant.core import (
HassJobType,
HomeAssistant,
ServiceCall,
ServiceResponse,
SupportsResponse,
callback,
)
from homeassistant.helpers import config_validation as cv, selector, storage
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import UNDEFINED, ConfigType, UndefinedType
from .const import (
ATTR_INSTRUCTIONS,
ATTR_REQUIRED,
ATTR_STRUCTURE,
ATTR_TASK_NAME,
DATA_COMPONENT,
DATA_PREFERENCES,
DOMAIN,
SERVICE_GENERATE_DATA,
AITaskEntityFeature,
)
from .entity import AITaskEntity
from .http import async_setup as async_setup_http
from .task import GenDataTask, GenDataTaskResult, async_generate_data
__all__ = [
"DOMAIN",
"AITaskEntity",
"AITaskEntityFeature",
"GenDataTask",
"GenDataTaskResult",
"async_generate_data",
"async_setup",
"async_setup_entry",
"async_unload_entry",
]
_LOGGER = logging.getLogger(__name__)
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
STRUCTURE_FIELD_SCHEMA = vol.Schema(
{
vol.Optional(CONF_DESCRIPTION): str,
vol.Optional(ATTR_REQUIRED): bool,
vol.Required(CONF_SELECTOR): selector.validate_selector,
}
)
def _validate_structure_fields(value: dict[str, Any]) -> vol.Schema:
"""Validate the structure fields as a voluptuous Schema."""
if not isinstance(value, dict):
raise vol.Invalid("Structure must be a dictionary")
fields = {}
for k, v in value.items():
field_class = vol.Required if v.get(ATTR_REQUIRED, False) else vol.Optional
fields[field_class(k, description=v.get(CONF_DESCRIPTION))] = selector.selector(
v[CONF_SELECTOR]
)
return vol.Schema(fields, extra=vol.PREVENT_EXTRA)
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Register the process service."""
entity_component = EntityComponent[AITaskEntity](_LOGGER, DOMAIN, hass)
hass.data[DATA_COMPONENT] = entity_component
hass.data[DATA_PREFERENCES] = AITaskPreferences(hass)
await hass.data[DATA_PREFERENCES].async_load()
async_setup_http(hass)
hass.services.async_register(
DOMAIN,
SERVICE_GENERATE_DATA,
async_service_generate_data,
schema=vol.Schema(
{
vol.Required(ATTR_TASK_NAME): cv.string,
vol.Optional(ATTR_ENTITY_ID): cv.entity_id,
vol.Required(ATTR_INSTRUCTIONS): cv.string,
vol.Optional(ATTR_STRUCTURE): vol.All(
vol.Schema({str: STRUCTURE_FIELD_SCHEMA}),
_validate_structure_fields,
),
}
),
supports_response=SupportsResponse.ONLY,
job_type=HassJobType.Coroutinefunction,
)
return True
async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Set up a config entry."""
return await hass.data[DATA_COMPONENT].async_setup_entry(entry)
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.data[DATA_COMPONENT].async_unload_entry(entry)
async def async_service_generate_data(call: ServiceCall) -> ServiceResponse:
"""Run the run task service."""
result = await async_generate_data(hass=call.hass, **call.data)
return result.as_dict()
class AITaskPreferences:
"""AI Task preferences."""
KEYS = ("gen_data_entity_id",)
gen_data_entity_id: str | None = None
def __init__(self, hass: HomeAssistant) -> None:
"""Initialize the preferences."""
self._store: storage.Store[dict[str, str | None]] = storage.Store(
hass, 1, DOMAIN
)
async def async_load(self) -> None:
"""Load the data from the store."""
data = await self._store.async_load()
if data is None:
return
for key in self.KEYS:
setattr(self, key, data[key])
@callback
def async_set_preferences(
self,
*,
gen_data_entity_id: str | None | UndefinedType = UNDEFINED,
) -> None:
"""Set the preferences."""
changed = False
for key, value in (("gen_data_entity_id", gen_data_entity_id),):
if value is not UNDEFINED:
if getattr(self, key) != value:
setattr(self, key, value)
changed = True
if not changed:
return
self._store.async_delay_save(self.as_dict, 10)
@callback
def as_dict(self) -> dict[str, str | None]:
"""Get the current preferences."""
return {key: getattr(self, key) for key in self.KEYS}

View File

@@ -0,0 +1,36 @@
"""Constants for the AI Task integration."""
from __future__ import annotations
from enum import IntFlag
from typing import TYPE_CHECKING, Final
from homeassistant.util.hass_dict import HassKey
if TYPE_CHECKING:
from homeassistant.helpers.entity_component import EntityComponent
from . import AITaskPreferences
from .entity import AITaskEntity
DOMAIN = "ai_task"
DATA_COMPONENT: HassKey[EntityComponent[AITaskEntity]] = HassKey(DOMAIN)
DATA_PREFERENCES: HassKey[AITaskPreferences] = HassKey(f"{DOMAIN}_preferences")
SERVICE_GENERATE_DATA = "generate_data"
ATTR_INSTRUCTIONS: Final = "instructions"
ATTR_TASK_NAME: Final = "task_name"
ATTR_STRUCTURE: Final = "structure"
ATTR_REQUIRED: Final = "required"
DEFAULT_SYSTEM_PROMPT = (
"You are a Home Assistant expert and help users with their tasks."
)
class AITaskEntityFeature(IntFlag):
"""Supported features of the AI task entity."""
GENERATE_DATA = 1
"""Generate data based on instructions."""

View File

@@ -0,0 +1,103 @@
"""Entity for the AI Task integration."""
from collections.abc import AsyncGenerator
import contextlib
from typing import final
from propcache.api import cached_property
from homeassistant.components.conversation import (
ChatLog,
UserContent,
async_get_chat_log,
)
from homeassistant.const import STATE_UNAVAILABLE, STATE_UNKNOWN
from homeassistant.helpers import llm
from homeassistant.helpers.chat_session import async_get_chat_session
from homeassistant.helpers.restore_state import RestoreEntity
from homeassistant.util import dt as dt_util
from .const import DEFAULT_SYSTEM_PROMPT, DOMAIN, AITaskEntityFeature
from .task import GenDataTask, GenDataTaskResult
class AITaskEntity(RestoreEntity):
"""Entity that supports conversations."""
_attr_should_poll = False
_attr_supported_features = AITaskEntityFeature(0)
__last_activity: str | None = None
@property
@final
def state(self) -> str | None:
"""Return the state of the entity."""
if self.__last_activity is None:
return None
return self.__last_activity
@cached_property
def supported_features(self) -> AITaskEntityFeature:
"""Flag supported features."""
return self._attr_supported_features
async def async_internal_added_to_hass(self) -> None:
"""Call when the entity is added to hass."""
await super().async_internal_added_to_hass()
state = await self.async_get_last_state()
if (
state is not None
and state.state is not None
and state.state not in (STATE_UNAVAILABLE, STATE_UNKNOWN)
):
self.__last_activity = state.state
@final
@contextlib.asynccontextmanager
async def _async_get_ai_task_chat_log(
self,
task: GenDataTask,
) -> AsyncGenerator[ChatLog]:
"""Context manager used to manage the ChatLog used during an AI Task."""
# pylint: disable-next=contextmanager-generator-missing-cleanup
with (
async_get_chat_session(self.hass) as session,
async_get_chat_log(
self.hass,
session,
None,
) as chat_log,
):
await chat_log.async_provide_llm_data(
llm.LLMContext(
platform=self.platform.domain,
context=None,
language=None,
assistant=DOMAIN,
device_id=None,
),
user_llm_prompt=DEFAULT_SYSTEM_PROMPT,
)
chat_log.async_add_user_content(UserContent(task.instructions))
yield chat_log
@final
async def internal_async_generate_data(
self,
task: GenDataTask,
) -> GenDataTaskResult:
"""Run a gen data task."""
self.__last_activity = dt_util.utcnow().isoformat()
self.async_write_ha_state()
async with self._async_get_ai_task_chat_log(task) as chat_log:
return await self._async_generate_data(task, chat_log)
async def _async_generate_data(
self,
task: GenDataTask,
chat_log: ChatLog,
) -> GenDataTaskResult:
"""Handle a gen data task."""
raise NotImplementedError

View File

@@ -0,0 +1,54 @@
"""HTTP endpoint for AI Task integration."""
from typing import Any
import voluptuous as vol
from homeassistant.components import websocket_api
from homeassistant.core import HomeAssistant, callback
from .const import DATA_PREFERENCES
@callback
def async_setup(hass: HomeAssistant) -> None:
"""Set up the HTTP API for the conversation integration."""
websocket_api.async_register_command(hass, websocket_get_preferences)
websocket_api.async_register_command(hass, websocket_set_preferences)
@websocket_api.websocket_command(
{
vol.Required("type"): "ai_task/preferences/get",
}
)
@callback
def websocket_get_preferences(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Get AI task preferences."""
preferences = hass.data[DATA_PREFERENCES]
connection.send_result(msg["id"], preferences.as_dict())
@websocket_api.websocket_command(
{
vol.Required("type"): "ai_task/preferences/set",
vol.Optional("gen_data_entity_id"): vol.Any(str, None),
}
)
@websocket_api.require_admin
@callback
def websocket_set_preferences(
hass: HomeAssistant,
connection: websocket_api.ActiveConnection,
msg: dict[str, Any],
) -> None:
"""Set AI task preferences."""
preferences = hass.data[DATA_PREFERENCES]
msg.pop("type")
msg_id = msg.pop("id")
preferences.async_set_preferences(**msg)
connection.send_result(msg_id, preferences.as_dict())

View File

@@ -0,0 +1,7 @@
{
"services": {
"generate_data": {
"service": "mdi:file-star-four-points-outline"
}
}
}

View File

@@ -0,0 +1,9 @@
{
"domain": "ai_task",
"name": "AI Task",
"codeowners": ["@home-assistant/core"],
"dependencies": ["conversation"],
"documentation": "https://www.home-assistant.io/integrations/ai_task",
"integration_type": "system",
"quality_scale": "internal"
}

View File

@@ -0,0 +1,25 @@
generate_data:
fields:
task_name:
example: "home summary"
required: true
selector:
text:
instructions:
example: "Generate a funny notification that the garage door was left open"
required: true
selector:
text:
entity_id:
required: false
selector:
entity:
domain: ai_task
supported_features:
- ai_task.AITaskEntityFeature.GENERATE_DATA
structure:
advanced: true
required: false
example: '{ "name": { "selector": { "text": }, "description": "Name of the user", "required": "True" } } }, "age": { "selector": { "number": }, "description": "Age of the user" } }'
selector:
object:

View File

@@ -0,0 +1,26 @@
{
"services": {
"generate_data": {
"name": "Generate data",
"description": "Uses AI to run a task that generates data.",
"fields": {
"task_name": {
"name": "Task name",
"description": "Name of the task."
},
"instructions": {
"name": "Instructions",
"description": "Instructions on what needs to be done."
},
"entity_id": {
"name": "Entity ID",
"description": "Entity ID to run the task on. If not provided, the preferred entity will be used."
},
"structure": {
"name": "Structured output",
"description": "When set, the AI Task will output fields with this in structure. The structure is a dictionary where the keys are the field names and the values contain a 'description', a 'selector', and an optional 'required' field."
}
}
}
}
}

View File

@@ -0,0 +1,82 @@
"""AI tasks to be handled by agents."""
from __future__ import annotations
from dataclasses import dataclass
from typing import Any
import voluptuous as vol
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from .const import DATA_COMPONENT, DATA_PREFERENCES, AITaskEntityFeature
async def async_generate_data(
hass: HomeAssistant,
*,
task_name: str,
entity_id: str | None = None,
instructions: str,
structure: vol.Schema | None = None,
) -> GenDataTaskResult:
"""Run a task in the AI Task integration."""
if entity_id is None:
entity_id = hass.data[DATA_PREFERENCES].gen_data_entity_id
if entity_id is None:
raise HomeAssistantError("No entity_id provided and no preferred entity set")
entity = hass.data[DATA_COMPONENT].get_entity(entity_id)
if entity is None:
raise HomeAssistantError(f"AI Task entity {entity_id} not found")
if AITaskEntityFeature.GENERATE_DATA not in entity.supported_features:
raise HomeAssistantError(
f"AI Task entity {entity_id} does not support generating data"
)
return await entity.internal_async_generate_data(
GenDataTask(
name=task_name,
instructions=instructions,
structure=structure,
)
)
@dataclass(slots=True)
class GenDataTask:
"""Gen data task to be processed."""
name: str
"""Name of the task."""
instructions: str
"""Instructions on what needs to be done."""
structure: vol.Schema | None = None
"""Optional structure for the data to be generated."""
def __str__(self) -> str:
"""Return task as a string."""
return f"<GenDataTask {self.name}: {id(self)}>"
@dataclass(slots=True)
class GenDataTaskResult:
"""Result of gen data task."""
conversation_id: str
"""Unique identifier for the conversation."""
data: Any
"""Data generated by the task."""
def as_dict(self) -> dict[str, Any]:
"""Return result as a dict."""
return {
"conversation_id": self.conversation_id,
"data": self.data,
}

View File

@@ -39,14 +39,14 @@ class AirlyFlowHandler(ConfigFlow, domain=DOMAIN):
)
self._abort_if_unique_id_configured()
try:
location_point_valid = await test_location(
location_point_valid = await check_location(
websession,
user_input["api_key"],
user_input["latitude"],
user_input["longitude"],
)
if not location_point_valid:
location_nearest_valid = await test_location(
location_nearest_valid = await check_location(
websession,
user_input["api_key"],
user_input["latitude"],
@@ -88,7 +88,7 @@ class AirlyFlowHandler(ConfigFlow, domain=DOMAIN):
)
async def test_location(
async def check_location(
client: ClientSession,
api_key: str,
latitude: float,

View File

@@ -71,7 +71,7 @@ class AirNowDataUpdateCoordinator(DataUpdateCoordinator[dict[str, Any]]):
async def _async_update_data(self) -> dict[str, Any]:
"""Update data via library."""
data = {}
data: dict[str, Any] = {}
try:
obs = await self.airnow.observations.latLong(
self.latitude,

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/airnow",
"iot_class": "cloud_polling",
"loggers": ["pyairnow"],
"requirements": ["pyairnow==1.2.1"]
"requirements": ["pyairnow==1.3.1"]
}

View File

@@ -10,7 +10,7 @@ from aioairq.core import AirQ, identify_warming_up_sensors
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_IP_ADDRESS, CONF_PASSWORD
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.aiohttp_client import async_create_clientsession
from homeassistant.helpers.device_registry import DeviceInfo
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator
@@ -39,7 +39,7 @@ class AirQCoordinator(DataUpdateCoordinator):
name=DOMAIN,
update_interval=timedelta(seconds=UPDATE_INTERVAL),
)
session = async_get_clientsession(hass)
session = async_create_clientsession(hass)
self.airq = AirQ(
entry.data[CONF_IP_ADDRESS], entry.data[CONF_PASSWORD], session
)

View File

@@ -7,5 +7,5 @@
"integration_type": "hub",
"iot_class": "local_polling",
"loggers": ["aioairq"],
"requirements": ["aioairq==0.4.4"]
"requirements": ["aioairq==0.4.6"]
}

View File

@@ -5,23 +5,22 @@ from __future__ import annotations
from datetime import timedelta
import logging
from airthings import Airthings, AirthingsDevice, AirthingsError
from airthings import Airthings
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_ID, Platform
from homeassistant.core import HomeAssistant
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import CONF_SECRET, DOMAIN
from .const import CONF_SECRET
from .coordinator import AirthingsDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
PLATFORMS: list[Platform] = [Platform.SENSOR]
SCAN_INTERVAL = timedelta(minutes=6)
type AirthingsDataCoordinatorType = DataUpdateCoordinator[dict[str, AirthingsDevice]]
type AirthingsConfigEntry = ConfigEntry[AirthingsDataCoordinatorType]
type AirthingsConfigEntry = ConfigEntry[AirthingsDataUpdateCoordinator]
async def async_setup_entry(hass: HomeAssistant, entry: AirthingsConfigEntry) -> bool:
@@ -32,21 +31,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: AirthingsConfigEntry) ->
async_get_clientsession(hass),
)
async def _update_method() -> dict[str, AirthingsDevice]:
"""Get the latest data from Airthings."""
try:
return await airthings.update_devices() # type: ignore[no-any-return]
except AirthingsError as err:
raise UpdateFailed(f"Unable to fetch data: {err}") from err
coordinator = AirthingsDataUpdateCoordinator(hass, airthings)
coordinator = DataUpdateCoordinator(
hass,
_LOGGER,
config_entry=entry,
name=DOMAIN,
update_method=_update_method,
update_interval=SCAN_INTERVAL,
)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator

View File

@@ -0,0 +1,36 @@
"""The Airthings integration."""
from datetime import timedelta
import logging
from airthings import Airthings, AirthingsDevice, AirthingsError
from homeassistant.core import HomeAssistant
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import DOMAIN
_LOGGER = logging.getLogger(__name__)
SCAN_INTERVAL = timedelta(minutes=6)
class AirthingsDataUpdateCoordinator(DataUpdateCoordinator[dict[str, AirthingsDevice]]):
"""Coordinator for Airthings data updates."""
def __init__(self, hass: HomeAssistant, airthings: Airthings) -> None:
"""Initialize the coordinator."""
super().__init__(
hass,
_LOGGER,
name=DOMAIN,
update_method=self._update_method,
update_interval=SCAN_INTERVAL,
)
self.airthings = airthings
async def _update_method(self) -> dict[str, AirthingsDevice]:
"""Get the latest data from Airthings."""
try:
return await self.airthings.update_devices() # type: ignore[no-any-return]
except AirthingsError as err:
raise UpdateFailed(f"Unable to fetch data: {err}") from err

View File

@@ -19,6 +19,7 @@ from homeassistant.const import (
SIGNAL_STRENGTH_DECIBELS,
EntityCategory,
UnitOfPressure,
UnitOfSoundPressure,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
@@ -27,32 +28,44 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import AirthingsConfigEntry, AirthingsDataCoordinatorType
from . import AirthingsConfigEntry
from .const import DOMAIN
from .coordinator import AirthingsDataUpdateCoordinator
SENSORS: dict[str, SensorEntityDescription] = {
"radonShortTermAvg": SensorEntityDescription(
key="radonShortTermAvg",
native_unit_of_measurement="Bq/m³",
translation_key="radon",
suggested_display_precision=0,
),
"temp": SensorEntityDescription(
key="temp",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
),
"humidity": SensorEntityDescription(
key="humidity",
device_class=SensorDeviceClass.HUMIDITY,
native_unit_of_measurement=PERCENTAGE,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"pressure": SensorEntityDescription(
key="pressure",
device_class=SensorDeviceClass.ATMOSPHERIC_PRESSURE,
native_unit_of_measurement=UnitOfPressure.MBAR,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=1,
),
"sla": SensorEntityDescription(
key="sla",
device_class=SensorDeviceClass.SOUND_PRESSURE,
native_unit_of_measurement=UnitOfSoundPressure.WEIGHTED_DECIBEL_A,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"battery": SensorEntityDescription(
key="battery",
@@ -60,40 +73,47 @@ SENSORS: dict[str, SensorEntityDescription] = {
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"co2": SensorEntityDescription(
key="co2",
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"voc": SensorEntityDescription(
key="voc",
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS_PARTS,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_BILLION,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"light": SensorEntityDescription(
key="light",
native_unit_of_measurement=PERCENTAGE,
translation_key="light",
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"lux": SensorEntityDescription(
key="lux",
device_class=SensorDeviceClass.ILLUMINANCE,
native_unit_of_measurement=LIGHT_LUX,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"virusRisk": SensorEntityDescription(
key="virusRisk",
translation_key="virus_risk",
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"mold": SensorEntityDescription(
key="mold",
translation_key="mold",
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"rssi": SensorEntityDescription(
key="rssi",
@@ -102,18 +122,21 @@ SENSORS: dict[str, SensorEntityDescription] = {
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"pm1": SensorEntityDescription(
key="pm1",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM1,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
"pm25": SensorEntityDescription(
key="pm25",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
device_class=SensorDeviceClass.PM25,
state_class=SensorStateClass.MEASUREMENT,
suggested_display_precision=0,
),
}
@@ -140,7 +163,7 @@ async def async_setup_entry(
class AirthingsHeaterEnergySensor(
CoordinatorEntity[AirthingsDataCoordinatorType], SensorEntity
CoordinatorEntity[AirthingsDataUpdateCoordinator], SensorEntity
):
"""Representation of a Airthings Sensor device."""
@@ -149,7 +172,7 @@ class AirthingsHeaterEnergySensor(
def __init__(
self,
coordinator: AirthingsDataCoordinatorType,
coordinator: AirthingsDataUpdateCoordinator,
airthings_device: AirthingsDevice,
entity_description: SensorEntityDescription,
) -> None:

View File

@@ -6,5 +6,5 @@
"documentation": "https://www.home-assistant.io/integrations/airtouch5",
"iot_class": "local_push",
"loggers": ["airtouch5py"],
"requirements": ["airtouch5py==0.2.11"]
"requirements": ["airtouch5py==0.3.0"]
}

View File

@@ -8,6 +8,7 @@ from .coordinator import AmazonConfigEntry, AmazonDevicesCoordinator
PLATFORMS = [
Platform.BINARY_SENSOR,
Platform.NOTIFY,
Platform.SENSOR,
Platform.SWITCH,
]
@@ -28,5 +29,8 @@ async def async_setup_entry(hass: HomeAssistant, entry: AmazonConfigEntry) -> bo
async def async_unload_entry(hass: HomeAssistant, entry: AmazonConfigEntry) -> bool:
"""Unload a config entry."""
await entry.runtime_data.api.close()
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
coordinator = entry.runtime_data
if unload_ok := await hass.config_entries.async_unload_platforms(entry, PLATFORMS):
await coordinator.api.close()
return unload_ok

View File

@@ -7,6 +7,7 @@ from dataclasses import dataclass
from typing import Final
from aioamazondevices.api import AmazonDevice
from aioamazondevices.const import SENSOR_STATE_OFF
from homeassistant.components.binary_sensor import (
BinarySensorDeviceClass,
@@ -28,7 +29,8 @@ PARALLEL_UPDATES = 0
class AmazonBinarySensorEntityDescription(BinarySensorEntityDescription):
"""Alexa Devices binary sensor entity description."""
is_on_fn: Callable[[AmazonDevice], bool]
is_on_fn: Callable[[AmazonDevice, str], bool]
is_supported: Callable[[AmazonDevice, str], bool] = lambda device, key: True
BINARY_SENSORS: Final = (
@@ -36,13 +38,49 @@ BINARY_SENSORS: Final = (
key="online",
device_class=BinarySensorDeviceClass.CONNECTIVITY,
entity_category=EntityCategory.DIAGNOSTIC,
is_on_fn=lambda _device: _device.online,
is_on_fn=lambda device, _: device.online,
),
AmazonBinarySensorEntityDescription(
key="bluetooth",
entity_category=EntityCategory.DIAGNOSTIC,
translation_key="bluetooth",
is_on_fn=lambda _device: _device.bluetooth_state,
is_on_fn=lambda device, _: device.bluetooth_state,
),
AmazonBinarySensorEntityDescription(
key="babyCryDetectionState",
translation_key="baby_cry_detection",
is_on_fn=lambda device, key: (device.sensors[key].value != SENSOR_STATE_OFF),
is_supported=lambda device, key: device.sensors.get(key) is not None,
),
AmazonBinarySensorEntityDescription(
key="beepingApplianceDetectionState",
translation_key="beeping_appliance_detection",
is_on_fn=lambda device, key: (device.sensors[key].value != SENSOR_STATE_OFF),
is_supported=lambda device, key: device.sensors.get(key) is not None,
),
AmazonBinarySensorEntityDescription(
key="coughDetectionState",
translation_key="cough_detection",
is_on_fn=lambda device, key: (device.sensors[key].value != SENSOR_STATE_OFF),
is_supported=lambda device, key: device.sensors.get(key) is not None,
),
AmazonBinarySensorEntityDescription(
key="dogBarkDetectionState",
translation_key="dog_bark_detection",
is_on_fn=lambda device, key: (device.sensors[key].value != SENSOR_STATE_OFF),
is_supported=lambda device, key: device.sensors.get(key) is not None,
),
AmazonBinarySensorEntityDescription(
key="humanPresenceDetectionState",
device_class=BinarySensorDeviceClass.MOTION,
is_on_fn=lambda device, key: (device.sensors[key].value != SENSOR_STATE_OFF),
is_supported=lambda device, key: device.sensors.get(key) is not None,
),
AmazonBinarySensorEntityDescription(
key="waterSoundsDetectionState",
translation_key="water_sounds_detection",
is_on_fn=lambda device, key: (device.sensors[key].value != SENSOR_STATE_OFF),
is_supported=lambda device, key: device.sensors.get(key) is not None,
),
)
@@ -60,6 +98,7 @@ async def async_setup_entry(
AmazonBinarySensorEntity(coordinator, serial_num, sensor_desc)
for sensor_desc in BINARY_SENSORS
for serial_num in coordinator.data
if sensor_desc.is_supported(coordinator.data[serial_num], sensor_desc.key)
)
@@ -71,4 +110,6 @@ class AmazonBinarySensorEntity(AmazonEntity, BinarySensorEntity):
@property
def is_on(self) -> bool:
"""Return True if the binary sensor is on."""
return self.entity_description.is_on_fn(self.device)
return self.entity_description.is_on_fn(
self.device, self.entity_description.key
)

View File

@@ -2,19 +2,45 @@
from __future__ import annotations
from collections.abc import Mapping
from typing import Any
from aioamazondevices.api import AmazonEchoApi
from aioamazondevices.exceptions import CannotAuthenticate, CannotConnect
from aioamazondevices.exceptions import CannotAuthenticate, CannotConnect, WrongCountry
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.const import CONF_CODE, CONF_COUNTRY, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
import homeassistant.helpers.config_validation as cv
from homeassistant.helpers.selector import CountrySelector
from .const import CONF_LOGIN_DATA, DOMAIN
STEP_REAUTH_DATA_SCHEMA = vol.Schema(
{
vol.Required(CONF_PASSWORD): cv.string,
vol.Required(CONF_CODE): cv.string,
}
)
async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> dict[str, Any]:
"""Validate the user input allows us to connect."""
api = AmazonEchoApi(
data[CONF_COUNTRY],
data[CONF_USERNAME],
data[CONF_PASSWORD],
)
try:
data = await api.login_mode_interactive(data[CONF_CODE])
finally:
await api.close()
return data
class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Alexa Devices."""
@@ -25,17 +51,14 @@ class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle the initial step."""
errors = {}
if user_input:
client = AmazonEchoApi(
user_input[CONF_COUNTRY],
user_input[CONF_USERNAME],
user_input[CONF_PASSWORD],
)
try:
data = await client.login_mode_interactive(user_input[CONF_CODE])
data = await validate_input(self.hass, user_input)
except CannotConnect:
errors["base"] = "cannot_connect"
except CannotAuthenticate:
errors["base"] = "invalid_auth"
except WrongCountry:
errors["base"] = "wrong_country"
else:
await self.async_set_unique_id(data["customer_info"]["user_id"])
self._abort_if_unique_id_configured()
@@ -44,8 +67,6 @@ class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
title=user_input[CONF_USERNAME],
data=user_input | {CONF_LOGIN_DATA: data},
)
finally:
await client.close()
return self.async_show_form(
step_id="user",
@@ -61,3 +82,43 @@ class AmazonDevicesConfigFlow(ConfigFlow, domain=DOMAIN):
}
),
)
async def async_step_reauth(
self, entry_data: Mapping[str, Any]
) -> ConfigFlowResult:
"""Handle reauth flow."""
self.context["title_placeholders"] = {CONF_USERNAME: entry_data[CONF_USERNAME]}
return await self.async_step_reauth_confirm()
async def async_step_reauth_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reauth confirm."""
errors: dict[str, str] = {}
reauth_entry = self._get_reauth_entry()
entry_data = reauth_entry.data
if user_input is not None:
try:
await validate_input(self.hass, {**reauth_entry.data, **user_input})
except CannotConnect:
errors["base"] = "cannot_connect"
except CannotAuthenticate:
errors["base"] = "invalid_auth"
else:
return self.async_update_reload_and_abort(
reauth_entry,
data={
CONF_USERNAME: entry_data[CONF_USERNAME],
CONF_PASSWORD: entry_data[CONF_PASSWORD],
CONF_CODE: user_input[CONF_CODE],
},
)
return self.async_show_form(
step_id="reauth_confirm",
description_placeholders={CONF_USERNAME: entry_data[CONF_USERNAME]},
data_schema=STEP_REAUTH_DATA_SCHEMA,
errors=errors,
)

View File

@@ -12,10 +12,10 @@ from aioamazondevices.exceptions import (
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_COUNTRY, CONF_PASSWORD, CONF_USERNAME
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryError
from homeassistant.exceptions import ConfigEntryAuthFailed
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import _LOGGER, CONF_LOGIN_DATA
from .const import _LOGGER, CONF_LOGIN_DATA, DOMAIN
SCAN_INTERVAL = 30
@@ -55,4 +55,8 @@ class AmazonDevicesCoordinator(DataUpdateCoordinator[dict[str, AmazonDevice]]):
except (CannotConnect, CannotRetrieveData) as err:
raise UpdateFailed(f"Error occurred while updating {self.name}") from err
except CannotAuthenticate as err:
raise ConfigEntryError("Could not authenticate") from err
raise ConfigEntryAuthFailed(
translation_domain=DOMAIN,
translation_key="invalid_auth",
translation_placeholders={"error": repr(err)},
) from err

View File

@@ -2,9 +2,39 @@
"entity": {
"binary_sensor": {
"bluetooth": {
"default": "mdi:bluetooth",
"default": "mdi:bluetooth-off",
"state": {
"off": "mdi:bluetooth-off"
"on": "mdi:bluetooth"
}
},
"baby_cry_detection": {
"default": "mdi:account-voice-off",
"state": {
"on": "mdi:account-voice"
}
},
"beeping_appliance_detection": {
"default": "mdi:bell-off",
"state": {
"on": "mdi:bell-ring"
}
},
"cough_detection": {
"default": "mdi:blur-off",
"state": {
"on": "mdi:blur"
}
},
"dog_bark_detection": {
"default": "mdi:dog-side-off",
"state": {
"on": "mdi:dog-side"
}
},
"water_sounds_detection": {
"default": "mdi:water-pump-off",
"state": {
"on": "mdi:water-pump"
}
}
}

View File

@@ -8,5 +8,5 @@
"iot_class": "cloud_polling",
"loggers": ["aioamazondevices"],
"quality_scale": "bronze",
"requirements": ["aioamazondevices==3.1.14"]
"requirements": ["aioamazondevices==3.2.3"]
}

View File

@@ -15,6 +15,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import AmazonConfigEntry
from .entity import AmazonEntity
from .utils import alexa_api_call
PARALLEL_UPDATES = 1
@@ -70,6 +71,7 @@ class AmazonNotifyEntity(AmazonEntity, NotifyEntity):
entity_description: AmazonNotifyEntityDescription
@alexa_api_call
async def async_send_message(
self, message: str, title: str | None = None, **kwargs: Any
) -> None:

View File

@@ -26,7 +26,7 @@ rules:
unique-config-entry: done
# Silver
action-exceptions: todo
action-exceptions: done
config-entry-unloading: done
docs-configuration-parameters: todo
docs-installation-parameters: todo
@@ -34,7 +34,7 @@ rules:
integration-owner: done
log-when-unavailable: done
parallel-updates: done
reauthentication-flow: todo
reauthentication-flow: done
test-coverage:
status: todo
comment: all tests missing

View File

@@ -0,0 +1,88 @@
"""Support for sensors."""
from __future__ import annotations
from collections.abc import Callable
from dataclasses import dataclass
from typing import Final
from aioamazondevices.api import AmazonDevice
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
)
from homeassistant.const import LIGHT_LUX, UnitOfTemperature
from homeassistant.core import HomeAssistant
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.typing import StateType
from .coordinator import AmazonConfigEntry
from .entity import AmazonEntity
# Coordinator is used to centralize the data updates
PARALLEL_UPDATES = 0
@dataclass(frozen=True, kw_only=True)
class AmazonSensorEntityDescription(SensorEntityDescription):
"""Amazon Devices sensor entity description."""
native_unit_of_measurement_fn: Callable[[AmazonDevice, str], str] | None = None
SENSORS: Final = (
AmazonSensorEntityDescription(
key="temperature",
device_class=SensorDeviceClass.TEMPERATURE,
native_unit_of_measurement_fn=lambda device, _key: (
UnitOfTemperature.CELSIUS
if device.sensors[_key].scale == "CELSIUS"
else UnitOfTemperature.FAHRENHEIT
),
),
AmazonSensorEntityDescription(
key="illuminance",
device_class=SensorDeviceClass.ILLUMINANCE,
native_unit_of_measurement=LIGHT_LUX,
),
)
async def async_setup_entry(
hass: HomeAssistant,
entry: AmazonConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up Amazon Devices sensors based on a config entry."""
coordinator = entry.runtime_data
async_add_entities(
AmazonSensorEntity(coordinator, serial_num, sensor_desc)
for sensor_desc in SENSORS
for serial_num in coordinator.data
if coordinator.data[serial_num].sensors.get(sensor_desc.key) is not None
)
class AmazonSensorEntity(AmazonEntity, SensorEntity):
"""Sensor device."""
entity_description: AmazonSensorEntityDescription
@property
def native_unit_of_measurement(self) -> str | None:
"""Return the unit of measurement of the sensor."""
if self.entity_description.native_unit_of_measurement_fn:
return self.entity_description.native_unit_of_measurement_fn(
self.device, self.entity_description.key
)
return super().native_unit_of_measurement
@property
def native_value(self) -> StateType:
"""Return the state of the sensor."""
return self.device.sensors[self.entity_description.key].value

View File

@@ -1,8 +1,7 @@
{
"common": {
"data_country": "Country code",
"data_code": "One-time password (OTP code)",
"data_description_country": "The country of your Amazon account.",
"data_description_country": "The country where your Amazon account is registered.",
"data_description_username": "The email address of your Amazon account.",
"data_description_password": "The password of your Amazon account.",
"data_description_code": "The one-time password to log in to your account. Currently, only tokens from OTP applications are supported."
@@ -12,10 +11,10 @@
"step": {
"user": {
"data": {
"country": "[%key:component::alexa_devices::common::data_country%]",
"country": "[%key:common::config_flow::data::country%]",
"username": "[%key:common::config_flow::data::username%]",
"password": "[%key:common::config_flow::data::password%]",
"code": "[%key:component::alexa_devices::common::data_description_code%]"
"code": "[%key:component::alexa_devices::common::data_code%]"
},
"data_description": {
"country": "[%key:component::alexa_devices::common::data_description_country%]",
@@ -23,17 +22,29 @@
"password": "[%key:component::alexa_devices::common::data_description_password%]",
"code": "[%key:component::alexa_devices::common::data_description_code%]"
}
},
"reauth_confirm": {
"data": {
"password": "[%key:common::config_flow::data::password%]",
"code": "[%key:component::alexa_devices::common::data_code%]"
},
"data_description": {
"password": "[%key:component::alexa_devices::common::data_description_password%]",
"code": "[%key:component::alexa_devices::common::data_description_code%]"
}
}
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"reauth_successful": "[%key:common::config_flow::abort::reauth_successful%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"error": {
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"invalid_auth": "[%key:common::config_flow::error::invalid_auth%]",
"wrong_country": "Wrong country selected. Please select the country where your Amazon account is registered.",
"unknown": "[%key:common::config_flow::error::unknown%]"
}
},
@@ -41,6 +52,21 @@
"binary_sensor": {
"bluetooth": {
"name": "Bluetooth"
},
"baby_cry_detection": {
"name": "Baby crying"
},
"beeping_appliance_detection": {
"name": "Beeping appliance"
},
"cough_detection": {
"name": "Coughing"
},
"dog_bark_detection": {
"name": "Dog barking"
},
"water_sounds_detection": {
"name": "Water sounds"
}
},
"notify": {
@@ -56,5 +82,13 @@
"name": "Do not disturb"
}
}
},
"exceptions": {
"cannot_connect": {
"message": "Error connecting: {error}"
},
"cannot_retrieve_data": {
"message": "Error retrieving data: {error}"
}
}
}

View File

@@ -14,6 +14,7 @@ from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from .coordinator import AmazonConfigEntry
from .entity import AmazonEntity
from .utils import alexa_api_call
PARALLEL_UPDATES = 1
@@ -60,6 +61,7 @@ class AmazonSwitchEntity(AmazonEntity, SwitchEntity):
entity_description: AmazonSwitchEntityDescription
@alexa_api_call
async def _switch_set_state(self, state: bool) -> None:
"""Set desired switch state."""
method = getattr(self.coordinator.api, self.entity_description.method)

View File

@@ -0,0 +1,40 @@
"""Utils for Alexa Devices."""
from collections.abc import Awaitable, Callable, Coroutine
from functools import wraps
from typing import Any, Concatenate
from aioamazondevices.exceptions import CannotConnect, CannotRetrieveData
from homeassistant.exceptions import HomeAssistantError
from .const import DOMAIN
from .entity import AmazonEntity
def alexa_api_call[_T: AmazonEntity, **_P](
func: Callable[Concatenate[_T, _P], Awaitable[None]],
) -> Callable[Concatenate[_T, _P], Coroutine[Any, Any, None]]:
"""Catch Alexa API call exceptions."""
@wraps(func)
async def cmd_wrapper(self: _T, *args: _P.args, **kwargs: _P.kwargs) -> None:
"""Wrap all command methods."""
try:
await func(self, *args, **kwargs)
except CannotConnect as err:
self.coordinator.last_update_success = False
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="cannot_connect",
translation_placeholders={"error": repr(err)},
) from err
except CannotRetrieveData as err:
self.coordinator.last_update_success = False
raise HomeAssistantError(
translation_domain=DOMAIN,
translation_key="cannot_retrieve_data",
translation_placeholders={"error": repr(err)},
) from err
return cmd_wrapper

View File

@@ -0,0 +1,27 @@
"""The Altruist integration."""
from __future__ import annotations
from homeassistant.const import Platform
from homeassistant.core import HomeAssistant
from .coordinator import AltruistConfigEntry, AltruistDataUpdateCoordinator
PLATFORMS = [Platform.SENSOR]
async def async_setup_entry(hass: HomeAssistant, entry: AltruistConfigEntry) -> bool:
"""Set up Altruist from a config entry."""
coordinator = AltruistDataUpdateCoordinator(hass, entry)
await coordinator.async_config_entry_first_refresh()
entry.runtime_data = coordinator
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
return True
async def async_unload_entry(hass: HomeAssistant, entry: AltruistConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)

View File

@@ -0,0 +1,107 @@
"""Config flow for the Altruist integration."""
import logging
from typing import Any
from altruistclient import AltruistClient, AltruistDeviceModel, AltruistError
import voluptuous as vol
from homeassistant.config_entries import ConfigFlow, ConfigFlowResult
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
from .const import CONF_HOST, DOMAIN
_LOGGER = logging.getLogger(__name__)
class AltruistConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Altruist."""
device: AltruistDeviceModel
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
errors: dict[str, str] = {}
ip_address = ""
if user_input is not None:
ip_address = user_input[CONF_HOST]
try:
client = await AltruistClient.from_ip_address(
async_get_clientsession(self.hass), ip_address
)
except AltruistError:
errors["base"] = "no_device_found"
else:
self.device = client.device
await self.async_set_unique_id(
client.device_id, raise_on_progress=False
)
self._abort_if_unique_id_configured()
return self.async_create_entry(
title=self.device.id,
data={
CONF_HOST: ip_address,
},
)
data_schema = self.add_suggested_values_to_schema(
vol.Schema({vol.Required(CONF_HOST): str}),
{CONF_HOST: ip_address},
)
return self.async_show_form(
step_id="user",
data_schema=data_schema,
errors=errors,
description_placeholders={
"ip_address": ip_address,
},
)
async def async_step_zeroconf(
self, discovery_info: ZeroconfServiceInfo
) -> ConfigFlowResult:
"""Handle zeroconf discovery."""
_LOGGER.debug("Zeroconf discovery: %s", discovery_info)
try:
client = await AltruistClient.from_ip_address(
async_get_clientsession(self.hass), str(discovery_info.ip_address)
)
except AltruistError:
return self.async_abort(reason="no_device_found")
self.device = client.device
_LOGGER.debug("Zeroconf device: %s", client.device)
await self.async_set_unique_id(client.device_id)
self._abort_if_unique_id_configured()
self.context.update(
{
"title_placeholders": {
"name": self.device.id,
}
}
)
return await self.async_step_discovery_confirm()
async def async_step_discovery_confirm(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Confirm discovery."""
if user_input is not None:
return self.async_create_entry(
title=self.device.id,
data={
CONF_HOST: self.device.ip_address,
},
)
self._set_confirm_only()
return self.async_show_form(
step_id="discovery_confirm",
description_placeholders={
"model": self.device.id,
},
)

View File

@@ -0,0 +1,5 @@
"""Constants for the Altruist integration."""
DOMAIN = "altruist"
CONF_HOST = "host"

View File

@@ -0,0 +1,64 @@
"""Coordinator module for Altruist integration in Home Assistant.
This module defines the AltruistDataUpdateCoordinator class, which manages
data updates for Altruist sensors using the AltruistClient.
"""
from datetime import timedelta
import logging
from altruistclient import AltruistClient, AltruistError
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers.aiohttp_client import async_get_clientsession
from homeassistant.helpers.update_coordinator import DataUpdateCoordinator, UpdateFailed
from .const import CONF_HOST
_LOGGER = logging.getLogger(__name__)
UPDATE_INTERVAL = timedelta(seconds=15)
type AltruistConfigEntry = ConfigEntry[AltruistDataUpdateCoordinator]
class AltruistDataUpdateCoordinator(DataUpdateCoordinator[dict[str, str]]):
"""Coordinates data updates for Altruist sensors."""
client: AltruistClient
def __init__(
self,
hass: HomeAssistant,
config_entry: AltruistConfigEntry,
) -> None:
"""Initialize the data update coordinator for Altruist sensors."""
device_id = config_entry.unique_id
super().__init__(
hass,
logger=_LOGGER,
config_entry=config_entry,
name=f"Altruist {device_id}",
update_interval=UPDATE_INTERVAL,
)
self._ip_address = config_entry.data[CONF_HOST]
async def _async_setup(self) -> None:
try:
self.client = await AltruistClient.from_ip_address(
async_get_clientsession(self.hass), self._ip_address
)
await self.client.fetch_data()
except AltruistError as e:
raise ConfigEntryNotReady("Error in Altruist setup") from e
async def _async_update_data(self) -> dict[str, str]:
try:
fetched_data = await self.client.fetch_data()
except AltruistError as ex:
raise UpdateFailed(
f"The Altruist {self.client.device_id} is unavailable: {ex}"
) from ex
return {item["value_type"]: item["value"] for item in fetched_data}

View File

@@ -0,0 +1,15 @@
{
"entity": {
"sensor": {
"pm_10": {
"default": "mdi:thought-bubble"
},
"pm_25": {
"default": "mdi:thought-bubble-outline"
},
"radiation": {
"default": "mdi:radioactive"
}
}
}
}

View File

@@ -0,0 +1,12 @@
{
"domain": "altruist",
"name": "Altruist",
"codeowners": ["@airalab", "@LoSk-p"],
"config_flow": true,
"documentation": "https://www.home-assistant.io/integrations/altruist",
"integration_type": "device",
"iot_class": "local_polling",
"quality_scale": "bronze",
"requirements": ["altruistclient==0.1.1"],
"zeroconf": ["_altruist._tcp.local."]
}

View File

@@ -0,0 +1,83 @@
rules:
# Bronze
action-setup:
status: exempt
comment: |
This integration does not provide additional actions.
appropriate-polling: done
brands: done
common-modules: done
config-flow-test-coverage: done
config-flow: done
dependency-transparency: done
docs-actions:
status: exempt
comment: |
This integration does not provide additional actions.
docs-high-level-description: done
docs-installation-instructions: done
docs-removal-instructions: done
entity-event-setup:
status: exempt
comment: |
Entities of this integration does not explicitly subscribe to events.
entity-unique-id: done
has-entity-name: done
runtime-data: done
test-before-configure: done
test-before-setup: done
unique-config-entry: done
# Silver
action-exceptions:
status: exempt
comment: |
This integration does not provide additional actions.
config-entry-unloading: done
docs-configuration-parameters:
status: exempt
comment: |
This integration does not provide options flow.
docs-installation-parameters: done
entity-unavailable: done
integration-owner: done
log-when-unavailable: done
parallel-updates: todo
reauthentication-flow: todo
test-coverage: done
# Gold
devices: done
diagnostics: todo
discovery-update-info: todo
discovery: done
docs-data-update: todo
docs-examples: todo
docs-known-limitations: todo
docs-supported-devices: todo
docs-supported-functions: todo
docs-troubleshooting: todo
docs-use-cases: todo
dynamic-devices:
status: exempt
comment: |
Device type integration
entity-category: todo
entity-device-class: done
entity-disabled-by-default: todo
entity-translations: done
exception-translations: todo
icon-translations: done
reconfiguration-flow: todo
repair-issues:
status: exempt
comment: No known use cases for repair issues or flows, yet
stale-devices:
status: exempt
comment: |
Device type integration
# Platinum
async-dependency: done
inject-websession: done
strict-typing: done

View File

@@ -0,0 +1,249 @@
"""Defines the Altruist sensor platform."""
from collections.abc import Callable
from dataclasses import dataclass
import logging
from homeassistant.components.sensor import (
SensorDeviceClass,
SensorEntity,
SensorEntityDescription,
SensorStateClass,
)
from homeassistant.const import (
CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
CONCENTRATION_PARTS_PER_MILLION,
PERCENTAGE,
SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
EntityCategory,
UnitOfPressure,
UnitOfSoundPressure,
UnitOfTemperature,
)
from homeassistant.core import HomeAssistant
from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, DeviceInfo
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from homeassistant.helpers.update_coordinator import CoordinatorEntity
from . import AltruistConfigEntry
from .coordinator import AltruistDataUpdateCoordinator
_LOGGER = logging.getLogger(__name__)
@dataclass(frozen=True)
class AltruistSensorEntityDescription(SensorEntityDescription):
"""Class to describe a Sensor entity."""
native_value_fn: Callable[[str], float] = float
state_class = SensorStateClass.MEASUREMENT
SENSOR_DESCRIPTIONS = [
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.HUMIDITY,
key="BME280_humidity",
translation_key="humidity",
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=2,
translation_placeholders={"sensor_name": "BME280"},
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.PRESSURE,
key="BME280_pressure",
translation_key="pressure",
native_unit_of_measurement=UnitOfPressure.PA,
suggested_unit_of_measurement=UnitOfPressure.MMHG,
suggested_display_precision=0,
translation_placeholders={"sensor_name": "BME280"},
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.TEMPERATURE,
key="BME280_temperature",
translation_key="temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_display_precision=2,
translation_placeholders={"sensor_name": "BME280"},
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.PRESSURE,
key="BMP_pressure",
translation_key="pressure",
native_unit_of_measurement=UnitOfPressure.PA,
suggested_unit_of_measurement=UnitOfPressure.MMHG,
suggested_display_precision=0,
translation_placeholders={"sensor_name": "BMP"},
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.TEMPERATURE,
key="BMP_temperature",
translation_key="temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_display_precision=2,
translation_placeholders={"sensor_name": "BMP"},
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.TEMPERATURE,
key="BMP280_temperature",
translation_key="temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_display_precision=2,
translation_placeholders={"sensor_name": "BMP280"},
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.PRESSURE,
key="BMP280_pressure",
translation_key="pressure",
native_unit_of_measurement=UnitOfPressure.PA,
suggested_unit_of_measurement=UnitOfPressure.MMHG,
suggested_display_precision=0,
translation_placeholders={"sensor_name": "BMP280"},
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.HUMIDITY,
key="HTU21D_humidity",
translation_key="humidity",
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=2,
translation_placeholders={"sensor_name": "HTU21D"},
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.TEMPERATURE,
key="HTU21D_temperature",
translation_key="temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_display_precision=2,
translation_placeholders={"sensor_name": "HTU21D"},
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.PM10,
translation_key="pm_10",
key="SDS_P1",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
suggested_display_precision=2,
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.PM25,
translation_key="pm_25",
key="SDS_P2",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
suggested_display_precision=2,
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.HUMIDITY,
key="SHT3X_humidity",
translation_key="humidity",
native_unit_of_measurement=PERCENTAGE,
suggested_display_precision=2,
translation_placeholders={"sensor_name": "SHT3X"},
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.TEMPERATURE,
key="SHT3X_temperature",
translation_key="temperature",
native_unit_of_measurement=UnitOfTemperature.CELSIUS,
suggested_display_precision=2,
translation_placeholders={"sensor_name": "SHT3X"},
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.SIGNAL_STRENGTH,
key="signal",
native_unit_of_measurement=SIGNAL_STRENGTH_DECIBELS_MILLIWATT,
entity_category=EntityCategory.DIAGNOSTIC,
suggested_display_precision=0,
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.SOUND_PRESSURE,
key="PCBA_noiseMax",
translation_key="noise_max",
native_unit_of_measurement=UnitOfSoundPressure.DECIBEL,
suggested_display_precision=0,
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.SOUND_PRESSURE,
key="PCBA_noiseAvg",
translation_key="noise_avg",
native_unit_of_measurement=UnitOfSoundPressure.DECIBEL,
suggested_display_precision=0,
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.CO2,
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
translation_key="co2",
key="CCS_CO2",
suggested_display_precision=2,
translation_placeholders={"sensor_name": "CCS"},
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.VOLATILE_ORGANIC_COMPOUNDS,
key="CCS_TVOC",
native_unit_of_measurement=CONCENTRATION_MICROGRAMS_PER_CUBIC_METER,
suggested_display_precision=2,
),
AltruistSensorEntityDescription(
key="GC",
native_unit_of_measurement="μR/h",
translation_key="radiation",
suggested_display_precision=2,
),
AltruistSensorEntityDescription(
device_class=SensorDeviceClass.CO2,
translation_key="co2",
native_unit_of_measurement=CONCENTRATION_PARTS_PER_MILLION,
key="SCD4x_co2",
suggested_display_precision=2,
translation_placeholders={"sensor_name": "SCD4x"},
),
]
async def async_setup_entry(
hass: HomeAssistant,
config_entry: AltruistConfigEntry,
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Add sensors for passed config_entry in HA."""
coordinator = config_entry.runtime_data
async_add_entities(
AltruistSensor(coordinator, sensor_description)
for sensor_description in SENSOR_DESCRIPTIONS
if sensor_description.key in coordinator.data
)
class AltruistSensor(CoordinatorEntity[AltruistDataUpdateCoordinator], SensorEntity):
"""Implementation of a Altruist sensor."""
_attr_has_entity_name = True
def __init__(
self,
coordinator: AltruistDataUpdateCoordinator,
description: AltruistSensorEntityDescription,
) -> None:
"""Initialize the Altruist sensor."""
super().__init__(coordinator)
self._device = coordinator.client.device
self.entity_description: AltruistSensorEntityDescription = description
self._attr_unique_id = f"{self._device.id}-{description.key}"
self._attr_device_info = DeviceInfo(
connections={(CONNECTION_NETWORK_MAC, self._device.id)},
manufacturer="Robonomics",
model="Altruist",
sw_version=self._device.fw_version,
configuration_url=f"http://{self._device.ip_address}",
serial_number=self._device.id,
)
@property
def available(self) -> bool:
"""Return True if entity is available."""
return (
super().available and self.entity_description.key in self.coordinator.data
)
@property
def native_value(self) -> float | int:
"""Return the native value of the sensor."""
string_value = self.coordinator.data[self.entity_description.key]
return self.entity_description.native_value_fn(string_value)

View File

@@ -0,0 +1,51 @@
{
"config": {
"flow_title": "{name}",
"step": {
"discovery_confirm": {
"description": "Do you want to start setup {model}?"
},
"user": {
"data": {
"host": "[%key:common::config_flow::data::host%]"
},
"data_description": {
"host": "Altruist IP address or hostname in the local network"
},
"description": "Fill in Altruist IP address or hostname in your local network"
}
},
"abort": {
"no_devices_found": "[%key:common::config_flow::abort::no_devices_found%]",
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]"
},
"error": {
"no_device_found": "[%key:common::config_flow::error::cannot_connect%]"
}
},
"entity": {
"sensor": {
"humidity": {
"name": "{sensor_name} humidity"
},
"pressure": {
"name": "{sensor_name} pressure"
},
"temperature": {
"name": "{sensor_name} temperature"
},
"noise_max": {
"name": "Maximum noise"
},
"noise_avg": {
"name": "Average noise"
},
"co2": {
"name": "{sensor_name} CO2"
},
"radiation": {
"name": "Radiation level"
}
}
}
}

View File

@@ -16,10 +16,7 @@ from amcrest import AmcrestError, ApiWrapper, LoginError
import httpx
import voluptuous as vol
from homeassistant.auth.models import User
from homeassistant.auth.permissions.const import POLICY_CONTROL
from homeassistant.const import (
ATTR_ENTITY_ID,
CONF_AUTHENTICATION,
CONF_BINARY_SENSORS,
CONF_HOST,
@@ -30,21 +27,17 @@ from homeassistant.const import (
CONF_SENSORS,
CONF_SWITCHES,
CONF_USERNAME,
ENTITY_MATCH_ALL,
ENTITY_MATCH_NONE,
HTTP_BASIC_AUTHENTICATION,
Platform,
)
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import Unauthorized, UnknownUser
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import config_validation as cv, discovery
from homeassistant.helpers.dispatcher import async_dispatcher_send, dispatcher_send
from homeassistant.helpers.event import async_track_time_interval
from homeassistant.helpers.service import async_extract_entity_ids
from homeassistant.helpers.typing import ConfigType
from .binary_sensor import BINARY_SENSOR_KEYS, BINARY_SENSORS, check_binary_sensors
from .camera import CAMERA_SERVICES, STREAM_SOURCE_LIST
from .camera import STREAM_SOURCE_LIST
from .const import (
CAMERAS,
COMM_RETRIES,
@@ -58,6 +51,7 @@ from .const import (
)
from .helpers import service_signal
from .sensor import SENSOR_KEYS
from .services import async_setup_services
from .switch import SWITCH_KEYS
_LOGGER = logging.getLogger(__name__)
@@ -455,47 +449,7 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
if not hass.data[DATA_AMCREST][DEVICES]:
return False
def have_permission(user: User | None, entity_id: str) -> bool:
return not user or user.permissions.check_entity(entity_id, POLICY_CONTROL)
async def async_extract_from_service(call: ServiceCall) -> list[str]:
if call.context.user_id:
user = await hass.auth.async_get_user(call.context.user_id)
if user is None:
raise UnknownUser(context=call.context)
else:
user = None
if call.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_ALL:
# Return all entity_ids user has permission to control.
return [
entity_id
for entity_id in hass.data[DATA_AMCREST][CAMERAS]
if have_permission(user, entity_id)
]
if call.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_NONE:
return []
call_ids = await async_extract_entity_ids(hass, call)
entity_ids = []
for entity_id in hass.data[DATA_AMCREST][CAMERAS]:
if entity_id not in call_ids:
continue
if not have_permission(user, entity_id):
raise Unauthorized(
context=call.context, entity_id=entity_id, permission=POLICY_CONTROL
)
entity_ids.append(entity_id)
return entity_ids
async def async_service_handler(call: ServiceCall) -> None:
args = [call.data[arg] for arg in CAMERA_SERVICES[call.service][2]]
for entity_id in await async_extract_from_service(call):
async_dispatcher_send(hass, service_signal(call.service, entity_id), *args)
for service, params in CAMERA_SERVICES.items():
hass.services.async_register(DOMAIN, service, async_service_handler, params[0])
async_setup_services(hass)
return True

View File

@@ -0,0 +1,62 @@
"""Support for Amcrest IP cameras."""
from __future__ import annotations
from homeassistant.auth.models import User
from homeassistant.auth.permissions.const import POLICY_CONTROL
from homeassistant.const import ATTR_ENTITY_ID, ENTITY_MATCH_ALL, ENTITY_MATCH_NONE
from homeassistant.core import HomeAssistant, ServiceCall, callback
from homeassistant.exceptions import Unauthorized, UnknownUser
from homeassistant.helpers.dispatcher import async_dispatcher_send
from homeassistant.helpers.service import async_extract_entity_ids
from .camera import CAMERA_SERVICES
from .const import CAMERAS, DATA_AMCREST, DOMAIN
from .helpers import service_signal
@callback
def async_setup_services(hass: HomeAssistant) -> None:
"""Set up the Amcrest IP Camera services."""
def have_permission(user: User | None, entity_id: str) -> bool:
return not user or user.permissions.check_entity(entity_id, POLICY_CONTROL)
async def async_extract_from_service(call: ServiceCall) -> list[str]:
if call.context.user_id:
user = await hass.auth.async_get_user(call.context.user_id)
if user is None:
raise UnknownUser(context=call.context)
else:
user = None
if call.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_ALL:
# Return all entity_ids user has permission to control.
return [
entity_id
for entity_id in hass.data[DATA_AMCREST][CAMERAS]
if have_permission(user, entity_id)
]
if call.data.get(ATTR_ENTITY_ID) == ENTITY_MATCH_NONE:
return []
call_ids = await async_extract_entity_ids(hass, call)
entity_ids = []
for entity_id in hass.data[DATA_AMCREST][CAMERAS]:
if entity_id not in call_ids:
continue
if not have_permission(user, entity_id):
raise Unauthorized(
context=call.context, entity_id=entity_id, permission=POLICY_CONTROL
)
entity_ids.append(entity_id)
return entity_ids
async def async_service_handler(call: ServiceCall) -> None:
args = [call.data[arg] for arg in CAMERA_SERVICES[call.service][2]]
for entity_id in await async_extract_from_service(call):
async_dispatcher_send(hass, service_signal(call.service, entity_id), *args)
for service, params in CAMERA_SERVICES.items():
hass.services.async_register(DOMAIN, service, async_service_handler, params[0])

View File

@@ -24,7 +24,7 @@ from homeassistant.components.recorder import (
get_instance as get_recorder_instance,
)
from homeassistant.config_entries import SOURCE_IGNORE
from homeassistant.const import ATTR_DOMAIN, __version__ as HA_VERSION
from homeassistant.const import ATTR_DOMAIN, BASE_PLATFORMS, __version__ as HA_VERSION
from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import entity_registry as er
@@ -225,7 +225,8 @@ class Analytics:
LOGGER.error(err)
return
configuration_set = set(yaml_configuration)
configuration_set = _domains_from_yaml_config(yaml_configuration)
er_platforms = {
entity.platform
for entity in ent_reg.entities.values()
@@ -370,3 +371,13 @@ class Analytics:
for entry in entries
if entry.source != SOURCE_IGNORE and entry.disabled_by is None
)
def _domains_from_yaml_config(yaml_configuration: dict[str, Any]) -> set[str]:
"""Extract domains from the YAML configuration."""
domains = set(yaml_configuration)
for platforms in conf_util.extract_platform_integrations(
yaml_configuration, BASE_PLATFORMS
).values():
domains.update(platforms)
return domains

View File

@@ -2,6 +2,7 @@
from __future__ import annotations
from homeassistant.components.camera import CameraEntityFeature
from homeassistant.components.mjpeg import MjpegCamera, filter_urllib3_logging
from homeassistant.const import (
CONF_HOST,
@@ -31,6 +32,7 @@ class IPWebcamCamera(MjpegCamera):
"""Representation of a IP Webcam camera."""
_attr_has_entity_name = True
_attr_supported_features = CameraEntityFeature.STREAM
def __init__(self, coordinator: AndroidIPCamDataUpdateCoordinator) -> None:
"""Initialize the camera."""
@@ -46,3 +48,17 @@ class IPWebcamCamera(MjpegCamera):
identifiers={(DOMAIN, coordinator.config_entry.entry_id)},
name=coordinator.config_entry.data[CONF_HOST],
)
self._coordinator = coordinator
async def stream_source(self) -> str:
"""Get the stream source for the Android IP camera."""
return self._coordinator.cam.get_rtsp_url(
video_codec="h264", # most compatible & recommended
# while "opus" is compatible with more devices,
# HA's stream integration requires AAC or MP3,
# and IP webcam doesn't provide MP3 audio.
# aac is supported on select devices >= android 4.1.
# The stream will be quiet on devices that don't support aac,
# but it won't fail.
audio_codec="aac",
)

View File

@@ -56,7 +56,7 @@ SERVICE_UPLOAD = "upload"
ANDROIDTV_STATES = {
"off": MediaPlayerState.OFF,
"idle": MediaPlayerState.IDLE,
"standby": MediaPlayerState.STANDBY,
"standby": MediaPlayerState.IDLE,
"playing": MediaPlayerState.PLAYING,
"paused": MediaPlayerState.PAUSED,
}

View File

@@ -5,26 +5,18 @@ from __future__ import annotations
from asyncio import timeout
import logging
from androidtvremote2 import (
AndroidTVRemote,
CannotConnect,
ConnectionClosed,
InvalidAuth,
)
from androidtvremote2 import CannotConnect, ConnectionClosed, InvalidAuth
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_NAME, EVENT_HOMEASSISTANT_STOP, Platform
from homeassistant.core import Event, HomeAssistant, callback
from homeassistant.exceptions import ConfigEntryAuthFailed, ConfigEntryNotReady
from .helpers import create_api, get_enable_ime
from .helpers import AndroidTVRemoteConfigEntry, create_api, get_enable_ime
_LOGGER = logging.getLogger(__name__)
PLATFORMS: list[Platform] = [Platform.MEDIA_PLAYER, Platform.REMOTE]
AndroidTVRemoteConfigEntry = ConfigEntry[AndroidTVRemote]
async def async_setup_entry(
hass: HomeAssistant, entry: AndroidTVRemoteConfigEntry
@@ -82,13 +74,17 @@ async def async_setup_entry(
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(
hass: HomeAssistant, entry: AndroidTVRemoteConfigEntry
) -> bool:
"""Unload a config entry."""
_LOGGER.debug("async_unload_entry: %s", entry.data)
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_update_options(hass: HomeAssistant, entry: ConfigEntry) -> None:
async def async_update_options(
hass: HomeAssistant, entry: AndroidTVRemoteConfigEntry
) -> None:
"""Handle options update."""
_LOGGER.debug(
"async_update_options: data: %s options: %s", entry.data, entry.options

View File

@@ -16,7 +16,7 @@ import voluptuous as vol
from homeassistant.config_entries import (
SOURCE_REAUTH,
ConfigEntry,
SOURCE_RECONFIGURE,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
@@ -33,7 +33,7 @@ from homeassistant.helpers.selector import (
from homeassistant.helpers.service_info.zeroconf import ZeroconfServiceInfo
from .const import CONF_APP_ICON, CONF_APP_NAME, CONF_APPS, CONF_ENABLE_IME, DOMAIN
from .helpers import create_api, get_enable_ime
from .helpers import AndroidTVRemoteConfigEntry, create_api, get_enable_ime
_LOGGER = logging.getLogger(__name__)
@@ -41,12 +41,6 @@ APPS_NEW_ID = "NewApp"
CONF_APP_DELETE = "app_delete"
CONF_APP_ID = "app_id"
STEP_USER_DATA_SCHEMA = vol.Schema(
{
vol.Required("host"): str,
}
)
STEP_PAIR_DATA_SCHEMA = vol.Schema(
{
vol.Required("pin"): str,
@@ -67,7 +61,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
async def async_step_user(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle the initial step."""
"""Handle the initial and reconfigure step."""
errors: dict[str, str] = {}
if user_input is not None:
self.host = user_input[CONF_HOST]
@@ -76,15 +70,32 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
await api.async_generate_cert_if_missing()
self.name, self.mac = await api.async_get_name_and_mac()
await self.async_set_unique_id(format_mac(self.mac))
if self.source == SOURCE_RECONFIGURE:
self._abort_if_unique_id_mismatch()
return self.async_update_reload_and_abort(
self._get_reconfigure_entry(),
data={
CONF_HOST: self.host,
CONF_NAME: self.name,
CONF_MAC: self.mac,
},
)
self._abort_if_unique_id_configured(updates={CONF_HOST: self.host})
return await self._async_start_pair()
except (CannotConnect, ConnectionClosed):
# Likely invalid IP address or device is network unreachable. Stay
# in the user step allowing the user to enter a different host.
errors["base"] = "cannot_connect"
else:
user_input = {}
default_host = user_input.get(CONF_HOST, vol.UNDEFINED)
if self.source == SOURCE_RECONFIGURE:
default_host = self._get_reconfigure_entry().data[CONF_HOST]
return self.async_show_form(
step_id="user",
data_schema=STEP_USER_DATA_SCHEMA,
step_id="reconfigure" if self.source == SOURCE_RECONFIGURE else "user",
data_schema=vol.Schema(
{vol.Required(CONF_HOST, default=default_host): str}
),
errors=errors,
)
@@ -217,10 +228,16 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
errors=errors,
)
async def async_step_reconfigure(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Handle reconfiguration."""
return await self.async_step_user(user_input)
@staticmethod
@callback
def async_get_options_flow(
config_entry: ConfigEntry,
config_entry: AndroidTVRemoteConfigEntry,
) -> AndroidTVRemoteOptionsFlowHandler:
"""Create the options flow."""
return AndroidTVRemoteOptionsFlowHandler(config_entry)
@@ -229,7 +246,7 @@ class AndroidTVRemoteConfigFlow(ConfigFlow, domain=DOMAIN):
class AndroidTVRemoteOptionsFlowHandler(OptionsFlow):
"""Android TV Remote options flow."""
def __init__(self, config_entry: ConfigEntry) -> None:
def __init__(self, config_entry: AndroidTVRemoteConfigEntry) -> None:
"""Initialize options flow."""
self._apps: dict[str, Any] = dict(config_entry.options.get(CONF_APPS, {}))
self._conf_app_id: str | None = None

View File

@@ -8,7 +8,7 @@ from homeassistant.components.diagnostics import async_redact_data
from homeassistant.const import CONF_HOST, CONF_MAC
from homeassistant.core import HomeAssistant
from . import AndroidTVRemoteConfigEntry
from .helpers import AndroidTVRemoteConfigEntry
TO_REDACT = {CONF_HOST, CONF_MAC}

View File

@@ -6,7 +6,6 @@ from typing import Any
from androidtvremote2 import AndroidTVRemote, ConnectionClosed
from homeassistant.config_entries import ConfigEntry
from homeassistant.const import CONF_HOST, CONF_MAC, CONF_NAME
from homeassistant.core import callback
from homeassistant.exceptions import HomeAssistantError
@@ -14,6 +13,7 @@ from homeassistant.helpers.device_registry import CONNECTION_NETWORK_MAC, Device
from homeassistant.helpers.entity import Entity
from .const import CONF_APPS, DOMAIN
from .helpers import AndroidTVRemoteConfigEntry
class AndroidTVRemoteBaseEntity(Entity):
@@ -23,7 +23,9 @@ class AndroidTVRemoteBaseEntity(Entity):
_attr_has_entity_name = True
_attr_should_poll = False
def __init__(self, api: AndroidTVRemote, config_entry: ConfigEntry) -> None:
def __init__(
self, api: AndroidTVRemote, config_entry: AndroidTVRemoteConfigEntry
) -> None:
"""Initialize the entity."""
self._api = api
self._host = config_entry.data[CONF_HOST]

View File

@@ -10,6 +10,8 @@ from homeassistant.helpers.storage import STORAGE_DIR
from .const import CONF_ENABLE_IME, CONF_ENABLE_IME_DEFAULT_VALUE
AndroidTVRemoteConfigEntry = ConfigEntry[AndroidTVRemote]
def create_api(hass: HomeAssistant, host: str, enable_ime: bool) -> AndroidTVRemote:
"""Create an AndroidTVRemote instance."""
@@ -23,6 +25,6 @@ def create_api(hass: HomeAssistant, host: str, enable_ime: bool) -> AndroidTVRem
)
def get_enable_ime(entry: ConfigEntry) -> bool:
def get_enable_ime(entry: AndroidTVRemoteConfigEntry) -> bool:
"""Get value of enable_ime option or its default value."""
return entry.options.get(CONF_ENABLE_IME, CONF_ENABLE_IME_DEFAULT_VALUE)

View File

@@ -7,6 +7,6 @@
"integration_type": "device",
"iot_class": "local_push",
"loggers": ["androidtvremote2"],
"requirements": ["androidtvremote2==0.2.2"],
"requirements": ["androidtvremote2==0.2.3"],
"zeroconf": ["_androidtvremote2._tcp.local."]
}

View File

@@ -5,7 +5,7 @@ from __future__ import annotations
import asyncio
from typing import Any
from androidtvremote2 import AndroidTVRemote, ConnectionClosed
from androidtvremote2 import AndroidTVRemote, ConnectionClosed, VolumeInfo
from homeassistant.components.media_player import (
BrowseMedia,
@@ -20,9 +20,9 @@ from homeassistant.core import HomeAssistant, callback
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import AndroidTVRemoteConfigEntry
from .const import CONF_APP_ICON, CONF_APP_NAME, DOMAIN
from .entity import AndroidTVRemoteBaseEntity
from .helpers import AndroidTVRemoteConfigEntry
PARALLEL_UPDATES = 0
@@ -75,13 +75,11 @@ class AndroidTVRemoteMediaPlayerEntity(AndroidTVRemoteBaseEntity, MediaPlayerEnt
else current_app
)
def _update_volume_info(self, volume_info: dict[str, str | bool]) -> None:
def _update_volume_info(self, volume_info: VolumeInfo) -> None:
"""Update volume info."""
if volume_info.get("max"):
self._attr_volume_level = int(volume_info["level"]) / int(
volume_info["max"]
)
self._attr_is_volume_muted = bool(volume_info["muted"])
self._attr_volume_level = volume_info["level"] / volume_info["max"]
self._attr_is_volume_muted = volume_info["muted"]
else:
self._attr_volume_level = None
self._attr_is_volume_muted = None
@@ -93,7 +91,7 @@ class AndroidTVRemoteMediaPlayerEntity(AndroidTVRemoteBaseEntity, MediaPlayerEnt
self.async_write_ha_state()
@callback
def _volume_info_updated(self, volume_info: dict[str, str | bool]) -> None:
def _volume_info_updated(self, volume_info: VolumeInfo) -> None:
"""Update the state when the volume info changes."""
self._update_volume_info(volume_info)
self.async_write_ha_state()
@@ -102,8 +100,10 @@ class AndroidTVRemoteMediaPlayerEntity(AndroidTVRemoteBaseEntity, MediaPlayerEnt
"""Register callbacks."""
await super().async_added_to_hass()
self._update_current_app(self._api.current_app)
self._update_volume_info(self._api.volume_info)
if self._api.current_app is not None:
self._update_current_app(self._api.current_app)
if self._api.volume_info is not None:
self._update_volume_info(self._api.volume_info)
self._api.add_current_app_updated_callback(self._current_app_updated)
self._api.add_volume_info_updated_callback(self._volume_info_updated)

View File

@@ -20,9 +20,9 @@ from homeassistant.components.remote import (
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import AndroidTVRemoteConfigEntry
from .const import CONF_APP_NAME
from .entity import AndroidTVRemoteBaseEntity
from .helpers import AndroidTVRemoteConfigEntry
PARALLEL_UPDATES = 0
@@ -63,7 +63,8 @@ class AndroidTVRemoteEntity(AndroidTVRemoteBaseEntity, RemoteEntity):
self._attr_activity_list = [
app.get(CONF_APP_NAME, "") for app in self._apps.values()
]
self._update_current_app(self._api.current_app)
if self._api.current_app is not None:
self._update_current_app(self._api.current_app)
self._api.add_current_app_updated_callback(self._current_app_updated)
async def async_will_remove_from_hass(self) -> None:

View File

@@ -6,6 +6,18 @@
"description": "Enter the IP address of the Android TV you want to add to Home Assistant. It will turn on and a pairing code will be displayed on it that you will need to enter in the next screen.",
"data": {
"host": "[%key:common::config_flow::data::host%]"
},
"data_description": {
"host": "The hostname or IP address of the Android TV device."
}
},
"reconfigure": {
"description": "Update the IP address of this previously configured Android TV device.",
"data": {
"host": "[%key:common::config_flow::data::host%]"
},
"data_description": {
"host": "The hostname or IP address of the Android TV device."
}
},
"zeroconf_confirm": {
@@ -16,6 +28,9 @@
"description": "Enter the pairing code displayed on the Android TV ({name}).",
"data": {
"pin": "[%key:common::config_flow::data::pin%]"
},
"data_description": {
"pin": "Pairing code displayed on the Android TV device."
}
},
"reauth_confirm": {
@@ -32,7 +47,9 @@
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_device%]",
"cannot_connect": "[%key:common::config_flow::error::cannot_connect%]",
"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%]",
"unique_id_mismatch": "Please ensure you reconfigure against the same device."
}
},
"options": {
@@ -40,7 +57,11 @@
"init": {
"data": {
"apps": "Configure applications list",
"enable_ime": "Enable IME. Needed for getting the current app. Disable for devices that show 'Use keyboard on mobile device screen' instead of the on screen keyboard."
"enable_ime": "Enable IME"
},
"data_description": {
"apps": "Here you can define the list of applications, specify names and icons that will be displayed in the UI.",
"enable_ime": "Enable this option to be able to get the current app name and send text as keyboard input. Disable it for devices that show 'Use keyboard on mobile device screen' instead of the on-screen keyboard."
}
},
"apps": {
@@ -53,8 +74,10 @@
"app_delete": "Check to delete this application"
},
"data_description": {
"app_name": "Name of the application as you would like it to be displayed in Home Assistant.",
"app_id": "E.g. com.plexapp.android for https://play.google.com/store/apps/details?id=com.plexapp.android",
"app_icon": "Image URL. From the Play Store app page, right click on the icon and select 'Copy image address' and then paste it here. Alternatively, download the image, upload it under /config/www/ and use the URL /local/filename"
"app_icon": "Image URL. From the Play Store app page, right click on the icon and select 'Copy image address' and then paste it here. Alternatively, download the image, upload it under /config/www/ and use the URL /local/filename",
"app_delete": "Check this box to delete the application from the list."
}
}
}

View File

@@ -6,13 +6,24 @@ from functools import partial
import anthropic
from homeassistant.config_entries import ConfigEntry
from homeassistant.config_entries import ConfigEntry, ConfigSubentry
from homeassistant.const import CONF_API_KEY, Platform
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import ConfigEntryNotReady
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers import (
config_validation as cv,
device_registry as dr,
entity_registry as er,
)
from homeassistant.helpers.typing import ConfigType
from .const import CONF_CHAT_MODEL, DOMAIN, LOGGER, RECOMMENDED_CHAT_MODEL
from .const import (
CONF_CHAT_MODEL,
DEFAULT_CONVERSATION_NAME,
DOMAIN,
LOGGER,
RECOMMENDED_CHAT_MODEL,
)
PLATFORMS = (Platform.CONVERSATION,)
CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
@@ -20,13 +31,24 @@ CONFIG_SCHEMA = cv.config_entry_only_config_schema(DOMAIN)
type AnthropicConfigEntry = ConfigEntry[anthropic.AsyncClient]
async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"""Set up Anthropic."""
await async_migrate_integration(hass)
return True
async def async_setup_entry(hass: HomeAssistant, entry: AnthropicConfigEntry) -> bool:
"""Set up Anthropic from a config entry."""
client = await hass.async_add_executor_job(
partial(anthropic.AsyncAnthropic, api_key=entry.data[CONF_API_KEY])
)
try:
model_id = entry.options.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL)
# Use model from first conversation subentry for validation
subentries = list(entry.subentries.values())
if subentries:
model_id = subentries[0].data.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL)
else:
model_id = RECOMMENDED_CHAT_MODEL
model = await client.models.retrieve(model_id=model_id, timeout=10.0)
LOGGER.debug("Anthropic model: %s", model.display_name)
except anthropic.AuthenticationError as err:
@@ -39,9 +61,120 @@ async def async_setup_entry(hass: HomeAssistant, entry: AnthropicConfigEntry) ->
await hass.config_entries.async_forward_entry_setups(entry, PLATFORMS)
entry.async_on_unload(entry.add_update_listener(async_update_options))
return True
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload Anthropic."""
return await hass.config_entries.async_unload_platforms(entry, PLATFORMS)
async def async_update_options(
hass: HomeAssistant, entry: AnthropicConfigEntry
) -> None:
"""Update options."""
await hass.config_entries.async_reload(entry.entry_id)
async def async_migrate_integration(hass: HomeAssistant) -> None:
"""Migrate integration entry structure."""
entries = hass.config_entries.async_entries(DOMAIN)
if not any(entry.version == 1 for entry in entries):
return
api_keys_entries: dict[str, ConfigEntry] = {}
entity_registry = er.async_get(hass)
device_registry = dr.async_get(hass)
for entry in entries:
use_existing = False
subentry = ConfigSubentry(
data=entry.options,
subentry_type="conversation",
title=entry.title,
unique_id=None,
)
if entry.data[CONF_API_KEY] not in api_keys_entries:
use_existing = True
api_keys_entries[entry.data[CONF_API_KEY]] = entry
parent_entry = api_keys_entries[entry.data[CONF_API_KEY]]
hass.config_entries.async_add_subentry(parent_entry, subentry)
conversation_entity = entity_registry.async_get_entity_id(
"conversation",
DOMAIN,
entry.entry_id,
)
if conversation_entity is not None:
entity_registry.async_update_entity(
conversation_entity,
config_entry_id=parent_entry.entry_id,
config_subentry_id=subentry.subentry_id,
new_unique_id=subentry.subentry_id,
)
device = device_registry.async_get_device(
identifiers={(DOMAIN, entry.entry_id)}
)
if device is not None:
device_registry.async_update_device(
device.id,
new_identifiers={(DOMAIN, subentry.subentry_id)},
add_config_subentry_id=subentry.subentry_id,
add_config_entry_id=parent_entry.entry_id,
)
if parent_entry.entry_id != entry.entry_id:
device_registry.async_update_device(
device.id,
remove_config_entry_id=entry.entry_id,
)
else:
device_registry.async_update_device(
device.id,
remove_config_entry_id=entry.entry_id,
remove_config_subentry_id=None,
)
if not use_existing:
await hass.config_entries.async_remove(entry.entry_id)
else:
hass.config_entries.async_update_entry(
entry,
title=DEFAULT_CONVERSATION_NAME,
options={},
version=2,
minor_version=2,
)
async def async_migrate_entry(hass: HomeAssistant, entry: AnthropicConfigEntry) -> bool:
"""Migrate entry."""
LOGGER.debug("Migrating from version %s:%s", entry.version, entry.minor_version)
if entry.version > 2:
# This means the user has downgraded from a future version
return False
if entry.version == 2 and entry.minor_version == 1:
# Correct broken device migration in Home Assistant Core 2025.7.0b0-2025.7.0b1
device_registry = dr.async_get(hass)
for device in dr.async_entries_for_config_entry(
device_registry, entry.entry_id
):
device_registry.async_update_device(
device.id,
remove_config_entry_id=entry.entry_id,
remove_config_subentry_id=None,
)
hass.config_entries.async_update_entry(entry, minor_version=2)
LOGGER.debug(
"Migration to version %s:%s successful", entry.version, entry.minor_version
)
return True

View File

@@ -5,20 +5,21 @@ from __future__ import annotations
from collections.abc import Mapping
from functools import partial
import logging
from types import MappingProxyType
from typing import Any
from typing import Any, cast
import anthropic
import voluptuous as vol
from homeassistant.config_entries import (
ConfigEntry,
ConfigEntryState,
ConfigFlow,
ConfigFlowResult,
OptionsFlow,
ConfigSubentryFlow,
SubentryFlowResult,
)
from homeassistant.const import CONF_API_KEY, CONF_LLM_HASS_API
from homeassistant.core import HomeAssistant
from homeassistant.const import CONF_API_KEY, CONF_LLM_HASS_API, CONF_NAME
from homeassistant.core import HomeAssistant, callback
from homeassistant.helpers import llm
from homeassistant.helpers.selector import (
NumberSelector,
@@ -36,6 +37,7 @@ from .const import (
CONF_RECOMMENDED,
CONF_TEMPERATURE,
CONF_THINKING_BUDGET,
DEFAULT_CONVERSATION_NAME,
DOMAIN,
RECOMMENDED_CHAT_MODEL,
RECOMMENDED_MAX_TOKENS,
@@ -72,7 +74,8 @@ async def validate_input(hass: HomeAssistant, data: dict[str, Any]) -> None:
class AnthropicConfigFlow(ConfigFlow, domain=DOMAIN):
"""Handle a config flow for Anthropic."""
VERSION = 1
VERSION = 2
MINOR_VERSION = 2
async def async_step_user(
self, user_input: dict[str, Any] | None = None
@@ -81,6 +84,7 @@ class AnthropicConfigFlow(ConfigFlow, domain=DOMAIN):
errors = {}
if user_input is not None:
self._async_abort_entries_match(user_input)
try:
await validate_input(self.hass, user_input)
except anthropic.APITimeoutError:
@@ -102,57 +106,93 @@ class AnthropicConfigFlow(ConfigFlow, domain=DOMAIN):
return self.async_create_entry(
title="Claude",
data=user_input,
options=RECOMMENDED_OPTIONS,
subentries=[
{
"subentry_type": "conversation",
"data": RECOMMENDED_OPTIONS,
"title": DEFAULT_CONVERSATION_NAME,
"unique_id": None,
}
],
)
return self.async_show_form(
step_id="user", data_schema=STEP_USER_DATA_SCHEMA, errors=errors or None
)
@staticmethod
def async_get_options_flow(
config_entry: ConfigEntry,
) -> OptionsFlow:
"""Create the options flow."""
return AnthropicOptionsFlow(config_entry)
@classmethod
@callback
def async_get_supported_subentry_types(
cls, config_entry: ConfigEntry
) -> dict[str, type[ConfigSubentryFlow]]:
"""Return subentries supported by this integration."""
return {"conversation": ConversationSubentryFlowHandler}
class AnthropicOptionsFlow(OptionsFlow):
"""Anthropic config flow options handler."""
class ConversationSubentryFlowHandler(ConfigSubentryFlow):
"""Flow for managing conversation subentries."""
def __init__(self, config_entry: ConfigEntry) -> None:
"""Initialize options flow."""
self.last_rendered_recommended = config_entry.options.get(
CONF_RECOMMENDED, False
)
last_rendered_recommended = False
async def async_step_init(
@property
def _is_new(self) -> bool:
"""Return if this is a new subentry."""
return self.source == "user"
async def async_step_set_options(
self, user_input: dict[str, Any] | None = None
) -> ConfigFlowResult:
"""Manage the options."""
options: dict[str, Any] | MappingProxyType[str, Any] = self.config_entry.options
) -> SubentryFlowResult:
"""Set conversation options."""
# abort if entry is not loaded
if self._get_entry().state != ConfigEntryState.LOADED:
return self.async_abort(reason="entry_not_loaded")
errors: dict[str, str] = {}
if user_input is not None:
if user_input[CONF_RECOMMENDED] == self.last_rendered_recommended:
if not user_input.get(CONF_LLM_HASS_API):
user_input.pop(CONF_LLM_HASS_API, None)
if user_input.get(
CONF_THINKING_BUDGET, RECOMMENDED_THINKING_BUDGET
) >= user_input.get(CONF_MAX_TOKENS, RECOMMENDED_MAX_TOKENS):
errors[CONF_THINKING_BUDGET] = "thinking_budget_too_large"
if not errors:
return self.async_create_entry(title="", data=user_input)
if user_input is None:
if self._is_new:
options = RECOMMENDED_OPTIONS.copy()
else:
# Re-render the options again, now with the recommended options shown/hidden
self.last_rendered_recommended = user_input[CONF_RECOMMENDED]
# If this is a reconfiguration, we need to copy the existing options
# so that we can show the current values in the form.
options = self._get_reconfigure_subentry().data.copy()
options = {
CONF_RECOMMENDED: user_input[CONF_RECOMMENDED],
CONF_PROMPT: user_input[CONF_PROMPT],
CONF_LLM_HASS_API: user_input.get(CONF_LLM_HASS_API),
}
self.last_rendered_recommended = cast(
bool, options.get(CONF_RECOMMENDED, False)
)
elif user_input[CONF_RECOMMENDED] == self.last_rendered_recommended:
if not user_input.get(CONF_LLM_HASS_API):
user_input.pop(CONF_LLM_HASS_API, None)
if user_input.get(
CONF_THINKING_BUDGET, RECOMMENDED_THINKING_BUDGET
) >= user_input.get(CONF_MAX_TOKENS, RECOMMENDED_MAX_TOKENS):
errors[CONF_THINKING_BUDGET] = "thinking_budget_too_large"
if not errors:
if self._is_new:
return self.async_create_entry(
title=user_input.pop(CONF_NAME),
data=user_input,
)
return self.async_update_and_abort(
self._get_entry(),
self._get_reconfigure_subentry(),
data=user_input,
)
options = user_input
self.last_rendered_recommended = user_input[CONF_RECOMMENDED]
else:
# Re-render the options again, now with the recommended options shown/hidden
self.last_rendered_recommended = user_input[CONF_RECOMMENDED]
options = {
CONF_RECOMMENDED: user_input[CONF_RECOMMENDED],
CONF_PROMPT: user_input[CONF_PROMPT],
CONF_LLM_HASS_API: user_input.get(CONF_LLM_HASS_API),
}
suggested_values = options.copy()
if not suggested_values.get(CONF_PROMPT):
@@ -163,19 +203,25 @@ class AnthropicOptionsFlow(OptionsFlow):
suggested_values[CONF_LLM_HASS_API] = [suggested_llm_apis]
schema = self.add_suggested_values_to_schema(
vol.Schema(anthropic_config_option_schema(self.hass, options)),
vol.Schema(
anthropic_config_option_schema(self.hass, self._is_new, options)
),
suggested_values,
)
return self.async_show_form(
step_id="init",
step_id="set_options",
data_schema=schema,
errors=errors or None,
)
async_step_user = async_step_set_options
async_step_reconfigure = async_step_set_options
def anthropic_config_option_schema(
hass: HomeAssistant,
is_new: bool,
options: Mapping[str, Any],
) -> dict:
"""Return a schema for Anthropic completion options."""
@@ -187,15 +233,24 @@ def anthropic_config_option_schema(
for api in llm.async_get_apis(hass)
]
schema = {
vol.Optional(CONF_PROMPT): TemplateSelector(),
vol.Optional(
CONF_LLM_HASS_API,
): SelectSelector(SelectSelectorConfig(options=hass_apis, multiple=True)),
vol.Required(
CONF_RECOMMENDED, default=options.get(CONF_RECOMMENDED, False)
): bool,
}
if is_new:
schema: dict[vol.Required | vol.Optional, Any] = {
vol.Required(CONF_NAME, default=DEFAULT_CONVERSATION_NAME): str,
}
else:
schema = {}
schema.update(
{
vol.Optional(CONF_PROMPT): TemplateSelector(),
vol.Optional(
CONF_LLM_HASS_API,
): SelectSelector(SelectSelectorConfig(options=hass_apis, multiple=True)),
vol.Required(
CONF_RECOMMENDED, default=options.get(CONF_RECOMMENDED, False)
): bool,
}
)
if options.get(CONF_RECOMMENDED):
return schema

View File

@@ -5,6 +5,8 @@ import logging
DOMAIN = "anthropic"
LOGGER = logging.getLogger(__package__)
DEFAULT_CONVERSATION_NAME = "Claude conversation"
CONF_RECOMMENDED = "recommended"
CONF_PROMPT = "prompt"
CONF_CHAT_MODEL = "chat_model"

View File

@@ -1,69 +1,17 @@
"""Conversation support for Anthropic."""
from collections.abc import AsyncGenerator, Callable, Iterable
import json
from typing import Any, Literal, cast
import anthropic
from anthropic import AsyncStream
from anthropic._types import NOT_GIVEN
from anthropic.types import (
InputJSONDelta,
MessageDeltaUsage,
MessageParam,
MessageStreamEvent,
RawContentBlockDeltaEvent,
RawContentBlockStartEvent,
RawContentBlockStopEvent,
RawMessageDeltaEvent,
RawMessageStartEvent,
RawMessageStopEvent,
RedactedThinkingBlock,
RedactedThinkingBlockParam,
SignatureDelta,
TextBlock,
TextBlockParam,
TextDelta,
ThinkingBlock,
ThinkingBlockParam,
ThinkingConfigDisabledParam,
ThinkingConfigEnabledParam,
ThinkingDelta,
ToolParam,
ToolResultBlockParam,
ToolUseBlock,
ToolUseBlockParam,
Usage,
)
from voluptuous_openapi import convert
from typing import Literal
from homeassistant.components import conversation
from homeassistant.config_entries import ConfigEntry
from homeassistant.config_entries import ConfigSubentry
from homeassistant.const import CONF_LLM_HASS_API, MATCH_ALL
from homeassistant.core import HomeAssistant
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, intent, llm
from homeassistant.helpers import intent
from homeassistant.helpers.entity_platform import AddConfigEntryEntitiesCallback
from . import AnthropicConfigEntry
from .const import (
CONF_CHAT_MODEL,
CONF_MAX_TOKENS,
CONF_PROMPT,
CONF_TEMPERATURE,
CONF_THINKING_BUDGET,
DOMAIN,
LOGGER,
MIN_THINKING_BUDGET,
RECOMMENDED_CHAT_MODEL,
RECOMMENDED_MAX_TOKENS,
RECOMMENDED_TEMPERATURE,
RECOMMENDED_THINKING_BUDGET,
THINKING_MODELS,
)
# Max number of back and forth with the LLM to generate a response
MAX_TOOL_ITERATIONS = 10
from .const import CONF_PROMPT, DOMAIN
from .entity import AnthropicBaseLLMEntity
async def async_setup_entry(
@@ -72,275 +20,29 @@ async def async_setup_entry(
async_add_entities: AddConfigEntryEntitiesCallback,
) -> None:
"""Set up conversation entities."""
agent = AnthropicConversationEntity(config_entry)
async_add_entities([agent])
for subentry in config_entry.subentries.values():
if subentry.subentry_type != "conversation":
continue
def _format_tool(
tool: llm.Tool, custom_serializer: Callable[[Any], Any] | None
) -> ToolParam:
"""Format tool specification."""
return ToolParam(
name=tool.name,
description=tool.description or "",
input_schema=convert(tool.parameters, custom_serializer=custom_serializer),
)
def _convert_content(
chat_content: Iterable[conversation.Content],
) -> list[MessageParam]:
"""Transform HA chat_log content into Anthropic API format."""
messages: list[MessageParam] = []
for content in chat_content:
if isinstance(content, conversation.ToolResultContent):
tool_result_block = ToolResultBlockParam(
type="tool_result",
tool_use_id=content.tool_call_id,
content=json.dumps(content.tool_result),
)
if not messages or messages[-1]["role"] != "user":
messages.append(
MessageParam(
role="user",
content=[tool_result_block],
)
)
elif isinstance(messages[-1]["content"], str):
messages[-1]["content"] = [
TextBlockParam(type="text", text=messages[-1]["content"]),
tool_result_block,
]
else:
messages[-1]["content"].append(tool_result_block) # type: ignore[attr-defined]
elif isinstance(content, conversation.UserContent):
# Combine consequent user messages
if not messages or messages[-1]["role"] != "user":
messages.append(
MessageParam(
role="user",
content=content.content,
)
)
elif isinstance(messages[-1]["content"], str):
messages[-1]["content"] = [
TextBlockParam(type="text", text=messages[-1]["content"]),
TextBlockParam(type="text", text=content.content),
]
else:
messages[-1]["content"].append( # type: ignore[attr-defined]
TextBlockParam(type="text", text=content.content)
)
elif isinstance(content, conversation.AssistantContent):
# Combine consequent assistant messages
if not messages or messages[-1]["role"] != "assistant":
messages.append(
MessageParam(
role="assistant",
content=[],
)
)
if content.content:
messages[-1]["content"].append( # type: ignore[union-attr]
TextBlockParam(type="text", text=content.content)
)
if content.tool_calls:
messages[-1]["content"].extend( # type: ignore[union-attr]
[
ToolUseBlockParam(
type="tool_use",
id=tool_call.id,
name=tool_call.tool_name,
input=tool_call.tool_args,
)
for tool_call in content.tool_calls
]
)
else:
# Note: We don't pass SystemContent here as its passed to the API as the prompt
raise TypeError(f"Unexpected content type: {type(content)}")
return messages
async def _transform_stream( # noqa: C901 - This is complex, but better to have it in one place
chat_log: conversation.ChatLog,
result: AsyncStream[MessageStreamEvent],
messages: list[MessageParam],
) -> AsyncGenerator[conversation.AssistantContentDeltaDict]:
"""Transform the response stream into HA format.
A typical stream of responses might look something like the following:
- RawMessageStartEvent with no content
- RawContentBlockStartEvent with an empty ThinkingBlock (if extended thinking is enabled)
- RawContentBlockDeltaEvent with a ThinkingDelta
- RawContentBlockDeltaEvent with a ThinkingDelta
- RawContentBlockDeltaEvent with a ThinkingDelta
- ...
- RawContentBlockDeltaEvent with a SignatureDelta
- RawContentBlockStopEvent
- RawContentBlockStartEvent with a RedactedThinkingBlock (occasionally)
- RawContentBlockStopEvent (RedactedThinkingBlock does not have a delta)
- RawContentBlockStartEvent with an empty TextBlock
- RawContentBlockDeltaEvent with a TextDelta
- RawContentBlockDeltaEvent with a TextDelta
- RawContentBlockDeltaEvent with a TextDelta
- ...
- RawContentBlockStopEvent
- RawContentBlockStartEvent with ToolUseBlock specifying the function name
- RawContentBlockDeltaEvent with a InputJSONDelta
- RawContentBlockDeltaEvent with a InputJSONDelta
- ...
- RawContentBlockStopEvent
- RawMessageDeltaEvent with a stop_reason='tool_use'
- RawMessageStopEvent(type='message_stop')
Each message could contain multiple blocks of the same type.
"""
if result is None:
raise TypeError("Expected a stream of messages")
current_message: MessageParam | None = None
current_block: (
TextBlockParam
| ToolUseBlockParam
| ThinkingBlockParam
| RedactedThinkingBlockParam
| None
) = None
current_tool_args: str
input_usage: Usage | None = None
async for response in result:
LOGGER.debug("Received response: %s", response)
if isinstance(response, RawMessageStartEvent):
if response.message.role != "assistant":
raise ValueError("Unexpected message role")
current_message = MessageParam(role=response.message.role, content=[])
input_usage = response.message.usage
elif isinstance(response, RawContentBlockStartEvent):
if isinstance(response.content_block, ToolUseBlock):
current_block = ToolUseBlockParam(
type="tool_use",
id=response.content_block.id,
name=response.content_block.name,
input="",
)
current_tool_args = ""
elif isinstance(response.content_block, TextBlock):
current_block = TextBlockParam(
type="text", text=response.content_block.text
)
yield {"role": "assistant"}
if response.content_block.text:
yield {"content": response.content_block.text}
elif isinstance(response.content_block, ThinkingBlock):
current_block = ThinkingBlockParam(
type="thinking",
thinking=response.content_block.thinking,
signature=response.content_block.signature,
)
elif isinstance(response.content_block, RedactedThinkingBlock):
current_block = RedactedThinkingBlockParam(
type="redacted_thinking", data=response.content_block.data
)
LOGGER.debug(
"Some of Claudes internal reasoning has been automatically "
"encrypted for safety reasons. This doesnt affect the quality of "
"responses"
)
elif isinstance(response, RawContentBlockDeltaEvent):
if current_block is None:
raise ValueError("Unexpected delta without a block")
if isinstance(response.delta, InputJSONDelta):
current_tool_args += response.delta.partial_json
elif isinstance(response.delta, TextDelta):
text_block = cast(TextBlockParam, current_block)
text_block["text"] += response.delta.text
yield {"content": response.delta.text}
elif isinstance(response.delta, ThinkingDelta):
thinking_block = cast(ThinkingBlockParam, current_block)
thinking_block["thinking"] += response.delta.thinking
elif isinstance(response.delta, SignatureDelta):
thinking_block = cast(ThinkingBlockParam, current_block)
thinking_block["signature"] += response.delta.signature
elif isinstance(response, RawContentBlockStopEvent):
if current_block is None:
raise ValueError("Unexpected stop event without a current block")
if current_block["type"] == "tool_use":
# tool block
tool_args = json.loads(current_tool_args) if current_tool_args else {}
current_block["input"] = tool_args
yield {
"tool_calls": [
llm.ToolInput(
id=current_block["id"],
tool_name=current_block["name"],
tool_args=tool_args,
)
]
}
elif current_block["type"] == "thinking":
# thinking block
LOGGER.debug("Thinking: %s", current_block["thinking"])
if current_message is None:
raise ValueError("Unexpected stop event without a current message")
current_message["content"].append(current_block) # type: ignore[union-attr]
current_block = None
elif isinstance(response, RawMessageDeltaEvent):
if (usage := response.usage) is not None:
chat_log.async_trace(_create_token_stats(input_usage, usage))
if response.delta.stop_reason == "refusal":
raise HomeAssistantError("Potential policy violation detected")
elif isinstance(response, RawMessageStopEvent):
if current_message is not None:
messages.append(current_message)
current_message = None
def _create_token_stats(
input_usage: Usage | None, response_usage: MessageDeltaUsage
) -> dict[str, Any]:
"""Create token stats for conversation agent tracing."""
input_tokens = 0
cached_input_tokens = 0
if input_usage:
input_tokens = input_usage.input_tokens
cached_input_tokens = input_usage.cache_creation_input_tokens or 0
output_tokens = response_usage.output_tokens
return {
"stats": {
"input_tokens": input_tokens,
"cached_input_tokens": cached_input_tokens,
"output_tokens": output_tokens,
}
}
async_add_entities(
[AnthropicConversationEntity(config_entry, subentry)],
config_subentry_id=subentry.subentry_id,
)
class AnthropicConversationEntity(
conversation.ConversationEntity, conversation.AbstractConversationAgent
conversation.ConversationEntity,
conversation.AbstractConversationAgent,
AnthropicBaseLLMEntity,
):
"""Anthropic conversation agent."""
_attr_has_entity_name = True
_attr_name = None
_attr_supports_streaming = True
def __init__(self, entry: AnthropicConfigEntry) -> None:
def __init__(self, entry: AnthropicConfigEntry, subentry: ConfigSubentry) -> None:
"""Initialize the agent."""
self.entry = entry
self._attr_unique_id = entry.entry_id
self._attr_device_info = dr.DeviceInfo(
identifiers={(DOMAIN, entry.entry_id)},
manufacturer="Anthropic",
model="Claude",
entry_type=dr.DeviceEntryType.SERVICE,
)
if self.entry.options.get(CONF_LLM_HASS_API):
super().__init__(entry, subentry)
if self.subentry.data.get(CONF_LLM_HASS_API):
self._attr_supported_features = (
conversation.ConversationEntityFeature.CONTROL
)
@@ -350,90 +52,25 @@ class AnthropicConversationEntity(
"""Return a list of supported languages."""
return MATCH_ALL
async def async_added_to_hass(self) -> None:
"""When entity is added to Home Assistant."""
await super().async_added_to_hass()
self.entry.async_on_unload(
self.entry.add_update_listener(self._async_entry_update_listener)
)
async def _async_handle_message(
self,
user_input: conversation.ConversationInput,
chat_log: conversation.ChatLog,
) -> conversation.ConversationResult:
"""Call the API."""
options = self.entry.options
options = self.subentry.data
try:
await chat_log.async_update_llm_data(
DOMAIN,
user_input,
await chat_log.async_provide_llm_data(
user_input.as_llm_context(DOMAIN),
options.get(CONF_LLM_HASS_API),
options.get(CONF_PROMPT),
user_input.extra_system_prompt,
)
except conversation.ConverseError as err:
return err.as_conversation_result()
tools: list[ToolParam] | None = None
if chat_log.llm_api:
tools = [
_format_tool(tool, chat_log.llm_api.custom_serializer)
for tool in chat_log.llm_api.tools
]
system = chat_log.content[0]
if not isinstance(system, conversation.SystemContent):
raise TypeError("First message must be a system message")
messages = _convert_content(chat_log.content[1:])
client = self.entry.runtime_data
thinking_budget = options.get(CONF_THINKING_BUDGET, RECOMMENDED_THINKING_BUDGET)
model = options.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL)
# To prevent infinite loops, we limit the number of iterations
for _iteration in range(MAX_TOOL_ITERATIONS):
model_args = {
"model": model,
"messages": messages,
"tools": tools or NOT_GIVEN,
"max_tokens": options.get(CONF_MAX_TOKENS, RECOMMENDED_MAX_TOKENS),
"system": system.content,
"stream": True,
}
if model in THINKING_MODELS and thinking_budget >= MIN_THINKING_BUDGET:
model_args["thinking"] = ThinkingConfigEnabledParam(
type="enabled", budget_tokens=thinking_budget
)
else:
model_args["thinking"] = ThinkingConfigDisabledParam(type="disabled")
model_args["temperature"] = options.get(
CONF_TEMPERATURE, RECOMMENDED_TEMPERATURE
)
try:
stream = await client.messages.create(**model_args)
except anthropic.AnthropicError as err:
raise HomeAssistantError(
f"Sorry, I had a problem talking to Anthropic: {err}"
) from err
messages.extend(
_convert_content(
[
content
async for content in chat_log.async_add_delta_content_stream(
user_input.agent_id,
_transform_stream(chat_log, stream, messages),
)
if not isinstance(content, conversation.AssistantContent)
]
)
)
if not chat_log.unresponded_tool_results:
break
await self._async_handle_chat_log(chat_log)
response_content = chat_log.content[-1]
if not isinstance(response_content, conversation.AssistantContent):
@@ -445,10 +82,3 @@ class AnthropicConversationEntity(
conversation_id=chat_log.conversation_id,
continue_conversation=chat_log.continue_conversation,
)
async def _async_entry_update_listener(
self, hass: HomeAssistant, entry: ConfigEntry
) -> None:
"""Handle options update."""
# Reload as we update device info + entity name + supported features
await hass.config_entries.async_reload(entry.entry_id)

View File

@@ -0,0 +1,393 @@
"""Base entity for Anthropic."""
from collections.abc import AsyncGenerator, Callable, Iterable
import json
from typing import Any, cast
import anthropic
from anthropic import AsyncStream
from anthropic._types import NOT_GIVEN
from anthropic.types import (
InputJSONDelta,
MessageDeltaUsage,
MessageParam,
MessageStreamEvent,
RawContentBlockDeltaEvent,
RawContentBlockStartEvent,
RawContentBlockStopEvent,
RawMessageDeltaEvent,
RawMessageStartEvent,
RawMessageStopEvent,
RedactedThinkingBlock,
RedactedThinkingBlockParam,
SignatureDelta,
TextBlock,
TextBlockParam,
TextDelta,
ThinkingBlock,
ThinkingBlockParam,
ThinkingConfigDisabledParam,
ThinkingConfigEnabledParam,
ThinkingDelta,
ToolParam,
ToolResultBlockParam,
ToolUseBlock,
ToolUseBlockParam,
Usage,
)
from voluptuous_openapi import convert
from homeassistant.components import conversation
from homeassistant.config_entries import ConfigSubentry
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import device_registry as dr, llm
from homeassistant.helpers.entity import Entity
from . import AnthropicConfigEntry
from .const import (
CONF_CHAT_MODEL,
CONF_MAX_TOKENS,
CONF_TEMPERATURE,
CONF_THINKING_BUDGET,
DOMAIN,
LOGGER,
MIN_THINKING_BUDGET,
RECOMMENDED_CHAT_MODEL,
RECOMMENDED_MAX_TOKENS,
RECOMMENDED_TEMPERATURE,
RECOMMENDED_THINKING_BUDGET,
THINKING_MODELS,
)
# Max number of back and forth with the LLM to generate a response
MAX_TOOL_ITERATIONS = 10
def _format_tool(
tool: llm.Tool, custom_serializer: Callable[[Any], Any] | None
) -> ToolParam:
"""Format tool specification."""
return ToolParam(
name=tool.name,
description=tool.description or "",
input_schema=convert(tool.parameters, custom_serializer=custom_serializer),
)
def _convert_content(
chat_content: Iterable[conversation.Content],
) -> list[MessageParam]:
"""Transform HA chat_log content into Anthropic API format."""
messages: list[MessageParam] = []
for content in chat_content:
if isinstance(content, conversation.ToolResultContent):
tool_result_block = ToolResultBlockParam(
type="tool_result",
tool_use_id=content.tool_call_id,
content=json.dumps(content.tool_result),
)
if not messages or messages[-1]["role"] != "user":
messages.append(
MessageParam(
role="user",
content=[tool_result_block],
)
)
elif isinstance(messages[-1]["content"], str):
messages[-1]["content"] = [
TextBlockParam(type="text", text=messages[-1]["content"]),
tool_result_block,
]
else:
messages[-1]["content"].append(tool_result_block) # type: ignore[attr-defined]
elif isinstance(content, conversation.UserContent):
# Combine consequent user messages
if not messages or messages[-1]["role"] != "user":
messages.append(
MessageParam(
role="user",
content=content.content,
)
)
elif isinstance(messages[-1]["content"], str):
messages[-1]["content"] = [
TextBlockParam(type="text", text=messages[-1]["content"]),
TextBlockParam(type="text", text=content.content),
]
else:
messages[-1]["content"].append( # type: ignore[attr-defined]
TextBlockParam(type="text", text=content.content)
)
elif isinstance(content, conversation.AssistantContent):
# Combine consequent assistant messages
if not messages or messages[-1]["role"] != "assistant":
messages.append(
MessageParam(
role="assistant",
content=[],
)
)
if content.content:
messages[-1]["content"].append( # type: ignore[union-attr]
TextBlockParam(type="text", text=content.content)
)
if content.tool_calls:
messages[-1]["content"].extend( # type: ignore[union-attr]
[
ToolUseBlockParam(
type="tool_use",
id=tool_call.id,
name=tool_call.tool_name,
input=tool_call.tool_args,
)
for tool_call in content.tool_calls
]
)
else:
# Note: We don't pass SystemContent here as its passed to the API as the prompt
raise TypeError(f"Unexpected content type: {type(content)}")
return messages
async def _transform_stream( # noqa: C901 - This is complex, but better to have it in one place
chat_log: conversation.ChatLog,
result: AsyncStream[MessageStreamEvent],
messages: list[MessageParam],
) -> AsyncGenerator[conversation.AssistantContentDeltaDict]:
"""Transform the response stream into HA format.
A typical stream of responses might look something like the following:
- RawMessageStartEvent with no content
- RawContentBlockStartEvent with an empty ThinkingBlock (if extended thinking is enabled)
- RawContentBlockDeltaEvent with a ThinkingDelta
- RawContentBlockDeltaEvent with a ThinkingDelta
- RawContentBlockDeltaEvent with a ThinkingDelta
- ...
- RawContentBlockDeltaEvent with a SignatureDelta
- RawContentBlockStopEvent
- RawContentBlockStartEvent with a RedactedThinkingBlock (occasionally)
- RawContentBlockStopEvent (RedactedThinkingBlock does not have a delta)
- RawContentBlockStartEvent with an empty TextBlock
- RawContentBlockDeltaEvent with a TextDelta
- RawContentBlockDeltaEvent with a TextDelta
- RawContentBlockDeltaEvent with a TextDelta
- ...
- RawContentBlockStopEvent
- RawContentBlockStartEvent with ToolUseBlock specifying the function name
- RawContentBlockDeltaEvent with a InputJSONDelta
- RawContentBlockDeltaEvent with a InputJSONDelta
- ...
- RawContentBlockStopEvent
- RawMessageDeltaEvent with a stop_reason='tool_use'
- RawMessageStopEvent(type='message_stop')
Each message could contain multiple blocks of the same type.
"""
if result is None:
raise TypeError("Expected a stream of messages")
current_message: MessageParam | None = None
current_block: (
TextBlockParam
| ToolUseBlockParam
| ThinkingBlockParam
| RedactedThinkingBlockParam
| None
) = None
current_tool_args: str
input_usage: Usage | None = None
async for response in result:
LOGGER.debug("Received response: %s", response)
if isinstance(response, RawMessageStartEvent):
if response.message.role != "assistant":
raise ValueError("Unexpected message role")
current_message = MessageParam(role=response.message.role, content=[])
input_usage = response.message.usage
elif isinstance(response, RawContentBlockStartEvent):
if isinstance(response.content_block, ToolUseBlock):
current_block = ToolUseBlockParam(
type="tool_use",
id=response.content_block.id,
name=response.content_block.name,
input="",
)
current_tool_args = ""
elif isinstance(response.content_block, TextBlock):
current_block = TextBlockParam(
type="text", text=response.content_block.text
)
yield {"role": "assistant"}
if response.content_block.text:
yield {"content": response.content_block.text}
elif isinstance(response.content_block, ThinkingBlock):
current_block = ThinkingBlockParam(
type="thinking",
thinking=response.content_block.thinking,
signature=response.content_block.signature,
)
elif isinstance(response.content_block, RedactedThinkingBlock):
current_block = RedactedThinkingBlockParam(
type="redacted_thinking", data=response.content_block.data
)
LOGGER.debug(
"Some of Claudes internal reasoning has been automatically "
"encrypted for safety reasons. This doesnt affect the quality of "
"responses"
)
elif isinstance(response, RawContentBlockDeltaEvent):
if current_block is None:
raise ValueError("Unexpected delta without a block")
if isinstance(response.delta, InputJSONDelta):
current_tool_args += response.delta.partial_json
elif isinstance(response.delta, TextDelta):
text_block = cast(TextBlockParam, current_block)
text_block["text"] += response.delta.text
yield {"content": response.delta.text}
elif isinstance(response.delta, ThinkingDelta):
thinking_block = cast(ThinkingBlockParam, current_block)
thinking_block["thinking"] += response.delta.thinking
elif isinstance(response.delta, SignatureDelta):
thinking_block = cast(ThinkingBlockParam, current_block)
thinking_block["signature"] += response.delta.signature
elif isinstance(response, RawContentBlockStopEvent):
if current_block is None:
raise ValueError("Unexpected stop event without a current block")
if current_block["type"] == "tool_use":
# tool block
tool_args = json.loads(current_tool_args) if current_tool_args else {}
current_block["input"] = tool_args
yield {
"tool_calls": [
llm.ToolInput(
id=current_block["id"],
tool_name=current_block["name"],
tool_args=tool_args,
)
]
}
elif current_block["type"] == "thinking":
# thinking block
LOGGER.debug("Thinking: %s", current_block["thinking"])
if current_message is None:
raise ValueError("Unexpected stop event without a current message")
current_message["content"].append(current_block) # type: ignore[union-attr]
current_block = None
elif isinstance(response, RawMessageDeltaEvent):
if (usage := response.usage) is not None:
chat_log.async_trace(_create_token_stats(input_usage, usage))
if response.delta.stop_reason == "refusal":
raise HomeAssistantError("Potential policy violation detected")
elif isinstance(response, RawMessageStopEvent):
if current_message is not None:
messages.append(current_message)
current_message = None
def _create_token_stats(
input_usage: Usage | None, response_usage: MessageDeltaUsage
) -> dict[str, Any]:
"""Create token stats for conversation agent tracing."""
input_tokens = 0
cached_input_tokens = 0
if input_usage:
input_tokens = input_usage.input_tokens
cached_input_tokens = input_usage.cache_creation_input_tokens or 0
output_tokens = response_usage.output_tokens
return {
"stats": {
"input_tokens": input_tokens,
"cached_input_tokens": cached_input_tokens,
"output_tokens": output_tokens,
}
}
class AnthropicBaseLLMEntity(Entity):
"""Anthropic base LLM entity."""
def __init__(self, entry: AnthropicConfigEntry, subentry: ConfigSubentry) -> None:
"""Initialize the entity."""
self.entry = entry
self.subentry = subentry
self._attr_name = subentry.title
self._attr_unique_id = subentry.subentry_id
self._attr_device_info = dr.DeviceInfo(
identifiers={(DOMAIN, subentry.subentry_id)},
name=subentry.title,
manufacturer="Anthropic",
model="Claude",
entry_type=dr.DeviceEntryType.SERVICE,
)
async def _async_handle_chat_log(
self,
chat_log: conversation.ChatLog,
) -> None:
"""Generate an answer for the chat log."""
options = self.subentry.data
tools: list[ToolParam] | None = None
if chat_log.llm_api:
tools = [
_format_tool(tool, chat_log.llm_api.custom_serializer)
for tool in chat_log.llm_api.tools
]
system = chat_log.content[0]
if not isinstance(system, conversation.SystemContent):
raise TypeError("First message must be a system message")
messages = _convert_content(chat_log.content[1:])
client = self.entry.runtime_data
thinking_budget = options.get(CONF_THINKING_BUDGET, RECOMMENDED_THINKING_BUDGET)
model = options.get(CONF_CHAT_MODEL, RECOMMENDED_CHAT_MODEL)
# To prevent infinite loops, we limit the number of iterations
for _iteration in range(MAX_TOOL_ITERATIONS):
model_args = {
"model": model,
"messages": messages,
"tools": tools or NOT_GIVEN,
"max_tokens": options.get(CONF_MAX_TOKENS, RECOMMENDED_MAX_TOKENS),
"system": system.content,
"stream": True,
}
if model in THINKING_MODELS and thinking_budget >= MIN_THINKING_BUDGET:
model_args["thinking"] = ThinkingConfigEnabledParam(
type="enabled", budget_tokens=thinking_budget
)
else:
model_args["thinking"] = ThinkingConfigDisabledParam(type="disabled")
model_args["temperature"] = options.get(
CONF_TEMPERATURE, RECOMMENDED_TEMPERATURE
)
try:
stream = await client.messages.create(**model_args)
except anthropic.AnthropicError as err:
raise HomeAssistantError(
f"Sorry, I had a problem talking to Anthropic: {err}"
) from err
messages.extend(
_convert_content(
[
content
async for content in chat_log.async_add_delta_content_stream(
self.entity_id,
_transform_stream(chat_log, stream, messages),
)
if not isinstance(content, conversation.AssistantContent)
]
)
)
if not chat_log.unresponded_tool_results:
break

View File

@@ -12,28 +12,44 @@
"timeout_connect": "[%key:common::config_flow::error::timeout_connect%]",
"authentication_error": "[%key:common::config_flow::error::invalid_auth%]",
"unknown": "[%key:common::config_flow::error::unknown%]"
},
"abort": {
"already_configured": "[%key:common::config_flow::abort::already_configured_service%]"
}
},
"options": {
"step": {
"init": {
"data": {
"prompt": "Instructions",
"chat_model": "[%key:common::generic::model%]",
"max_tokens": "Maximum tokens to return in response",
"temperature": "Temperature",
"llm_hass_api": "[%key:common::config_flow::data::llm_hass_api%]",
"recommended": "Recommended model settings",
"thinking_budget_tokens": "Thinking budget"
},
"data_description": {
"prompt": "Instruct how the LLM should respond. This can be a template.",
"thinking_budget_tokens": "The number of tokens the model can use to think about the response out of the total maximum number of tokens. Set to 1024 or greater to enable extended thinking."
"config_subentries": {
"conversation": {
"initiate_flow": {
"user": "Add conversation agent",
"reconfigure": "Reconfigure conversation agent"
},
"entry_type": "Conversation agent",
"step": {
"set_options": {
"data": {
"name": "[%key:common::config_flow::data::name%]",
"prompt": "Instructions",
"chat_model": "[%key:common::generic::model%]",
"max_tokens": "Maximum tokens to return in response",
"temperature": "Temperature",
"llm_hass_api": "[%key:common::config_flow::data::llm_hass_api%]",
"recommended": "Recommended model settings",
"thinking_budget_tokens": "Thinking budget"
},
"data_description": {
"prompt": "Instruct how the LLM should respond. This can be a template.",
"thinking_budget_tokens": "The number of tokens the model can use to think about the response out of the total maximum number of tokens. Set to 1024 or greater to enable extended thinking."
}
}
},
"abort": {
"reconfigure_successful": "[%key:common::config_flow::abort::reconfigure_successful%]",
"entry_not_loaded": "Cannot add things while the configuration is disabled."
},
"error": {
"thinking_budget_too_large": "Maximum tokens must be greater than the thinking budget."
}
},
"error": {
"thinking_budget_too_large": "Maximum tokens must be greater than the thinking budget."
}
}
}

View File

@@ -12,6 +12,7 @@ from homeassistant.components.sensor import (
)
from homeassistant.const import (
PERCENTAGE,
EntityCategory,
UnitOfApparentPower,
UnitOfElectricCurrent,
UnitOfElectricPotential,
@@ -35,6 +36,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
"alarmdel": SensorEntityDescription(
key="alarmdel",
translation_key="alarm_delay",
entity_category=EntityCategory.DIAGNOSTIC,
),
"ambtemp": SensorEntityDescription(
key="ambtemp",
@@ -47,15 +49,18 @@ SENSORS: dict[str, SensorEntityDescription] = {
key="apc",
translation_key="apc_status",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"apcmodel": SensorEntityDescription(
key="apcmodel",
translation_key="apc_model",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"badbatts": SensorEntityDescription(
key="badbatts",
translation_key="bad_batteries",
entity_category=EntityCategory.DIAGNOSTIC,
),
"battdate": SensorEntityDescription(
key="battdate",
@@ -82,6 +87,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
key="cable",
translation_key="cable_type",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"cumonbatt": SensorEntityDescription(
key="cumonbatt",
@@ -94,52 +100,63 @@ SENSORS: dict[str, SensorEntityDescription] = {
key="date",
translation_key="date",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"dipsw": SensorEntityDescription(
key="dipsw",
translation_key="dip_switch_settings",
entity_category=EntityCategory.DIAGNOSTIC,
),
"dlowbatt": SensorEntityDescription(
key="dlowbatt",
translation_key="low_battery_signal",
entity_category=EntityCategory.DIAGNOSTIC,
),
"driver": SensorEntityDescription(
key="driver",
translation_key="driver",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"dshutd": SensorEntityDescription(
key="dshutd",
translation_key="shutdown_delay",
entity_category=EntityCategory.DIAGNOSTIC,
),
"dwake": SensorEntityDescription(
key="dwake",
translation_key="wake_delay",
entity_category=EntityCategory.DIAGNOSTIC,
),
"end apc": SensorEntityDescription(
key="end apc",
translation_key="date_and_time",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"extbatts": SensorEntityDescription(
key="extbatts",
translation_key="external_batteries",
entity_category=EntityCategory.DIAGNOSTIC,
),
"firmware": SensorEntityDescription(
key="firmware",
translation_key="firmware_version",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"hitrans": SensorEntityDescription(
key="hitrans",
translation_key="transfer_high",
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
device_class=SensorDeviceClass.VOLTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
),
"hostname": SensorEntityDescription(
key="hostname",
translation_key="hostname",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"humidity": SensorEntityDescription(
key="humidity",
@@ -163,10 +180,12 @@ SENSORS: dict[str, SensorEntityDescription] = {
key="lastxfer",
translation_key="last_transfer",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"linefail": SensorEntityDescription(
key="linefail",
translation_key="line_failure",
entity_category=EntityCategory.DIAGNOSTIC,
),
"linefreq": SensorEntityDescription(
key="linefreq",
@@ -198,15 +217,18 @@ SENSORS: dict[str, SensorEntityDescription] = {
translation_key="transfer_low",
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
device_class=SensorDeviceClass.VOLTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
),
"mandate": SensorEntityDescription(
key="mandate",
translation_key="manufacture_date",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"masterupd": SensorEntityDescription(
key="masterupd",
translation_key="master_update",
entity_category=EntityCategory.DIAGNOSTIC,
),
"maxlinev": SensorEntityDescription(
key="maxlinev",
@@ -217,11 +239,13 @@ SENSORS: dict[str, SensorEntityDescription] = {
"maxtime": SensorEntityDescription(
key="maxtime",
translation_key="max_time",
entity_category=EntityCategory.DIAGNOSTIC,
),
"mbattchg": SensorEntityDescription(
key="mbattchg",
translation_key="max_battery_charge",
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
),
"minlinev": SensorEntityDescription(
key="minlinev",
@@ -232,41 +256,48 @@ SENSORS: dict[str, SensorEntityDescription] = {
"mintimel": SensorEntityDescription(
key="mintimel",
translation_key="min_time",
entity_category=EntityCategory.DIAGNOSTIC,
),
"model": SensorEntityDescription(
key="model",
translation_key="model",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"nombattv": SensorEntityDescription(
key="nombattv",
translation_key="battery_nominal_voltage",
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
device_class=SensorDeviceClass.VOLTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
),
"nominv": SensorEntityDescription(
key="nominv",
translation_key="nominal_input_voltage",
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
device_class=SensorDeviceClass.VOLTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
),
"nomoutv": SensorEntityDescription(
key="nomoutv",
translation_key="nominal_output_voltage",
native_unit_of_measurement=UnitOfElectricPotential.VOLT,
device_class=SensorDeviceClass.VOLTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
),
"nompower": SensorEntityDescription(
key="nompower",
translation_key="nominal_output_power",
native_unit_of_measurement=UnitOfPower.WATT,
device_class=SensorDeviceClass.POWER,
entity_category=EntityCategory.DIAGNOSTIC,
),
"nomapnt": SensorEntityDescription(
key="nomapnt",
translation_key="nominal_apparent_power",
native_unit_of_measurement=UnitOfApparentPower.VOLT_AMPERE,
device_class=SensorDeviceClass.APPARENT_POWER,
entity_category=EntityCategory.DIAGNOSTIC,
),
"numxfers": SensorEntityDescription(
key="numxfers",
@@ -291,21 +322,25 @@ SENSORS: dict[str, SensorEntityDescription] = {
key="reg1",
translation_key="register_1_fault",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"reg2": SensorEntityDescription(
key="reg2",
translation_key="register_2_fault",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"reg3": SensorEntityDescription(
key="reg3",
translation_key="register_3_fault",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"retpct": SensorEntityDescription(
key="retpct",
translation_key="restore_capacity",
native_unit_of_measurement=PERCENTAGE,
entity_category=EntityCategory.DIAGNOSTIC,
),
"selftest": SensorEntityDescription(
key="selftest",
@@ -315,20 +350,24 @@ SENSORS: dict[str, SensorEntityDescription] = {
key="sense",
translation_key="sensitivity",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"serialno": SensorEntityDescription(
key="serialno",
translation_key="serial_number",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"starttime": SensorEntityDescription(
key="starttime",
translation_key="startup_time",
entity_category=EntityCategory.DIAGNOSTIC,
),
"statflag": SensorEntityDescription(
key="statflag",
translation_key="online_status",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"status": SensorEntityDescription(
key="status",
@@ -337,6 +376,7 @@ SENSORS: dict[str, SensorEntityDescription] = {
"stesti": SensorEntityDescription(
key="stesti",
translation_key="self_test_interval",
entity_category=EntityCategory.DIAGNOSTIC,
),
"timeleft": SensorEntityDescription(
key="timeleft",
@@ -360,23 +400,28 @@ SENSORS: dict[str, SensorEntityDescription] = {
key="upsname",
translation_key="ups_name",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"version": SensorEntityDescription(
key="version",
translation_key="version",
entity_registry_enabled_default=False,
entity_category=EntityCategory.DIAGNOSTIC,
),
"xoffbat": SensorEntityDescription(
key="xoffbat",
translation_key="transfer_from_battery",
entity_category=EntityCategory.DIAGNOSTIC,
),
"xoffbatt": SensorEntityDescription(
key="xoffbatt",
translation_key="transfer_from_battery",
entity_category=EntityCategory.DIAGNOSTIC,
),
"xonbatt": SensorEntityDescription(
key="xonbatt",
translation_key="transfer_to_battery",
entity_category=EntityCategory.DIAGNOSTIC,
),
}

View File

@@ -260,11 +260,18 @@ class APIEntityStateView(HomeAssistantView):
if not user.is_admin:
raise Unauthorized(entity_id=entity_id)
hass = request.app[KEY_HASS]
body = await request.text()
try:
data = await request.json()
data: Any = json_loads(body) if body else None
except ValueError:
return self.json_message("Invalid JSON specified.", HTTPStatus.BAD_REQUEST)
if not isinstance(data, dict):
return self.json_message(
"State data should be a JSON object.", HTTPStatus.BAD_REQUEST
)
if (new_state := data.get("state")) is None:
return self.json_message("No state specified.", HTTPStatus.BAD_REQUEST)
@@ -477,9 +484,19 @@ class APITemplateView(HomeAssistantView):
@require_admin
async def post(self, request: web.Request) -> web.Response:
"""Render a template."""
body = await request.text()
try:
data: Any = json_loads(body) if body else None
except ValueError:
return self.json_message("Invalid JSON specified.", HTTPStatus.BAD_REQUEST)
if not isinstance(data, dict):
return self.json_message(
"Template data should be a JSON object.", HTTPStatus.BAD_REQUEST
)
tpl = _cached_template(data["template"], request.app[KEY_HASS])
try:
data = await request.json()
tpl = _cached_template(data["template"], request.app[KEY_HASS])
return tpl.async_render(variables=data.get("variables"), parse_result=False) # type: ignore[no-any-return]
except (ValueError, TemplateError) as ex:
return self.json_message(

View File

@@ -191,7 +191,7 @@ class AppleTvMediaPlayer(
self._is_feature_available(FeatureName.PowerState)
and self.atv.power.power_state == PowerState.Off
):
return MediaPlayerState.STANDBY
return MediaPlayerState.OFF
if self._playing:
state = self._playing.device_state
if state in (DeviceState.Idle, DeviceState.Loading):
@@ -200,7 +200,7 @@ class AppleTvMediaPlayer(
return MediaPlayerState.PLAYING
if state in (DeviceState.Paused, DeviceState.Seeking, DeviceState.Stopped):
return MediaPlayerState.PAUSED
return MediaPlayerState.STANDBY # Bad or unknown state?
return MediaPlayerState.IDLE # Bad or unknown state?
return None
@callback

View File

@@ -1119,6 +1119,7 @@ class PipelineRun:
) is not None:
# Sentence trigger matched
agent_id = "sentence_trigger"
processed_locally = True
intent_response = intent.IntentResponse(
self.pipeline.conversation_language
)
@@ -1207,6 +1208,15 @@ class PipelineRun:
self._streamed_response_text = True
self.process_event(
PipelineEvent(
PipelineEventType.INTENT_PROGRESS,
{
"tts_start_streaming": True,
},
)
)
async def tts_input_stream_generator() -> AsyncGenerator[str]:
"""Yield TTS input stream."""
while (tts_input := await tts_input_stream.get()) is not None:

View File

@@ -1,13 +1,23 @@
"""Base class for assist satellite entities."""
from dataclasses import asdict
import logging
from pathlib import Path
from typing import Any
from hassil.util import (
PUNCTUATION_END,
PUNCTUATION_END_WORD,
PUNCTUATION_START,
PUNCTUATION_START_WORD,
)
import voluptuous as vol
from homeassistant.components.http import StaticPathConfig
from homeassistant.config_entries import ConfigEntry
from homeassistant.core import HomeAssistant
from homeassistant.const import ATTR_ENTITY_ID
from homeassistant.core import HomeAssistant, ServiceCall, SupportsResponse
from homeassistant.exceptions import HomeAssistantError
from homeassistant.helpers import config_validation as cv
from homeassistant.helpers.entity_component import EntityComponent
from homeassistant.helpers.typing import ConfigType
@@ -23,6 +33,7 @@ from .const import (
)
from .entity import (
AssistSatelliteAnnouncement,
AssistSatelliteAnswer,
AssistSatelliteConfiguration,
AssistSatelliteEntity,
AssistSatelliteEntityDescription,
@@ -34,6 +45,7 @@ from .websocket_api import async_register_websocket_api
__all__ = [
"DOMAIN",
"AssistSatelliteAnnouncement",
"AssistSatelliteAnswer",
"AssistSatelliteConfiguration",
"AssistSatelliteEntity",
"AssistSatelliteEntityDescription",
@@ -59,9 +71,9 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
cv.make_entity_service_schema(
{
vol.Optional("message"): str,
vol.Optional("media_id"): str,
vol.Optional("preannounce"): bool,
vol.Optional("preannounce_media_id"): str,
vol.Optional("media_id"): _media_id_validator,
vol.Optional("preannounce", default=True): bool,
vol.Optional("preannounce_media_id"): _media_id_validator,
}
),
cv.has_at_least_one_key("message", "media_id"),
@@ -69,15 +81,16 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"async_internal_announce",
[AssistSatelliteEntityFeature.ANNOUNCE],
)
component.async_register_entity_service(
"start_conversation",
vol.All(
cv.make_entity_service_schema(
{
vol.Optional("start_message"): str,
vol.Optional("start_media_id"): str,
vol.Optional("preannounce"): bool,
vol.Optional("preannounce_media_id"): str,
vol.Optional("start_media_id"): _media_id_validator,
vol.Optional("preannounce", default=True): bool,
vol.Optional("preannounce_media_id"): _media_id_validator,
vol.Optional("extra_system_prompt"): str,
}
),
@@ -86,6 +99,62 @@ async def async_setup(hass: HomeAssistant, config: ConfigType) -> bool:
"async_internal_start_conversation",
[AssistSatelliteEntityFeature.START_CONVERSATION],
)
async def handle_ask_question(call: ServiceCall) -> dict[str, Any]:
"""Handle a Show View service call."""
satellite_entity_id: str = call.data[ATTR_ENTITY_ID]
satellite_entity: AssistSatelliteEntity | None = component.get_entity(
satellite_entity_id
)
if satellite_entity is None:
raise HomeAssistantError(
f"Invalid Assist satellite entity id: {satellite_entity_id}"
)
ask_question_args = {
"question": call.data.get("question"),
"question_media_id": call.data.get("question_media_id"),
"preannounce": call.data.get("preannounce", True),
"answers": call.data.get("answers"),
}
if preannounce_media_id := call.data.get("preannounce_media_id"):
ask_question_args["preannounce_media_id"] = preannounce_media_id
answer = await satellite_entity.async_internal_ask_question(**ask_question_args)
if answer is None:
raise HomeAssistantError("No answer from satellite")
return asdict(answer)
hass.services.async_register(
domain=DOMAIN,
service="ask_question",
service_func=handle_ask_question,
schema=vol.All(
{
vol.Required(ATTR_ENTITY_ID): cv.entity_domain(DOMAIN),
vol.Optional("question"): str,
vol.Optional("question_media_id"): _media_id_validator,
vol.Optional("preannounce", default=True): bool,
vol.Optional("preannounce_media_id"): _media_id_validator,
vol.Optional("answers"): [
{
vol.Required("id"): str,
vol.Required("sentences"): vol.All(
cv.ensure_list,
[cv.string],
has_one_non_empty_item,
has_no_punctuation,
),
}
],
},
cv.has_at_least_one_key("question", "question_media_id"),
),
supports_response=SupportsResponse.ONLY,
)
hass.data[CONNECTION_TEST_DATA] = {}
async_register_websocket_api(hass)
hass.http.register_view(ConnectionTestView())
@@ -110,3 +179,46 @@ async def async_setup_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
async def async_unload_entry(hass: HomeAssistant, entry: ConfigEntry) -> bool:
"""Unload a config entry."""
return await hass.data[DATA_COMPONENT].async_unload_entry(entry)
def has_no_punctuation(value: list[str]) -> list[str]:
"""Validate result does not contain punctuation."""
for sentence in value:
if (
PUNCTUATION_START.search(sentence)
or PUNCTUATION_END.search(sentence)
or PUNCTUATION_START_WORD.search(sentence)
or PUNCTUATION_END_WORD.search(sentence)
):
raise vol.Invalid("sentence should not contain punctuation")
return value
def has_one_non_empty_item(value: list[str]) -> list[str]:
"""Validate result has at least one item."""
if len(value) < 1:
raise vol.Invalid("at least one sentence is required")
for sentence in value:
if not sentence:
raise vol.Invalid("sentences cannot be empty")
return value
# Validator for media_id fields that accepts both string and media selector format
_media_id_validator = vol.Any(
cv.string, # Plain string format
vol.All(
vol.Schema(
{
vol.Required("media_content_id"): cv.string,
vol.Required("media_content_type"): cv.string,
vol.Remove("metadata"): dict, # Ignore metadata if present
}
),
# Extract media_content_id from media selector format
lambda x: x["media_content_id"],
),
)

View File

@@ -4,12 +4,16 @@ from abc import abstractmethod
import asyncio
from collections.abc import AsyncIterable
import contextlib
from dataclasses import dataclass
from dataclasses import dataclass, field
from enum import StrEnum
import logging
import time
from typing import Any, Literal, final
from hassil import Intents, recognize
from hassil.expression import Expression, ListReference, Sequence
from hassil.intents import WildcardSlotList
from homeassistant.components import conversation, media_source, stt, tts
from homeassistant.components.assist_pipeline import (
OPTION_PREFERRED,
@@ -105,6 +109,20 @@ class AssistSatelliteAnnouncement:
"""Media ID to be played before announcement."""
@dataclass
class AssistSatelliteAnswer:
"""Answer to a question."""
id: str | None
"""Matched answer id or None if no answer was matched."""
sentence: str
"""Raw sentence text from user response."""
slots: dict[str, Any] = field(default_factory=dict)
"""Matched slots from answer."""
class AssistSatelliteEntity(entity.Entity):
"""Entity encapsulating the state and functionality of an Assist satellite."""
@@ -122,6 +140,7 @@ class AssistSatelliteEntity(entity.Entity):
_wake_word_intercept_future: asyncio.Future[str | None] | None = None
_attr_tts_options: dict[str, Any] | None = None
_pipeline_task: asyncio.Task | None = None
_ask_question_future: asyncio.Future[str | None] | None = None
__assist_satellite_state = AssistSatelliteState.IDLE
@@ -309,6 +328,112 @@ class AssistSatelliteEntity(entity.Entity):
"""Start a conversation from the satellite."""
raise NotImplementedError
async def async_internal_ask_question(
self,
question: str | None = None,
question_media_id: str | None = None,
preannounce: bool = True,
preannounce_media_id: str = PREANNOUNCE_URL,
answers: list[dict[str, Any]] | None = None,
) -> AssistSatelliteAnswer | None:
"""Ask a question and get a user's response from the satellite.
If question_media_id is not provided, question is synthesized to audio
with the selected pipeline.
If question_media_id is provided, it is played directly. It is possible
to omit the message and the satellite will not show any text.
If preannounce is True, a sound is played before the start message or media.
If preannounce_media_id is provided, it overrides the default sound.
Calls async_start_conversation.
"""
await self._cancel_running_pipeline()
if question is None:
question = ""
announcement = await self._resolve_announcement_media_id(
question,
question_media_id,
preannounce_media_id=preannounce_media_id if preannounce else None,
)
if self._is_announcing:
raise SatelliteBusyError
self._is_announcing = True
self._set_state(AssistSatelliteState.RESPONDING)
self._ask_question_future = asyncio.Future()
try:
# Wait for announcement to finish
await self.async_start_conversation(announcement)
# Wait for response text
response_text = await self._ask_question_future
if response_text is None:
raise HomeAssistantError("No answer from question")
if not answers:
return AssistSatelliteAnswer(id=None, sentence=response_text)
return self._question_response_to_answer(response_text, answers)
finally:
self._is_announcing = False
self._set_state(AssistSatelliteState.IDLE)
self._ask_question_future = None
def _question_response_to_answer(
self, response_text: str, answers: list[dict[str, Any]]
) -> AssistSatelliteAnswer:
"""Match text to a pre-defined set of answers."""
# Build intents and match
intents = Intents.from_dict(
{
"language": self.hass.config.language,
"intents": {
"QuestionIntent": {
"data": [
{
"sentences": answer["sentences"],
"metadata": {"answer_id": answer["id"]},
}
for answer in answers
]
}
},
}
)
# Assume slot list references are wildcards
wildcard_names: set[str] = set()
for intent in intents.intents.values():
for intent_data in intent.data:
for sentence in intent_data.sentences:
_collect_list_references(sentence, wildcard_names)
for wildcard_name in wildcard_names:
intents.slot_lists[wildcard_name] = WildcardSlotList(wildcard_name)
# Match response text
result = recognize(response_text, intents)
if result is None:
# No match
return AssistSatelliteAnswer(id=None, sentence=response_text)
assert result.intent_metadata
return AssistSatelliteAnswer(
id=result.intent_metadata["answer_id"],
sentence=response_text,
slots={
entity_name: entity.value
for entity_name, entity in result.entities.items()
},
)
async def async_accept_pipeline_from_satellite(
self,
audio_stream: AsyncIterable[bytes],
@@ -351,6 +476,11 @@ class AssistSatelliteEntity(entity.Entity):
self._internal_on_pipeline_event(PipelineEvent(PipelineEventType.RUN_END))
return
if (self._ask_question_future is not None) and (
start_stage == PipelineStage.STT
):
end_stage = PipelineStage.STT
device_id = self.registry_entry.device_id if self.registry_entry else None
# Refresh context if necessary
@@ -433,6 +563,16 @@ class AssistSatelliteEntity(entity.Entity):
self._set_state(AssistSatelliteState.IDLE)
elif event.type is PipelineEventType.STT_START:
self._set_state(AssistSatelliteState.LISTENING)
elif event.type is PipelineEventType.STT_END:
# Intercepting text for ask question
if (
(self._ask_question_future is not None)
and (not self._ask_question_future.done())
and event.data
):
self._ask_question_future.set_result(
event.data.get("stt_output", {}).get("text")
)
elif event.type is PipelineEventType.INTENT_START:
self._set_state(AssistSatelliteState.PROCESSING)
elif event.type is PipelineEventType.TTS_START:
@@ -443,6 +583,12 @@ class AssistSatelliteEntity(entity.Entity):
if not self._run_has_tts:
self._set_state(AssistSatelliteState.IDLE)
if (self._ask_question_future is not None) and (
not self._ask_question_future.done()
):
# No text for ask question
self._ask_question_future.set_result(None)
self.on_pipeline_event(event)
@callback
@@ -577,3 +723,15 @@ class AssistSatelliteEntity(entity.Entity):
media_id_source=media_id_source,
preannounce_media_id=preannounce_media_id,
)
def _collect_list_references(expression: Expression, list_names: set[str]) -> None:
"""Collect list reference names recursively."""
if isinstance(expression, Sequence):
seq: Sequence = expression
for item in seq.items:
_collect_list_references(item, list_names)
elif isinstance(expression, ListReference):
# {list}
list_ref: ListReference = expression
list_names.add(list_ref.slot_name)

View File

@@ -10,6 +10,9 @@
},
"start_conversation": {
"service": "mdi:forum"
},
"ask_question": {
"service": "mdi:microphone-question"
}
}
}

View File

@@ -5,5 +5,6 @@
"dependencies": ["assist_pipeline", "http", "stt", "tts"],
"documentation": "https://www.home-assistant.io/integrations/assist_satellite",
"integration_type": "entity",
"quality_scale": "internal"
"quality_scale": "internal",
"requirements": ["hassil==2.2.3"]
}

View File

@@ -14,7 +14,9 @@ announce:
media_id:
required: false
selector:
text:
media:
accept:
- audio/*
preannounce:
required: false
default: true
@@ -23,7 +25,9 @@ announce:
preannounce_media_id:
required: false
selector:
text:
media:
accept:
- audio/*
start_conversation:
target:
entity:
@@ -40,7 +44,9 @@ start_conversation:
start_media_id:
required: false
selector:
text:
media:
accept:
- audio/*
extra_system_prompt:
required: false
selector:
@@ -52,5 +58,57 @@ start_conversation:
boolean:
preannounce_media_id:
required: false
selector:
media:
accept:
- audio/*
ask_question:
fields:
entity_id:
required: true
selector:
entity:
domain: assist_satellite
supported_features:
- assist_satellite.AssistSatelliteEntityFeature.START_CONVERSATION
question:
required: false
example: "What kind of music would you like to play?"
default: ""
selector:
text:
question_media_id:
required: false
selector:
media:
accept:
- audio/*
preannounce:
required: false
default: true
selector:
boolean:
preannounce_media_id:
required: false
selector:
media:
accept:
- audio/*
answers:
required: false
selector:
object:
label_field: sentences
description_field: id
multiple: true
translation_key: answers
fields:
id:
required: true
selector:
text:
sentences:
required: true
selector:
text:
multiple: true

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